### A Tool Box Author Ernie Wright Date 3 July 2001 In the first three installments of this tutorial, I introduced the basics of plug-in creation, including the organization of plug-ins into classes, the use of the SDK headers, the activation function, the server record, and function pointers. We walked through the build process with a specific compiler. We used globals that allowed us to create a user interface, query the surface list, and convert between text and binary representations of numbers. We learned how to process a command line so that our plug-in can be run in batch mode. And we created a box in Modeler, both by issuing a command (in two different ways) and by calling mesh edit functions. In this final installment, we'll apply what we've learned to create a tool, a plug-in that interacts with the user in the same way that Modeler's native tools do. The user will be able to click and drag in Modeler's interface to position and size our box, and our non-modal panel will open when the user requests our numeric options. Unlike the first three versions of our box plug-in, this one isn't a CommandSequence plug-in. Modeler tools are of the MeshEditTool class. The complete source code for the box tool can be found in `sample/boxes/box4/box.c`, `tool.c` and `ui.c`. Because it's difficult to use a windowed debugger to trace the execution of code that responds to mouse clicks and drags, I've also written debug versions of the tool and interface modules called `wdbtool.c` and `wdbui.c` that write event information to a file. `wdbtool.c` contains a few lines of Windows-specific code related to hooking mouse events. Hopefully they can be easily replaced for use with other operating systems. The Basic Idea Tool plug-ins supply a set of callbacks, functions that Modeler calls while the tool is active. These callbacks respond to user actions by drawing the tool and generating the geometry that the tool creates or modifies. A handle is a point that the user can grab and move to change the operation of the tool. Our plug-in will support two handles, one for the center of the box, and the other at the (+x, +y, +z) corner to control the size. (More sophisticated tools will usually support many more handles.) The following table shows the user clicking to establish the box center, then dragging to pull out the corner handle. The callbacks are listed in the order in which they're typically called during each part of this operation.
mouse down | mouse move | mouse up | spacebar
`count start dirty test` | `handle adjust dirty test build draw` | `end` | `done`
We'll cover the implementation of each of these callbacks, pretty much in the same order. But before we do that, note that all of the callbacks take an LWInstance (a pointer to void) as their first argument. This is the tool's instance, a structure we design to hold all of the information we need to maintain the tool's state and generate the geometry. Our instance data structure is called BoxData. One of these is allocated in our activation function, and it persists until the user is finished with the tool. Count, Start These two callbacks are related. They're only called when the user clicks the left mouse button to begin dragging the tool, and `start` is only called if `count` returns 0.
       static int Count( BoxData *box, LWToolEvent *event )
       {
          return box->active ? 2 : 0;
       }
From our tool's point of view, there are two different kinds of mouse down events. The first is the initial mouse down, before any box has been dragged out and before we've drawn the handles. For that case, our `box->active` is FALSE, and our `Count` returns 0, so that our `Start` will be called. The other kind of mouse down occurs after the first one. The user is modifying an existing box, rather than starting a new one. In this second case, `Count` returns 2 (because we have 2 handles), and Modeler doesn't call `Start`.
       static int Start( BoxData *box, LWToolEvent *event )
       {
          int i;

          if ( !box->active )
             box->active = 1;

          for ( i = 0; i < 3; i++ ) {
             box->center[ i ] = event->posSnap[ i ];
             box->size[ i ] = 0.0;
          }
          calc_handles( box );

          return 1;
       }
When Modeler calls `Start`, the user has just clicked the left mouse button to begin a new box. We make sure `box->active` is now TRUE, and we set the size of the box to 0 and the center to the point at which the user clicked. We initialize the precalculated handle positions, then return 1, the index of the second handle, to indicate that the user has grabbed the sizing handle. While the left mouse button remains down, the `handle` callback will only be called for this handle. Dirty, Test These two callbacks are also somewhat related. The `dirty` callback tells Modeler whether the tool needs to be redrawn on the screen. The `test` callback tells Modeler whether the tool needs to create new geometry or discard existing geometry.
       static int Dirty( BoxData *box )
       {
          return box->dirty ? LWT_DIRTY_WIREFRAME | LWT_DIRTY_HELPTEXT : 0;
       }
