Skip to content

Instantly share code, notes, and snippets.

@howiemnet
Last active January 14, 2024 20:48
Show Gist options
  • Save howiemnet/9ab718844ecf66952268addfdbdf4385 to your computer and use it in GitHub Desktop.
Save howiemnet/9ab718844ecf66952268addfdbdf4385 to your computer and use it in GitHub Desktop.
Early version of Houdini to Fusion animation exporter
################################################################
#
# 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