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
- the session.outputNode attribute is gone, as there may now be zero, one, or many Output nodes in the session. Use session.outputs to get a list of them.
- the session.outputMask attribute is gone, as there may no longer be a single Output Node. Each node must be asked for its outputMask.
- the session.renderInfo attribute is gone. Global output settings can be queried from the session and from specific output nodes.
New Scripting Features
- Actions can now be added to the File and Edit menus by setting the optional root argument to "File" or "Edit"
- Action order can be controlled by setting the order argument to an ordinal index.
- new hooks: attach_pipe, remove_pipe, triggered when nodes are wired up or disconnected in the UI
- new hook: , triggered when a new Node, Shape, Layer, or Tracker is created in the UI
- 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
|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|
- Matrix.transpose() returns a transposed version of the matrix
- multi-part output format API
- multiPart attribute returns True if format supports multi-part
- dataWindow attribute returns True if format supports data-window
- Object.prepare(object) prepares the object for adding to the target Object. It returns the object that should be added, which may possibly be a new object. For instance, when adding a cloned Roto Shape into a Morph Node, this function will convert the Shape for use in the Morph node.
- Object.isType(type) returns True if the object is or is derived from the specified type name.
- Object.uniqueLabel(label) returns a label that is unique for the object's Session
- Object.addProperty(prop, after=None) adds the user-defined Property to the object
- Object.removeProperty(prop) removes the user-defined Property from the object
- nodes can be created, added, and removed from a Session
- nodes can be connected and disconnected
- the Node.outputMask attribute has been added to query a node for the output streams it can produce.
- the Node.state attribute allows read-only access to the node's state dictionary
- the Node.metadata(frame) function returns a metadata dictionary derived from up-stream Sources
- Node.connectedInputs returns a list of connected input Ports.
- Node.connectedOutputs returns a list of connected output Ports.
- Node.pipes returns a list of connected Pipes.
- Node.ports returns a list of all input/output Ports.
- Node.matte returns the Obey Matte input Port, or None if the node does not have one
- Node.render() has a new signature. See Rendering for more information.
- Node.disconnect(port) disconnects the specified Port.
- Node.getInput(index=0) returns the specified input Port
- Node.getOutput(index=0) returns the specified output Port
- Node.port(name) returns the Port with the specified name/id.
- Port.canConnect(target) returns True if this Port can be connected to the target Port
- Port.connect(target) connects the Port to target and returns a new Pipe
- Port.disconnect(pipe) disconnects the Pipe
- Port.connected returns True if the Port has any connected PIpes
- Port.name returns the name associated with it
- Port.source returns the source Port if this is connected to an Output Port, or None
- Port.targets returns a List of Input Ports this Port is connected to
- Port.hidden returns True if this Port is hidden
- Port.is_input returns True if this is an Input Port
- Port.is_output returns True if this is an Output Port
- Port.is_aux returns True if this is tagged as an Aux Port (affects Node coloring)
- Port.is_matte returns True if this is the Obey Matte Input
- prefs.add() can now create List prefs (by passing an items list)
- Project.addItem(item) can add Sources or Sessions to the Project
- Project.removeItem(item) removes the specified item from the Project
- Project.version returns the Project version
- Project.sources returns a list of the Project's Sources
- Project.sessions returns a list of the Project's Sessions
- user-defined properties can be added and removed from Objects
- Object-reference properties (such as the Source Node source references) can now be set
- Property.isKeyExtrapolated(index) returns True if the key interpolation is set to extrapolated
- Property.moveKeys(offset) moves the selected keys by the specified offset
- Property.prevKeyTime(frame) returns the time of the key before this one, or None if there is no key
- Property.nextKeyTime(frame) returns the time of the key after this one, or None if there is no key
- Property.getKeyIndex(frame) returns the index of the key at the specified frame, or None if there is no key
- Property.isKey(frame) returns True if there is a key at frame
- Property.dynamic returns True if the Property is dynamic
- Property.item returns the current item as a stringt if the Property is of type List
- Property.items returns a list of string items if the Property is of type List
- Property.obsolete returns True if the Property is obsolete
- Property.userDefined returns True if the Property is user-defined
- Property.value returns the Property value if it has no key frames
- Raster.crop(roi) returns a new Raster cropped or padded (with black) to the new ROI
- Raster.dod returns the Raster's DOD Recti
A new rectangle type with integer coordinates, used for ROI and DOD regions.
- 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
- Session.getNodes(type) returns a list of nodes of the desired type
- Session.addNode(node) adds the Node
- Session.removeNode(node) removes the Node
- Session.roi(frame) returns the ROI as a Recti for the specified frame
- Session.displayWindow attribute returns the session frame as a Recti
- Session.outputStreams attribute was removed
- Session.outputs a list of Output nodes
- Session.outputRange the session's output range as a tuple (start, end)
- Session.renderRange the session's render range as a tuple (start, end)
Note the output range is in output frame numbers and the render range is the zero-indexed frames needing to be rendered.
- Shapes can now perform feathering in two different ways, legacy and per-point. Use Shape.featherType (read-only) to determine the per-shape feathering type (Shape.LegacyFeather or Shape.PerPointFeather).
- Shape.capStyle can be used to query or set the open shape cap style (Shape.FlatCap or Shape.RoundCap).
- Shape.inverted is now read/write.
- ShapePath.insertPoint(t) can be used to insert a new control point at t.
- ShapePath.deletePoint(index) is used to delete a control point designed by index.
- ShapePath.evalNormal(t) returns the normal at t.
- ShapePath.evalPoint(t) now returns more information if the point has per-point feathering.
|Shape Type||No Feather||Per-point Feather|
|Closed Bezier||(in_tangent, point, out_tangent)||(in_tangent, point, out_tangent, feather_point, feather_in_tangent, feather_out_tangent)|
|Open Bezier||(in_tangent, point, out_tangent)||(in_tangent, point, out_tangent, stroke_width, feather_width)|
|Closed Bspline||(point, repeat, weight)||(point, repeat, weight, feather_point, feather_weight)|
|Open Bspline||(point, repeat, weight)||(point, repeat, weight, width, feather_width)|
|Closed Xspline||(point, weight)||(point, weight, feather_point, feather_weight)|
|Open Xspline||(point, weight)||(point, weight, width, feather_width)|
- Source.metadata(frame) returns the meta-data dictionary for the specified frame.
- To construct a new Source, use Source(path, part=-1) where part is the part index if it is a multi-part source. Use MediaFormat.parts(path) to query the available parts in the file.
The path passed to Source should contain the frame range to use, in the form: /path/to/file.[0001-0999].ext. Use tools.SequenceBuilder() to aid in building the path in the proper format.
- Timer.start() now has an optional singleShot argument that only fires the timer once if set to True.
- fx.setWorkspace(name) will set the current workspace with the one identified by name.
- New scripting utilities: tools.node, tools.renderOptions, tools.renderer, tools.selection, tools.sequenceBuilder, tools.string
- Use fx.activate(object) can activate either a Project or Session.
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)
New Environment Variables
|SFX_BRIGHTNESS||A floating point number to scale the GUI brightness by (defaults to 1.0).|
|SFX_TEXT_BRIGHTNESS||A floating point number to scale the GUI text brightness by (defaults to 1.0).|
|SFX_PROJECT_IS_BUNDLE||Set to '1' to save the project XML file using the same name as the project bundle name (defaults to project.sfx).|
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.
Usually Output nodes will be rendered but any Port of any Node can also be rendered, including input Ports.
In V6 the Node.render() signature has changed:
render(frame, stream=Stream_Left, channels=Channel_RGBA, depth=None, resolution=Proxy_FullRes, roi=None, port=None)
depth and roi default to the Session's depth and ROI, respectively. If port is omitted, the first output Port will be used.
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 tools.renderOptions module contains the getOptions() function which builds an options dictionary from the selection, and when running in the GUI, opens the Render Options dialog.
The tools.renderer module contains the Renderer class, which handles all details of rendering a set of Output nodes. It has various methods that can be overriden by a subclass to customize the rendering process in various ways.
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. This is useful for actions that render a specific Node type, such as the multi-part shape layer render action.
tools.sequenceBuilder contains the SequenceBuilder class which can be used to parse a filename and generate details such as start and end frames. path should be a frame from a numbered sequence and SequenceBuilder will find the start and end frames and construct a resulting path with the frame range built-in, suitable for constructing Sources from, in the form: /path/to/file.[start-end].ext.
from tools.sequenceBuilder import SequenceBuilder # the image sequence has frames 1-999 path = "/images/sequence.0001.exr" 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(builder.path) print builder.path
This prints out /images/sequence.[0001-0999].exr.
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.