Custom Properties (XSISDK)

Custom Properties are the "bread and butter" of both Custom UI and User Data in XSI. Practically every plug-in that goes beyond a simple batch script will probably include a Custom Property.

Custom Properties are sometimes called "Custom PSets" or "Custom Property Sets".

Note: A "PPG" (short form for "Property Page") is the user interface that shows the parameter values of a XSI object on the screen. So when dealing with Custom Properties, its appearance inside its PPG is very important.

Firstly, anyone who wants to get into Custom Properties should be sure to have read the Custom Properties chapter (http://softimage.wiki.softimage.com/sdkdocs/cus_ppg.htm).It includes information about when you should use a Custom Property, how to build a self-installed custom property and what controls are possible. From there you should also make sure to review the examples in the Object Model or C++ Reference, in particular those found with the PPG (http://softimage.wiki.softimage.com/sdkdocs/PPG.htm), CustomProperty (http://softimage.wiki.softimage.com/sdkdocs/CustomProperty.htm), PPGLayout (http://softimage.wiki.softimage.com/sdkdocs/PPGLayout.htm) and PPGItem (http://softimage.wiki.softimage.com/sdkdocs/PPGItem.htm) objects.

This wiki page adds some extra information to supplement and re-enforce the information above, focusing on some of the most common questions, with details about limitations and workarounds.


Table of contents

Migration

See Custom Property Migration to learn about how to convert your existing Custom Properties to a self-installed custom property.

Persistence and Versioning

Note: Some of the same techniques as can be found in Custom Property Migration section also apply to this scenario.

Adding Parameters to Existing Properties

In the OnInit callback, check whether or not the new parameters exist. If they don't exist, add them and rebuild the layout.

Here's an example that shows the general idea.

function MyTestProperty_OnInit( )
{
	// User may have multiple copies of the property in the PPG,
	// so we have to check each one
	oEnum = new Enumerator( PPG.Inspected ) ;
	for (;!oEnum.atEnd();oEnum.moveNext() )
	{
		var oCustomProperty = oEnum.item() ;
		addParameters( oCustomProperty );
	}

	MyTestProperty_RebuildLayout( PPG.PPGLayout );
	PPG.Refresh();
} 

function addParameters( oCustomProperty ) {
	// If the parameter is not there, add it
	if ( oCustomProperty.Parameters.Item( "MyNewParam" ) == null )
	{
		LogMessage( "no such parameter" );
		oCustomProperty.AddParameter2 "NyNewParam",siString,"Cool!",null,null,null,null,0,siPersistable);
	}
}


function MyTestProperty_RebuildLayout( oLayout ) {
	LogMessage( "MyTestProperty_RebuildLayout" );
	oLayout.Clear();
	// Add all the other parameters...

	// Now add the new parameters to the layout:
	oLayout.AddItem("NyNewParam");
	return true;
}


Modifying Parameters of Existing Properties

Use the EditParameterDefinition command.

Proxy Parameters and the Wizard

The Custom Property wizard does not support proxy parameters. But this is only a limitation of the UI. To create proxy parameters just update the _Define() callback with calls to CustomProperty::AddProxyParameter. The Layout and Logic sections work the same way for parameters and proxy parameters so no other special consideration is necessary.

Refreshing the Layout

It is a common confusion that a user will change the code in the _DefineLayout section but not see the change until they restart XSI. Or even worse they will introduce a bug into _DefineLayout and lose the entire PPG.

This is because the Layout is built only once per XSI session per Property Type. So _DefineLayout() is not called for each single property, instead it is only called once. This is an optimization that works well for "static" property pages that are not actively being developed.

XSI will not call this function automatically, even if you unload and reload a Self-installed plugin.

As a developer you can force the _DefineLayout() method to be called by right clicking on the tab area of the property page and selected the Refresh option.

Image:RefreshPPG.JPG


Or this can be done programmatically by calling XSIUtils.Reload()

If you have a "dynamic" PPG, where list boxes are filled "on-the-fly", or parameters hidden, or some other such change, then sometimes you want to change the content of the PPG even after it has already visible on the screen. In that case you can rebuild the layout and then call PPG.Refresh to force the new Layout to appear on the screen. It is not necessary to call PPG. Refresh after changing the values of a parameter - those changes are automatically reflected in the UI. Check the implementation of the Custom Property Wizard for a full example of this technique.

