Jump to content
Storm Keeper

HDK - Easier way to define parameters

Recommended Posts

Hello, guys. While developing some complex operators in HDK I realized that defining lots of parameters becomes a bit tedious and routine task. So I decided to simplify this using C++ templates, and that's what I want to share. All you need is to define these parameters inside newSopOperator() function.

 

Here's how it looks like:

void
newSopOperator(OP_OperatorTable *table)
{
    PRM_LIST_START(myTemplateList);
    
    PRM_FLT(flt, "FLT", 0.01);
    PRM_INT(integer, "INT", 3);
    PRM_TOGGLE(toggle, "TOGGLE", ON);
    PRM_STRING(string, "STRING", "Hello, World!");
    PRM_FLT2(flt2, "FLT2", 0.01, 0.02);
    PRM_FLT3(flt3, "FLT3", 0.01, 0.02, 0.03);
    PRM_XYZ(xyz, "XYZ", 0.01, 0.02, 0.03);
    PRM_RGB(rgb, "RGB", 0.3, 0.15, 0.85);
    PRM_UVW(uvw, "UVW", 1, 0.5, 0);
    PRM_BUTTON(btn, "Button", callback);
    
    PRM_LIST_END();
    
    table->addOperator(
   new OP_Operator("hdkParmsDemo",
   "HDK Parms Demo",
    SOP_HDKParmsDemo::myConstructor,
    myTemplateList,
    0, // Min # of sources
    0, // Max # of sources
    0)
   );
}

The same easy thing is about reading:

    // Automatically create variables with parameter names
    READ_FLT(flt);           // double flt;
    READ_INT(integer);       // int integer;
    READ_TOGGLE(toggle);     // bool toggle;
    READ_STRING(string);     // UT_String string;
    READ_VEC2(flt2);         // UT_Vector2 flt2;
    READ_VEC3(flt3);         // UT_Vector3 flt3;

Header and sample SOP are attached in the archive. Would appreciate your feedback

HDK_Parms.zip

Share this post


Link to post
Share on other sites
Guest mantragora

So I decided to simplify this using C++ templates, and that's what I want to share.

 

Macros NOT templates.

 

Idea is old. I'm using it for more than parameters. For example to define selector for my SOP I have two lines of code. One is definition and second implementation. ;)

REUSABLESELECTOR_DECLARATION(PointGroup)
REUSABLESELECTOR_IMPLEMENTATION(PointGroup, SELECTOR_FOO_CLASSNAME, SOP_FOO_OPERATOR_SMALLNAME, SOP_PARMS_NAMESPACE_NAME(____unique_sop_title____)::UI::input0PointGroup_Parameter, false)
As for parameters, I'm building them as a piramid. Example:

#define ____default_toggle_data____(smallname, bigname, defaultstate, varname)\
static auto varname##ToggleParm_Name = PRM_Name(smallname, bigname);\
static auto varname##ToggleParm_Default = PRM_Default(defaultstate);
 
#define DEFAULT_TOOGLE_NO_JOIN(smallname, bigname, defaultstate, varname)\
____default_toggle_data____(smallname, bigname, defaultstate, varname)\
auto varname##Toggle_Parameter = PRM_Template(PRM_TOGGLE, 1, &varname##ToggleParm_Name, &varname##ToggleParm_Default);
 
#define DEFAULT_TOOGLE_JOIN(smallname, bigname, defaultstate, varname)\
____default_toggle_data____(smallname, bigname, defaultstate, varname)\
auto varname##Toggle_Parameter = PRM_Template(PRM_TOGGLE | PRM_TYPE_JOIN_NEXT, 1, &varname##ToggleParm_Name, &varname##ToggleParm_Default);
 
#define CUSTOM_TOOGLE_ON_PARAMETER(smallname, bigname, varname) DEFAULT_TOOGLE_NO_JOIN(smallname, bigname, 1, varname)
#define CUSTOM_TOOGLE_OFF_PARAMETER(smallname, bigname, varname) DEFAULT_TOOGLE_NO_JOIN(smallname, bigname, 0, varname)
#define CUSTOM_TOOGLE_ON_JOIN_PARAMETER(smallname, bigname, varname) DEFAULT_TOOGLE_JOIN(smallname, bigname, 1, varname)
#define CUSTOM_TOOGLE_OFF_JOIN_PARAMETER(smallname, bigname, varname) DEFAULT_TOOGLE_JOIN(smallname, bigname, 0, varname)
 
#define DEFAULT_FLIP_NORMALS_PARAMETER() CUSTOM_TOOGLE_OFF_PARAMETER("flipnormals", "Flip Normals", flipNormals)
____default_toggle_data____ is a base macro. I'm using ____foo____ notation to mark all things that shouldn't be used directly or shouldn't be used outside of the file they are defined in. So in this example base macro is used as a starting point for other macros.

Then you got macros that start as DEFAULT_TOOGLE_NO_JOIN and DEFAULT_TOOGLE_JOIN in which you can modify all data, including variable name in which they will be stored.