`Dirty` is only concerned with the tool's appearance to the user. After the initial mouse down for a new box, `box->dirty` is FALSE, since we haven't drawn anything yet, and we tell Modeler that nothing needs to be redrawn. During mouse move events, our `Adjust` callback is called, and this sets `box->dirty` to TRUE so that we get redrawn to follow the user's mouse moves. We're also dirty after receiving reset and activate events in our `Event` callback. Our `Draw` callback sets `box->dirty` to FALSE again after redrawing the tool.
       static int Test( BoxData *box )
       {
          return box->update;
       }
Like `box->dirty`, our `Adjust` and `Event` callbacks set `box->update`, depending on our tool's state at that point. `Build` also sets it (to `LWT_TEST_NOTHING`) after creating the box geometry. Our `Test` just returns the value in `box->update`. Handle This callback tells Modeler about one of our handles.
       static int Handle( BoxData *box, LWToolEvent *event, int handle,
          LWDVector pos )
       {
          if ( handle >= 0 && handle < 2 ) {
             pos[ 0 ] = box->hpos[ handle ][ 0 ];
             pos[ 1 ] = box->hpos[ handle ][ 1 ];
             pos[ 2 ] = box->hpos[ handle ][ 2 ];
          }
          return handle + 1;
       }
`Handle` is called during mouse moves, but only for the handle the user is currently moving. It's also called right after mouse down, if `Count` returns a non-zero number of handles. In that case, it's called for every handle, and Modeler uses the positions to determine which handle the user has selected. The return value is the priority of the handle, which is used to decide between handles that overlap visually (have the same apparent position in the viewport). When the user points to two or more overlapping handles, Modeler chooses the one with the highest priority. Adjust The `adjust` callback is called during mouse moves to tell you that a handle is being dragged.
       static int Adjust( BoxData *box, LWToolEvent *event, int handle )
       {
          if ( event->portAxis >= 0 ) {
             if ( event->flags & LWTOOLF_CONSTRAIN ) {
                int x, y, xaxis[] = { 1, 2, 0 }, yaxis[] = { 2, 0, 1 };
                x = xaxis[ event->portAxis ];
                y = yaxis[ event->portAxis ];
                if ( event->flags & LWTOOLF_CONS_X )
                   event->posSnap[ x ] -= event->deltaSnap[ x ];
                else if ( event->flags & LWTOOLF_CONS_Y )
                   event->posSnap[ y ] -= event->deltaSnap[ y ];
             }
          }
Before we move the handle, we check whether its new position should be quantized or fixed by a constraint. Typically, this is to account for the user holding down the Ctrl key. The fact that Modeler doesn't do this for us means that we aren't required to honor this convention, but in our case (and in most cases), we have no reason not to.
          if ( handle == 0 ) {  /* center */
             box->center[ 0 ] = event->posSnap[ 0 ];
             box->center[ 1 ] = event->posSnap[ 1 ];
             box->center[ 2 ] = event->posSnap[ 2 ];
          }
          else if ( handle == 1 ) {  /* corner */
             box->size[ 0 ] = 2.0 * fabs( event->posSnap[ 0 ]
                - box->center[ 0 ] );
             box->size[ 1 ] = 2.0 * fabs( event->posSnap[ 1 ]
                - box->center[ 1 ] );
             box->size[ 2 ] = 2.0 * fabs( event->posSnap[ 2 ]
                - box->center[ 2 ] );
          }

          calc_handles( box );
          box->dirty = 1;
          box->update = LWT_TEST_UPDATE;
          return handle;
       }