Outside of PPG logic it is not so easy to change the layout once the PPG is on the screen. A common workaround would be to change the value of hidden variable which will force the PPG to call a PPG Logic callback, which makes it possible to call PPG.Refresh. You could also add and delete a temporary parameter to achieve the same affect.

PPG Logic Only Called When Property Page Visible

PPG Logic does not get invoked if the Custom Property is not being inspected on the screen. So it is dangerous to rely on PPG Logic to keep data fully synchronized if values are changed through scripting.


When to use a SPDL-base Custom Property

Basic answer is "never". Self-installed custom properties can do all the same things and more.

If you have an existing SPDL-based Custom Property that works just fine that you could be justified in leaving it as is. This is because migration is a challenging process.

But for new development a self-installed Custom Property is strongly recommended.

When is Self-Installed Not Necessary

If you build a custom property that has no special layout, e.g. just a few proxy parameter sliders, then you don't need it to be self-installed and you can build it directly into the UI.

Also, if the custom property is only used for a temporary script-based UI (never persisted inside a scene), then you can build the layout on-the-fly and even stuff PPG Logic code into the layout. This technique is demonstrated frequently in the Object Model Reference. However, as soon as you have a fairly complex UI with a lot of logic it is better to use a self-installed custom property.

If in doubt use the wizard and create a Self-installed Custom Property.

Multiple Custom Properties in the Same File

Although the wizard outputs a new plug-in each time, you can easily merge the contents of the files together. The only trick is to make sure that a single XSILoadPlugin method exists.

When to Use C++

If you are familiar with JScript or another scripting language you can have a faster development experience using scripting instead of C++. There is no performance issue because drawing UI takes longer than running most scripts.

However the C++ equivalent is available for users who are most familiar with C++ and want to centralize all the code into a single .dll. Unlike scripts, the code for a .dll is hidden, which might be useful for hiding intellectual property.

One difference is that PPG Logic is centralized in a single callback. This might result in a bit of an ugly switch statement or lots of if/else, but it has the advantage of supporting really dynamic property pages (e.g. where buttons and parameters are built on the fly)

Note: There was a bug in the C++ version of the API in version 4.2, where the name of the clicked button was not sent to the PPGEvent callback. This means it was not possible to have more than one button on a property page.

PPG Layout

The PPGLayout object represents the visual organization of objects on the screen. Layout is usually specified via the PPGLayout object, for example in the _DefineLayout callback. It can also be specified using a SPDL file.

Layout Is Not Persisted

