### Let's Make Another Box Author Ernie Wright Date 1 June 2001 In the first installment of this tutorial, we looked at a simple plug-in that creates a box in Modeler by calling the `MAKEBOX` command. In this installment we'll assume you're comfortable with the plug-in basics covered there. We'll call `MAKEBOX` in a different way, and we'll add a user interface. The complete source is in `sample/boxes/box2/box.c`. Our Second Box Plug-in In this plug-in, we'll move the box creation out of the activation function into its own function, and we'll use `lookup` and `execute` rather than `evaluate` to issue the `MAKEBOX` command. Here's our new `makebox` function.
       int makebox( LWModCommand *local, float *size, float *center,
          int nsegments )
       {
          static LWCommandCode ccode = 0;
          DynaValue argv[ 3 ];

          argv[ 0 ].type = DY_VFLOAT;
          argv[ 0 ].fvec.val[ 0 ] = center[ 0 ] - 0.5f * size[ 0 ];
          argv[ 0 ].fvec.val[ 1 ] = center[ 1 ] - 0.5f * size[ 1 ];
          argv[ 0 ].fvec.val[ 2 ] = center[ 2 ] - 0.5f * size[ 2 ];

          argv[ 1 ].type = DY_VFLOAT;
          argv[ 1 ].fvec.val[ 0 ] = center[ 0 ] + 0.5f * size[ 0 ];
          argv[ 1 ].fvec.val[ 1 ] = center[ 1 ] + 0.5f * size[ 1 ];
          argv[ 1 ].fvec.val[ 2 ] = center[ 2 ] + 0.5f * size[ 2 ];

          if ( nsegments ) {
             argv[ 2 ].type = DY_VINT;
             argv[ 2 ].ivec.val[ 0 ] =
             argv[ 2 ].ivec.val[ 1 ] =
             argv[ 2 ].ivec.val[ 2 ] = nsegments;
          }
          else argv[ 2 ].type = DY_NULL;

          if ( !ccode )
             ccode = local->lookup( local->data, "MAKEBOX" );

          return local->execute( local->data, ccode, 3, argv, 0, NULL );
       }
Now we're getting into some stuff! Before I explain it, you might wonder why we'd bother with this apparently more complicated method at all, when we can use the simpler `evaluate` function. One answer is speed. The `lookup` and `execute` functions are Modeler's native mechanism for processing commands. When you use `evaluate` instead, Modeler parses the command string you pass and then calls the `lookup` and `execute` functions itself. You save some time by using `lookup` and `execute` directly, rather than building an `evaluate` string in your plug-in that Modeler is just going to take apart again. Also on the plus side, you only have to write your `makebox` function once. After that, you can call it as often as you like with a single line of code, and you can cut and paste it into other plug-ins. The modlib SDK sample is a library of about a hundred functions like `makebox`, each of which issues one of the Modeler commands. And if the low and high corner arguments to the `MAKEBOX` command are inconvenient, you can make up your own, as we did above with the size and center arguments. Obviously, your `makebox` could call `evaluate`, but if you're going to the trouble to write a `makebox` at all, you might as well go all the way and use the faster `lookup` and `execute`. Arguments Except for the last line, `makebox` spends all of its time constructing the arguments for the `execute` function, one of which is the argument list for the `MAKEBOX` command. Let's take a closer look at all of these arguments.
       int execute( void *mddata, LWCommandCode ccode, int argc,
          DynaValue *argv, EltOpSelect opsel, int *result );