If the user's moving the center handle, we set the box center to the new position, and if the user is moving the size handle, we recalculate the size. In both cases, we precalculate the handle positions for the next `Handle` and `Draw` calls, and we tell Modeler that we need to be both redrawn and rebuilt. Build Finally! This callback creates geometry based on what the user is doing.
       static LWError Build( BoxData *box, MeshEditOp *edit )
       {
          makebox( edit, box );
          box->update = LWT_TEST_NOTHING;
          return NULL;
       }
All we have to do here is call our old friend `makebox`, passing it the MeshEditOp and the size and center set by the user. And since we've just built the geometry, we set `box->update` to `NOTHING`. Draw Here we draw the tool itself. We don't have to draw the geometry we create, since Modeler takes care of that for us.
       static void Draw( BoxData *box, LWWireDrawAccess *draw )
       {
          if ( !box->active ) return;
          draw->moveTo( draw->data, box->hpos[ 0 ], LWWIRE_SOLID );
          draw->lineTo( draw->data, box->hpos[ 1 ], LWWIRE_ABSOLUTE );
          box->dirty = 0;
       }
To keep this simple, we're drawing a single line segment connecting our two handles. More typically, you'll draw a bounding box or some other representation of the scope of your tool's effects, and you'll draw the handles in some way, so that the user knows where they are. Help The `help` callback returns a line of text that Modeler draws while the tool is selected. Modeler calls `Help` whenever `Dirty` returns the `LWT_DIRTY_HELPTEXT` bit. It also calls `Help` each time the user moves the mouse cursor to a new viewport, so that you can return a different string for each view.
       static const char *Help( BoxData *box, LWToolEvent *event )
       {
          static char buf[] = "Box Tool Plug-in Tutorial";
          return buf;
       }
Event This is called when the user drops, resets or re-activates the tool.
       static void Event( BoxData *box, int code )
       {
          switch ( code )
          {
             case LWT_EVENT_DROP:
             if ( box->active ) {
                box->update = LWT_TEST_REJECT;
                break;
             }
The user can drop a tool by clicking in a blank area of Modeler's interface outside the viewports. Generally this means that the user wants to discard the geometry created with the tool, so if we've created some geometry (`box->active` is TRUE), we set `box->update` to `LWT_TEST_REJECT`, so that Modeler will discard the geometry the next time it calls `Test`. If `box->active` is FALSE, we fall through to the next case, treating a drop like a reset.
             case LWT_EVENT_RESET:
                box->size[ 0 ] = box->size[ 1 ] = box->size[ 2 ] = 1.0;
                box->center[ 0 ] = box->center[ 1 ] = box->center[ 2 ] = 0.0;
                strcpy( box->surfname, "Default" );
                strcpy( box->vmapname, "MyUVs" );
                box->update = LWT_TEST_UPDATE;
                box->dirty = 1;
                calc_handles( box );
                break;
A reset event occurs when the user selects the Reset action on Modeler's Numeric panel. We set all of the box parameters to default values and set our state variables so that Modeler will both rebuild and redraw us.
             case LWT_EVENT_ACTIVATE:
                box->update = LWT_TEST_UPDATE;
                box->active = 1;
                box->dirty = 1;
                break;
          }
       }
An activate event can be triggered from the Numeric window or with a keystroke, and it should restart the edit operation with its current settings. End, Done These sound confusingly alike. The `end` callback is called at the completion of a mouse down, mouse move, mouse up sequence. While the tool is selected, you may get any number of `end` calls. The `done` callback is called when the user is finished with the tool and has deselected it, and it's typically used to free memory allocated by the activation function.
       static void End( BoxData *box, int keep )
       {
          box->update = LWT_TEST_NOTHING;
          box->active = 0;
       }
Our `End` sets `box->update` to `NOTHING` and `box->active` to FALSE, the state we want our tool data to be in the next time `Count` is called.
       static void Done( BoxData *box )
       {
          free( box );
       }
