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()

The case of the slow license checkout

In this case, the customer had several different license servers set up in his company, and he noticed that one license server in particular was very slow. When he used that license server, all his Autodesk software took a long time to start up.

Also, there was another symptom: he had to use the IP address to specify the [slow] license server location. If he used the name of the license server, the software wouldn’t find the license server.

Ultimately, the problem was solved by installing the IPv6 update for the Autodesk Network License Manager.

Disabling IPv6 on the client may have also worked.

I don’t know that there is a general problem related to IPv6, but I did find a handful of other, similar support cases logged over the last couple of years. There were a couple about slow license checkouts, and a couple about having to use the IP address instead of the computer name.

The case of Softimage stuck in layout editing mode after a crash

In this case, a customer couldn’t start Softimage again after he crashed while editing a layout.
When he started Softimage, it went into the layout editing mode and stopped responding.

In the past, we’ve fixed problems like this by asking customers to rename the User folder, or to delete any bogus .xsily files (for example, zero-length files) from the Application\layouts folder in their User location.

However, I learned from Luc-Eric that there’s a specific preference that specifies whether Softimage is in the layout editing mode. So, to stop Softimage from starting up in the Layout Editor, we had to edit %XSI_USERHOME%\Data\Preferences\default.xsipref and change this

	xsiprivate.UI_LAYOUT_DEFAULT	= Layout Editor

to this:

	xsiprivate.UI_LAYOUT_DEFAULT	= Default

Passing arguments to xsibatch scripts

You can use xsibatch to execute a script:

	xsibatch -processing -script %TEMP%\test.js 

The -processing flag tells xsibatch to run without a license (you can do this for anything that does not involve rendering).
The -script flag specifies the full path the script file.

If the script file contains functions, you can specify the name of the function to execute with the -main flag. For example, if the .js file contained a function named “test”:

	xsibatch -processing -script %TEMP%\test1.js -main test 

If the main function takes arguments, you can specify them with the -args flag. For example, if the function is defined like this (in JScript)

	function test( sPath, sList, nValue )
		// [body]

then you would specify the arguments like this on the xsibatch command line:

	-args -sPath "%TEMP%" -sList "X,Y,Z" -nValue 22

Putting it all together, the xsibatch command line would look like this:

	xsibatch -processing -script %TEMP%\test1.js -main test -args -sPath "%TEMP%" -sList "X,Y,Z" -nValue 22

Argument values are passed in as strings, and any quotation marks are stripped off.

Here’s a simple example of a test.js:

function test( sPath, sList, nValue )
	LogMessage( sPath );
	LogMessage( sList );
	LogMessage( nValue );
	LogMessage( typeof(nValue) );

The xsibatch command:

xsibatch -processing -script %TEMP%\test1.js -main test -args -sPath "%TEMP%" -sList "X,Y,Z" -nValue 22

And the output:

 Autodesk Softimage

License information: using [Processing]
COMMAND: -processing -script C:\Users\blairs\AppData\Local\Temp\test.js -main test -args -sPath "C:\Users\blairs\AppData\Local\Temp" -sList "X,Y,Z" -nValue 22
// INFO : C:\Users\blairs\AppData\Local\Temp
// INFO : X,Y,Z
// INFO : 22
// INFO : string

Using modulo to do something different for odd and even values of a repeat loop counter

If you have a Repeat with Counter setup in an ICE tree, and you want to do one thing for odd values of the loop counter, and another thing for even values, you can use the Modulo node.
Doing a modulo by 2 on the loop counter (self.CounterValue) will tell you whether the loop counter is odd (modulo by 2 = 1) or even (modulo by 2 = 0)

Here’s a [super simple] example that populates an array with different values based on whether the loop counter is odd or even.
Click to view full size.

ICE troubleshooting with Show Values

Troubleshooting doesn’t have to be fancy. Sometimes you just have to focus in on a specific part of an ICE Tree, and use Show Values to understand what is going on.

For example, as part of a larger ICE tree, I was using Test in Geometry and Get Set Sum as described by gray on xsibase (reply #8). But I wasn’t getting the result I expected.

So I broke out the Test in Geometry and Get Set Sum branch into a simple test scene, and used Show Values to help understand what was going on. As you can see in these screenshots, Get Set Sum is giving me different results based on the set I feed into it.

If I start with a set of Bools per Point and use an If node to map those boolean to integer values, I don’t get the result I expect.

Troubleshooting with Show Values

Simplified tree for testing