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