Our `Done` frees the BoxData structure. The Interface The panel we create for a tool is displayed inside Modeler's Numeric panel when the tool is active. We don't open it ourselves. We create the panel in yet another callback, and Modeler takes care of opening or closing it. The panel becomes just another way for the user to interact with the tool. As settings are changed on the panel, the geometry is changed and the tool is redrawn, just as if the user were dragging the mouse in the viewport. So our panel is now non-modal. It differs from previous incarnations of our interface in a couple of other ways, too. Since tools use instances (our BoxData structure), it's more natural to make our panel an `LWXP_VIEW` instead of an `LWXP_FORM`. And the surface name list is built with the popup callbacks I avoided in Part 3.
       LWXPanelID Panel( BoxData *box )
       {
          LWXPanelID panel;

          static LWXPanelControl ctl[] = {
             { ID_SIZE,     "Size",      "distance3"  },
             { ID_CENTER,   "Center",    "distance3"  },
             { ID_SURFLIST, "Surface",   "iPopChoice" },
             { ID_VMAPNAME, "VMap Name", "string"     },
             { 0 }
          };
          static LWXPanelDataDesc cdata[] = {
             { ID_SIZE,     "Size",      "distance3" },
             { ID_CENTER,   "Center",    "distance3" },
             { ID_SURFLIST, "Surface",   "integer"   },
             { ID_VMAPNAME, "VMap Name", "string"    },
             { 0 }
          };
          LWXPanelHint hint[] = {
             XpLABEL( 0, "Box Tutorial Part 4" ),
             XpPOPFUNCS( ID_SURFLIST, get_surfcount, get_surfname ),
             XpDIVADD( ID_SIZE ),
             XpDIVADD( ID_CENTER ),
             XpEND
          };
The control and data description arrays are the same as before, with one important difference: they've been declared static. Our panel is no longer modal. It persists after the `Panel` function returns, and the control and data descriptions must also. The `XpSTRLIST` hint has been replaced by an `XpPOPFUNCS` hint that tells XPanels to use the `get_surfcount` and `get_surfname` callbacks with the surface name popup. These callbacks will be called to initialize the list each time the user clicks on it to open it. They use the same techniques for enumerating the surfaces in Modeler that `init_surflist` used in Part 3.
          panel = xpanf->create( LWXP_VIEW, ctl );
          if ( !panel ) return NULL;

          xpanf->describe( panel, cdata, Get, Set );
          xpanf->hint( panel, 0, hint );

          return panel;
       }
