Last active
January 14, 2024 20:48
-
-
Save howiemnet/9ab718844ecf66952268addfdbdf4385 to your computer and use it in GitHub Desktop.
Early version of Houdini to Fusion animation exporter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################################################################ | |
# | |
# h's hacky Houdini --> Fusion camera animation exporter | |
# | |
# Last mod: 20/3/2020 / [email protected] | |
# | |
# | |
# | |
# | |
# 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