Writing Plugin Brushes 

 

Overview 

In Edit Guides operator you can use plugin brushes to brush hair in any user-defined manner. This provides unlimited possibilities to hair editing via our brushing interface by allowing you to quickly and easily prototype and develop custom brush tools. The brushes are written in VFX-friendly Python language and saved as .py files into our common "User Brush" directory. Ornatrix will automatically discover user brush scripts and make them available for selection via the Attribute Editor.

 

User brush directory 

The plugin brush files are located in "scripts/Brushes" in the Ornatrix installation directory. The standard locations are:
  • Windows: "C:\Program Files\Ephere\Ornatrix for Maya\scripts\Brushes".
  • Linux: "/opt/Ephere/Plugins/Autodesk/Maya/Ornatrix/scripts/Brushes".

Brush scripts are read once when Ornatrix is loaded. To refresh the brush list with newly added brushes and/or reload modified scripts, press the "Reload Brushes" button in Attribute Editor. All "*.py" files from the user brush directory are read; error and diagnostic messages are printed in the Maya Script Editor window.

 

Writing a brush Python file 

 

Structure and hooks 

The brush script contains standard Python code that is executed by the Maya Python interpreter. The Ornatrix functionality can be accessed by importing the Ornatrix Python module.

The entire brush script is executed once when it is loaded. After that, hooks are executed on each brush stroke.

 

Evaluate 

def Evaluate( guidesEditor, brushParameters, brushDirectionInObjectSpace, brushStrokeLength, strandIndices )
An Evaluate function with the above signature must be defined in the brush file and is called on each brush stroke. It receives the following parameters for processing:
  • guidesEditor - an IGuidesEditor object
  • brushParameters - a BrushParameters object
  • brushDirectionInObjectSpace - a Vector3 vector giving the direction of the brush stroke
  • brushStrokeLength - float giving the length of the brush stroke
  • strandIndices - a list of integer strand indices affected by the brush stroke
 

PreCompute and PostCompute 

TODO

 

Brush name 

A BrushName string can be defined in the script to identify this brush in the list of user-defined brushes. If this is not defined, the file name is used.

 

Ornatrix Python Module 

The embedded Ornatrix Python module (from Ornatrix plugin file) provides access to Ornatrix functionality for the brush script, including functions for querying and editing the hair object, access to brush parameters, etc. The module name is ephere_ornatrix, and it can be imported e.g. as:

import ephere_ornatrix as ox

This module can only be used when the Python interpreter is started by Ornatrix.

 

Vectors 

The ox.Vector3 class provides common vector operators and functions such as addition, subtraction, dot product (Dot), squared length (LengthSquared), etc.

 

Coordinate space 

The ox.CoordinateSpace enum is used to indicate the coordinate space for hair vertex coordinates. The allowed values are ox.CoordinateSpace.Strand (strand space) and ox.CoordinateSpace.Object (hair object space).

 

Guides editor 

The IGuidesEditor object is passed as first parameter to Evaluate and has the following methods:
  • GetEditableGuides() - returns a hair interface, IHair, that can be used to manipulate the current hair object.
  • GetVertexWeights(strandIndex) - returns a list of weights for the vertices of the corresponding strandIndex during the current brush stroke. Vertex weights vary from 0 (vertex not affected) to 1 (vertex fully affected) and reflect fall-off from the brush center.
 

Hair interface 

The hair interface IHair provides the following methods for querying and modifying a hair object:
  • GetStrandPoints(strandIndex, coordinateSpace) - returns list of Vector3 coordinates of strand vertices.
  • SetStrandPoints(strandIndex, pointsList, coordinateSpace) - sets strand vertex coordinates to the Vector3 values from pointsList

In the above functions, coordinateSpace is an ox.CoordinateSpace enum value.

Also, the following methods for manipulating hair data channels are available:
  • GetChannelIndex(strandDataType, channelName)
  • GetStrandChannelCount(strandDataType)
  • SetStrandChannelCount(strandDataType, count)
  • GetStrandChannelData(channelIndex, strandIndex)
  • GetStrandChannelData(channelIndex, strandIndex, pointIndex)
  • GetVertexChannelData(channelIndex, vertexIndex)
  • SetStrandChannelData(channelIndex, strandIndex, floatValue)
  • SetStrandChannelData(channelIndex, strandIndex, pointIndex, floatValue)
  • GetUniqueStrandChannelName(strandDataType, rootName)
  • GetStrandChannelName(strandDataType, channelIndex)
  • SetStrandChannelName(strandDataType, channelIndex, name)
 

Brush options 

The BrushParameters object provides read-only access to brush options set in the Attribute Editor. The currently implemented parameters are:

  • GetStrength(usePenPressure = True) - retrieves the Brush Strength (float)
The following properties can be used to query GUI brush options. The property names are the same as in the GUI
  • affectSelectedOnly (bool)
  • affectByRootsOnly (bool)
  • respectVertexWeights (bool)
  • preserveStrandLength (bool)
  • optimizeStrandGeometry (bool)
  • resampleStrands (bool)
 

A quick example 

# Import embedded Ornatrix module
import ephere_ornatrix as ox

# User-defined brush name, shown in the drop-down list for brush selection. If not defined, the file name will be used
BrushName = 'Demo Move'

def Evaluate( guidesEditor, brushParameters, brushDirectionInObjectSpace, brushStrokeLength, strandIndices ):
    """Applies a brush stroke to a list of strands.""" 

    hair = guidesEditor.GetEditableGuides()
    brushStrength = brushParameters.GetStrength() * brushStrokeLength

    for strandIndex in strandIndices:
        # Multiply strength by soft selection if only affecting selected strands
        strength = brushStrength if not brushParameters.affSelOnly else brushStrength * hair.GetStrandChannelData( 0, strandIndex )
        delta = brushDirectionInObjectSpace * strength
        strandPoints = hair.GetStrandPoints( strandIndex, ox.CoordinateSpace.Object )
        vertexWeights = guidesEditor.GetVertexWeights( strandIndex )
        # Start from 1 to avoid modifying the root vertex
        for pointIndex in range( 1, len( strandPoints ) ):
            strandPoints[pointIndex] += delta * vertexWeights[pointIndex]
        hair.SetStrandPoints( strandIndex, strandPoints, ox.CoordinateSpace.Object )