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; }