Then you have CUSTOM_TOOGLE_ON_PARAMETER etc... which are the most common presets I use for toggle parameter.

And the last one is DEFAULT_FLIP_NORMALS_PARAMETER in which you cannot change anything, including variable name. This forces consistency in all my operators, they all use exactly the same small and big name for Flip normals + variable is always called the same, so I don't have to search to much WTF I was thinking when I defined it.

 

Another example with Int prameter:

#define ____custom_integer_rangemintype_t_rangemaxtype_t_parameter____(smallname, bigname, rangemin, rangemax, rangemintype, rangemaxtype, defaultvalue, varname)\
static auto varname##IntegerParm_Name = PRM_Name(smallname, bigname);\
static auto varname##IntegerParm_Default = PRM_Default(defaultvalue);\
static auto varname##IntegerParm_Range = PRM_Range(rangemintype, rangemin, rangemaxtype, rangemax);\
auto varname##Integer_Parameter = PRM_Template(PRM_INT, 1, &varname##IntegerParm_Name, &varname##IntegerParm_Default, 0, &varname##IntegerParm_Range);
 
#define CUSTOM_INTEGER_0R_TO_MAXU_PARAMETER(smallname, bigname, rangemax, defaultvalue, varname) ____custom_integer_rangemintype_t_rangemaxtype_t_parameter____(smallname, bigname, 0, rangemax, PRM_RANGE_RESTRICTED, PRM_RANGE_UI, defaultvalue, varname)
#define CUSTOM_INTEGER_0R_TO_MAXR_PARAMETER(smallname, bigname, rangemax, defaultvalue, varname) ____custom_integer_rangemintype_t_rangemaxtype_t_parameter____(smallname, bigname, 0, rangemax, PRM_RANGE_RESTRICTED, PRM_RANGE_RESTRICTED, defaultvalue, varname)
#define CUSTOM_INTEGER_MINU_TO_MAXU_PARAMETER(smallname, bigname, rangemin, rangemax, defaultvalue, varname) ____custom_integer_rangemintype_t_rangemaxtype_t_parameter____(smallname, bigname, rangemin, rangemax, PRM_RANGE_UI, PRM_RANGE_UI, defaultvalue, varname)
#define CUSTOM_INTEGER_MINR_TO_MAXU_PARAMETER(smallname, bigname, rangemin, rangemax, defaultvalue, varname) ____custom_integer_rangemintype_t_rangemaxtype_t_parameter____(smallname, bigname, rangemin, rangemax, PRM_RANGE_RESTRICTED, PRM_RANGE_UI, defaultvalue, varname)
#define CUSTOM_INTEGER_MINR_TO_MINR_PARAMETER(smallname, bigname, rangemin, rangemax, defaultvalue, varname) ____custom_integer_rangemintype_t_rangemaxtype_t_parameter____(smallname, bigname, rangemin, rangemax, PRM_RANGE_RESTRICTED, PRM_RANGE_RESTRICTED, defaultvalue, varname)
I have similar thing for defining list of parameters, but it's a little more extended to help manging visibility of parameters later on in UpdateParmsFlags()

SOP_START_PARAMETERLIST()
    // all parameters
    SWITCH_VISIBILITY()
    // those that should be turned OFF if input is not connected
    SWITCH_INVERT_VISIBILITY()
    // those that should be turned ON if input is not connected
    SWITCH_BUTDONTFORCE_VISIBILITY()
    // those that should be turned off if input is not connected, but don't have to be turned on if is connected because they are managed by some other variable
SOP_END_PARAMETERLIST()
Lastly, a word of warning. MAKE SURE THAT YOU TEST THOSE MACROS PROPERLY, otherwise you may end up with REALLY fucked up errors that will make no sense, and you will end up chasing errors that are not the reason of your problems. Edited by mantragora

Share this post


Link to post
Share on other sites

The OpenVDB team wrote a nice wrapper class for creating parameters called ParmFactory.

 

https://github.com/dreamworksanimation/openvdb/blob/master/openvdb_houdini/houdini/ParmFactory.cc

 

Using it looks like this:

    hutil::ParmList parms;

    // Define a string-valued group name pattern parameter and add it to the list.
    parms.add(hutil::ParmFactory(PRM_STRING, "group", "Group")
        .setHelpText("Specify a subset of the input VDB grids to be processed.")
        .setChoiceList(&hutil::PrimGroupMenuInput1));

    // amplitude
    parms.add(hutil::ParmFactory(PRM_FLT_J, "amp", "Amplitude")
        .setDefault(PRMoneDefaults)
        .setRange(PRM_RANGE_RESTRICTED, 0.0, PRM_RANGE_FREE, 10.0));

    // Register this operator. (OpFactory)
    hvdb::MyOpFactory("My Operator", My_Opertor::myConstructor, parms, *table)
        .addAlias("OpenVDB LevelSet Noise")
        .addInput("VDB grids to noise")
        .addOptionalInput("Optional VDB grid to use as mask");




  • Like 4

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×