Writing Moov Simulation Scripts
- Writing Moov Simulation Scripts
Overview
Structure and hooks
The Moov simulation script is interpreted by the built-in Python 2.7 interpreter and should be correct Python code. Errors from the Python interpreter will be printed out to the script console.
In addition to being correctly formed Python, the script is expected to contain the following names:
MoovDefaultParameters
: tuple of tuples containing the descriptions and default values for the script's input parameters that will be exposed through the host's user interface ("Moov Simulation Parameters" in Maya's Attribute Editor, "Script Parameters" rollout in 3ds Max, "User Data" parameters in C4D). For a detailed description see User Interface.
Evaluate
: main hook; this function is invoked when the animation time changes or when any of the parameters is changed from the UI. More details here: Evaluation function.
_solver
: (optional) the global Moov solver object. If it doesn't exist in the script, it will be created behind the scenes. More details: Moov solver.
Simple example
Here is the simplest possible example script that contains only the bare minimum necessary to run: the MoovDefaultParameters
tuple (empty in this case) and the Evaluate
function, set up to print the strand count on each evaluation.
# Parameter descriptions and defaults
MoovDefaultParameters = ()
# Main hook
def Evaluate( hair, time, resetSolver, resetHairModel, newInputParamsDict, initialStateCapture ):
print hair.GetStrandCount()
return True
Parameters and user interface
The simulation script can define any number of parameters that are exposed to the user interface. These parameters are described by theMoovDefaultParameters
global variable. It is a tuple where each element describes a parameter in the form (name: string, type: string [, additional_values])
:
name
is a string representing the unique name of the parameter. Parameter names have to be unique, two parameters with the same name will trigger an error. The parameter name is used in the corresponding GUI control and also to access the current value of this parameter from thenewInputParamsDict
passed to theEvaluate
function.type
is a string describing the type of the parameter. Possible values are'int'
,'float'
,'bool'
,'string'
,'time'
,'enum'
,'ChannelSelector'
,'RampCurve'
, and'DataGenerator'
. There are also two additional types'Group'
and'Separator'
that help to format the user interface.additional_values
are one or more additional values used to initialize the parameter. Their number and type depends on the type of the parameter; some types don't require additional values. Most parameters require at least one additional value giving the default value of the parameter, and some allow optional values defining the numerical range of the UI slider controls.
The parameter values stored in the MoovDefaultParameters
variable specify only the initial (default) values that are used when building the user interface for a new MoovPhysics operator. The current values of the parameters can be read from the dictionary newInputParamsDict
passed to the Evaluate
function. The parameters are read-only; their values cannot be set from the Python script (except the defaults).
Parameter types
int
Holds an integer value. The parameter description is (name: string, 'int', default: int [, min: int, max: int])
. Specifying a default value is mandatory, minimum and maximum values are optional.
float
Holds a floating point value. The parameter description is (name: string, 'float', default: float [, min: float, max: float])
. Specifying a default value is mandatory. Minimum and maximum values are optional.
bool
Holds a boolean value (True
or False
). The parameter description is (name: string, 'bool', default: bool)
. Specifying a default value is mandatory.
string
Holds a string value. The parameter description is (name: string, 'string', default: string)
. Specifying a default value is mandatory.
time
Holds a time value (floating point) in seconds. The parameter description is (name: string, 'time', default: float)
. Specifying a default value is mandatory.
The difference from an ordinary floating point value is that it can be displayed in different units (i.e. frames) that can be set up by the user.
enum
Holds an integer value representing the index of a selected item in a drop-down box. The parameter description is (name: string, 'enum', default: int, enumName1: string, ...)
. Specifying a default value is mandatory. The remaining string values define the names of all items displayed in the drop-down box.
ChannelSelector
Holds an integer value, representing an encoded data channel index as selected by the user. The parameter description is (name: string, 'ChannelSelector')
. The user interface displays a drop-down box which is automatically filled in with the available data channels.
- 0 means no channel is selected;
- 1-1001 correspond to per-strand channels 0-1000;
- values larger than 1001 correspond to per-vertex channels with index
(1001 - channelSelectorValue)
.
Some useful utilities for working with data channels (including index decoding and access to channel data) can be found in the class HairModel.StrandChannel
.
RampCurve
Holds an IFunction1
object, representing a function with one parameter that allows to obtain ramp values (see the documentation for the ephere_ornatrix
Python module). The parameter description is (name: string, 'RampCurve' [, control_point_values])
. Each control point of the ramp is described by two floating point and one integer value: xPosition: float, yPosition: float, interpolationType: int
. The xPosition
and yPosition
values should be between 0 and 1. The allowed interpolation types are:
- 0: no interpolation;
- 1: linear interpolation;
- 2: smooth interpolation;
- 3: spline interpolation.
For example, let us take the following parameter definition:
MoovDefaultParameters = (
( 'ExampleRamp', 'RampCurve',
0.0, 1.0, 3, # First point: x = 0, y = 1, interpolation 3 (spline)
0.3, 0.5, 3, # Second point: x = 0.3, y = 0.5, interpolation 3 (spline)
1.0, 0.0, 3, # Third point: x = 1, y = 0, interpolation 3 (spline)
),
)
This produces the following ramp-down curve:
Specifying the control points is optional; if absent, the ramp curve will be initialized with a default ramp-down curve.
DataGenerator
Holds a MoovDataGenerator
object, allowing the evaluation of geometric queries on user-selected scene objects. The parameter description is (name: string, 'DataGenerator')
. The user interface displays a list box with scene objects and buttons for adding and removing objects from the list. For more information on how to use this parameter see the documentation for the ephere_ornatrix
Python module.
Separator
Creates a vertical separator between parameters. Used only for formatting the user interface. The parameter description is (name: string, 'Separator')
.
Group
Creates a group of parameters. Used only for formatting the user interface. The parameter description is (name: string, 'Group', collapsedByDefault: bool)
. The boolean parameter specifies if the group will be collapsed (True
) or expanded (False
) by default.
Evaluation function
The Evaluate
function is called when the MoovPhysics operator is evaluated. This happens when the current time of the scene changes, when the hair object passed to the operator changes, or when a parameter value is changed by the user. The function's signature is
def Evaluate( moovHairSimulator, time, resetSolver, resetHairModel, newInputParamsDict, initialStateCapture ):
# Do business
return True # if everything is fine, else return False
Parameters
moovHairSimulator: MoovHairSimulator
- aMoovHairSimulator
object that is updated by the caller with current data on the hair, collision meshes, and force fields specified in the MoovPhysics interface. The underlyingIHair
object can be retrieved usingmoovHairSimulator.GetHair()
. For more information see the documentation of theephere_ornatrix
Python module.time: float
- current scene time in seconds.resetSolver: bool
- indicates if the Moov solver needs to be reset.resetHairModel: bool
- indicates if the Python hair model needs to be reset (e.g. if the underlying hair geometry has changed).newInputParamsDict: dict
- dictionary containing the current values of all parameters, with parameter names as keys.initialStateCapture: StateCapture
- a Moov state capture object containing the initial state of the simulation. For more information see the documentation of theephere_moov
Python module.
Return value
True
if evaluation proceeds as planned;False
if there was an error during evaluation.
Accessing the GUI parameters
The values of the GUI parameters are passed to Evaluate
in the dictionary newInputParamsDict
, and can be accessed using the parameter names as keys.
Simple-valued parameters
For simple parameter types like int
, float
, bool
, string
, the dictionary contains the corresponding value. Parameters of type time
contain a floating-point time value in seconds. enum
parameters contain an integer index of the selected item.
Here is an example script demonstrating the usage of simple-valued parameters:
# Immutable tuples defining the GUI parameters
MoovDefaultParameters = (
( 'IntParam', 'int', 10, 1, 20 ),
( 'FloatParam', 'float', 10000.0, 0.1, 1000000000.0 ),
( 'StringParam', 'string', '' ),
( 'EnumParam', 'enum', 0, 'Choice0', 'Choice1', 'Choice2' ),
)
def Evaluate( moovHairSimulator, time, resetSolver, resetHairModel, newInputParamsDict, initialStateCapture ):
print "parameter value for 'IntParam': {}".format( newInputParamsDict['IntParam'] )
print "parameter value for 'FloatParam': {}".format( newInputParamsDict['FloatParam'] )
print "parameter value for 'StringParam': {}".format( newInputParamsDict['StringParam'] )
print "parameter value for 'EnumParam': {}".format( newInputParamsDict['EnumParam'] )
return True
RampCurve
The value of a RampCurve
parameter is an IFunction1
object. The y-values of the ramp can be obtained using the IFunction1.Evaluate
function. Here is an example of its usage that prints the ramp values for x-values at 0.1 intervals:
# Immutable tuples defining the GUI parameters
MoovDefaultParameters = (
( 'CurveParam', 'RampCurve' ), # initialized with a default ramp-down curve
)
def Evaluate( moovHairSimulator, time, resetSolver, resetHairModel, newInputParamsDict, initialStateCapture ):
curve = newInputParamsDict['CurveParam']
# Print curve values for x = [0..1]:
xValues = [ float( i )/10.0 for i in range( 11 ) ]
for x in xValues:
print "ramp curve value at {} is {}".format( x, curve.Evaluate( x ) )
return True
DataGenerator
The value of this parameter is a DataGenerator
object. It provides the following interface:
Evaluate(pointsList: list, method: DataGenerationMethod)
- allows evaluating a geometric function on a list of points against the scene objects set in the user interface. The function returns a list of floating-point values (one or several values per each point), depending on the chosen method. The available data generation methods are:DataGenerationMethod.IsInsideAny
: generates a single value of 0 or 1 per point based on whether the point is inside one of the specified scene shapes.DataGenerationMethod.IsInsideEach
: generates a list of 0/1 values based on whether each point is inside each of the specified scene shapes.DataGenerationMethod.IsInsideDistanceAndNormal
: generates a set of five values per point, containing:- one 0/1 value indicating if point is inside a shape;
- one value giving the closest distance of the point to the shape;
- three values giving the X, Y, and Z components of the normal to the nearest polygon.
GetObjectCount()
- returns the number of user-specified scene objects currently assigned to this data generator.
Here is an example of the usage:
import ephere_ornatrix as ox
# Immutable tuples defining the GUI parameters
MoovDefaultParameters = (
( 'SampleDataGenerator', 'DataGenerator' ),
)
def Evaluate( moovHairSimulator, time, resetSolver, resetHairModel, newInputParamsDict, initialStateCapture ):
dataGenerator = newInputParamsDict['SampleDataGenerator']
points = [ [3, 3, 3], [0, 0, 0] ]
# A simple example:
# The 'IsInsideAny' data generation method returns a single value per point:
# 1 if the point is inside any of the scene objects
# 0 if the point is outside all scene objects
generatedData = dataGenerator.Evaluate( points, ox.DataGenerationMethod.IsInsideAny )
for point, value in zip( points, generatedData ):
print "point {} value is: {}".format( point, value )
# A slightly more complex example:
# The 'IsInsideEach' data generation method returns as many values per point as there are scene objects.
objectCount = dataGenerator.GetObjectCount()
generatedData = dataGenerator.Evaluate( points, ox.DataGenerationMethod.IsInsideEach )
for pointIndex in range( len( points ) ):
print "point {}: {}".format( pointIndex, points[pointIndex] )
for objectIndex in range( objectCount ):
generatedValue = generatedData[pointIndex * objectCount + objectIndex]
state = 'inside' if generatedValue > 0.5 else 'outside'
print " is {} object {}".format( state, objectIndex )
return True
Embedded Python modules
The Ornatrix plugin contains two embedded Python modules: ephere_moov
for access to the Moov simulation engine; and ephere_ornatrix
for access to Ornatrix hair and scene objects. These modules are accessible for import when the Ornatrix plugin is loaded.
Utility Python modules
The following Python modules are used by the default Moov hair simulator script and can be found in the Ornatrix script directory:HairModel
: utility classes for creating and manipulating a particle model for hair using the Moov simulation engine.SolverUpdater
: utility classes for updating the Moov simulation with root positions and external forces at each simulation step. Contains both OpenCL and non-OpenCL implementations.Math
: contains utility classes implementing vector, matrix, and quaternion math functions.