What is a Custom Operator?

Operators are calculators or functions that manipulate (deform) or generate data. They are connected to objects or properties via any number of input and output ports. Operators read data from their input port(s), perform the specified calculation and then write the result onto their output port(s) using the operator's Update() method.

How are Custom Operators Constructed?

To understand custom operators, you need to understand the difference between runtime and preset-based operators.

Runtime Operators

A Runtime operator is an operator that is not persisted and cannot be applied in other scenes or to other items in the same scene. Runtime operators are saved as part of the scene and can be created on-the-fly in one of the following ways:

• Create it with the Scripted Operator Editor—see the Scripted Operators chapter of the Customization (user) guide.

• Build the runtime operator dynamically from scripting or C++. For example, you could run a script inside the Script Editor that builds a custom operator somewhere in the current scene. There are two types of dynamically generated operators:

- Operators where the code has been embedded directly in the operator using the CustomOperator.Code property

- Operators where the code is in an external file, specified using the CustomOperator.FileName property

| Although a custom operator implemented in a scripted or compiled file is technically a runtime operator, you can reuse it in other scenes or projects because it is file-based. However, you cannot use the ApplyOp command to apply it because it is not associated with a preset; instead you must get a pointer to the CustomOperator object and then use the Operator.Connect method. For more information, see Applying or Connecting Your Custom Operator.

Preset-based Operators

A Preset-based operator is a scripted or compiled operator that has a preset (and therefore a SPDL file) associated to it. These operators can be applied across the same or different scenes and may be authored with the Scripted Operator Editor or by writing a scripted or compiled file which is then explicitly installed (see Installing Your Preset-Based Custom Operator in XSI).

| A runtime operator can be converted to a preset-based operator by saving a preset of it in the Scripted Operator Editor, and it is important to understand this relationship because most scripted preset-based operators start out as runtime operators, and compiled operators can be prototyped with scripted preset-based operators:

A preset-based custom operator consists of a SPDL file which defines the input and output ports as connections and the implementation for the calculation, which can be either scripted or compiled.

| Exactly where and how this implementation is authored depends on whether the implementation is scripted or compiled: • Scripted operators can either embed the implementation directly in the SPDL file inside the Plugin = Scripted section or, like compiled operators, simply provide a reference to the external implementation (script) file. • Compiled operators provide a reference to the external implementation (library) file inside the Plugin = Library section.

Plugin = Scripted

For scripted operators, the Plugin section declares which scripting language was used to implement the operator and information about the implementation:

• Language—Any scripting language that XSI supports (VBScript, JScript, PerlScript, and PythonScript) may be used to author the implementation.

• One or the other of the following items appears, depending on whether the implementation is embedded or saved in a script file:

- Filename—This is where you specify the base name of the script file (no path or extension) where the operator is implemented (for file-based implementations only).

- Update—This is the main entry point of the operator, which contains the basic calculations, read and write operations (for embedded implementations only).

• Helpers—This section (appears for embedded implementation only) is where you can add extra helper functions.

Plugin = Library

For compiled operators, the Plugin section declares which API was used to implement the operator and also contains the location of the library file and the entry points:

• api—Either the COM API (com) or the C++ API (cpp) may be used to author the implementation.

• Filename—This is where you specify the base name of the library file (no path or extension) where the operator is implemented.

• EntryPoints—This section is where you declare the individual names of the functions for the following callbacks:

- Update—This is the main entry point of the operator, which contains the basic calculations, read and write operations.

- Init—This is the set of functions that run when the operator loads or reloads. For example, if you are creating an instance-based operator, you can use the Init function to create an instance of the class and save its pointer to the UserData on the UpdateContext of the operator. It will then be available to the Update and Term entry points.

- Term—This is the set of functions that run when the operator unloads or is destroyed. Continuing with the example of an instance-based operator introduced in Init, you can use the Term function to delete the user data containing the pointer to this instance.

Understanding Operators

An XSI scene is organized around data nodes (such as a 3D Object, a Transform, a Primitive, a Cluster, etc.) and the operators that manipulate them (such as generators, deformers, expressions, constraints, etc.). The dependency relationship between nodes and operators is declared by port connections. These port connections are created when operators are applied to data nodes:

The nesting hierarchy of the data nodes is represented in the Scene Explorer. Underneath this data hierarchy there is an operator graph that extends beyond the parent/child nesting relationships that you see in the Explorer. This operator graph declares a multitude of dependency relationships between the data and the operators, and which XSI uses to determine the evaluation sequence for the data nodes drawn in a scene.

The Operator Stack

The operators that manipulate a node are grouped on a data's stack based on each operator's categorization. This order enables operator prioritization that does not change at runtime.

The user dictates the evaluation order. The core algorithms follow the user's interaction with XSI. The position of a new operator added into the stack is determined by the following:

• the categorization of the newly created operator

• the operators already connected to the stack

• user workflow

• structured data dependencies

• avoidance of cyclical graph errors

One particular example of this ordering prioritization is with the direction constraint. The direction constraint is applied after other constraints on the global transform so that it can always take into account other constraints applied to the object. For example, the direction constraint has to take into account the position and orientation of the object--otherwise the direction would not be correct. This is especially useful for upvectors in rigs to avoid gimbal lock.

This evaluation priority enables a user to generate, model, animate and render in a non-destructive and non-linear fashion. Users may go back and tweak their models more without destroying the animation. This is because this priority is respected not just within one stack, but across all stacks, and as the individual stacks intersect they maintain a consistent and structured form.

How Operators are Evaluated

The XSI core evaluates the system of XSI data nodes, using dirty and clean flags per evaluation time. These flags indicate whether something has changed or not in the scene:

• dirty—indicates that the data is not up-to-date and needs to be recalculated

• clean—indicates that the data is already evaluated and ready for use

Notifications push the dirtiness through the graph and evaluations pull the cleanness through the graph. The core XSI algorithms keep track of whether data is clean or dirty and propagates or merges notifications. In the diagram below, the green node remains clean because it is an input to the operator and has not been changed, while the brown nodes and operators become dirty since their data has changed or one of it's input dependencies has been invalidated:

| The only way for a notification to occur in XSI is through some action of the user. Notifications are not pushed when the current frame has changed because the software is in playback mode.

Aside from managing notifications, the core XSI algorithms also ensure lazy evaluation of the data. They service requests for evaluations after determining dependencies between the data and operators to achieve minimal evaluation. They only compute what is needed when it is needed which improves performance and scalability.

Evaluations in XSI are almost always for viewing data in a scene view port, for rendering a region or for opening a property page. Hidden objects that are not dependent on viewed objects are not evaluated since they are not required for viewing. For example, a deformed sphere, if viewed, evaluates the deformation on the sphere, and determines the points in space; however, if hidden, no calculation of the deformation occurs. Data is often evaluated to return correct data while running commands or running scripts calling for data results in the SDK or due to a user change through an action in the user interface.

The following diagram illustrates the evaluation sequence required to clean the following operator graph. The green triangle is never evaluated because it is already clean and the brown data is evaluated with the calculations of operators 1 and 2.

The XSI core evaluation algorithms read the scene graph and dynamically create from the relations an optimal map of the connections, called the evaluation graph.