The linkObjectExample command history plug-in builds linked geometry.
Its input is 2 curve nodes and its output is 2 straight 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 is rebuilt.
#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>
#include <AlAttributes.h>
#include <AlLineAttributes.h>
#include <string.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
//
static
statusCode firstPosition( AlObject *obj, double& x, double& y, double& z );
static
statusCode lastPosition( AlObject *obj, double& x, double& y, double& z );
static
statusCode createLine( AlCurveNode *&cp, double a[3], double b[3] );
static
void updateLinePosition( AlCurveNode *curveNode,
double x, double y, double z, double x2, double y2, double z2 );
#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.
const int kIntId = 100;
const int kCharId = 101;
const int kDoubleId = 102;
//
// 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;
AlData *charData;
AlData *intData;
AlData *doubleData;
};
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 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 );
private:
boolean requireStrongValidity;
};
//
// Start command definition
//
genericCmd::genericCmd()
//
// Initialize the structure
//
{
firstDagObject = NULL;
secondDagObject = NULL;
firstLineDag = NULL;
secondLineDag = NULL;
requireStrongValidity = TRUE;
charData = NULL;
intData = NULL;
doubleData = 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;
if ( charData )
delete charData;
if ( intData )
delete intData;
if ( doubleData )
delete doubleData;
}
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 and so on.
if( firstDagObject == NULL || secondDagObject == NULL )
return kInvalid;
if ( requireStrongValidity )
if ( firstLineDag == NULL || secondLineDag == 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.
//
{
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 ( firstLineDag == NULL )
{
if ( createLine( firstLineDag, a, b ) == sSuccess )
printf("created new line\n");
else
printf("failed to create new line\n");
}
else
{
updateLinePosition( firstLineDag, a[0], a[1], a[2], b[0], b[1], b[2] );
}
}
if ( lastPosition( firstDagObject,a[0], a[1], a[2] ) == sSuccess &&
lastPosition( secondDagObject, b[0], b[1], b[2] ) == sSuccess )
{
if ( secondLineDag == NULL )
{
if ( createLine( secondLineDag, a, b ) == sSuccess )
printf("created new line\n");
else
printf("failed to create new line\n");
}
else
{
updateLinePosition( secondLineDag, a[0], a[1], a[2], b[0], b[1], b[2] );
}
}
// 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 to the 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 and so on.
//
{
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");
// Replace the old pointers with the newly relocated ones. The
// order of resolving must be the same as the declare when
// we store.
AlObject *objDag = input->resolveObject();
AlObject *objDag2 = input->resolveObject();
AlObject *objDag3 = input->resolveObject();
AlObject *objDag4 = input->resolveObject();
if ( !objDag || ! objDag2 || !objDag3 || !objDag4 )
return sFailure;
// 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 sFailure;
// Test info;
if ( charData )
delete charData;
int i;
if ( ( charData = input->resolveData( AlData::kDataChar, kCharId )) == NULL )
return sFailure;
printf("char data <%s>\n",charData->asCharPtr()); // Null terminated string.
if ( intData )
delete intData;
if ( ( intData = input->resolveData( AlData::kDataInt, kIntId ) ) == NULL )
return sFailure;
const int *idata = intData->asIntPtr();
printf("int data <");
for ( i = 0; i< intData->count(); i++ )
printf("%d ",idata[i]);
printf(">\n");
if ( doubleData )
delete doubleData;
if ( ( doubleData = input->resolveData( AlData::kDataDouble, kDoubleId ) ) == NULL )
return sFailure;
printf("double data <");
const double *ddata = doubleData->asDoublePtr();
for ( i = 0; i< doubleData->count(); i++ )
printf("%g ",ddata[i]);
printf(">\n");
return sSuccess;
}
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;
// 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 );
// Test info
AlData *data1 = new AlData;;
char *cdata = "Save this data.";
int count = strlen( cdata ) + 1;
if ( data1->create( kCharId, cdata, count ) != sSuccess )
return sFailure;
if ( output->declareData( data1 ) != sSuccess )
return sFailure;
AlData *data2 = new AlData;
int idata[10] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
count = 10;
if ( data2->create( kIntId, idata, count ) != sSuccess )
return sFailure;
if ( output->declareData( data2 ) != sSuccess )
return sFailure;
AlData *data3 = new AlData;;
double ddata[9] = { 10.1, 11.1, 12.1, 13.1, 14.2, 15.1, 16.1, 17.1, 18.1 };
count = 9;
if ( data3->create( kDoubleId, ddata, count ) != sSuccess )
return sFailure;
if ( output->declareData( data3 ) != sSuccess )
return sFailure;
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();
requireStrongValidity = FALSE;
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"
PLUGINAPI_DECL 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"
PLUGINAPI_DECL int plugin_exit( void )
{
(void) AlCommand::remove(CMD_NAME );
(void) h.deleteObject();
(void) hFunc.deleteObject();
// A redraw is required to ensure history
// is no longer displayed for the plug-in’s
// entities.
AlUniverse::redrawScreen( kRedrawAll );
// 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;
}
//
// Function for updating the position of the line.
//
static void updateLinePosition( AlCurveNode *curveNode, double x, double y, double z, double x2, double y2, double z2 )
{
if ( curveNode == NULL )
return;
AlCurve *curve = curveNode->curve();
if ( curve == NULL )
return;
AlAttributes *attr = curve->firstAttribute();
if ( attr == NULL )
return;
AlLineAttributes* lineAttr = attr->asLineAttributesPtr();
if ( lineAttr )
{
lineAttr->setStartPoint( x, y, z );
lineAttr->setEndPoint( x2, y2, z2 );
}
delete curve;
delete attr;
}