Selecting rows in a GridWidget


Here’s a basic Python example that shows how to:

  • Create a dynamic, on-the-fly custom property
  • Add a grid to the PPG
  • Set the PPG Logic in Python
  • Select rows in a GridWidget
import win32com.client
from win32com.client import constants

oProp = Application.ActiveSceneRoot.AddProperty( "CustomProperty", False, "GridWidgetDemo" )
oProp.AddParameter2("Row",constants.siInt4,1,1,20,0,100,constants.siClassifUnknown,constants.siPersistable + constants.siKeyable)
			
oParameter = oProp.AddGridParameter( "DemoGrid" )
oGridData = oParameter.Value
oGridData.ColumnCount = 3
oGridData.RowCount = 20

for i in range(0,oGridData.RowCount):
	for j in range(0,oGridData.ColumnCount):
		oGridData.SetCell( j, i, str(i) + "." + str(j) )

oPPGLayout = oProp.PPGLayout
oGridPPGItem = oPPGLayout.AddItem( "DemoGrid" )

oGridPPGItem.SetAttribute( constants.siUINoLabel, True )
oGridPPGItem.SetAttribute( constants.siUIGridSelectionMode, constants.siSelectionHeader )
oGridPPGItem.SetAttribute( constants.siUIGridColumnWidths, "25:100:75:100" )
oGridPPGItem.SetAttribute( constants.siUIGridReadOnlyColumns, "1" )

oPPGLayout.AddRow() ;
oPPGLayout.AddItem( "Row" )
oPPGLayout.AddButton( "SelectRow", "Select Row" )
oPPGLayout.EndRow()



oPPGLayout.Language = "Python"
oPPGLayout.Logic = '''
def SelectRow_OnClicked():
	Application.LogMessage( "Select Row" )
	oGridData = PPG.DemoGrid.Value
	oGridWidget = oGridData.GridWidget
	oGridWidget.ClearSelection()
	oGridWidget.AddToSelection( -1, PPG.Row.Value-1 )
'''


Application.InspectObj( oProp )

Centering a button on a PPG


By default, buttons are left-aligned.

I don’t think there is any way to center a control on a layout. At least not one that I can find. You could set an X position, which will look centered until a user resizes the PPG.

item = oLayout.AddButton( L"MyButton", L"ddd" ) ;
item.PutAttribute( siUICX, <x position in pixels>) 

Or you could use this hack, which uses group width percentages to approximate the centering a button.

var oGroup = oLayout.AddGroup("", false, 40);
oLayout.EndGroup();

// button is left-aligned within its group
// so the centering is not 'true'
var oGroup = oLayout.AddGroup("", false, 20);
var oButton = oLayout.AddButton( "OK" );
oLayout.EndGroup();

var oGroup = oLayout.AddGroup("", false, 30);
oLayout.EndGroup();

oLayout.EndRow();

Getting the shader connected to a material


In previous versions of Softimage, you could use Parameter.Source to get the shader/texture connected to a material parameter such as the diffuse port.

As of 2011, Source returns a ShaderParameter. To get the connected shader, use ShaderParameter.Parent (in C++, GetSource().GetParent()).

var m = Dictionary.GetObject( "Sources.Materials.DefaultLib.Material.Blinn" );
var p = m.Parameters("diffuse");
LogMessage( p.Source );
// INFO : Sources.Materials.DefaultLib.Material.Image.out

LogMessage( ClassName( p.Source ) );
// INFO : ShaderParameter

LogMessage( p.Source.Parent );
// INFO : Sources.Materials.DefaultLib.Material.Image

LogMessage( ClassName( p.Source.Parent ) );
// INFO : Texture

Python: Using Plugin.UserData to pass data to PPG callbacks


UPDATE: Please see the comment from Patrick for a tip. Thanks!

You can use Plugin.UserData to pass data into a plugin.

For example, from outside the plugin, you can store a list in the UserData:

p = Application.Plugins("MyTestPropertyPlugin")
p.UserData = ['a', 'b', 'mpilgrim', 'z', 'example']

In the plugin callbacks, you would access the UserData like this:

def MyTestProperty_Test_OnClicked( ):

    p = Application.Plugins("MyTestPropertyPlugin")

    if p.UserData == None:
        Application.LogMessage( "UserData is empty" )
    else:
        Application.LogMessage( p.UserData )
        Application.LogMessage( p.UserData[2] )

This will work with lists, but not with dictionaries.
Softimage cannot convert Python dictionaries into a COM Variant type. If you try to store a dict in User Data:

x = Application.Plugins("MyTestPropertyPlugin")
x.UserData = {"author":"blairs", "name":"testplugin"}

You’ll get this error:

# TypeError: Objects of type 'dict' can not be converted to a COM VARIANT

I’m afraid there’s no way around that error for Python dictionaries.
This also prevents you from saving dictionaries with SetGlobal or SetGlobalObject.

Plugin user data


If you look up “UserData” in the index, you’ll see three different UserData properties that apply to a plugin.

Plugin.UserData is probably the most useful. It allows you to store global data that you can access from anywhere in the plugin code. You can use it to share data between external code and the plugin, between instances of the plugin, or between any callback (in a scripted plugin, not all callbacks get a context argument).

Context.UserData allows you to share data between callbacks (except for those callbacks that don’t get a context, like the OnClicked callbacks in a scripted plugin).

