Early version of Houdini to Fusion animation exporter
################################################################ | |
# | |
# h's hacky Houdini --> Fusion camera animation exporter | |
# | |
# Last mod: 20/3/2020 / h@howiem.net | |
# | |
# | |
# | |
# | |
# Notes: | |
# - Create a new tool on a shelf in Houdini, and paste this | |
# script into the Script tab. Give the tool a nice name | |
# - With an animated node selected in your scene, click | |
# the tool button | |
# | |
# - Pause in anticipation | |
# | |
# - Go to Fusion and hit Ctrl/Cmd-V to paste. If a camera | |
# was selected in Houdini, a 3D Camera node will be set up, | |
# otherwise it'll be a Custom Tool node. | |
# | |
# Caveats and warnings: | |
# - Note that no camera parameters other than Trans/Rot/RotOrder | |
# will be set up - everything else needs to be copied manually | |
# for now | |
# - This script has been created for use with my fulldome/planetarium | |
# projects, and has not been thoroughly tested. It's unlikely | |
# to set your hair on fire, but if it does please don't sue. | |
# | |
# | |
################################################################ | |
import hou | |
import math | |
theNodes = hou.selectedNodes() | |
theObject = theNodes[0] | |
objectType = theObject.type().name() | |
startFrame = int(hou.playbar.playbackRange()[0]) | |
endFrame = int(hou.playbar.playbackRange()[1]) | |
totalFrames = (endFrame - startFrame) + 1 | |
# The rotation order from Houdini is only communicated to | |
# Fusion if it's a camera node being created - otherwise it's | |
# up to you to set it up correctly when Connecting parameters | |
# in your composition. | |
rotationOrder = theObject.parm("rOrd").evalAsString() | |
################################################ | |
# | |
# GRAB THE DATA | |
# | |
################################################ | |
theList = [] | |
for theFrame in range (startFrame,endFrame+1): | |
mat = theObject.worldTransformAtTime(hou.frameToTime(theFrame)) | |
pos = mat.extractTranslates('srt') | |
rot = mat.extractRotates('srt',rotationOrder,hou.Vector3()) | |
theData = [pos[0],pos[1],pos[2],rot[0],rot[1],rot[2]] | |
theList.append(theData) | |
################################################ | |
# | |
# PROCESS THE DATA FOR WIND-UP AND FLIPPING | |
# | |
################################################ | |
currRots = [0,0,0] | |
currFlips = [0,0,0] | |
for theFrame in range (1,totalFrames): | |
for theAxis in range (0, 3): | |
# take the previous (corrected) angle | |
prev = theList[theFrame-1][theAxis+3] | |
# correct the current one by the current number of rots | |
curr = theList[theFrame][theAxis+3] + (360 * currRots[theAxis]) | |
curr = curr + (180 * currFlips[theAxis]) | |
# FULL ROTATIONS - wind-up | |
# check if we've rotated more than +/- 270 | |
if (abs(curr-prev) > 270): | |
if curr<prev: | |
currRots[theAxis] = currRots[theAxis] + 1 | |
curr = curr + 360 | |
else: | |
currRots[theAxis] = currRots[theAxis] - 1 | |
curr = curr - 360 | |
# EULER FLIPS | |
if (abs(curr-prev) > 90): | |
if curr<prev: | |
currFlips[theAxis] = currFlips[theAxis] + 1 | |
curr = curr + 180 | |
else: | |
currFlips[theAxis] = currFlips[theAxis] - 1 | |
curr = curr - 180 | |
theList[theFrame][theAxis+3] = curr | |
################################################ | |
# | |
# CULL REDUNDANT FRAMES | |
# | |
################################################ | |
theDataMarkRedundant = [] # yes this is a silly name | |
# always record first frame | |
theDataMarkRedundant.append([True,True,True,True,True,True]) | |
for theFrame in range (1,totalFrames-1): | |
axisFlagsForThisFrame = [True,True,True,True,True,True] | |
for theAxis in range (0, 6): | |
# if a value hasn't changed between now and the last frame, ... | |
if (theList[theFrame][theAxis] == theList[theFrame-1][theAxis]): | |
# ... and the following frame, it can be marked as redundant: | |
if (theList[theFrame][theAxis] == theList[theFrame+1][theAxis]): | |
axisFlagsForThisFrame[theAxis] = False | |
theDataMarkRedundant.append(axisFlagsForThisFrame) | |
# always record last frame (for now) | |
theDataMarkRedundant.append([True,True,True,True,True,True]) | |
####################################### | |
# | |
# HELPER FUNCTION: makeOutputString | |
# | |
# This function is called for each channel in turn. | |
# It creates a string containing all the keyframes | |
# for the whole duration. | |
# | |
####################################### | |
def makeOutputString(channel): | |
returnString = "\t\t\tKeyFrames = {\n" | |
# first line only has RH handle: | |
returnString = returnString + "\t\t\t\t[" + str(startFrame) + "] = { " + str(theList[0][channel]) + ", RH = { " + str(startFrame+0.25) + ", " + str(theList[0][channel]) + " }, Flags = { Linear = true } },\n" | |
# do all the frames between: | |
for i in range (1,totalFrames-1): | |
# i iterates through the array starting at 1, | |
# but the frame number to be written may be offset | |
# if the frame range in Houdini didn't start at 1: | |
theFrame = i+startFrame | |
# only actually record arrays that have been marked | |
if theDataMarkRedundant[i][channel]: | |
returnString = returnString + "\t\t\t\t[" + str(theFrame) + "] = { " + str(theList[i][channel]) + ", LH = { " + str(theFrame-0.25) + ", " + str(theList[i][channel]) + "}, RH = { " + str(theFrame+0.25) + ", " + str(theList[i][channel]) + " }, Flags = { Linear = true } },\n" | |
# last line only has LH handle: | |
returnString = returnString + "\t\t\t\t[" + str(endFrame) + "] = { " + str(theList[totalFrames-1][channel]) + ", LH = { " + str(endFrame+0.75) + ", " + str(theList[totalFrames-1][channel]) + " }, Flags = { Linear = true } }\n" | |
returnString = returnString + "\t\t\t}\n" | |
return returnString | |
####################################### | |
# | |
# Set up some useful string lists | |
# | |
####################################### | |
colStrings = ["{ Red = 251, Green = 50, Blue = 50 }", | |
"{ Red = 50, Green = 203, Blue = 50 }", | |
"{ Red = 50, Green = 50, Blue = 250 }"] | |
if objectType == 'cam': | |
channelNames = ["Camera3D1XOffset","Camera3D1YOffset","Camera3D1ZOffset","Camera3D1XRotation","Camera3D1YRotation","Camera3D1ZRotation"] | |
else: | |
channelNames = ["CustomTool1Xposition","CustomTool1Yposition","CustomTool1Zposition","CustomTool1Xrotation","CustomTool1Yrotation","CustomTool1Zrotation"] | |
####################################### | |
# | |
# CREATE THE MONSTER OUTPUT STRING | |
# | |
####################################### | |
# PREAMBLE | |
if objectType == 'cam': | |
myOutputString = "{\n" | |
myOutputString = myOutputString + "\tTools = ordered() {\n" | |
myOutputString = myOutputString + "\t\tCamera3D1 = Camera3D {\n" | |
myOutputString = myOutputString + "\t\t\tCtrlWZoom = false,\n" | |
myOutputString = myOutputString + "\t\t\tInputs = {\n" | |
myOutputString = myOutputString + "\t\t\t\t[\"Transform3DOp.Translate.X\"] = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"Camera3D1XOffset\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\t[\"Transform3DOp.Translate.Y\"] = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"Camera3D1YOffset\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\t[\"Transform3DOp.Translate.Z\"] = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"Camera3D1ZOffset\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\t[\"Transform3DOp.Rotate.RotOrder\"] = Input { Value = FuID { \"" + rotationOrder.upper() + "\" }, },\n" | |
myOutputString = myOutputString + "\t\t\t\t[\"Transform3DOp.Rotate.X\"] = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"Camera3D1XRotation\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\t[\"Transform3DOp.Rotate.Y\"] = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"Camera3D1YRotation\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\t[\"Transform3DOp.Rotate.Z\"] = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"Camera3D1ZRotation\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\tFilmBack = Input { Value = 1, },\n" | |
myOutputString = myOutputString + "\t\t\t\tFilmGate = Input { Value = FuID { \"HD\" }, },\n" | |
myOutputString = myOutputString + "\t\t\t\tApertureW = Input { Value = 0.3775, },\n" | |
myOutputString = myOutputString + "\t\t\t\tApertureH = Input { Value = 0.2123, },\n" | |
myOutputString = myOutputString + "\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\tViewInfo = OperatorInfo { Pos = { 1210, 181.5 } },\n" | |
myOutputString = myOutputString + "\t\t},\n" | |
else: | |
myOutputString = "{\n" | |
myOutputString = myOutputString + "\tTools = ordered() {\n" | |
myOutputString = myOutputString + "\t\tHoudiniAnim = Custom {\n" | |
myOutputString = myOutputString + "\t\t\tCtrlWZoom = false,\n" | |
myOutputString = myOutputString + "\t\t\tInputs = {\n" | |
myOutputString = myOutputString + "\t\t\t\tNumberIn1 = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"CustomTool1Xposition\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\tNumberIn2 = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"CustomTool1Yposition\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\tNumberIn3 = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"CustomTool1Zposition\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\tNumberIn4 = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"CustomTool1Xrotation\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\tNumberIn5 = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"CustomTool1Yrotation\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\tNumberIn6 = Input {\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSourceOp = \"CustomTool1Zrotation\",\n" | |
myOutputString = myOutputString + "\t\t\t\t\tSource = \"Value\",\n" | |
myOutputString = myOutputString + "\t\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\t\tNumberControls = Input { Value = 1, },\n" | |
myOutputString = myOutputString + "\t\t\t\tNameforNumber1 = Input { Value = \"X position\", },\n" | |
myOutputString = myOutputString + "\t\t\t\tNameforNumber2 = Input { Value = \"Y position\", },\n" | |
myOutputString = myOutputString + "\t\t\t\tNameforNumber3 = Input { Value = \"Z position\", },\n" | |
myOutputString = myOutputString + "\t\t\t\tNameforNumber4 = Input { Value = \"X rotation\", },\n" | |
myOutputString = myOutputString + "\t\t\t\tNameforNumber5 = Input { Value = \"Y rotation\", },\n" | |
myOutputString = myOutputString + "\t\t\t\tNameforNumber6 = Input { Value = \"Z rotation\", },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowNumber7 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowNumber8 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tPointControls = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowPoint1 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowPoint2 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowPoint3 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowPoint4 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tLUTControls = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowLUT1 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowLUT2 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowLUT3 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t\tShowLUT4 = Input { Value = 0, },\n" | |
myOutputString = myOutputString + "\t\t\t},\n" | |
myOutputString = myOutputString + "\t\t\tViewInfo = OperatorInfo { Pos = { 1210, 181.5 } },\n" | |
myOutputString = myOutputString + "\t\t},\n" | |
####################################### | |
# | |
# For each channel, create the keyframe data | |
# part of the output: | |
# | |
####################################### | |
for theChannel in range (0, 6): | |
myOutputString = myOutputString + "\t\t" + channelNames[theChannel] + " = BezierSpline {\n" | |
myOutputString = myOutputString + "\t\t\tSplineColor = " + colStrings[theChannel % 3] + ",\n" | |
myOutputString = myOutputString + "\t\t\tNameSet = true,\n" | |
myOutputString = myOutputString + makeOutputString(theChannel) | |
if theChannel == 5: | |
myOutputString = myOutputString + "\t\t}\n" | |
else: | |
myOutputString = myOutputString + "\t\t},\n" | |
###### POSTAMBLE | |
if objectType == 'cam': | |
myOutputString = myOutputString + "\t},\n" | |
myOutputString = myOutputString + "\tActiveTool = \"Camera3D1_1\"\n" | |
myOutputString = myOutputString + "}\n" | |
else: | |
myOutputString = myOutputString + "\t},\n" | |
myOutputString = myOutputString + "\tActiveTool = \"CustomTool1\"\n" | |
myOutputString = myOutputString + "}\n" | |
####################################### | |
# | |
# Dump the whole thing onto the clipboard | |
# ready to be pasted into Fusion: | |
# | |
####################################### | |
hou.ui.copyTextToClipboard(myOutputString) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment