-- I/O Morpher -- Dani Rosen, May 2006 -- I/O Morpher imports and exports morpher data. -- v1.0 - Jun 20, 2006 - compare channels more flexible - accepts any prefix. -- v1.1 - Aug 31, 2006 - added option not to resize the timeline on import; added process hourglass. -- v2.0 - Dec 08, 2007 - added semi-support for morpher-controller export+import (only first controller). -- - added support to import non-keyed values (values with no keyframes) macroScript IOMorpher category:"DaniTools" internalcategory:"DaniTools" tooltip:"I/O Morpher" buttontext:"I/O Morpher" Icon:#("Maintoolbar",100) ( global about_rol global import_rol global export_rol global IOMorpherFloater -- import globals global mdFilename global wavFilename global my_head global MDchans global HeadChans global matchChans global headMDcomp global numTracks = 1 global f global trackName global numKeys global highestTime = 1 -- export globals global morpherName global morphes = #() global morphercount = 0 global soundPathArray global soundfileName global soundfile -- rollout heights about_rol_height = 145 import_rol_height = 220 export_rol_height = 135 rol_closed_height = 25 -- ############################ FUNCTIONS ################################### fn rol_size = ( if about_rol.open == true then ( if import_rol.open == true then ( if export_rol.open == true then ( IOMorpherFloater.size = [300,about_rol_height+import_rol_height+export_rol_height] ) else ( IOMorpherFloater.size = [300,about_rol_height+import_rol_height+rol_closed_height] ) ) else ( if export_rol.open == true then ( IOMorpherFloater.size = [300,about_rol_height+rol_closed_height+export_rol_height] ) else ( IOMorpherFloater.size = [300,about_rol_height+rol_closed_height+rol_closed_height] ) ) ) else ( if import_rol.open == true then ( if export_rol.open == true then ( IOMorpherFloater.size = [300,rol_closed_height+import_rol_height+export_rol_height] ) else ( IOMorpherFloater.size = [300,rol_closed_height+import_rol_height+rol_closed_height] ) ) else ( if export_rol.open == true then ( IOMorpherFloater.size = [300,rol_closed_height+rol_closed_height+export_rol_height] ) else ( IOMorpherFloater.size = [300,rol_closed_height+rol_closed_height+rol_closed_height] ) ) ) ) -- close fn rol_size fn HeadMDcomp = ( MDchans = #() HeadChans = #() matchChans = 0 f = openFile mdFilename skipToString f "// Num. Tracks" skipToNextLine f numTracks = readLine f for i in 1 to (numTracks as integer) do ( skipToString f ("// Track " + i as string) skipToString f "// Name" skipToNextLine f chans = trimleft (readLine f) append MDchans chans ) close f for t = 1 to 100 do -- count morph targets on head ( morpherName = WM3_MC_GetName my_head.Morpher t if morpherName != "- empty -" then ( append HeadChans morpherName ) ) for c = 1 to MDchans.count do -- counting matches ( for m = 1 to HeadChans.count do ( if (matchPattern HeadChans[m] pattern:("*" + MDchans[c])) then ( matchChans = matchChans + 1 ) ) ) ) -- end fn -- ############################ ABOUT ################################### rollout about_rol "About" ( label ab1 "I/O Morpher v2.0" label ab2 "Dani Rosen \xa9 2007" progressBar horizLine "ProgressBar" height:3 enabled:true value:100 color:(color 10 10 0) label ab3 "I/O Morpher imports and exports morpher data." align:#center label ab4 "When importing, the timerange will be set to the length of" align:#center label ab5 "the sound file. If no sound file is selected, the timerange" align:#center label ab6 "will be set to the length of the morpher keys." align:#center on about_rol rolledup state do ( rol_size() ) ) -- ############################ IMPORT ################################### rollout import_rol "Import Morpher" ( button bWAV "WAV (optional)" pos:[12,15] height:20 button bMD "Morpher Data" pos:[12,45] height:20 editText eWAV "" pos:[100,15] width:184 height:20 editText eMD "" pos:[100,45] width:184 height:20 pickButton bHead "Pick Head" pos:[40,80] width:100 height:35 button bCompare "Compare Channels" pos:[150,80] width:100 height:20 enabled:false checkbox sortBox "Sort" pos:[180,100] enabled:false checkbox timelineAdjust "Do not adjust timeline length" pos:[64,120] button bImport "Import" pos:[76,150] width:120 height:32 enabled:false -- WAV file on bWAV pressed do ( global wavFilename = getOpenFileName caption:"WAV file to open:" \ types:"WAV Sound File (*.wav)|*.wav|" if wavFilename == undefined then return 0 eWAV.text = wavFilename ) -- MD file on bMD pressed do ( mdFilename = getOpenFileName caption:"MD file to open:" \ types:"Morpher Data file (*.MD)|*.md|All|*.*" if mdFilename == undefined then return 0 -- test MD f = openFile mdFilename fileTest = readLine f if fileTest != "// Morpher Data" then ( messagebox "This is not a valid MD file" f = undefined eMD.text = "" ) else ( eMD.text = mdFilename if bHead.text != "Pick Head" and eMD.text != "" then ( bCompare.enabled=true sortBox.enabled=true HeadMDcomp () bImport.enabled=true ) ) close f ) -- HEAD object on bHead picked obj do ( try -- test if morpher exists on object ( If IsValidMorpherMod obj.morpher == true then my_head = obj bHead.text=obj.name if my_head != undefined and eMD.text != "" then ( bCompare.enabled=true sortBox.enabled=true HeadMDcomp () bImport.enabled=true ) ) catch ( messagebox "Object does not have a Morpher modifier" my_head=undefined ) ) -- close bHead -- SortBox on sortBox changed state do ( if sortBox.state == true then ( if MDchans.count != 0 do ( sort MDchans sort HeadChans ) ) else ( if MDchans.count != 0 do ( HeadMDcomp () ) ) ) -- close sortBox -- COMPARE channels on bCompare pressed do ( rollout chanComp "Channel Comparison" width:290 height:400 ( listBox lbx1 "Morpher-Data channels" pos:[8,10] items:MDchans width:130 height:(numTracks as integer) listBox lbx2 "Head channels" pos:[150,10] items:HeadChans width:130 height:(numTracks as integer) label lMatch "Channels matched: " label MatchLabel "" on chanComp open do ( chanComp.height = ((numTracks as integer)*15 + 50) MatchLabel.text = (matchChans as string) ) ) createdialog chanComp ) -- close bCompare -- IMPORT keyframes on bImport pressed do ( setWaitCursor() deleteKeys my_head.Morpher #allKeys -- delete previous keys on morpher f = openFile mdFilename for i in 1 to (numTracks as integer) do ( -- extract keyframes from MD trackTimes = #() trackValues = #() skipToString f ("// Track " + i as string) skipToString f "// Name" skipToNextLine f trackName = trimleft (readLine f) skipToString f "// Num. Keys" skipToNextLine f numKeys = trimleft (readLine f) if numKeys != "0" then -- test if no keys are present ( skipToString f "// Times" skipToNextLine f trackTimes = filterstring (trimleft (readLine f)) " " for h in 1 to trackTimes.count do -- check for largest time. ( if (trackTimes[h] as integer) > (highestTime as integer) then highestTime = trackTimes[h] ) skipToString f "// Values" skipToNextLine f trackValues = filterstring (trimleft (readLine f)) " " for t = 1 to 100 do -- add keyframes to morpher ( morpherName = WM3_MC_GetName my_head.Morpher t if morpherName != "- empty -" then ( -- old (was case sensitive): -- if morpherName == curveName then if (matchPattern morpherName pattern:("*" + trackName)) then ( for u in 1 to trackTimes.count do ( timeVal = (trackTimes[u] as float) * framerate / 1000 addnewkey my_head.morpher[t] timeVal if my_head.morpher[t].keys != undefined then ( my_head.morpher[t].keys[u].value = (trackValues[u] as float) ) else ( my_head.morpher[t].controller[1].keys[u].value = (trackValues[u] as float) ) ) ) -- close matchpattern ) -- close morpehrName ) -- close t ) -- close numKeys test else ( -- copy value with no key if numKeys == "0" then ( skipToString f "// Values" skipToNextLine f trackValues = filterstring (trimleft (readLine f)) " " for t = 1 to 100 do ( morpherName = WM3_MC_GetName my_head.Morpher t if morpherName != "- empty -" then ( if (matchPattern morpherName pattern:("*" + trackName)) then ( if trackValues.count != 0 do ( my_head.morpher[t].value = (trackValues[1] as float) ) ) -- close matchpattern ) -- close morpehrName ) -- close t ) -- close if numkey is 0 ) -- close keyless value ) -- close i close f if timelineAdjust.checked != true then ( if eWAV.text != "" then ( wavsound.filename = wavFilename animationRange = interval wavsound.start wavsound.end slidertime = wavsound.start ) else ( highestTime = (highestTime as float) * framerate / 1000 + 1 animationRange = interval 0 (highestTime as integer) slidertime = 0 ) ) else ( if eWAV.text != "" then wavsound.filename = wavFilename ) setArrowCursor() ) -- close bImport on import_rol rolledup state do ( rol_size() ) ) -- end import_rol -- ############################ EXPORT ################################### rollout export_rol "Export Morpher" ( pickbutton current_head_button "Undefined" width:110 height:25 pos:[90,15] label headlabel "Pick Head:" pos:[20,20] height:30 button Export_Button "Export" width:110 height:30 pos:[90,55] enabled:false label null3 height:15 on current_head_button picked obj do ( try -- test if morpher exists on object ( If IsValidMorpherMod obj.morpher==true then my_head=obj current_head_button.text=obj.name ) catch ( messagebox "Object does not have a Morpher modifier" my_head=undefined ) if my_head!=undefined then Export_Button.enabled=true ) -- end current_head_button picked on Export_Button pressed do ( null3.text = "" -- get sound global getsound=wavsound.filename if wavsound.filename=="" then getsound="\\no-sound.wav" global soundPath=filterstring getsound "\\" global soundfileName=filterstring soundPath[soundPath.count] "." -- save filename trackfilename = getSaveFileName \ filename:(soundfileName[1] + ".md") \ types:"Morpher Data (*.MD)|*.md|All|*.*|" if trackfilename == undefined then return 0 trackfilestream=createfile trackfilename --Get track count setWaitCursor() for t = 1 to 100 do ( morpherName = WM3_MC_GetName my_head.Morpher t if morpherName != "- empty -" then ( append morphes morpherName morphercount = morphercount + 1 ) ) -- end morpher loop -- formatting header of MD file Format "// Morpher Data\n" to: trackfilestream Format "\n\n" to: trackfilestream Format "// Num. Tracks\n" to: trackfilestream Format "%\n\n" morphercount to: trackfilestream -- formatting tracks for i = 1 to morphercount do ( Format "// Track %\n" i to: trackfilestream Format "\t// Name\n" to: trackfilestream Format "\t%\n\n" morphes[i] to: trackfilestream Format "\t// Num. Keys\n" to: trackfilestream if my_head.morpher[i].keys != undefined then ( Format "\t%\n\n" my_head.morpher[i].keys.count to: trackfilestream ) else ( Format "\t%\n\n" my_head.morpher[i].controller[1].keys.count to: trackfilestream ) Format "\t// Times\n" to: trackfilestream Format "\t" to: trackfilestream -- TIME is displayed in milliseconds if my_head.morpher[i].keys != undefined then ( for j=1 to my_head.morpher[i].keys.count do ( thiskey=my_head.morpher[i].keys[j] thistime=(1000 / (framerate as float) * thiskey.time) format "% " thistime to: trackfilestream ) ) else ( for j=1 to my_head.morpher[i].controller[1].keys.count do ( thiskey=my_head.morpher[i].controller[1].keys[j] thistime=(1000 / (framerate as float) * thiskey.time) format "% " thistime to: trackfilestream ) ) Format "\n\n" to: trackfilestream Format "\t// Values\n" to: trackfilestream Format "\t" to: trackfilestream -- VALUE is normally between 0 to 100 if my_head.morpher[i].keys != undefined then ( for j=1 to my_head.morpher[i].keys.count do ( thiskey=my_head.morpher[i].keys[j] thisvalue=thiskey.value if thisvalue==0.0 then thisvalue=(thisvalue as integer) -- not really needed but looks nicer format "% " thisvalue to: trackfilestream ) if my_head.morpher[i].keys.count == 0 then (format "% " my_head.morpher[i].value to: trackfilestream) ) else ( for j=1 to my_head.morpher[i].controller[1].keys.count do ( thiskey=my_head.morpher[i].controller[1].keys[j] thisvalue=thiskey.value if thisvalue==0.0 then thisvalue=(thisvalue as integer) -- not really needed but looks nicer format "% " thisvalue to: trackfilestream ) if my_head.morpher[i].controller[1].keys.count == 0 then (format "% " my_head.morpher[i].value to: trackfilestream) ) Format "\n\n" to: trackfilestream ) -- end current phoneme export flush trackfilestream close trackfilestream null3.text = "Done!" setArrowCursor() ) -- end Export_Button pressed on export_rol rolledup state do ( rol_size() ) ) -- end export_rol IOMorpherFloater = newRolloutFloater "I/O Morpher" 300 200 addRollout about_rol IOMorpherFloater addRollout import_rol IOMorpherFloater rolledUp:true addRollout export_rol IOMorpherFloater rolledUp:true )