Write the Cook Method
Declaring a New Operator
In order that your CHOP can be found by Houdini, it must be added to
the existing operators. This is done through the newChopOperator(...)
method. This function simply creates a new OP_Operator type and adds
it to the incoming operator table. Let's look at an example as we
go through and describe the different parts of the operator.
void
newChopOperator(OP_OperatorTable *table)
{
table->addOperator(
new OP_Operator("stair", // Internal name
"Stair", // UI name
CHOP_Stair::myConstructor, // CHOP constructor
&CHOP_Stair::myTemplatePair, // Parameters
0, // Min # of inputs
0, // Max # of inputs
&CHOP_Stair::myVariablePair, // Local variables
OP_FLAG_GENERATOR) // generator/source
);
}
The OP_Operator has eight parts.
1. The Internal Name
This is used as the initial name of the CHOP when it is first created.
i.e. the first stair CHOP we add will be named stair1.
2. UI name
This will be the label of the CHOP in the parameter dialog. It has no use
internally.
3. CHOP Constructor
The method to construct the CHOP.
4. Parameters
This is the list of the parameters used in the CHOP.
5. Min # of Inputs
The minimum number of inputs for the CHOP. If it is a generator there
are usually no inputs coming in. (However, there are cases like the constant,
noise, and object CHOPs where it is desirable to have inputs.) If it is a
filter type then this entry will be at least one.
6. Max # of Inputs
The maximum number of sources allowed for the CHOP. This specifies how
many possible inputs the CHOP can have at one time. i.e. the merge CHOP
allows for 9999 total inputs.
7. Local Variables
This is a list of local variables that are used within the CHOP. i.e.
you may want to declare $C, or $NC for the current channel number and the
total number of channels in the CHOP. If you have no local variables then
enter a 0 here.
8. Generator/Source Flag
If the CHOP is to be a generator CHOP (i.e. generates channels) then
this flag must be set to OP_FLAG_GENERATOR. If you wish it to be a filter
then nothing needs to be entered as it is the default.
Defining the Parameters
Parameters are defined through a parameter list. You should read
Using Parameters for a complete
description of parameters before looking at the following example.
CHOP_SWITCHER2(4, "Stair", 7, "Channel");
// Names of gadgets
static PRM_Name names[] =
{
PRM_Name("number", "Number of Stairs"),
PRM_Name("height", "Stair Height"),
PRM_Name("offset", "Offset"),
PRM_Name("direction", "Direction"),
PRM_Name("channelname", "Channel Name"),
PRM_Name("start", "Start"),
PRM_Name("end", "End"),
// SampleRateName
// ExtendLeftName
// ExtendRightName
// DefaultValueName
};
// Menus
static PRM_Name CHOPstairMenu[] = {
PRM_Name("up", "Up"),
PRM_Name("down", "Down"),
PRM_Name(0),
};
static PRM_ChoiceList stairMenu((PRM_ChoiceListType)
(PRM_CHOICELIST_EXCLUSIVE
| PRM_CHOICELIST_REPLACE),
CHOPstairMenu);
// Defaults
static PRM_Default nameDefault(0,"chan1");
static PRM_Range stairRange(PRM_RANGE_RESTRICTED, 0, PRM_RANGE_UI, 10);
PRM_Template
CHOP_Stair::myTemplateList[] =
{
PRM_Template(PRM_SWITCHER, 3, &PRMswitcherName, switcher),
// page 1
PRM_Template(PRM_INT_J, 1, &names[0], PRMoneDefaults, 0, &stairRange),
PRM_Template(PRM_FLT_J, 1, &names[1], PRMoneDefaults, 0, &CHOP_DefaultValueRange),
PRM_Template(PRM_FLT_J, 1, &names[2], PRMzeroDefaults, 0, &CHOP_DefaultValueRange),
PRM_Template(PRM_ORD, 1, &names[3], PRMzeroDefaults, &stairMenu),
// page 2
PRM_Template(PRM_STRING, 1, &names[4], &nameDefault, 0),
PRM_Template(PRM_FLT_J, 1, &names[5], &CHOP_StartDefault, 0, &CHOP_FrameRange),
PRM_Template(PRM_FLT_J, 1, &names[6], &CHOP_EndDefault, 0, &CHOP_FrameRange),
PRM_Template(PRM_FLT_J, 1, &CHOP_SampleRateName, &CHOP_SampleRateDefault, 0, &CHOP_SampleRateRange),
PRM_Template(PRM_ORD, 1, &CHOP_ExtendLeftName, 0, &CHOP_ExtendMenu),
PRM_Template(PRM_ORD, 1, &CHOP_ExtendRightName, 0, &CHOP_ExtendMenu),
PRM_Template(PRM_FLT_J, 1, &CHOP_DefaultValueName, PRMzeroDefaults, 0, &CHOP_DefaultValueRange),
PRM_Template(),
};
This will produce eleven parameters on two pages (four on the first page,
seven on the second) along with a third page for common parameters found
in every CHOP. The third page is generated automatically and does not
need to be explicitly defined.
The first line determines how many pages will be available for the CHOP
parameters, the number of parameters on each page and the title displayed
on each tab. In this case, two pages are required so CHOP_SWITCHER2
is used. If four pages were needed, CHOP_SWITCHER4 would be used.
An array of PRM_Name type is created, holding the internal name of
each parameter as well as the UI name (the title that will be displayed on
the page). Some of the names are predefined common CHOPs names found in
PRM_ChopShared.h and do not need to be included in the
array.
The first PRM_Template entry is for the page switcher. The total
number of pages including the common page must be provided.
The first parameter is an integer value with an animatable parameter (_J).
Its internal name is number and its UI name is Number of Stairs.
It uses a common default value of one and has a range defined as
stairRange. This allows for integer values no less than zero
(and up to ten showing on the slider).
The second parameter is associated with the stair height and the third is
the offset. Both are floating point values with animatable parameters and
use default ranges. The height defaults to one and the offset to zero.
The fourth parameter is an ordinal type. Ordinal types are usually menu
types. It is very similar to the integer type except ordinals are not
animatable. The fifth entry specifies a menu, stairMenu, which
has been defined with two entries (Up and Down) corresponding to integer
values 0 and 1.
The parameters on the second page are standard Channel parameters found in
every generator CHOP. These parameters describe the channel name, start,
end, sample rate and extend options.
Writing the Cook Method
The cook method basically comes in two flavours, cooks that have
inputs and those that don't. All cooking involves evaluating the
parameters, processing the sample data and returning the status.
CHOPs without inputs generally begin the cook by calling the
destroyClip method to make sure all the sample data is
created fresh. Cooks that have inputs call the
copyInput method
which gets information from the parent clip.
Below are the two common examples of cooking CHOPs.
Generator example:
OP_ERROR
CHOP_Stair::cookMyChop(OP_Context &context)
{
CL_Track *track;
float samplerate;
float *data;
UT_String name;
int nchan;
char *cnames[MAX_CHANS];
...
destroyClip();
samplerate = RATE(context.myTime);
if(UTequalZero(samplerate))
{
addError(CHOP_ERROR_ZERO_SAMPLE_RATE);
return error();
}
myClip->setSampleRate(samplerate);
// evaluate parameters
CHAN_NAME(name, context.myTime);
nchan = name.expandArrays(cnames, MAX_CHANS);
my_NC = nchan;
...
// create the tracks
for (my_C=0; my_C < nchan; my_C++)
{
track = myClip->addTrack(cnames[my_C]);
track->...;
...
data = track->getData();
...
}
return error();
}
Filter example:
OP_ERROR
CHOP_Stair::cookMyChop(OP_Context &context)
{
CL_Track *track;
const CL_Clip *parent_clip;
const CL_Track *parent_track;
...
parent_clip = copyInput(context, 0, 1, 1); // data and slerps
// evaluate parameters
my_NC = myClip->getNumTracks();
...
// modify the tracks
for (my_C=0; my_C < my_NC; my_C++)
{
...
track = myClip->getTrack(my_C);
parent_track = parent_clip->getTrack(my_C);
...
track->...;
}
return error();
}
Please see the CHOP_Node base
class for more information on the class and its methods.
Cooking and Changing Handles
Handles allow for interactive modification of parameters in the graph
window. They appear as boxes or squares and may have bars in horizontal
or vertical directions depending on their function. CHOPs are not required
to have handles.
Handles are based on CHOP_Handle.h and required methods
for cooking and recognizing changes.
cookMyHandles example:
#define OFFSET_HANDLE 1
void
CHOP_Stair::cookMyHandles(OP_Context &context)
{
CHOP_Handle *handle;
CHOP_HandleLook hlook;
float start, end;
float xoffset, yoffset;
destroyHandles();
xoffset = (end - start) * 0.75 + start;
yoffset = OFFSET(context.myTime);
hlook = myChannels->getChannel("offset") ? HANDLE_GUIDE : HANDLE_BOX;
handle = new CHOP_Handle(this, "offset", OFFSET_HANDLE, xoffset, yoffset,
hlook, HANDLE_VERTICAL, (HANDLE_BAR |
HANDLE_LABEL_RIGHT | HANDLE_LABEL_BOTTOM));
myHandles.append(handle);
...
}
This creates a handle which modifies the offset parameter and allows for
motion in the vertical direction only. The handle is positioned three
quarters of the way between the start and end of the clip.
The general form of the CHOP_Handle allows for many variations:
CHOP_Handle( CHOP_Node *dad, // the parent
const char *name, // the handle label
int id, // used to determine changes
float xoffset, // the handle's x position
float yoffset, // the handle's y position
CHOP_HandleLook look, // what the handle looks like
CHOP_HandleMotion motion, // how the handle moves
int flags) // other handle descriptions
CHOP_HandleLook can be one of three types:
- HANDLE_GUIDE - a line that cannot be moved
- HANDLE_BOX - a filled in square
- HANDLE_SQUARE - a square outline.
CHOP_HandleMotion can be one of three types as well:
- HANDLE_HORIZONTAL - a vertical line that slides horizontally
- HANDLE_VERTICAL - a horizontal line that slides vertically
- HANDLE_PLANE - a square that allows both types of motion
Other flags further describe the appearance of the handle:
- HANDLE_LABEL_LEFT - place the label to the left of the handle
- HANDLE_LABEL_RIGHT - place the label to the right of the handle
-
- HANDLE_LABEL_TOP - place the label above the handle
- HANDLE_LABEL_BOTTOM - place the label below the handle
-
- HANDLE_BAR - allows the handle to be used in bar mode
- HANDLE_GRAPH - allows the handle to be used in graph mode
-
- HANDLE_WIDTH_END - the handle is at the end of the clip
The DEFAULT_FLAGS are set to position the label below and to the
left of the handle in graph mode.
Along with a cook method, a method to handle changes in the CHOP
(including changes to the handles) is also necessary.
handleChanged example:
float
CHOP_Stair::handleChanged(CHOP_Handle *handle, CHOP_HandleData *hdata)
{
float result = 0.0f;
OP_Network *net = 0;
float offset;
if (hdata->shift)
{
setCurrent(1);
if (net = getParent())
net->pickRequest(this, 0);
}
switch(handle->getId())
{
case START_HANDLE:
offset = CL_RINT(hdata->xoffset);
if (!hdata->shift)
SET_START(myHandleCookTime, offset);
result = toUnit(offset, hdata->unit);
break;
case END_HANDLE:
offset = CL_RINT(hdata->xoffset);
if (!hdata->shift)
SET_END(myHandleCookTime, offset);
result = toUnit(offset, hdata->unit, 1);
break;
case OFFSET_HANDLE:
if (!hdata->shift)
SET_OFFSET(myHandleCookTime, hdata->yoffset);
result = hdata->yoffset;
break;
}
...
return result;
}
This example checks for changes in three handles and modifies the parameter
associated with the affected handle. Handles are selected based on their
Id numbers. Any changes in the CHOP from something other than a handle
should also be included in this method.
When dealing with handles, it is important to make sure the units are
correct. The xoffset is always in samples and should be rounded off to the
nearest integer value just to be safe. In order to return the proper
value, the toUnit method must be used. This converts the xoffset
value to the type of unit currently displayed by the graph. If the handle
is at the end of an interval (has a HANDLE_WIDTH_END flag), a 1
must be provided as the third parameter. Only values in the x-plane need
to be converted to the proper units.
Table of Contents
Operators |
Surface Operations |
Particle Operations |
Composite Operators |
Channel Operators
Material & Texture |
Objects |
Command and Expression |
Render Output |
Mantra Shaders |
Utility Classes |
Geometry Library |
Image Library |
Clip Library
Customizing UI |
Questions & Answers
Copyright © 2004 Side Effects Software Inc.
477 Richmond Street West, Toronto, Ontario, Canada M5V 3E7