Recall that in Part 3, the third and fourth arguments to `describe` were NULL. Since our panel is a view, we now pass get and set callbacks. Get, Set It's easy to get these two mixed up. Just try to remember that the names are from LightWave®'s point of view, not yours (you're the server, LightWave® is the client). XPanels calls the `Get` callback when it wants to get the value of a control from you. It calls the `Set` callback when it wants you to write the value of a control into your instance data.
       static void *Get( BoxData *box, unsigned int vid )
       {
          static int i;

          switch ( vid ) {
             case ID_SIZE:      return &box->size;
             case ID_CENTER:    return &box->center;
             case ID_SURFLIST:  i = get_surfindex( box->surfname );
                                return &i;
             case ID_VMAPNAME:  return &box->vmapname;
             default:           return NULL;
          }
       }
`Get` is usually pretty straightforward. Just return a pointer to the appropriate element of your instance data.
       static int Set( BoxData *box, unsigned int vid, void *value )
       {
          const char *a;
          double *d;
          int i;

          switch ( vid )
          {
             case ID_SIZE:
                d = ( double * ) value;
                sbox.size[ 0 ] = box->size[ 0 ] = d[ 0 ];
                sbox.size[ 1 ] = box->size[ 1 ] = d[ 1 ];
                sbox.size[ 2 ] = box->size[ 2 ] = d[ 2 ];
                break;

             case ID_CENTER:
                ...
`Set` adds a few wrinkles. The first is that you generally need to cast the value argument before assigning its contents to your instance data, so it's convenient to have temporary pointers of the right type handy. The second, for us, is that we'd like to keep a local copy of the instance, so that we can use it to initialize the tool instance the next time the user activates the tool. The user's perception of this is that the tool "remembers" what was done previously. So all of our assignments are duplicated for the local copy.
             default:
                return LWXPRC_NONE;
          }

          box->update = LWT_TEST_UPDATE;
          box->dirty = 1;
          calc_handles( box );
          return LWXPRC_DRAW;
       }
Lastly, when the value of a control changes, we want to tell Modeler to redraw and rebuild us the next time it calls `Dirty` and `Test`, so we set `box->update` and `box->dirty` accordingly and precalculate the positions of our handles. The Activation Function Our activation function is significantly different from the ones in previous installments of this tutorial. Instead of being finished when the function returns, tool plug-ins haven't really begun yet. The only thing a tool's activation function is required to do, and all ours does, is create an instance and tell Modeler where to find the callbacks. In this sense, Modeler tools are like Layout handlers, which supply callbacks that Layout later calls during animation and rendering.
       XCALL_( int )
       Activate( int version, GlobalFunc *global, LWMeshEditTool *local,
          void *serverData )
       {
          BoxData *box;

          if ( version != LWMESHEDITTOOL_VERSION )
             return AFUNC_BADVERSION;
Note that the third argument is now LWMeshEditTool instead of LWModCommand. Each plug-in class gets its own local data. As always, the first thing we do is ensure that the version of this structure in our copy of the headers is the same as the version being passed to us by Modeler.
          if ( !get_xpanf( global )) return AFUNC_BADGLOBAL;
          box = new_box();
          if ( !box ) return AFUNC_OK;
          local->instance = box;
The `get_xpanf` and `new_box` functions are in `ui.c`, since that's where the LWXPanelFuncs and LWSurfaceFuncs pointers and the local copy of the box settings are stored and used. `get_xpanf` gets the globals used by the interface, and `new_box` allocates a BoxData and initializes it with default values (or values remembered from previous invocations). The BoxData will be freed when `Done` is called.
          local->tool->done   = Done;
          local->tool->help   = Help;
          local->tool->count  = Count;
          local->tool->handle = Handle;
          local->tool->adjust = Adjust;
          local->tool->start  = Start;
          local->tool->draw   = Draw;
          local->tool->dirty  = Dirty;
          local->tool->event  = Event;
          local->tool->panel  = Panel;

          local->build        = Build;
          local->test         = Test;
          local->end          = End;

          return AFUNC_OK;
       }
And we're done! After returning from the activation function, Modeler will start calling your callbacks through the function pointers you've supplied. Server Tags Finally, note that we've added server tags to the ServerRecord array.
       static ServerTagInfo srvtag[] = {
          { "Tutorial: Box 4",    SRVTAG_USERNAME | LANGID_USENGLISH },
          { "create",             SRVTAG_CMDGROUP },
          { "objects/primitives", SRVTAG_MENU },
          { "Tut Box 4",          SRVTAG_BUTTONNAME },
          { "", 0 }
       };
These are explained in detail on the Common Elements page of the SDK. The user name appears in the interface in plug-in lists and popup menus. The server name is used if this isn't supplied, but there are lexical constraints on server names (they can't contain spaces, for example) that make them less user-friendly. Modeler is currently ignoring the `MENU` and `CMDGROUP` tags, but it may not in the future. What's Next Unless you had the evidence in front of you, you might not believe that a 40-page tutorial about writing box plug-ins was possible. But on this thin pretext, we've briefly visited most of the important techniques used to write plug-ins for LightWave® Modeler. In the future, we might be seeing even more boxes on a similar tour of Layout...