`mddata` As with `evaluate`, this is the `data` field of the LWModCommand structure, which provides Modeler the context in which the command will be processed. You never need to know what's in this data. You just have to pass it to the functions that require it. `ccode` A command code obtained by calling `lookup`. A code is used to specify the command instead of the name of the command (in our case, "MAKEBOX") because it's faster. Modeler doesn't have to do a lot of string comparisons to figure out which command you're issuing. The string search need only be done once per Modeler session, when you call `lookup`. In our plug-in, the command code returned by `lookup` is stored in a static variable. We only call `lookup` the first time through `makebox`, when our static variable hasn't been initialized yet. `argc, argv` The argument list for the `MAKEBOX` command. `argv` is an array of DynaValues, and `argc` is the number of elements in the `argv` array. DynaValues are the union of a number of different data types. To initialize a DynaValue, you set the type field, then set the value of the union member appropriate for the type. DynaValues allow you to create a single array in which each element can be a different data type. `opsel` This is the selection mode for the command. It determines which existing geometry your command will interact with. You want some commands to work only on the polygons the user has selected, or on all polygons regardless of the selection, and so on. This is ignored for `MAKEBOX`, so we just set it to 0. `result` A few commands return command-specific result codes in this argument. `MAKEBOX` isn't one of them, so we set this to NULL. Before we move on to the interface part, there's one other thing to notice here. The third argument to the `MAKEBOX` command is optional. You don't have to specify a number of segments. To support this in our `makebox` function, we allow the `nsegments` argument to be 0. In that case, the third element of the `argv` array is a DynaValue of type `DY_NULL`. The `DY_NULL` type serves as a placeholder when the argument list for a command contains an optional argument that you aren't supplying. The Interface Our interface function displays a modal input window, or panel, that looks like this.
This is built with XPanels, a component of the platform-independent user interface API. The panel layout and event handling in XPanels are highly automated, so to create this panel, all we have to do is create a list of controls and initialize them, display the panel, and then collect the results. Here's the source code for our interface function.
       int get_user( LWXPanelFuncs *xpanf, double *size, double *center,
          int *nsegments )
       {
          LWXPanelID panel;
          int ok = 0;

          enum { ID_SIZE = 0x8001, ID_CENTER, ID_NSEGMENTS };
          LWXPanelControl ctl[] = {
             { ID_SIZE,      "Size",     "distance3" },
             { ID_CENTER,    "Center",   "distance3" },
             { ID_NSEGMENTS, "Segments", "integer"   },
             { 0 }
          };
          LWXPanelDataDesc cdata[] = {
             { ID_SIZE,      "Size",     "distance3" },
             { ID_CENTER,    "Center",   "distance3" },
             { ID_NSEGMENTS, "Segments", "integer"   },
             { 0 }
          };
          LWXPanelHint hint[] = {
             XpMIN( ID_NSEGMENTS, 0 ),
             XpMAX( ID_NSEGMENTS, 200 ),
             XpDIVADD( ID_SIZE ),
             XpDIVADD( ID_CENTER ),
             XpEND
          };

          panel = xpanf->create( LWXP_FORM, ctl );
          if ( !panel ) return 0;

          xpanf->describe( panel, cdata, NULL, NULL );
          xpanf->hint( panel, 0, hint );
          xpanf->formSet( panel, ID_SIZE, size );
          xpanf->formSet( panel, ID_CENTER, center );
          xpanf->formSet( panel, ID_NSEGMENTS, nsegments );

          ok = xpanf->post( panel );

          if ( ok ) {
             double *d;
             int *i;

             d = xpanf->formGet( panel, ID_SIZE );
             size[ 0 ] = d[ 0 ];
             size[ 1 ] = d[ 1 ];
             size[ 2 ] = d[ 2 ];

             d = xpanf->formGet( panel, ID_CENTER );
             center[ 0 ] = d[ 0 ];
             center[ 1 ] = d[ 1 ];
             center[ 2 ] = d[ 2 ];

             i = xpanf->formGet( panel, ID_NSEGMENTS );
             *nsegments = *i;
          }

          xpanf->destroy( panel );
          return ok;
       }
Dissecting the Interface Let's take a closer look.
       int get_user( LWXPanelFuncs *xpanf, double *size, double *center,
          int *nsegments )
The first argument to `get_user` is a pointer to LWXPanelFuncs. When we get to our activation function, I'll explain where this comes from. But for now, it's a structure containing the functions we need to work with our panel. The other three arguments are used to initialize our controls, and they'll be modified with the values entered by the user. Note that `size` and `center` are arrays of three doubles, while `nsegments` is a pointer to a single integer.
          LWXPanelID panel;
          int ok = 0;
LWXPanelID is just an opaque pointer that LightWave® uses to identify our panel. The double and integer pointers will be used when we retrieve the user's entries from the controls, and `ok` will be true if the user presses OK to dismiss the panel.
          enum { ID_SIZE = 0x8001, ID_CENTER, ID_NSEGMENTS };
XPanels uses integer codes to identify panel controls. User-defined controls have IDs that start at 0x8001.
          LWXPanelControl ctl[] = {
             { ID_SIZE,      "Size",     "distance3" },
             { ID_CENTER,    "Center",   "distance3" },
             { ID_NSEGMENTS, "Segments", "integer"   },
             { 0 }
          };
          LWXPanelDataDesc cdata[] = {
             { ID_SIZE,      "Size",     "distance3" },
             { ID_CENTER,    "Center",   "distance3" },
             { ID_NSEGMENTS, "Segments", "integer"   },
             { 0 }
          };
These two arrays define our controls. They look redundant, and to a certain extent, for what we're doing, they are. XPanels distinguishes between controls (the widgets drawn on your panel) and data descriptions, which define how you'll represent the values of your controls. You can define controls that don't have corresponding data descriptions, and vice versa. This amount of abstraction is useful for more sophisticated panels, but we're setting up the simplest kind of relationship between our controls and their values, so our control list and our data descriptions are parallel. Note that XPanels, as of this writing, doesn't support controls of type "integer3". We could simulate one with three separate "integer" controls, but for the sake of simplicity I chose not to do this. As a result, our `makebox` will create the same number of segments along all three axes.
          LWXPanelHint hint[] = {
             XpMIN( ID_NSEGMENTS, 1 ),
             XpMAX( ID_NSEGMENTS, 200 ),
             XpDIVADD( ID_SIZE ),
             XpDIVADD( ID_CENTER ),
             XpEND
          };
