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.FCurve # camera.Translation (X) print translationNode.Nodes.FCurve # camera.Translation (Y) print translationNode.Nodes.FCurve # camera.Translation (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'] # 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:
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 firstname.lastname@example.org.