What's New in Silhouette V6
There have been many scripting enhancements in V6. The biggest change was to the rendering loop, which is now implemented entirely in Python. Now that a Session can have multiple output nodes, as well as multiple Paint and Roto nodes, many Actions were updated to support the new workflow.
- there can be multiple nodes of each type in a Session, including Roto, Paint, Output, and various OFX nodes
- the complete rendering loop is implemented in scripts/tools/renderer.py
- rendering is triggered with the new Action in scripts/actions/render.py
- bundled actions that target specific nodes are updated to handle connected Output nodes
- shape path data can now contain per-point feathering information if per-point feathering is used
New Scripting Features
- nodes can be created, added, and removed from a Session
- nodes can be connected and disconnected
- new hooks: attach_pipe, remove_pipe, triggered when nodes are wired up or disconnected in the UI
- new hook: object_created, triggered when a new Node, Shape, Layer, or Tracker is created in the UI
- user-defined properties can be added and removed from Objects
- multi-part output format API
- new hook: output_node, triggered for each output node
- drop hooks: hooks can be called when a registered mime type is dropped in the new Trees view
- new Sessions can be created and activated
- new Sources can be created and added to the Project
- adding a Source to a Session creates a SourceNode and connects it
- new scripting utilities: node, renderOptions, renderer, selection, sequenceBuilder, string
Nodes must be created using their interal node name. Registered node names can be queried using fx.nodes. Note that internal nodes have named like RotoNode and PaintNode. Most of the rest of the stock Silhouette nodes are implemented using OFX, and have node IDs such as com.digitalfilmtools.ofx.silhouette.sfx_composite and com.digitalfilmtools.ofx.silhouette.blur.
Nodes can be connected to other nodes using pipes and ports. Ports have specific types (usuallying image or data) and can only be connected to other ports with the same type. Output ports can have multiple pipes connected to them but input ports may only have one.
roto = Node("RotoNode") activeSession().addNode(roto) slapComp = Node("com.digitalfilmtools.ofx.silhouette.slapComp") activeSession().addNode(slapComp) roto.port("output").connect(slapComp.port("input"))
Hooks are only triggered from the UI, so if you want hooks triggered when nodes are created or connected, you must do so manually.
import hook roto = Node("RotoNode") activeSession().addNode(roto) hook.run("object_created", roto) pipe = roto.output(0).connect(slapComp.input(0)) hook.run("attach_pipe", pipe)
|called after a new object is created in the UI. The object is passed as the argument.||6.0|
|called after a new pipe connects two nodes||6.0|
|called just before a pipe is removed||6.0|
|called after an Output node finishes writing all of its parts. A meta-data dictionary of useful information is passed.||6.0|
The complete rendering loop is now encapsulated in the new Renderer class, implemented in the scripts/tools/renderer module. It is launched from the GUI using the new Render action in scripts/actions/render. Special-purpose renderers, such as Render Shapes to Separate Files or Render Layers to Separate Files make use of the new renderer by sublassing it and overriding the renderFrame method to control what objects are rendered and when, overring the output file name as necessary.
The important rendering change from V5 to V6 is in V5 there was only one output node and one output path. In V6 there can be multiple output nodes each rendering a specific part of the tree, and each output node can have a different file format, path, channel set, etc. The new multi-part output node can also generate an output file (in OpenEXR format) with multiple parts, each with their own resolution and other attributes. The new renderer embodies all of the logic to handle all of this output complexity, and its behavior can be overriden or outright replaced as needed.
The I/O module API has been enhanced to provide access to multi-part OpenEXR images. Use the new parts() method to query the list of available parts in a multi-part image (a List of part names). To write an image file, first create the file, write one more images using putImage(), then close the file.
module = MediaFormat("OpenEXR") module.create(path) parts = renderParts() for part in parts: module.putImage(part.raster, part=part.name, frame=frame, pixelAspect=pixelAspect, roi=roi, displayWindow=session_rect, channels=part.channelMask, premultiply=False, metadata=part.metadata) module.close() # actually writes the file
There are several utility modules located in resources/scripts/tools.
The getNode(object) function can be used to fetch the parent Node of an arbitrary object. findUpstream(node, type) looks for a None of the specified type that is connected to the target node. For example, use this to look for a Roto node connected to a selected Output node.
The selection module has utility functions for dealing with selected nodes.
- nodes(type=None): returns a list of selected nodes, optionally of the desired type
- outputNodes(): returns a list of selected Output nodes
- outputAndTarget(type="RotoNode") : returns a tuple (output, target) of the selected output node and an up-stream node of the desired type
tools.sequenceBuilder contains the SequenceBuilder class which can be used to parse a filename and generate details such as start and end frames.
from tools.sequenceBuilder import SequenceBuilder builder = SequenceBuilder(path) for f in range(0, builder.frames): print builder.build(f) # construct Sources using self.path, which has the frame range included source = Source(self.path)
The tools.string module has the function to_filename() which slugifies a string into a new string suitable to use as a filename. This is a useful utility function for converting object names to filenames.