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\", 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 ) )

Using a pop-up scene explorer in a property page

Here’s how to use a pop-up (transient) explorer to allow users to select multiple objects in the scene.

In this approach, you have a text box and a button.

The OnClicked callback for the button would use OpenTransientExplorer to pop up an explorer where the user can select multiple objects. OpenTransientExplorer returns a collection, so you can store that in a string parameter.

null = None
false = 0
true = 1

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

	#RegistrationInsertionPoint - do not remove this line

	return true

def XSIUnloadPlugin( in_reg ):
	strPluginName = in_reg.Name
	Application.LogMessage(str(strPluginName) + str(" has been unloaded."),constants.siVerbose)
	return true

def MyMultiSelectTest_Define( in_ctxt ):
	oCustomProperty = in_ctxt.Source
	oCustomProperty.AddParameter2("Objects",constants.siString,"",null,null,null,null,constants.siClassifUnknown,constants.siPersistable + constants.siKeyable)
	return true

# Tip: Use the "Refresh" option on the Property Page context menu to 
# reload your script changes and re-execute the DefineLayout callback.
def MyMultiSelectTest_DefineLayout( in_ctxt ):
	oLayout = in_ctxt.Source
	return true

def MyMultiSelectTest_OnInit( ):
	Application.LogMessage("MyMultiSelectTest_OnInit called",constants.siVerbose)

def MyMultiSelectTest_OnClosed( ):
	Application.LogMessage("MyMultiSelectTest_OnClosed called",constants.siVerbose)

def MyMultiSelectTest_Param_OnChanged( ):
	Application.LogMessage("MyMultiSelectTest_Param_OnChanged called",constants.siVerbose)
	oParam = PPG.Param
	paramVal = oParam.Value
	Application.LogMessage(str("New value: ") + str(paramVal),constants.siVerbose)

def MyMultiSelectTest_Param1_OnChanged( ):
	Application.LogMessage("MyMultiSelectTest_Param1_OnChanged called",constants.siVerbose)
	oParam = PPG.Param1
	paramVal = oParam.Value
	Application.LogMessage(str("New value: ") + str(paramVal),constants.siVerbose)

def MyMultiSelectTest_Explore_OnClicked( ):
	Application.LogMessage("MyMultiSelectTest_Explore_OnClicked called",constants.siVerbose)
	sel = Application.OpenTransientExplorer( Application.ActiveProject.ActiveScene.Passes, constants.siSEFilterAllNodesNoParams, 0, False, True )
	PPG.Objects.Value = sel

For testing, I use the Plugin Tree (in the Plugin Manager) to create an instance of the property: just right-click the custom property and click Create.

That runs this command:

Application.SIAddProp("MyMultiSelectTest", "", "siDefaultPropagation", "", "")

Finding groups for an object

If you want to find the groups that contain an object, you have to look through the Owners collection. Look for owners of type = “#group”. Note that you can filter the Owners collection by type, but when you filter by “#group”, you’ll also get layers and partitions, because they are types of groups.

import win32com.client
oGroups = win32com.client.Dispatch( "XSI.Collection" )

o = Application.Selection(0)

oOwners = o.Owners.Filter( "#Group" )

for g in oOwners:
	if g.Type == "#Group":
		#Application.LogMessage( ''.join([g.FullName, ' ', g.Type] ) )
		oGroups.Add( g )

Application.LogMessage( oGroups.GetAsText() )

If you had a naming convention for your groups, you could use the third argument to Filter. For example, if all group names started with “grp”, then could do this:

from win32com.client import constants

object = Application.Selection(0)

# Filter by type and then by name
groups = object.Owners.Filter( "#Group", '', "grp*" )
print groups.GetAsText()

# OR filter by family and then by name
groups = object.Owners.Filter( '', constants.siGroupFamily, "grp*" )
print groups.GetAsText()