Remembering Object Names (XSISDK)

This article discusses various ways to remember a object name inside XSI.

This is a common scenario for plug-ins. For example, in a complex rig there may be extra relationships between objects that need to be remembered for usage by scripts. Or a certain object may be considered "Active" by a plugin. Or the names of objects may be remembered as part of an import/export plug-in.

Often the object that needs to be remembered is an X3DObject, for example a Null or Geometry. But it could also a nested object underneath an X3DObject, for example a specific Property, Cluster or Parameter. Or it could be an Operator or Shader that needs to be remembered.

Table of contents

1 Resolving the object name
2 User Interface Considerations
3 Error Cases

Store the FullName

The basic answer is that you should remember the value returned by "SIObject.FullName". This returns a name that can be resolved back to the original object easily. The name includes the model name if it is nested under a model.

This works for all X3DObjects and most nested objects. It is only for fringe cases that the FullName would not work - for example Particles, and FCurves.

It could be tempting to store the actual Object itself, e.g. the Object Model object. This approach works well for temporary usage but an object model variable is intended for temporary usage only. In fact it is normally "locked" to the current frame and will return invalid results if accessed after the underlying object is significantly changed. So for long term storage the FullName is better.

Where to Store it

The most common place to store this name is inside the string parameter of a CustomProperty object. A plug-in will be able to retrieve the object any time it wants to by reading the parameter value from the CustomProperty and turning this back to the original object. Another common place to store object names is within an external file, for example a dotXSI or proprietary format.

You can also store the data inside UserDataBlobs, UserDataMaps, or if it is for temporary usage you can store it in memory inside the userdata of a Context object or as a global variable inside a .DLL/.SO. In all cases

And often the name is actually stored directly within a script or .dll/.so. In other words the script is "hardcoded" to expect a certain object to exist within the scene.

Resolving the object name

Given the fullname of an object it is easy to retrieve a Object Model object.

The most straightfoward way is with Dictionary.GetObject (Scripting) and CRef::Set (C++ API). Other techniques used within scripting include the GetValue command, or XSICollection.SetAsText.

User Interface Considerations

If you need to ask the user for an object that the plugin will remember you can use the PickObject command. This can be easier than expecting them to type in a long object name directly into a custom property string edit box.

You can also use the current selection.

The following is an example of how to build a user interface for remembering object names inside a custom property.

// PickObjectDemo
//
// This is a new version of the PickObjectDemo that now
// uses the v4.0 features.  It is implemented as a jscript
// self-installed plugin instead of a SPDL file.  And it uses
// the dynamic UI capabilities to improve the listing of objects.

function XSILoadPlugin( in_reg )
{
	in_reg.Author = "Softimage";
	in_reg.Name = "PickObjectDemoJscriptPlugin";
	in_reg.Major = 1;
	in_reg.Minor = 1;
	in_reg.RegisterProperty( "PickObjectDemoJscript" );
	in_reg.RegisterMenu( siMenuTbGetPropertyPSetID,"PickObjectDemoJscript_Menu",false,false );
	return true;
}

function PickObjectDemoJscript_Define( io_Context )
{
	// Called each time an instance of the custom property is created

	var oCustomProperty;
	oCustomProperty = io_Context.Source;
	oCustomProperty.AddParameter3( "SingleObj",siString ) ;
		
	oCustomProperty.AddParameter3( "ObjForList",siString ) ;	
	
	var oGridParam = oCustomProperty.AddGridParameter( "ObjList" );
	
	// The grid will remain hidden until we add something
	oGridParam.Show(false);
	
	// Set up the dimensions (rows will be added dynamically)
	oGridParam.Value.ColumnCount = 1 ;	
	
	oGridParam.Value.SetColumnLabel( 0, "Object List" ) ;	
	
	// Read-only parameter to force user to use a pick session
	oCustomProperty.AddParameter3( "PickSessionPick",siString,"","","",false,true ) ;
}

function PickObjectDemoJscript_DefineLayout( io_Context )
{
	// We build the layout each time the PPG is opened
	// to ensure that the contents of the object list 
	// is up to date. (see PickObjectDemoJscript_OnInit )
}

function PickObjectDemoJscript_GetObjectChoiceArray()
{
	//Simply find all the X3DObjects recursively under 
	//the scene root, but many other custom filtering 
	//of objects in the scene could be implemented.
	var oItems = ActiveSceneRoot.FindChildren() ;

	var aItems = new Array ;

	var j = 0 

	for( var i = 0 ; i < oItems.Count ; i++ )
	{
		aItems[j++] = oItems.Item(i).Name ;			
		aItems[j++] = oItems.Item(i).Name ;		
	}
	
	// TODO: it can be better to sort the list.
	// However 	XSIUtils.quicksort is not available for JScript
				
	return aItems ;
}

