Share

Command history plug-in example

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.

linkObject.c++

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

Was this information helpful?