camera = FBCamera('testCamera')
camera.Show = True
FBSystem().Renderer.CurrentCamera = camera
# Prints True, indicating that Roll is an FBPropertyAnimatable
print camera.Roll.IsAnimatable()
# Once a property is set to animated, it has an animation node that we can access
camera.Roll.SetAnimated(True)
rollNode = camera.Roll.GetAnimationNode()
print rollNode.Name, rollNode
camera.Translation.SetAnimated(True)
translationNode = camera.Translation.GetAnimationNode()
print translationNode.Name, translationNode
# float
print rollNode.FCurve # camera.Roll
# FBVector3d
print translationNode.Nodes[0].FCurve # camera.Translation[0] (X)
print translationNode.Nodes[1].FCurve # camera.Translation[1] (Y)
print translationNode.Nodes[2].FCurve # camera.Translation[2] (Z)
# CopyTake makes the new take current after creating it
newTake = FBSystem().CurrentTake.CopyTake('NewTakeName')
FBPlayerControl().GotoStart()
newTake.ClearAllProperties(False)
newTake.LocalTimeSpan = FBTimeSpan(FBTime(0), FBTime(0, 0, 0, 150))
def GetLayerIndex(take, layerVariant):
'''
Given a variable representing a layer, returns the index of that layer
within the given take, or -1 if no such layer exists. layerVariant can
either be an actual layer object, the name of a layer, or, for the sake of
convenience, a layer's index.
'''
# If the layer variant is already an index, we simply return it
if isinstance(layerVariant, int):
return layerVariant
# Otherwise, we need to qualify it to obtain a name
if isinstance(layerVariant, basestring):
name = layerVariant
else:
name = layerVariant.Name # It must be an FBAnimationLayer
# Finally, search for the layer by name
for i in range(0, take.GetLayerCount()):
if take.GetLayer(i).Name == name:
return i
return -1
def CreateNewLayer(take, name):
'''
Creates a new layer in the given take, but conveniently allows an initial
name to be supplied. Returns the new FBAnimationLayer object.
'''
# Puzzlingly, this API method doesn't return the new layer
take.CreateNewLayer()
# However, new layers are always created at the top of the stack
layer = take.GetLayer(take.GetLayerCount() - 1)
layer.Name = name
return layer
def SetCurrentLayer(take, layerVariant):
'''
Given a variable representing a layer (either an index, a name, or an
actual layer object), makes that layer current.
'''
layerIndex = GetLayerIndex(layerOrName)
assert layerIndex >= 0
take.SetCurrentLayer(layerIndex)
take = FBSystem().CurrentTake
# Create a new override layer and make it current
layer = CreateNewLayer(take, 'TestLayer')
layer.LayerMode = FBLayerMode.kFBLayerModeOverride
SetCurrentLayer(take, layer)
At 2:15, I mention that the animation nodes for each property are contained under a single input animation node. It's not worth getting too hung up on this point, but in case you're curious, there are a couple of things to get straight:
For one, animation nodes are stored separately from their corresponding properties. To use our camera as an example:
# The properties live in camera.PropertyList:
camera.PropertyList.Find('Roll')
# However, they're conveniently accessible via member variables:
camera.Roll
# The animation nodes live beneath camera.AnimationNode:
[n for n in camera.AnimationNode.Nodes if n.Name == 'Roll'][0]
# However, they're conveniently accessible via GetAnimationNode:
camera.Roll.GetAnimationNode()
The first time we set camera.Roll to animated, a new animation node is created and added as a child of camera.AnimationNode. camera.Roll.GetAnimationNode will then give us a reference to that new node. Because of this convenient interface, we don't have to worry too much about the top-level input node if we're only dealing with animated properties.
So why is it the input animation node? Well, recall that all animated objects, FBCamera included, are subclasses of FBBox. A box is a component that has an input animation node and an output animation node: you pipe data in on one side, and you get out some modified data on the other side. If you've ever used a relation constraint, you'll know what I mean. These two nodes are accessible via these two methods of FBBox:
camera.AnimationNodeInGet()
camera.AnimationNodeOutGet()
Naturally, all the property values are inputs, so they're contained under the input animation node. For the sake of convenience, FBModel provides access to this same node via the AnimationNode member variable. So the following two approaches are functionally equivalent:
for node in camera.AnimationNodeInGet().Nodes:
print node.Name
for node in camera.AnimationNode.Nodes:
print node.Name
If you see any errors that you'd like to point out, feel free to email me at awforsythe@gmail.com.