Writing the Code for a Custom Operator
This section provides information on the following:
• Writing Preset-Based Custom Operators
• Writing RunTime Custom Operators
• Things to Watch Out for When Creating Your Custom Operator
Writing Preset-Based Custom Operators
The elements of all preset-based custom operators include:
• Initialization and Termination Entry Points
• Variables (Custom Parameters)
• The Operator’s GUID (Globally Unique IDentifier)
• Writing a Preset-Based Scripted Operator
• Writing a Preset-Based Compiled Custom Operator
A SPDL file is a text file that describes a plug-in. SOFTIMAGE|XSI uses the information in this file to host the plug-in. Preset-based custom operators always have a SPDL file that defines the entry points and parameters associated with the operator. For embedded scripted operators, the implementation is embedded within the SPDL file. For file-based scripted operators and compiled operators, the SPDL file contains a reference to the script or library file containing the implementation and the entry points.
Recommendation Use the Scripted Operator Editor to create the SPDL file for both scripted and compiled operators. The following sections describe how to use this approach to create either a script-based or a compiled custom operator.
This is the main engine of the custom operator: it performs some calculations (generally based on scene data) and writes new data back to the scene graph. For embedded scripted operators, this is the only entry point available and contains the implementation embedded within the Update section of the SPDL file. For compiled operators, SPDL files use the Update section to define the signature of the update entry point callback in the library file.
![]()
|
Scripted operators that use an external script file specified in the FileName entry do not need an Update entry in the SPDL file, but you need to use the Update callback for the handler function in the script file with the name (from FileName) prefix like so (where FileName = DoubleTrouble.): |
<Custom_Operator_Name_Update( In_UpdateContext, Out, Input1, ... InputN )
For file-based scripted operators, the Update entry point takes the following parameters:
|
Parameter |
Description |
|
In_UpdateContext |
Reference to the operator UpdateContext object. |
|
Out |
Output port to write to. Note: At least one output must be defined. |
|
InputN |
Input ports to read from. |
![]()
|
For information about the signatures and entry points for compiled operators, see About Entry Points for Compiled Operators. |
Declaring Input and Output Ports
The PortSet section declares the operator's input and output ports. Ports are organized into groups. Each group connects to one object or the same way to a number of objects.
PortSet "CppSplatter_Ports"
{
Group "Group_0"
{
Origin = Select;
Input "InGeom"
{
...
}
Input "InGPosY"
{
...
}
Output "OutGeom"
{
...
}
}
}
![]()
|
Don’t forget: there must be at least one output for any operator. |
Initialization and Termination Entry Points
File-based scripted operators and compiled operators can use these two callbacks to set some intial values (for example, initializing user data or setting custom parameters) and perform some cleanup.
The Init callback is fired when the custom operator is applied to a parameter (regardless of when it was installed) and the Term callback is fired when the scripted operator is removed from the parameter.
Use these callback signatures in the implementation file:
<Custom_Operator_Name>_Init( In_UpdateContext ) <Custom_Operator_Name>_Term( In_UpdateContext )
For file-based scripted operators, the Init and Term entry points take the following parameter:
|
Parameter |
Description |
|
In_UpdateContext |
Reference to the operator UpdateContext. |
Compiled operators use the Init and Term entries in the SPDL to define the signature of the initialization and termination (entry point) callbacks in the library file.
![]()
|
Embedded scripted operators do not have Init and Term entries in the SPDL file. You must use a file-based custom operator if you want to perform setup or cleanup tasks. |
Connections are ports that the Update entry point reads from (input) and writes to (output). There are two types of connections:
• Output ports are those elements that the Update implementation is responsible for updating.
• Input ports let your implementation access data from the scene. You can create an input connection for any scene data that your Update entry point requires in its calculations.
The connections must be properly defined for the operator to be evaluated correctly. Any scene data that is used in the computation should be added as an input connection; otherwise, the operator will not be called to update when the input changes. Similarly, any scene data that is set by the Update entry point should be added as an output connection; otherwise, the operator will not be called when an updated value is required.
![]()
|
At least one output must be defined and the connection can be to either an object or a an object’s animatable parameter. Groups and instances are complex and part of the connection information. See Declaring Input and Output Ports for information on how to declare inputs and outputs in the SPDL file. |
Custom operators can have variables as well as connections. Variables are custom parameters that belong to the custom operator and can be used to control its effect. You can edit and animate variables just like any other custom parameter in a scene. In addition, the variable is available to the custom operator in its calculations.
For example, if you create a custom deformation, you could define a variable to control the amplitude of the deformation. You can then animate the amplitude using keys, expressions, actions, and so on.
Describing the Property Set and Its Parameters
After you identify the plug-in, you need to declare the plug-in’s data. In the Splatter example, the data is grouped under the parameter set called “Splatter_Params”:
![]()
|
Make sure this GUID is the same as the one at the beginning of this SPDL file. Using the same GUID binds this parameter to the SPDL file. |
In addition to the main Update routine, you can also include extra code as a Helper. For scripted (embedded) operators, this code appears in the Helper section of the SPDL file. For compiled and file-based scripted operators, any exported function can be used (no Helper section in the SPDL file).
The Operator’s GUID (Globally Unique IDentifier)
Each SPDL file gets its own GUID which XSI uses to identify the operator in the Reference section. For operators created with the Scripted Operator Editor, the GUID is generated automatically when you save a preset of the operator (along with the rest of the SPDL file). XSI uses GUIDs to connect between the Preset and the SPDL file.
You can keep the same GUID and SPDL file when you convert from a scripted to a compiled operator, but you cannot copy GUIDs between separate operators and you cannot keep using the scripted operator with the same GUID.
![]()
|
Another alternative to copying a GUID from the scripted version to the compiled version is to use the XSIFactory.CreateGuid method to generate a new GUID. |
![]()
|
This section presents only a brief overview of SPDL files as they pertain to creating plug-ins. For more detailed information on SPDL files in general, see the SPDL Reference. |
Every plug-in needs to have a unique ID defined in the SPDL file so that XSI can distinguish one plug-in from another. To create a unique ID, you can use Microsoft's Guidgen tool or the XSIFactory.CreateGuid method.
![]()
|
Do not copy these IDs from one plug-in to another. Because they need to be unique, you must always generate a new one for each plug-in. |
The first thing that appears in a SPDL file is the unique ID (GUID) for your plug-in and its human name. For example, the SPDL for the Splatter example begins with these two statements:
Reference = "{7C006AC6-1A4D-11d5-900F-00A0C9830266}";
Name = "CppSplatter";
Writing a Preset-Based Scripted Operator
You can simplify the process of developing a compiled custom operator for SOFTIMAGE|XSI in C++ by using the Scripted Operator Editor to develop a prototype. For more information on using the scripted operator editor in SOFTIMAGE|XSI, see the Scripted Operators chapter of the Customization (user) guide.
The editor makes it easy to set up the connections and parameters and to try out some initial implementation of your Update function.
Once you are ready to convert the operator from being a runtime operator to a preset-based operator, the scripted operator editor can handle installing the scripted operator, generating the preset and generating the SPDL file.
Once you have generated the SPDL file you can start updating it directly. However, if you change the SPDL file, you cannot edit the operator using the Scripted Operator Editor anymore, because regenerating the SPDL file would overwrite your changes.
![]()
|
For more information about the components of a SPDL file, see Adjusting the SPDL Files for Compiled Operators. |
You can use the scripted operator editor in SOFTIMAGE|XSI to generate a SPDL file for a set of connections and parameters, using these tasks:
• Setting up the connections and parameters in the scripted operator editor.
• Applying the scripted operator.
• Saving it as a SPDL file.
• Modifying the SPDL file so that it describes the C++ plug-in.
![]()
|
For more information on creating scripted operators, see the Scripted Operators chapter of the Customization (user) guide. For more information on converting scripted code to compiled, see Writing a Preset-Based Compiled Custom Operator.
|
Writing a Preset-Based Compiled Custom Operator
Creating the initial SPDL file for a compiled custom operator is largely the same as creating a script-based operator (see Writing a Preset-Based Compiled Custom Operator). You can use the Scripted Operator Editor to set up the connections and parameters of the operator. Then once it’s ready you can generate an initial SPDL file and Preset file.
To convert the SPDL file of an embedded scripted operator to a compiled or file-based scripted operator, you have to manually change the Plugin section of the SPDL file.
This section includes information about the following topics:
• Adjusting the SPDL Files for Compiled Operators
• About Entry Points for Compiled Operators
• Exporting Compiled Functions
• Differentiating Compiled Plug-ins from Scripted Plug-ins
Adjusting the SPDL Files for Compiled Operators
For a compiled plug-in, you need to modify the plug-in section of the SPDL file to point to a DLL or DSO instead of defining a script. The Plug-in section provides the name of the DLL and the name of the entry points that XSI can call.
![]()
|
The SPDL and DLL (or DSO) files must reside in the same directory in order for your plug-in to work. |
A valid SPDL file for a plug-in consists of four main sections:
• Identification—see Identifying the Plug-in.
• The PropertySet section, including multiple Parameter subsections—see Describing the Property Set and Its Parameters.
• The PortSet section—see Declaring Input and Output Ports.
• The Plug-in section—see Differentiating Compiled Plug-ins from Scripted Plug-ins.
About Entry Points for Compiled Operators
The plug-in section in the SPDL file declares one or more entry points. An entry point is an exported function in the plug-in's DLL, which XSI can resolve and call at runtime.
When calling an entry point, XSI passes a context through which the entry point can get the information, services, and additional arguments it needs:
XSI::CStatus <Update function>( XSI::UpdateContext& in_ctxt, XSI::OutputPort& out_port);
|
Parameter |
Description |
|
in_ctxt |
Reference to the operator update context object. |
|
out_port |
Output port to write to. |
You can unload and reload an add-on library file while you are debugging it. The Init function manages loading and reloading of add-on library files:
XSI::CStatus <Init function>( XSI::UpdateContext& in_ctxt, long in_loadState );
|
Parameter |
Description |
|
in_ctxt |
Reference to the operator update context object. |
|
in_loadState |
Specifies whether Init is called when the DLL is loaded for the first time (0) or when it is reloaded (XSI::siDLLLoadingUnloading). |
You can unload and reload an add-on library file while you are debugging it. The Term function manages unloading and destruction of add-on library files:
XSI::CStatus <Term function>( XSI::UpdateContext& in_ctxt, long in_unloadState );
|
Parameter |
Description |
|
in_ctxt |
Reference to the operator update context object. |
|
in_loadState |
Specifies whether Term is called when the operator object is being destroyed (0) or unloaded (XSI::siDLLLoadingUnloading). |
The functions that you write depend on what you want your plug-in to do. Most plug-ins need to get parameters and inputs, and set outputs in their Update entry points. XSI passes an UpdateContext and an OutputPort to the Update entry point. Parameters and inputs are obtained from the Operator which can be retrieved from the UpdateContext. The output value or object to modify is obtained from the OutputPort.
If the plug-in has more than one output, the Update entry point is called to update only one output at a time. The plug-in can find out which output is being updated from the OutputPort.
Any entry point that you write as part of a complied operator for XSI needs to be exported so that XSI can find it, but you can divide your code any way that you see fit. For example, you could break the details of the calculation into helper functions or use an object-oriented approach with the Update callback calling a method of an object. These details are hidden from XSI, which only needs the Update, Init and Term functions exposed.
You can export your entry points by specifying a DEF file similar to this one:
LIBRARY MYFUNC
DESCRIPTION "My custom function."
EXPORTS
CppOperator_Update @1 PRIVATE
CppOperator_Init @2 PRIVATE
CppOperator_Term @3 PRIVATE
For instructions on how to specify your DEF file to the linker and add it to your project, see the MSDN Library.
![]()
|
Remember that for calling callback functions, XSI uses the name of the function, not its ordinal value. |
Differentiating Compiled Plug-ins from Scripted Plug-ins
The plug-in section declares the plug-in as being a compiled plug-in implemented in a DLL. This is the mainly where the SPDL file of a compiled plug-in differs from that of a scripted plug-in.
![]()
|
File-based scripted operators are only supported for SPDL version 2.0.0.2 or later. |
Writing Runtime Custom Operators
You can use the Scripted Operator Editor to create a simple scripted operator which could be very useful for prototyping more complex operators. However, for more flexibility you can create your runtime operator using scripting, which is described in this section.
Writing Runtime operators consists of these steps:
1. Create a custom operator using one of the following methods:
-XSIFactory.CreateScriptedOp
-XSIFactory.CreateScriptedOpFromFile
-Parameter.AddScriptedOp
-Parameter.AddScriptedOpFromFile
-ProjectItem.AddScriptedOp
-ProjectItem.AddScriptedOpFromFile
These methods return the new CustomOperator object which allows you to define the ports and parameters and connect the operator. The XSIFactory methods allow you to create a custom operator independently of any scene information.
2. Write the code for the Update callback. You can either leave it as part of the current script (via the CustomOperator.Code property) or save it in an external implementation file (use the CustomOperator.FileName property).
![]()
|
Implementing the code in the CustomOperator.Code property is convenient when prototyping but can cause maintenance problems when the many instances of the operator are saved into different scenes. Because each has its own copy of the code, it is harder to fix bugs in the operator’s code. For this reaon a runtime operator with an external file or a preset-based operator can be better. |
In the Update, Init or Term callbacks, you may need to read data from input ports and parameters. For more information on accessing operator information through the UpdateContext object, see Custom Operator Callbacks.
3. You can define input and output ports on the custom operator with the following methods:
-CustomOperator.AddInputPort
-CustomOperator.AddOutputPort
-CustomOperator.AddIOPort
4. You can define parameters on the custom operator by first creating a parameter definition (see XSIFactory.CreateParamDef or XSIFactory.CreateParamDef2) and then using it with the CustomOperator.AddParameter method.
5. When your custom operator is ready, you can use the Operator.Connect method to connect it.
All custom operator callbacks should be prefixed with the name of the custom operator. They all take the UpdateContext object as an input argument from which you can get the Operator itself (via the UpdateContext.Operator property), which in turn gives you access to the input and output ports (via the Operator.InputPorts and Operator.OutputPorts properties) and the ParameterCollection (via ProjectItem.Parameters).
The Update callback takes additional arguments. Both Init and Term are fired only once during the lifespan of the operator in the scene.
![]()
|
Don’t confuse these callbacks with the callbacks used to implement self-installing plug-ins such as Custom Properties or Menus. Custom Operators cannot be implemented as plug-in items in a self-installing plug-in file. |
[CustomOperatorName]_Update( context, out, in1, in2, ... inN );
You use the Update function to implement the main logic for your operator. Update takes the UpdateContext object as an input argument as well as one output port and any number of input arguments (one per input port).
[CustomOperatorName]_Init( context );
You can use the Init callback to initialize any data. For example, you may want to initialize the values of your custom parameters based on the values of any input ports at connection time. This is called once for each instance of the operator (before the Update function is called. You can use the UpdateContext.UserData property to store per-instance data, then retrieve this data every time Update is called.
[CustomOperatorName]_Term( context );
You can use the Term callback to implement any cleanup after the operator. For example, you may want to restore any scene settings that were modified when the operator was connected.
Things to Watch Out for When Creating Your Custom Operator
Connecting an operator into the XSI Operator graph correctly requires using several services and following several principles:
• Connect an input port to all data accessed and an output port to all data changed.
XSI operators pull all inputs before using the data to ensure that the data is correct. Scripted operators have all inputs automatically pulled before the Update() entry point is called. In the updating routine, compute the result of the operation and, when complete, write it onto its output port connection data. Do not traverse the graph from inside the operator—only use the data that is accessible through the connected ports.
• Only read from read ports, only write to write ports.
In the case of an operator that reads and writes, it is possible that your input and output may contain different versions of the data. You have to make sure your operator works correctly either way. To ensure correct data, read from the input ports at the beginning of the evaluation, and write to the output ports at the end.
• Connect to Object or Parameter connections.
All object connections have a reference to a copy of the data while parameter connections contain the access to a leaf in the parameter hierarchy of a piece of data (leaf data source). For example, connecting on a property would be an object connection and connecting directly on one of its parameters would be a parameter connection.
• Connect to both input and output if the operator is not a source operator.
If the operator is not a source operator (does not define and create a piece of data), it must be connected with an input and output on the object to ensure the correct and full evaluation of the data.
• Do not send notifications (use Commands in the Update entry point) during an evaluation of an operator.
The fundamental principles of the lazy evaluation model are:
- Do not send notifications during an evaluation of an operator.
- Do not pull an evaluation while servicing a notification.
If these principles are broken the scene may at best result in inconsistent or inaccurate scene data and at worst case cause instability in the software.
This means that a user must not use commands inside the Update() function of a scripted or compiled operator. The object model has appropriate access mechanisms that follow this principle, which are marked with an indicator on their reference pages.
• Maximize operator port groupings.
Groupings of ports enlarge the set of connections of an operator at runtime. The port type inside a group is fixed; however, additions may be added to a port grouping. These groupings are also used to arrange connections in logical data sets.
• Specialize the operator port types.
Specializing operator ports enables you to specialize the services they provide to specific object types. You can do this with specified object definitions via major and minor connection category distinctions on each port. For example, an operator connecting to the geometry on a primitive may choose to only service NURBS primitives and not Mesh primitives by specializing the connection port type to a NURBS-only object.
• Maximize the efficiency of your multi-output operator.
The Update() entry point is called once for each output port. So if all your outputs are by-products of the same computation, you either need to find a way to cache your results or redo your computation each time and ignore irrelevant data.
• Make sure you do not mistake the system time for the evaluation time.
The evaluation time is not necessarily the current system time. Motion blur, ghosting, and simulation operators legally pull graph evaluations at different times. Operators must use the passed-in time to evaluate.