PluginRegistrar.UserData allows you to share data between XSILoadPlugin and XSIUnloadPlugin.

Updating a combo box from an OnClicked callback


To update the contents of a combo box from a button OnClicked callback, you use the PPGItem.UIItems property.

Here’s a simple example that shows how:

import win32com.client
from win32com.client import constants

null = None
false = 0
true = 1

def XSILoadPlugin( in_reg ):
    in_reg.Author = "blairs"
    in_reg.Name = "ComboTestPlugin"
    in_reg.Major = 1
    in_reg.Minor = 0

    in_reg.RegisterProperty("ComboTest")

    return true

def XSIUnloadPlugin( in_reg ):
    strPluginName = in_reg.Name
    return true

def ComboTest_Define( in_ctxt ):
    oCustomProperty = in_ctxt.Source
    oCustomProperty.AddParameter2("List",constants.siInt4,0,0,100,0,100,constants.siClassifUnknown,constants.siPersistable + constants.siKeyable)
    return true

def ComboTest_DefineLayout( in_ctxt ):
    oLayout = in_ctxt.Source
    oLayout.Clear()
    oLayout.AddEnumControl("List", ("chocolate", 0, "vanilla", 1, "strawberry", 2), "Flavor", constants.siControlCombo )
    oLayout.AddButton("Update")
   
    return true

def ComboTest_Update_OnClicked( ):
    Application.LogMessage("ComboTest_Test_OnClicked called")
    x = ("Coffee Heath Bar Crunch", 0, "Cherry Garcia", 1, "Dulce Delux", 2 )
    PPG.PPGLayout.Item("List").UIItems = x
    Application.LogMessage( PPG.PPGLayout.Item("List").UIItems )
    PPG.Refresh()

Using a hidden parameter to store values


Here’s a simple self-installing property that shows how to use a “hidden” parameter to store a collection as a comma-separated string. I say the parameter is “hidden” because it is not shown on the property page (PPG).

function XSILoadPlugin( in_reg )
{
	in_reg.Author = "blairs";
	in_reg.Name = "SaveCollectionPlugin";
	in_reg.Major = 1;
	in_reg.Minor = 0;

	in_reg.RegisterProperty("SaveCollection");

	return true;
}

function XSIUnloadPlugin( in_reg )
{
	var strPluginName;
	strPluginName = in_reg.Name;
	Application.LogMessage(strPluginName + " has been unloaded.",siVerbose);
	return true;
}

function SaveCollection_Define( in_ctxt )
{
	var oCustomProperty;
	oCustomProperty = in_ctxt.Source;
	
	// Used to store the list of passes as a comma-separated string
	oCustomProperty.AddParameter2("Passes",siString,"",null,null,null,null,siClassifUnknown,siPersistable | siKeyable);
	
	return true;
}

function SaveCollection_DefineLayout( in_ctxt )
{
	var oLayout,oItem;
	oLayout = in_ctxt.Source;
	oLayout.Clear();
	oLayout.AddButton("Set");
	oLayout.AddButton("Get");
	return true;
}

// Save a list of passes in the Passes parameter
function SaveCollection_Set_OnClicked( )
{
	Application.LogMessage("SaveCollection_Test_OnClicked called",siVerbose);
	PPG.Passes.Value = ActiveProject.ActiveScene.Passes.GetAsText();
}

// Get the list of passes from the Passes parameter
function SaveCollection_Get_OnClicked( )
{
	Application.LogMessage("SaveCollection_Test_OnClicked called",siVerbose);

	// Split the string into an array
	var x = PPG.Passes.Value.split(",");
	for ( var i = 0; i &lt; x.length; i++ )
	{
		LogMessage( x[i] );
	}

}

Adding sub-menus in Python


In Python, use Menu.AddSubMenu to add a submenu.

def SubMenuTest_Menu_Init( in_ctxt ):
	oMenu = in_ctxt.Source
	subMnu = oMenu.AddSubMenu( "Test SubMenu" )
	subMnu.AddCommandItem("Test", "Test")
	return true

Don’t use AddItem, because in Python the derived class methods of the returned object are not resolved properly (it’s an issue with late binding). Basically, with AddItem you end up with a Menu object that supports just the MenuItem interface. So, methods like AddCommandItem, which belong to the derived Menu class, are not resolved and you get errors like this:

# ERROR : Traceback (most recent call last):
#   File "<Script Block 2>", line 55, in Test_Menu_Init
#     subMnu.AddCommandItem("Duplicate Single", "Duplicate Single")
#   File "C:\Program Files\Autodesk\Softimage 2011\Application\python\Lib\site-packages\win32com\client\__init__.py", line 454, in __getattr__
#     raise AttributeError, "'%s' object has no attribute '%s'" % (repr(self), attr)
# AttributeError: '<win32com.gen_py.Softimage|XSI Object Model Library v1.5.MenuItem instance at 0x517703560>' object has no attribute 'AddCommandItem'

Notice how it says that MenuItem instance has no attribute ‘AddCommandItem’.
AddCommandItem is defined by the derived Menu class.

Before the AddSubMenu method was added, you had to workaround this with win32com.client.Dispatch:

subMnu = win32com.client.Dispatch( oMenu.AddItem("Test SubMenu", constants.siMenuItemSubmenu ) )