[SESI logo]

Houdini Development Toolkit - Version 6.5

Side Effects Software Inc. 2004

Command And Expression

Adding a Custom Expression

Adding a custom expression can be done anywhere and is not confined to a particular part of the code. For example, if a custom SOP needs a specific expression function, that function can be added in the SOP code.

This document deals with the mechanism for adding custom expressions using Command Library extensions.


The Callback Function

An expression function is added by specifying a callback function for evaluation. This callback function takes two arguments. The first is a place to store the result of the evaluation, the second is an array of arguments. These parameters are EV_SYMBOL types.

Each EV_SYMBOL contains the following data field:

struct EV_SYMBOL {
    // ...
    EV_TOKENVALUE   value;
    // ...
};
which is defined as follows
typedef union {
    float   fval;
    int     ival;
    char    *sval;
    void    *data;
} EV_TOKENVALUE;
When you register your callback function, you will specify the type of each argument to the function. Make sure that your callback's reference from the union matches the argument type that you registered.

Here is a simple call back function. The function simply returns the maximum of two floating point values.

#include <expr/libExpr.h> static void fn_max(EV_SYMBOL *result, EV_SYMBOL **argv) { if (argv[0]->value.fval > argv[1]->value.fval) result->value.fval = argv[0]->value.fval; else result->value.fval = argv[1]->value.fval; }

Installing the Callback

There's a class in the expression library called EV_FUNCTION. This is the definition of a callback entry. The structure is basically: int flag; char *function_name; int number_of_arguments; int return_type; int *argument_types; callback the_callback_function; void *data; The only time you should ever set the flag to anything but 0 is when your function is a time dependent function. For example, if you're hooking in a motion capture device, you may want the function to cause the user of the function to re-cook whenever time changes. In this case, you should make set the flag to CH_EXPRTIME (found in CH_Support.h). Aside from that, you should probably leave the flag alone (unless you really know what you're doing).

Also, the data field should be initialized to 0.

The return type should be a type that you feel comfortable with. Ususally, the return type is either one of EV_TYPEFLOAT or EV_TYPESTRING. The argument types are also usually these types. The only time that you might want to have a different type is if you extend the types in the expression library. However, that subject is beyond the scope of this document.

The easiest way of installing functions is to create a table containing all the definitions, then in the DSO install function, you can simply traverse all the table entries and install them. For example, if we wanted to add the function described above:

#include <UT/UT_DSOVersion.h> // needed for every custom DSO #include <CMD/CMD_Manager.h> static int maxArgTypes[] = { EV_TYPEFLOAT, EV_TYPEFLOAT }; static EV_FUNCTION theFunctionTable[] = { { 0, // The flag "max", // The name of the function 2, // The number of arguments EV_TYPEFLOAT, // My return type maxArgTypes, // The argument types for the function fn_max, // The callback 0 // Place holder (don't initialize to anything but 0) }, { 0 } // Null entry to indicate it's the end of the table }; void CMDextendLibrary(CMD_Manager *) { int i; for (i = 0; theFunctionTable[i].name; i++) ev_AddFunction(&theFunctionTable[i]); }

Data Types

There are really only four predominant types in the expression library, vectors, matricies, strings, and floats. All values are stored in the EV_TOKENVALUE structure.

To store a float, simply reference the fval element of the value field.

Strings are stored in the sval element of the value field. A string is responsible for allocating space for itself. If your function returns a string value, you must allocate space for the string. You do not have to worry about freeing the space, that is done automatically. If you do have to free the space, make sure to set the sval element to NULL.

Be careful with strings as the sval element may be a NULL pointer. In this case, it's up to you to determine how to interpret the value.

Vector types use the ev_Vector class to hold their data (EX_Vector.h). There are methods to query the size of the vector and to set values. The vector is stored as a void pointer in the value.data field of a symbol, so to get the vector out of a symbol, you would use something like:

ev_Vector *vector = (ev_Vector *)arg[0]->value.data;
So, for example, the function to do a sum of two vectors might be written as: static void fn_vsum(EV_FUNCTION *, EV_SYMBOL *result, EV_SYMBOL *arg[]) { ev_Vector *v0 = (ev_Vector *)arg[0]->value.data; ev_Vector *v1 = (ev_Vector *)arg[1]->value.data; ev_Vector *sum = (ev_Vector *)result->value.data; int len0, len1, i; len0 = v0->getSize(); len1 = v1->getSize(); if (len0 > len1) { // Here, v0 has more elements than v1 sum->grow(len0); for (i = 0; i < len1; i++) sum->setValue(i, v0->fastGet(i)+v1->fastGet(i)); // Now fill out the rest for (; i < len0; i++) sum->setValue(i, v0->vastGet(i)); } else { // Here, v1 has more elements than v0 (or the same) sum->grow(len1); for (i = 0; i < len0; i++) sum->setValue(i, v0->fastGet(i)+v1->fastGet(i)); // Now fill out the rest for (; i < len1; i++) sum->setValue(i, v1->vastGet(i)); } }

The matrix type is similar to the vector type, except that it uses the ev_Matrix class (found in EX_Matrix.h) to store its data.


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