Command history plug-ins are very complex, and it is advisable not to attempt creating one until a firm understanding of momentary and continuous plug-ins has been developed.
Command history plug-ins are not menu based but rather they are invoked when geometry changes. A command plug-in is installed but selecting a menu item. When the menu item is selected, plugin_init() is called and the command is installed in Alias. Once installed, the command plug-in can run without reselecting the command plug-in menu item.
A command or construction history object has knowledge of how it is created. The constructors of the object and tracked as well as the targets( outputs ) of the command. When, a constructor is modified, the geometry of the targets will be rebuilt. A command history plug-in would program the behaviour of the modifications of the targets based on the changes of the constructors.
See the documentation on AlCommand, AlUserCommand, and AlNotifyDagNode, as well as the examples constrainCV.c++ and polyhist.c++.
It is important to note that when modifying geometry, the doUpdates() method of the geometry should be called with FALSE, and never called with TRUE. This is necessary since the command is being called from within the message system, and updates generate messages. If updates are performed during a command then unnecessary messages will be generated, and it is possible that a loop could be created where the command is responding to a message it generated.
As mentioned above, installed command history plug-ins and invoked when specific events occur in Alias. Implementing a command history plug-in involves deriving a new class from AlUserCommand and implementing the bodies of the necessary methods of the new class. Below is a outline of the necessary components of a command history plug-in:
The linkObject command history plug-in builds linked geometry. Its input are 2 curve nodes and output are 2 lines( also curve nodes ) which link the first 2 CVs and the last 2 CVs of the curves nodes together. When a constructor curve node is modified, the linked geometry automatically rebuilds.
#include <AlUniverse.h>
#include <AlCurve.h>
#include <AlCurveCV.h>
#include <AlCurveNode.h>
#include <AlLiveData.h>
#include <AlFunction.h>
#include <AlFunctionHandle.h>
#include <AlCommand.h>
#include <AlUserCommand.h>
#include <AlNotifyDagNode.h>
#include <AlPickList.h>
// Construction history plug-in example:
// =====================================
//
// The following code implements a simple construction history
// command plug-in. Construction history plug-ins tie into the
// command mechanism of Alias and allow the rebuilding of geometry
// on the fly.
//
// This plug-in accepts two curve nodes from the pick list as input.
// The output of the command are two lines. The first line joins
// the start points of the two picked curve nodes together. The
// second line joins the end points of the two picked curve nodes
// together. The ouput lines are rebuilt as changes are made to
// the constructors to provide the appearance of link objects.
//
// Notes on construction history:
// ==============================
//
// 1. Construction history commands contain 'constructor' objects
// which are used in the construction or creation of new objects.
//
// 2. The new objects created are called targets.
//
// 3. AlUserCommand is a class that is tied into the command
// command mechanism of Alias. Events happen in Alias which
// may force a rebuild or save, retrieve etc in a command. The
// construction history plug-in implements handlers for the above events.
// These handlers are called when required by Alias.
//
// 4. The inputs or constructors of the command can be taken from
// the pick list as this example illustrates.
//
// 5. For an Alias created object that has construction history,
// moving the targets causes construction history to be broken. A
// plug-in would have to program this enforcement if it did not want
// the geometry to become out of sync. One of the reasons why
// construction history should not update if a target is moved is
// that it is very difficult to know how to update a constructor
// based on the target's new state. To enforce this condition,
// a plug-in would have to make sure it called addTargetRef() on
// its targets. If this is done properly, the targets will automatically
// be given the default construction history colour which is green.
// In addition, the user will be prompted if construction history
// is about to be broken.
//
// 6. There are several helper classes required by construction
// history plug-ins. These include AlNotifyDagNode, AlInput,
// AlOutput and AlCommand. It would be very useful to read
// the documentation for these classes before attempting to
// write a construction history plug-in.
//
// 7. It is not usually the case that one AlUserCommand method calls
// another. Instead the Alias command engine calls into the
// AlUserCommand methods or the AlCommand class is used to make
// calls to the AlUserCommand.
//
// 8. It is possible to save plug-in construction history data into
// a wire file so that the next time the file is loaded with the
// plug-in also loaded, the command will automatically be reinstated
// for the objects.
// If the plug-in is not loaded the construction history data is loaded,
// and a message is emitted to the promptline history saying
// that the plug-in associated with the construction history
// command cannot be found. The construction history data will
// be resaved when the wire file is written out again regardless
// of if the plug-in is loaded.
//
// Notes on this plug-in:
// ======================
//
// 1. This plug-in deletes the targets every time the command
// is executed for simplicity. This would cause many problems
// if there was animation on the constructors and we played
// the frames. See the AlPlayFrame class for more details. It
// is better to modify the geometry than to delete and recreate
// it.
//
// 2. Many parts of the code below are generic to all construction
// history plug-ins. The specific code that should be replaced
// for a new plug-in is any code snippet that references the constructors
// or targets. The cmdData class would also have to be updated
// to support the data types required by your plug-in.
//
// 3. Don't underestimate the usefullness of the printfs() in
// the code below. Construction history plug-ins are difficult
// to write or understand because of the multiple entry points that are
// required by the code. The printfs() will help a great deal in
// analyzing how this example works.
// Also if you call AlCommand::setDebug( TRUE ), messages related
// to the command will be written to the errlog.
//
//
// Prototypes
//
extern char * makeAltPath(const char *dirName, const char *suffix);
statusCode firstPosition( AlObject *obj, double& x, double& y, double& z );
statusCode lastPosition( AlObject *obj, double& x, double& y, double& z );
statusCode createLine( AlCurveNode *&cp, double a[3], double b[3] );
#define CMD_NAME "linkObject" // Used by OpenAlias for creation and
// destruction of the command plug-in.
#define CMD_CLASS_ID 50 // A user defined type in case you have
// more than 1 command.
//
// A user defined data class. This class contains two
// constructor dag nodes and their targets. firstDagObject
// and secondDagObject are the constructors. firstLineDag and
// secondLineDag are the targets and are
// rebuilt based on the changes to the constructors.
//
class cmdData
{
public:
cmdData* cmdDataPtr() { return this; }
AlCurveNode* firstDagObject;
AlCurveNode* secondDagObject;
AlCurveNode* firstLineDag;
AlCurveNode* secondLineDag;
};
class genericCmd: public AlUserCommand, private cmdData
{
enum error { kOkay = 0, kInvalid = 1, kDagNodeNotHandled };
public:
genericCmd();
~genericCmd();
virtual int isValid();
virtual int execute();
virtual int declareReferences();
virtual int instanceDag( AlDagNode *oldDag, AlDagNode *newDag );
virtual int undo();
virtual int geometryModified( AlDagNode *dag );
virtual int listModifiedDagNodes( const AlNotifyDagNode *dagMod, AlObject *obj );
virtual int debug( const char *prefix );
virtual void * asDerivedPtr();
virtual statusCode retrieveWire( AlInput *input );
virtual statusCode resolveWire( AlInput *input );
virtual statusCode storeWire( AlOutput *output );
virtual int dagModified( AlDagNode *dag );
// for your own use
virtual int type();
// We didn't bother to implement these commands since they don't apply
// to us (they would be similar to instanceDag() and geometryModified() )
//
// virtual int curveOnSurfaceModified( AlCurveOnSurface *surf );
// The following command is not yet supported
// virtual statusCode storeSDL( ofstream &outputSDL );
public:
// Methods that the UI commands can use to set our fields
statusCode set( AlDagNode *firstDag, AlDagNode *secondDag );
};
//
// Start command definition
//
genericCmd::genericCmd()
//
// Initialize the structure
//
{
firstDagObject = NULL;
secondDagObject = NULL;
firstLineDag = NULL;
secondLineDag = NULL;
}
genericCmd::~genericCmd()
//
// Provide a safe cleanup.
//
{
if ( firstDagObject != NULL )
delete firstDagObject;
if ( secondDagObject != NULL )
delete secondDagObject;
if ( firstLineDag != NULL )
delete firstLineDag;
if ( secondLineDag != NULL )
delete secondLineDag;
}
void *genericCmd::asDerivedPtr()
//
// Provide safe down casting.
//
{
return this;
}
int genericCmd::type()
//
// User defined value so you can determine the class type
// of the command.
//
{
return CMD_CLASS_ID;
}
int genericCmd::isValid()
//
// Since the construction history plug-in maintains its own data,
// it is necessary for it to implement the isValid() method to
// tell the command layer that it is ok to call this command.
//
{
// Testing will involve NULL pointer checks, making sure you
// have the correct kind of Dags etc.
if( firstDagObject == NULL || secondDagObject == NULL )
return kInvalid;
int result1, result2;
switch( firstDagObject->type() )
{
case kCurveNodeType:
result1 = kOkay;
break;
default:
result1 = kDagNodeNotHandled;
break;
}
switch( secondDagObject->type() )
{
case kCurveNodeType:
result2 = kOkay;
break;
default:
result2 = kDagNodeNotHandled;
break;
}
if ( result1 == kOkay && result2 == kOkay )
return kOkay;
return kDagNodeNotHandled;
}
int genericCmd::execute()
//
// This method is called when the geometry needs to be updated.
// This will occur if the constructor dag nodes are modified.
//
{
// Cleanup any old objects.
if ( AlIsValid( firstLineDag ) )
{
firstLineDag->deleteObject();
delete firstLineDag;
}
if ( AlIsValid( secondLineDag ) )
{
secondLineDag->deleteObject();
delete secondLineDag;
}
// Recreate/modify any geometry required.
double a[3],b[3];
if ( firstPosition( firstDagObject,a[0], a[1], a[2] ) == sSuccess &&
firstPosition( secondDagObject, b[0], b[1], b[2] ) == sSuccess )
{
if ( createLine( firstLineDag, a, b ) == sSuccess )
printf("created new line\n");
else
printf("failed to create new line\n");
}
if ( lastPosition( firstDagObject,a[0], a[1], a[2] ) == sSuccess &&
lastPosition( secondDagObject, b[0], b[1], b[2] ) == sSuccess )
{
if ( createLine( secondLineDag, a, b ) == sSuccess )
printf("created new line\n");
else
printf("failed to create new line\n");
}
// Force the redrawing of the screen
AlUniverse::redrawScreen( kRedrawAll );
return kOkay;
}
int genericCmd::instanceDag( AlDagNode *oldDag, AlDagNode *newDag )
//
// Handle a dag node being instanced. Go through the class and replace
// any references to oldDag with newDag
//
{
printf("genericCmd::instanceDag()\n");
if ( oldDag == NULL || newDag == NULL )
return -1;
if( AlAreEqual( firstDagObject, oldDag ) )
{
// Toss our old wrapper and replace it with a new one.
delete firstDagObject;
firstDagObject = newDag->copyWrapper()->asCurveNodePtr();
}
if( AlAreEqual( secondDagObject, oldDag ) )
{
// Toss our old wrapper and replace it with a new one.
delete secondDagObject;
secondDagObject = newDag->copyWrapper()->asCurveNodePtr();
}
return kOkay;
}
int genericCmd::declareReferences()
//
// Declare any references to constructors and targets.
// The constructors are the inputs to the command and
// the targets are the outputs. By setting this association,
// Alias will know to call the methods implemented in
// the plug-in for modifications tothe constructor and target dags.
//
{
printf("genericCmd::declareReferences()\n");
if ( firstDagObject != NULL )
addConstructorRef( firstDagObject );
if ( secondDagObject != NULL )
addConstructorRef( secondDagObject );
if ( firstLineDag != NULL )
addTargetRef( firstLineDag );
if ( secondLineDag != NULL )
addTargetRef( secondLineDag );
return kOkay;
}
int genericCmd::geometryModified( AlDagNode *dag )
//
// The geometry for the constructor dags has been modified.
//
{
if ( dag == NULL )
return -1;
if ( dag->name() != NULL )
printf("genericCmd::geometryModified( %s )\n",dag->name());
// If the parameter dag is the same as one of our dags
// then we don't have to do much.
if( AlAreEqual( firstDagObject, dag) )
{
return kOkay;
}
if( AlAreEqual( secondDagObject, dag) )
{
return kOkay;
}
// If we have gotten to this point, then one of the
// dags our command knows about has changed. Free
// the dags up and let the command know it has been
// modified.
delete firstDagObject; firstDagObject = NULL;
delete secondDagObject; secondDagObject = NULL;
// Signal that the command has been modified by making the
// appropriate call.
AlCommand *cmd = command();
cmd->modified();
delete cmd;
return kOkay;
}
int genericCmd::dagModified( AlDagNode *dag )
//
// The dag was modified. This method will be called if the
// dag is translated etc.
//
{
if ( dag == NULL )
return -1;
if ( dag->name() != NULL )
printf("genericCmd::dagModified( %s )\n",dag->name());
// This method does not need to do much for this plug-in.
if( ( AlIsValid( dag ) && AlIsValid( firstDagObject ) && AlAreEqual( dag,
firstDagObject ) ) ||
( AlIsValid( dag ) && AlIsValid( secondDagObject ) && AlAreEqual( dag,
secondDagObject ) ))
{
}
return kOkay;
}
int genericCmd::debug( const char *prefix )
{
if ( prefix != NULL )
printf("genericCmd::debug( %s )\n",prefix);
return kOkay;
}
int genericCmd::undo()
//
// Undo everything the 'execute' did. The cmdData class would need
// to store the previous state of the world so we can undo one
// step.
//
// Note: for this simple example undo does not need to be written.
// If a user transforms the constructors curves and then undo's the
// transform from the Alias Edit menu, the ::execute() command will
// be called and the curves redrawn properly because of the dag
// modification handler.
//
{
printf("genericCmd::undo() called\n");
return kOkay;
}
int genericCmd::listModifiedDagNodes( const AlNotifyDagNode *dagMod, AlObject *obj )
//
// This routine should call dagMod->notify on every dag node that might
// be affected if obj is modified.
// In our example, if one of the constructor is modified, we
// call the notify() method on the targets of the command.
//
{
printf("genericCmd::listModifiedDagNodes() called\n");
if ( dagMod == NULL || obj == NULL )
return -1;
if ( AlAreEqual( obj, firstDagObject ) || AlAreEqual( obj, secondDagObject ) )
{
dagMod->notify( firstLineDag );
dagMod->notify( secondLineDag );
}
return -1;
}
statusCode genericCmd::retrieveWire( AlInput *input )
//
// Handler called by the Alias retrieve code for the construction
// history objects.
//
{
printf("genericCmd::retrieveWire()\n");
if ( input == NULL )
return sFailure;
// copy over the data into our data section
if( input->inputRemaining() != -1 &&
input->inputRemaining() != sizeof( cmdData ))
return sFailure;
input->input( cmdDataPtr(), sizeof( cmdData ) );
return sSuccess;
}
statusCode genericCmd::resolveWire( AlInput *input )
//
// When the wire file is retrieved, pointer values can be different,
// so the following method resolves the pointers.
//
{
printf("genericCmd::resolveWire()\n");
if ( input == NULL )
return sFailure;
// Replace the old pointers with the newly relocated ones
AlObject *objDag = input->resolveObject( firstDagObject );
AlObject *objDag2 = input->resolveObject( secondDagObject );
AlObject *objDag3 = input->resolveObject( firstLineDag );
AlObject *objDag4 = input->resolveObject( secondLineDag );
// If a pointer was not resolved, then NULL is returned
//
// This can happen if the geometry has been deleted when
// the plug-in is not loaded.
// Alternatively, we could have just returned sSuccess.
// When our command is executed, it will be invalid and so it will be
// deleted.
firstDagObject = objDag->asCurveNodePtr();
secondDagObject = objDag2->asCurveNodePtr();
firstLineDag = objDag3->asCurveNodePtr();
secondLineDag = objDag4->asCurveNodePtr();
if( firstDagObject && secondDagObject && firstLineDag && secondLineDag )
return sSuccess;
else
return sFailure;
}
statusCode genericCmd::storeWire( AlOutput *output )
//
// This routine is the handler called by the Alias store code so
// that it can get a pointer to the construction history plug-ins
// data.
//
{
printf("genericCmd::storeWire()\n");
if ( output == NULL )
return sFailure;
output->output( cmdDataPtr(), sizeof( cmdData) );
// Declare all of our references to data so that we can get them back
// on retrieval. We are telling Alias to keep track of these
// pointers in the wire file.
output->declareObject( firstDagObject );
output->declareObject( secondDagObject );
output->declareObject( firstLineDag );
output->declareObject( secondLineDag );
return sSuccess;
}
statusCode genericCmd::set( AlDagNode *firstDag, AlDagNode *secondDag )
//
// Our helper function for setting the cmdData constructors.
//
{
if ( firstDag == NULL || secondDag == NULL )
return sFailure;
firstDagObject = (AlCurveNode *) firstDag->copyWrapper();
secondDagObject = (AlCurveNode *) secondDag->copyWrapper();
return sSuccess;
}
//
// End command definition
//
//
// Begin command invocation (the UI chunk of the code)
//
AlUserCommand *allocLinkObjectCmd()
//
// Allocate & return a new command. This function will be passed
// to the AlCommand::add() routine
//
{
return new genericCmd;
}
void do_add_cmd( AlCurveNode *firstDag, AlCurveNode *secondDag )
{
AlCommand::setDebug( TRUE ); // Static member function call
AlCommand cmd;
if( sSuccess == cmd.create( CMD_NAME ) )
{
genericCmd *g_cmd = (genericCmd *)cmd.userCommand()->asDerivedPtr();
g_cmd->set( firstDag, secondDag );
if( cmd.execute() == 0 )
cmd.install();
else
cmd.deleteObject();
}
}
//
// Procedure to find two picked curve nodes and pass them to the
// construction history command so that they can be used as the
// constructors of the command.
//
void do_linkObject()
{
AlObject *firstPickedItem, *secondPickedItem;
firstPickedItem = NULL;
secondPickedItem = NULL;
for( statusCode stat = AlPickList::firstPickItem();
stat == sSuccess;
stat = AlPickList::nextPickItem() )
{
AlObject *pickedItem = AlPickList::getObject();
if( pickedItem )
{
if( pickedItem->asCurveNodePtr() )
{
if ( firstPickedItem == NULL )
firstPickedItem = pickedItem->copyWrapper();
else if ( secondPickedItem == NULL )
secondPickedItem = pickedItem->copyWrapper();
}
delete pickedItem;
}
}
if ( firstPickedItem && secondPickedItem )
{
do_add_cmd( firstPickedItem->asCurveNodePtr(), secondPickedItem-
>asCurveNodePtr() );
}
else
printf("Failed to get the two picked curve nodes.\n");
if ( firstPickedItem )
delete firstPickedItem;
if ( secondPickedItem )
delete secondPickedItem;
}
static AlFunctionHandle h;
static AlMomentaryFunction hFunc;
extern "C"
int plugin_init( const char *dirName )
{
AlUniverse::initialize( kZUp );
//
// Create a new construction history command
//
if ( AlCommand::add( allocLinkObjectCmd, CMD_NAME ) != sSuccess )
{
AlPrintf( kPrompt, "The linkObject plug-in failed to install.\n");
return 1;
}
if ( hFunc.create( do_linkObject ) != sSuccess )
return 1;
if ( h.create( "linkObject command", &hFunc ) != sSuccess )
return 1;
if ( h.setAttributeString( "linkObject plugin cmd" ) != sSuccess )
return 1;
if ( h.setIconPath( makeAltPath( dirName, NULL ) ) != sSuccess )
return 1;
if ( h.appendToMenu( "mp_objtools" ) != sSuccess )
return 1;
AlPrintf( kPrompt, "linkObject installed on Palette 'Object Edit'");
return 0;
}
extern "C"
int plugin_exit( void )
{
(void) AlCommand::remove(CMD_NAME );
(void) h.deleteObject();
(void) hFunc.deleteObject();
// do nothing
return 0;
}
//
// Helper functions.
//
static statusCode firstPosition( AlObject *obj, double& x, double& y, double& z )
//
// A simple function for returning the x,y,z values of the first CV.
//
{
AlCurveNode *cnode = NULL;
AlCurve *curve = NULL;
AlCurveCV *cv = NULL;
statusCode result = sFailure;
if ( ( cnode = obj->asCurveNodePtr()) != NULL )
{
curve = cnode->curve();
if ( curve != NULL )
{
cv = curve->firstCV();
if ( cv != NULL )
{
double w;
if ( cv->worldPosition( x, y, z, w ) == sSuccess )
result = sSuccess;
}
}
}
// The cnode wrapper is really the same as obj which is the
// same as one of the constructors of the command so do not
// delete it.
if ( curve )
delete curve;
if ( cv )
delete cv;
return result;
}
static statusCode lastPosition( AlObject *obj, double& x, double& y, double& z )
//
// A simple function for returning the x,y,z values of the last CV.
//
{
AlCurveNode *cnode = NULL;
AlCurve *curve = NULL;
AlCurveCV *cv = NULL;
statusCode result = sFailure;
if ( ( cnode = obj->asCurveNodePtr()) != NULL )
{
curve = cnode->curve();
if ( curve != NULL )
{
int numCVs = curve->numberOfCVs();
cv = curve->getCV( numCVs -1 );
if ( cv != NULL )
{
double w;
if ( cv->worldPosition( x, y, z, w ) == sSuccess )
result = sSuccess;
}
}
}
// The cnode wrapper is really the same as obj which is the
// same as one of the constructors of the command so do not
// delete it.
if ( curve )
delete curve;
if ( cv )
delete cv;
return result;
}
static statusCode createLine( AlCurveNode *&cp, double a[3], double b[3] )
//
// A simple function for creating a line between points a and b. The
// curve node created is returned in the 'cp' parameter.
//
{
AlCurveNode *cnode = NULL;
AlCurve *curve = NULL;
statusCode result = sFailure;
curve = new AlCurve;
if ( curve )
{
if ( curve->createLine( a, b ) == sSuccess )
{
cnode = new AlCurveNode;
if ( cnode )
{
if ( cnode->create( curve ) == sSuccess )
{
cp = cnode;
result = sSuccess;
}
}
}
}
if ( curve )
delete curve;
return result;
}