function PickObjectDemoJscript_OnInit( )
{
	// Get the list of objects currently in the scene
	var aItemArray = PickObjectDemoJscript_GetObjectChoiceArray() ;

	// Rebuild the entire layout
	var oLayout,oItem;
	
	oLayout = PPG.PPGLayout;
	oLayout.Clear(  );
	
	// Top group is for selecting one object
	// at a time from a drop down

	oLayout.AddGroup( "Single Object Selection" );	
		
	oItem = oLayout.AddEnumControl( "SingleObj", aItemArray );
	oItem.SetAttribute( siUIValueOnly,true );

	oLayout.EndGroup();


	// Second group is for an object
	// drop down and adding it to a list

	oLayout.AddGroup( "Object List" );
	oLayout.AddRow();	
	
	oItem = oLayout.AddEnumControl( "ObjForList", aItemArray );
	oItem.SetAttribute( siUIValueOnly,true );		

	if ( PPG.ObjForList.Value.length == 0 && aItemArray.length > 0 )
	{
		// Set an initial value for the parameter
		// so that the drop down already has the first
		// item selected
		PPG.ObjForList.Value = aItemArray[0] ;
	}

	oLayout.AddButton( "PickForList","Add" );
	oLayout.EndRow() ;

	oLayout.AddGroup() ; // Nested group
		
	oItem = oLayout.AddItem( "ObjList" );
	oItem.SetAttribute( siUIValueOnly,true );
	oItem.SetAttribute( siUIGridHideRowHeader, true ) ;	
	oItem.SetAttribute( siUIGridReadOnlyColumns, "1" );
	oItem.SetAttribute( siUIGridColumnWidths, "200" );		

	oLayout.EndGroup() ;

	oLayout.EndGroup();
	
	// Third group is to show the Pick Session results
	
	oLayout.AddGroup( "Using Pick Session" ); 
	
	oLayout.AddRow();
	oItem = oLayout.AddItem( "PickSessionPick" );
	oItem.SetAttribute( siUIValueOnly,true );
	
	oLayout.AddButton( "LaunchPick", "..." );
	oLayout.EndRow();
	
	oLayout.EndGroup() ;
	
	// Make sure the new layout is visible
	PPG.Refresh() ;
}

function PickObjectDemoJscript_PickForList_OnClicked()
{
	strPickedObj = PPG.ObjForList.Value
	
	if ( strPickedObj.length == 0 )
	{
		XSIUIToolkit.MsgBox( "Please select an object from the dropdown" ) ;
		return ;
	}
	
	oGridData = PPG.ObjList.Value ; 
	
	cntItems = oGridData.RowCount ;

	// Check for duplicates (this also
	// shows how you would be able to read 
	// the items out of the list
	// if you wanted to do something with them)

	for ( var i = 0 ; i < cntItems ; i++ )
	{	
		strExisting = oGridData.GetCell( 0, i )
		
		if ( strExisting == strPickedObj )
		{
			// Already in the list
			return ;
		}
	}

	// Make sure that the list is visible	
	if ( cntItems == 0 )
	{
		PPG.ObjList.Show(true);
	}
	
	// Add the new item	
	oGridData.RowCount = cntItems + 1  ;	
	oGridData.SetCell( 0, cntItems, strPickedObj ) ;
		
	PPG.Refresh() ;
}

function PickObjectDemoJscript_LaunchPick_OnClicked()
{
	//See documentation of PickObject command for details

	var aRtn = PickObject("Select Object", "Select Object")
	
	var buttonChoice = aRtn.Value( "ButtonPressed" ) ;
	var oItem = aRtn.Value( "PickedElement" );

	if ( buttonChoice != 0 )
	{
			PPG.PickSessionPick.Value = oItem.FullName
	}
}

// Code to provide a menu item for creating this custom property
function PickObjectDemoJscript_Menu_Init( io_Context )
{
	var oMenu;
	oMenu = io_Context.Source;
	oMenu.AddCallbackItem( "PickObjectDemoJscript","OnPickObjectDemoJscriptMenuClicked" );
}

function OnPickObjectDemoJscriptMenuClicked( io_Context )
{
	var oProp,oParent;
	if (Selection.Count == 0)
	{
		oParent = ActiveSceneRoot ;
	}
	else
	{
		oParent = Selection.Item( 0 );
	}
	
	oProp = oParent.AddProperty( "PickObjectDemoJscript" );
	InspectObj( oProp );
	return 1;
}