The PPGLayout (http://softimage.wiki.softimage.com/sdkdocs/PPGLayout.htm) is not persisted with the object or with the scene. However, it is cached during the XSI session. All objects of the same Type share the same PPGLayout.

A common confusion occurs when a scene file is moved to another machine and the Custom Properties lose their layouts. This is because the original self-installed plug-in, (or SPDL file) needs to be installed on each machine (or via a workgroup). Some custom properties, especially in the OM reference examples, are created "on-the-fly" with no self-installed plugin. These are great for temporary custom properties but because the layout it not persisted, they should only be used for temporary property pages that are deleted after creation.

To have persistent layout you MUST use a self-installed plug-in, with the _DefineLayout callback implemented.

Refreshing a PPG in C++

PPG.Refresh (http://softimage.wiki.softimage.com/sdkdocs/PPG_Refresh.htm) is not available in the C++ API, but the equivalent behavior can be achieved by setting a certain attribute on the event context. This code snippet is from the PSetUIDemo example:

XSIPLUGINCALLBACK CStatus PSetUIDemoCPP_PPGEvent( const CRef& io_Ctx )
{
	PPGEventContext ctx( io_Ctx ) ;
...
		//Redraw the PPG to show the new combo items
		ctx.PutAttribute(L"Refresh",true);

Showing and Hiding Parameters

When you create parameters you can use the siNotInspectable flag to hide them. Such as:

	oParam.PutCapabilityFlag(siNotInspectable, true);

For unhiding a parameter such as from the siParameterChange you can set it to false:

	oParam.PutCapabilityFlag(siNotInspectable, false);

However if you are using this (in C++ only perhaps?) be sure to add the parameter to the layout before setting siNotInspectable to true. Otherwise when you attempt to set it to false it won't reappear.

Positioning Controls in a Layout

Generally PPGs do not have hardcoded positioning of the controls, which makes them friendly for resizing and adding additional parameters. However for advanced UI it is often desirable to build a custom PPGLayout and control the exact positioning of each control.

To get ideas about positioning check out PSetUIDemo, internal XSI objects and other existing examples.

PPGLayout.AddSpacer is a powerful method, because it can be used to indent controls or add blank lines.

Groups are often used for different effects, including the ability to build multiple columns of controls.

Many controls support "cx" and "cy" attributes to provide a specific size for the control. Having a hardcoded width can be useful when trying to mix multiple controls onto the same row.

The SDK Explorer lets you see the raw data of a PPGLayout, so it can be a useful debugging tool.

The alignment attributes (AlignCenter, AlignRight, AlignLeft) that were present in some older versions of the sofware no longer work.

Centering a Control

It is often desirable to center a control, especially buttons on the Property Page. AlignCenter does not work, but Bradley Gabe posted this workaround to the XSI list.

PPGlayout.AddRow();
        PPGlayout.AddGroup('',false, 30);
        PPGLayout.EndGroup();

        PPGlayout.AddGroup('',false, 40);
        PPGLayout.AddButton('Activate The Enforcer');

        PPGlayout.AddGroup('',false, 30);
        PPGLayout.EndGroup();
PPGlayout.EndRow();

How to fill a List Box Dynamically

A list box is one of various "enum" controls that are supported. The list is associated with a normal numeric or string parameter, so the value of the parameter always reflects the currently selected item.

The data of the control's content is stored in the PPGLayout, so if the layout is updated the new data will be shown the next time the PPG is redrawn. A redraw can be forced by calling PPG.Refresh in the context of a PPG callback.

See PPGItems.UIItems (http://softimage.wiki.softimage.com/sdkdocs/PPGItem_UIItems.htm) for a JScript example.

This is part of the C++ API example PSetUIDemoCPP:

XSIPLUGINCALLBACK CStatus PSetUIDemoCPP_PPGEvent( const CRef& io_Ctx )
...
	if ( eventID == PPGEventContext::siOnInit )
	{
		CustomProperty prop = ctx.GetSource() ;	
		PPGLayout oPPGLayout = prop.GetPPGLayout();		
		PPGItem oPPGItem = oPPGLayout.GetItem(L"DynamicCombo");

		CValueArray newDynamicComboItem( 4 ) ;
		newDynamicComboItem[0] = L"StringLabel1"; newDynamicComboItem[1] = L"StringValue1" ;
		newDynamicComboItem[2] = L"StringLabel2"; newDynamicComboItem[3] = L"StringValue2" ;

		oPPGItem.PutUIItems(newDynamicComboItem);
...
		//Redraw the PPG to show the new combo items
		ctx.PutAttribute(L"Refresh",true);


Tip: The PPGLayout is not persisted. However, a GridData parameter on a custom property is persisted, and is an easy way to store 1 or 2 dimensional arrays. Hence it is possible to save the list box enum values inside a hidden GridData parameter if you want them stored inside the scene file.

No Insertion

The API does not allow insertion or deletion of controls into an existing layout. The only thing that can be changed once the layout is built is the attributes of each existing PPGItem, or addition of new items at the end.

However, in practice, this is not really a limitation. For dynamic layouts where some controls are added in some conditions and hidden in others it is recommended that there be a single "RebuildLayout" function which flushes the entire layout and rebuilts it according to the current conditions. This approach is demonstrated in the implementation of the Custom Property Wizard, where the Add Parameter Tab shows controls directly related to the type of parameter selected in the type list box.

Property Page Sizing

When using InspectObj you cannot specify the size of the Property Page. XSI uses its own heuristic based on information about the controls in the layout and remembered information about any resizing that had been done.

However in some cases it is desirable to specify an actual size and position. The current best way to do this is by embedding the custom property in a floating view. This is the approach taken by the Custom Event Wizard in XSI v5.0. Also see the second example here (http://softimage.wiki.softimage.com/sdkdocs/View.htm).

PPG Logic

Originally called "SPDL Logic", these are the scripting callbacks that are called when a parameter value changes, when the PPG appears on the screen, when the tab is changed, or when a button is clicked. This mechanism is older than the Self-installed Plugin mechanism, but PPG Logic can be included directly in the Self-installed Plugin containing the Custom Property.

In C++, PPGLogic is implemented in the single PPGEvent callback.

Notification of Parameter Value Change

PPG Logic is a good way to get notification of a parameter value change on a particular object. However there is a major limitation - this callback only occurs when the Property Page is visible on the screen. So it is not suitable for low level notification designs. It is recommended instead for UI related behavior.

An example suitable use of PPG Logic is to have an "Advanced" check box on a custom property. When the parameter value is set to true, the PPG Logic callback will show various extra controls that are hidden in the default Property Page Layout.

(One alternative is a Custom Display Host, which receives more notifications, but only when the Custom Display Host is active.)

Notification when Frame Changes

When you change frames or finish a playback, XSI will launch the SPDL logic callbacks for the property pages that are visible. This means that your callback may be called even if the parameter has not actually changed in value.

Find Custom Properties

The FindObjects command provides a fast way to find all Custom Properties in a scene. A further filtering step, such as shown in the second function below, can be used to reduce the list of Custom Properties to the ones that you are interested in finding.

// General function to find all custom properties
// This returns a XSICollection and includes all custom properties
// that are part of the scene, or installed as custom preferences
// or "free-floating"
function FindAllCustomProperties( oParent )
{
	// All Custom Properties are registered with this GUID
	return FindObjects( null, "{76332571-D242-11d0-B69C-00AA003B3EA6}" ) ;
}

// Function to only find the "CustomColor" Custom Property
function FindCustomColorProperties()
{
	var oAllCustomPropertie = FindObjects( 
					null, 
					"{76332571-D242-11d0-B69C-00AA003B3EA6}" ) ;
	
	var oFilteredList = new ActiveXObject( "XSI.Collection" ) ;
	
	for ( var i = 0 ; i < oAllCustomProperties.Count ; i++ )
	{
		if ( oAllCustomProperties(i).Type == "CustomColor" )
		{
			oFilteredList.Add( oAllCustomProperties(i) ) ;
		}
	}		
	
	return oFilteredList ;
}



// In C++, you can use Application::ExecuteCommand() to run the FindObjects command.
// For convenience, you can use this FindObjects wrapper from cmdstubs.cpp:

CValue FindObjects( const CString& in_path, const CString& in_type )
{
	CValueArray args(2);
	CValue retval;
	LONG i(0);

	args[i++]= in_path;
	args[i++]= in_type;

	CStatus st = gApp.ExecuteCommand( L"FindObjects", args, retval );

	return retval;
}


// The FindObjects function returns a CValue. 
// The CValue is actually a CValueArray. 
// The CValues in that CValueArray are CRefs.

// So, doing it the long way:

CValue val = FindObjects( L"", L"{76332571-D242-11d0-B69C-00AA003B3EA6}" );
CValueArray varr = val;
for (long i=0; i<varr.GetCount(); i++)
{
   CRef ref = varr[i];
   CustomProperty cp = ref;

   if ( cp.GetType() == L"CustomColor" )
   {
	   app.LogMessage( cp.GetFullName() );
   }
}

// Or, taking advantage of the casting and conversion operators provided by the SDK:

CValueArray objects = FindObjects( L"", L"{76332571-D242-11d0-B69C-00AA003B3EA6}" );
for (long i=0; i<objects.GetCount(); i++)
{
	CustomProperty cp = objects[i];
	if ( cp.GetType() == L"CustomColor" )
	{
		app.LogMessage( cp.GetFullName() );
	}
}


This page was last modified 19:00, 3 Nov 2009.
This page has been accessed 42311 times.

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