XPanels automates most aspects of control layout. The rules it uses resemble those used to build LightWave®'s own interface, so your plug-in's panels are aesthetically and functionally consistent with the rest of the program. In exchange for this, you must sacrifice some low-level control over the appearance of your panel. You can't specify the pixel positions of your controls, for example. Instead, you use hints to describe your controls and the appearance of your panel in a more abstract way. Here we define a sane range for the segments control and add dividers between the controls. The positions and sizes of the controls, their labels, and decorations like the dividers are all calculated for us. You can also use hints to group controls, put controls on different tabs, establish dependencies between controls, and lots of other things.
          panel = xpanf->create( LWXP_FORM, ctl );
          if ( !panel ) return 0;
This is where we create the panel. If panel creation fails for some reason, we return 0, which is also what we return when the user presses the Cancel button. XPanels supports two kinds of panels, called forms and views. Views are designed primarily for the panels associated with handler class plug-ins in Layout. Views work with instances, the unique data pointers returned by each invocation of a handler plug-in. But you're free to choose. You can use forms in your handlers, and we could have used a view here. Forms are just a little easier to grasp initially.
          xpanf->describe( panel, cdata, NULL, NULL );
          xpanf->hint( panel, 0, hint );
          xpanf->formSet( panel, ID_SIZE, size );
          xpanf->formSet( panel, ID_CENTER, center );
          xpanf->formSet( panel, ID_NSEGMENTS, nsegments );
These calls initialize the panel. The last two arguments to the `describe` function are NULL because our panel is a form. If it had been a view, these arguments would contain pointers to our get and set callbacks. The IDs in the `formSet` calls are value IDs corresponding to entries in the data description array (as opposed to control IDs from our control array). This is a distinction without a difference for us now, but I wanted to plant this in the back of your mind for a time when it will make a difference.
          ok = xpanf->post( panel );
The `post` function displays the panel and waits for the user. Using `post` makes the panel modal, which just means that everything else stops until the user presses OK or Cancel on the panel.
          if ( ok ) {
             double *d;
             int *i

             d = xpanf->formGet( panel, ID_SIZE );
             size[ 0 ] = d[ 0 ];
             size[ 1 ] = d[ 1 ];
             size[ 2 ] = d[ 2 ];

             d = xpanf->formGet( panel, ID_CENTER );
             center[ 0 ] = d[ 0 ];
             center[ 1 ] = d[ 1 ];
             center[ 2 ] = d[ 2 ];

             i = xpanf->formGet( panel, ID_NSEGMENTS );
             nsegments[ 2 ] = nsegments[ 1 ] = nsegments[ 0 ] = *i;
          }
If the user presses OK, we retrieve the value of each control and store it for use later in our plug-in. The `formGet` function returns a pointer to a variable of the appropriate type for the value.
          xpanf->destroy( panel );
          return ok;
       }
We're done with the panel, so we destroy it. If we needed to open this panel more than once, we could create it once, post it as many times as we need it, then destroy it when we exit. Activation All that remains is our activation function, which really hasn't changed very much.
       XCALL_( int )
       Activate( int version, GlobalFunc *global, LWModCommand *local,
          void *serverData )
       {
          LWXPanelFuncs *xpanf;
          double size[ 3 ]   = { 1.0, 1.0, 1.0 };
          double center[ 3 ] = { 0.0, 0.0, 0.0 };
          int nsegments = 1;

          if ( version != LWMODCOMMAND_VERSION )
             return AFUNC_BADVERSION;

          xpanf = global( LWXPANELFUNCS_GLOBAL, GFUSE_TRANSIENT );
          if ( !xpanf ) return AFUNC_BADGLOBAL;

          if ( get_user( xpanf, size, center, &nsegments ))
             makebox( local, size, center, nsegments );

          return AFUNC_OK;
       }
We've added storage for the box parameters so that these can be user-defined, and we call our `get_user` and `makebox` functions rather than `sprintf` and `evaluate`. We've also added a couple of lines having to do with that LWXPanelFuncs pointer, and as promised, I'll now explain where that pointer comes from.
       LWXPanelFuncs *xpanf;
       xpanf = global( LWXPANELFUNCS_GLOBAL, GFUSE_TRANSIENT );
       if ( !xpanf ) return AFUNC_BADGLOBAL;
The second argument to every activation function is the global function. The globals returned by this function are services provided by LightWave®. The XPanels global is a common example. When called with an `LWXPANELFUNCS_GLOBAL` argument, the global function returns a pointer to an LWXPanelFuncs structure containing the functions you need to build and display an XPanels interface. The Globals page of the SDK describes the global function in detail and lists the globals available in LightWave®. What's Next We'll be taking our user interface with us into the next installment, but we'll be leaving behind the `MAKEBOX` command as we explore the construction of boxes from individual points and polygons.