Error Cases

Several things can go wrong:

Reparenting of the object

If the object is moved to a different location in the scene then it may break the link between the name and the object.

Howeever because all X3DObjects have a single namespace within a Model the name will survive any move of an X3DObject within a single Model. So XSI is pretty robust in this area.

If the object is moved to a different model the link will break.

Deleting the object

If the object no long exists then obviously the link will fail. In some cases it may be possible to lock the object or hide the object that is referenced to help prevent it from being deleted.

A good approach to deal with this scenario is to give a very clear error message as soon as you realise the object is missing or to self-fix the scene by recreating the object on the fly. Both approaches are described in more detail in the next section.

Renaming the object

This is the most common concern. If an object is renamed then the string that is stored will no longer resolve to that object - the link is broken.

The severity of this problem can vary. It may be justified to say that the user must not rename any objects - e.g. that the naming scheme is set in stone and not open for any tweaking once the scene is set up. For example it may be reasonable that a Rig would break if someone changed the name of "LKneeCap".

The important consideration if names are set in stone is to protect yourself from painful diagnosis problems if things go wrong. To protect yourself from long, painful debugging sessions you should add error handling that will proactively tell the user exactly what object is missing as soon as the object is found to be missing. This is better than having the code break down with a script error much later, or even worse it might not log any error at all (perhaps because of a try/catch or on error resume next) and just do the wrong things.

Here is an example JScript function that will turn a string into an object, but it will also "freak out" in a very explicit fashion if there is a failure.

var oObj = SafeGetObjFromString( "RigModel.LKneeCap" )

function SafeGetObjFromString( in_str )
{
	var oReturnObj = Dictionary.GetObject( in_str, false ) ;
	
	if ( oReturnObj == null )
	{
		strErrorMsg ="Object missing from scene: " + in_str ; 
	
		// For UI based scripts
		XSIUIToolkit.MsgBox( strErrorMsg, siMsgCritical  ) ;
				
		// Stop the script in its tracks - this
		// includes a message logged into the script history
		// so it is useful for Batch mode
		throw new Error( 0, strErrorMsg 	) ;
	}

	return oReturnObj ;
}

You could extend this further by actually making the scene self-fixing. For example if there is a failure you could ask the user to pick the object again. E.g. "The script is searching for an object call LKneeCap and can't find it. Please pick the left knee cap object" This is similar to the behavior of Microsoft Windows when you delete the file underneath a shortcut - it asks the user to fix its link. Another self-fixing technique is to recreate the object that is missing so you ignore the renamed object.

Protecting Against Object Renaming

If good error handling isn't enough you can get more sophisticated in your technique of remembering the object. No technique is 100% foolproof, but they can provide robustness to recover from most object renaming scenarios.

(Many of these techniques are based on an interesting discussion from the XSI mailing list called "scripting: find an object by its unique GUID")

Custom Operator Technique You can use a "dummy" custom operator, that connects from the object you want to remember to your custom property. By following the link back you will find the object, even if it gets renamed. Not you can't attach a scripted operator to a string parameter so you need to connect it directly to the custom property or to a numeric parameter. This technique has to be used with some care because it introduces more complexity to the scene. In fact the custom property may not be necessary at all - the presence of the operator itself connected to an object may be sufficient. (In which case you can use the FindObjects command to retrieve the instances).

GUID Tag Technique Another suggestion is to store a custom property underneath the object you want to remember, than then use FindObjects to find the custom property. In other words you are tagging the object. If you want to tag many objects you can store a GUID or other unique identifier inside the custom property, then remember the GUID rather than the fullname of the object. Then if you want to find the object you find all the custom properties, look at the GUIDs that they persist until you find the GUID that you remembered. This is actually similar to the way that XSI keeps track of objects internally. You can use the XSIFactory.CreateGuid to generate a GUID.

Internal GUIDs. XSI actually exposes its own internal GUIDs for objects via the DataRespository object. (See the advanced tab of the SDK explorer). Each object has its own unique GUID. However these GUIDs are not guaranteed to be preserved indefinitely. For example if you load the same model twice or merge a scene into another scene then the GUIDs must change. So this internal GUID must be used with caution.

By Group Membership. You can remember a list of objects by making them all members of a group. This membership will survive object renaming.


This page was last modified 16:53, 21 Jul 2006.
This page has been accessed 7389 times.

© Copyright 2009 Autodesk Inc. All Rights Reserved. Privacy Policy | Legal Notices and Trademarks | Report Piracy