Python (XSISDK)

Python, also called PythonScript, is growing in popularity and is an interesting choice for XSI programming.

A good, freely downloadable book about Python can be found at www.diveintopython.org.

Table of contents

Installation

Windows

  • For Windows 64bit, you can download a special version of XSI with Python support for 64bit from the Softimage downloads site (http://softimage.com/downloads/default.aspx).

Linux

  • For Linux 32bit, Python is installed as part of the XSI setup. This includes both the core Python library (version 2.3) and a special Linux compilation of the pywin32 library.

Python Advantages

  • As a new language, it has a concise syntax and all the important modern language features (object orientation, exception handling, etc)
  • Python is a clean, sparse language that can do more in fewer characters.
  • Python is much better than jscript at manipulating the file system.
  • Python can include files without having to register them as commands in XSI.
  • Free language not associated with a large software manufacturer
  • Many libraries are built-in or freely available to provide file access, XML parsing, UI, access to the Windows Registry, Database access and so on.
  • Just-in-time compilation support
  • Support for building libraries of code as Custom Modules

Examples

"Gotchas"

Note: Please refer to Issues with Python (http://softimage.wiki.avid.com/sdkdocs/script_python.htm) for more common Python questions and answers.

This section covers workarounds for known issues with Python.

Python Is More Case-Sensitive

Python is more picky about the Case-Sensitivity of method and property names in the Object Model. This is unlike vbscript (totally case insensitive) and JScrict (case-sensitive for its own variables and functions but case-insensitive for the Object Model and Commands)

Example:

myVector = XSIMath.CreateVector3()

# Doesn't work
myVector.set(1,1,1)

# Works
myVector.Set(1,1,1)

So it is highly recommended to always use the correct case when using the OM or calling commands. This can be a real "gotcha" when porting existing scripts to Python.


Python Not Appearing in XSI list of Scripting Languages on Windows

  • Python and Pywin32 must be installed.
  • If it is installed and still not showing up try running this script (adjust the path according to your own machine):
C:\Python23\Lib\site-packages\win32comext\axscript\client\pyscript.py

Note: In recent versions of pywin32 the install package seems to work better so that running this script is not necessary.


  • If, after all of this, Python is still not showing up in XSI, it can help to make sure that your Python installation path is listed in your system's "path" environment variable. During Python installation a separate environment variable called "PythonPath" is usually automatically created by the installer, pointing to the root folder of the active Python installation, but this is not it! This path must also be included in the actual "Path" variable of your system.

Errors after re-installation

If you start to get unexpected run-time errors inside XSI after updating pywin32, you many need to manually delete the gen_py directory (for example, C:\Python24\Lib\site-packages\win32com\gen_py). It seems that the uninstall does not correctly clean out this directory. This directory is a performance cache and its contents will be correctly regenerated anytime it is deleted.

This may also be a conflict resulting from running multiple different versions of XSI on the same machine.

This case can also happen when XSI is reinstalled.

If you need to be sure that Python is working good in XSI 100% of the time on all your computers, you can create a python plug-in in your shared workgroup that will detect the cache corruption and makes XSI stop.

Here is some code you can use as a base: it will display a user friendly message just before exiting XSI by brute force just after XSI starts (the detection is done during the loading of the plugins). If the user starts XSI again, the cache will be clean and all the Python plugins will work!

Plugin code:

from win32com.client import constants as c

def XSILoadPlugin( in_reg ):
    in_reg.Author = "Aloys Baillet"
    in_reg.Name = "TestMyPythonPlugin"
    in_reg.Major = 1
    in_reg.Minor = 0
    try:
        in_reg.RegisterCommand( "TestMyPython", "TestMyPython" )
    except:
        import shutil, os, win32com, os.path
        genPath = os.path.join(os.path.dirname(win32com.__file__), 'gen_py')
        shutil.rmtree(genPath)
        msg = 'Python is not working on this computer!\n'
        msg += 'Please restart XSI to automatically repair your Python installation.\n'
        msg += 'This message should disappear, if not try to get some help!'
        Application.LogMessage(msg, c.siError)
        if Application.Interactive:
            XSIUIToolkit.MsgBox(msg)
        os._exit(3)

def TestMyPython_Init( io_Context ):
    oCmd = io_Context.Source
    oCmd.Description = "Test Python Installation"
    oCmd.ReturnValue = True

def TestMyPython_Execute(  ):
    return True

Missing Methods or Properties Workaround

In versions prior to v6.0 XSI sometimes returned objects with an incorrectly set (as per PythonWin standards) multi-dispatch interface. This problem has been fixed in v6.0.

Tips

Calling Properties that take Arguments

Python does not support properties that take arguments.

The XSI SDK includes a number of such properties. As of v6.0, new methods have been added which are Python-compliant:

Non-Python compliant Properties New Python-compliant Methods
Parameter_Value (http://softimage.wiki.avid.com/sdkdocs/Parameter_Value.htm) Parameter_GetValue2 (http://softimage.wiki.avid.com/sdkdocs/Parameter_GetValue2.htm)
Primitive_Geometry (http://softimage.wiki.avid.com/sdkdocs/Primitive_Geometry.htm) Primitive_GetGeometry2 (http://softimage.wiki.avid.com/sdkdocs/Primitive_GetGeometry2.htm)
ProjectItem_Selected (http://softimage.wiki.avid.com/sdkdocs/ProjectItem__Selected.htm) ProjectItem_IsSelected (http://softimage.wiki.avid.com/sdkdocs/ProjectItem_IsSelected.htm) and ProjectItem_SetAsSelected (http://softimage.wiki.avid.com/sdkdocs/ProjectItem_SetAsSelected.htm)
X3DObject_Models (http://softimage.wiki.avid.com/sdkdocs/X3DObject_Models.htm) X3DObject_GetModels2 (http://softimage.wiki.avid.com/sdkdocs/X3DObject_GetModels2.htm)
Operator_Port (http://softimage.wiki.avid.com/sdkdocs/Operator_Port.htm) Operator_GetPort2 (http://softimage.wiki.avid.com/sdkdocs/Operator_GetPort2.htm)
XSIApplication_InstallationPath (http://softimage.wiki.avid.com/sdkdocs/XSIApplication_InstallationPath.htm) XSIApplication_GetInstallationPath2 (http://softimage.wiki.avid.com/sdkdocs/XSIApplication_GetInstallationPath2.htm)
FCurveKey_Constraint (http://softimage.wiki.avid.com/sdkdocs/FCurveKey_Constraint.htm) FCurveKey_GetConstraint2 (http://softimage.wiki.avid.com/sdkdocs/FCurveKey_GetConstraint2.htm)
KinematicState_Transform (http://softimage.wiki.avid.com/sdkdocs/KinematicState_Transform.htm) KinematicState_GetTransform2 (http://softimage.wiki.avid.com/sdkdocs/KinematicState_GetTransform2.htm) and KinematicState_PutTransform2 (http://softimage.wiki.avid.com/sdkdocs/KinematicState_PutTransform2.htm)
StaticKinematicState_Transform (http://softimage.wiki.avid.com/sdkdocs/StaticKinematicState_Transform.htm) StaticKinematicState_GetTransform2 (http://softimage.wiki.avid.com/sdkdocs/StaticKinematicState_GetTransform2.htm)
UserDataMap_Item (http://softimage.wiki.avid.com/sdkdocs/UserDataMap_Item.htm) UserDataMap_GetItem2 (http://softimage.wiki.avid.com/sdkdocs/UserDataMap_GetItem2.htm)
UserDataMap_ItemValue (http://softimage.wiki.avid.com/sdkdocs/UserDataMap_ItemValue.htm) UserDataMap_GetItemValue2 (http://softimage.wiki.avid.com/sdkdocs/UserDataMap_ItemValue.htm)
UserDataMap_ItemSize (http://softimage.wiki.avid.com/sdkdocs/UserDataMap_ItemSize.htm) UserDataMap_GetItemSize2 (http://softimage.wiki.avid.com/sdkdocs/UserDataMap_GetItemSize2.htm)
FCurveEditor_SelectedKeys (http://softimage.wiki.avid.com/sdkdocs/FCurveEditor_SelectedKeys.htm) FCurveEditor_GetSelectedKeys2 (http://softimage.wiki.avid.com/sdkdocs/FCurveEditor_GetSelectedKeys2.htm)
FCurveEditor_EditorAttribute (http://softimage.wiki.avid.com/sdkdocs/FCurveEditor_EditorAttribute.htm) FCurveEditor_GetEditorAttribute2 (http://softimage.wiki.avid.com/sdkdocs/FCurveEditor_GetEditorAttribute2.htm)
Envelope_Weights (http://softimage.wiki.avid.com/sdkdocs/Envelope_Weights.htm) Envelope_GetWeights2 (http://softimage.wiki.avid.com/sdkdocs/Envelope_GetWeights2.htm)

Calling the Item Method of a Collection

To read an item out of a collection use the [] or () syntax.

# Showing how to call the Item method of a Collection
oObj = Application.ActiveSceneRoot.AddGeometry("Cube","MeshSurface")
oEdges = oObj.ActivePrimitive.Geometry.Edges

try:
	oEdge = oEdges.Item( 10 )
except:
	Application.LogMessage( "You cannot call .Item() method explicitly" ) ;
	
# But this syntax is identical
oEdge = oEdges[10]
Application.LogMessage( oEdge.Vertices.Count ) 

# And so is this
oEdge = oEdges(10)
Application.LogMessage( oEdge.Index ) 

#Output:
#INFO : 2
#INFO : 10

And a second example:

XSIUtils.Environment["MYVAR"] = "bar" ;
Application.LogMessage( XSIUtils.Environment["MYVAR"] ) ;


Python Introspection

Ever wondered what function an XSI object exposes at runtime?

# As posted by Jerry Gamache on XSI mailing list
def GetFunctions( dynDisp ):
	"""returns a sorted and unique list of all functions defined in a dynamic dispatch"""
	dict = {}
	try:
		for iTI in xrange(0,dynDisp._oleobj_.GetTypeInfoCount()):
			typeInfo = dynDisp._oleobj_.GetTypeInfo(iTI)
			typeAttr = typeInfo.GetTypeAttr()
			for iFun in xrange(0,typeAttr.cFuncs):
				funDesc = typeInfo.GetFuncDesc(iFun)
				name = typeInfo.GetNames(funDesc.memid)[0]
				dict[name] = 1
	except:
		pass # Object is not the dynamic dispatch I knew
	ret = dict.keys()
	ret.sort()
	return ret

import pprint

funcs = GetFunctions(Application)
Application.LogMessage(pprint.pformat(funcs))

funcs = GetFunctions(Application.ActiveSceneRoot)
Application.LogMessage(pprint.pformat(funcs))

It is even possible to generate some basic Object Model documentation based on the information exposed by Python:

def FormatDocumentation(typeInfo, funDesc):
	nameAndParms = typeInfo.GetNames(funDesc.memid)
	docum = typeInfo.GetDocumentation(funDesc.memid)
	import pythoncom
	# This one differentiates between "functions" and "accessors"
	if funDesc.invkind == pythoncom.INVOKE_FUNC:
		docStr = nameAndParms[0] + "(" + ",".join(nameAndParms[1:]) + ")\n"
	else:
		docStr = nameAndParms[0] + "\n" 
	if docum[1]:
		docStr += "\t" + docum[1]
	return docStr

def GetDocumentation( dynDisp, funcName = None ):
	allTypeInfoDoc = []
	for iTI in xrange(0,dynDisp._oleobj_.GetTypeInfoCount()):
		typeInfo = dynDisp._oleobj_.GetTypeInfo(iTI)
		typeAttr = typeInfo.GetTypeAttr()
		if funcName:
			for iFun in xrange(0,typeAttr.cFuncs):
				funDesc = typeInfo.GetFuncDesc(iFun)
				name = typeInfo.GetNames(funDesc.memid)[0]
				if name.upper() == funcName.upper():
					return FormatDocumentation(typeInfo, funDesc)
		else:
			dict = {}
			className = dynDisp._oleobj_.GetTypeInfo(0).GetDocumentation(-1)
			classDoc = "class %s \n\t%s\n\n"%(className[0],className[1])
			allFuncDoc = []
			for iFun in xrange(0,typeAttr.cFuncs):
				funDesc = typeInfo.GetFuncDesc(iFun)
				name = typeInfo.GetNames(funDesc.memid)[0]
				if not dict.has_key(name):
					dict[name] = 1
					allFuncDoc.append(FormatDocumentation(typeInfo, funDesc))
			allFuncDoc.sort()				
			allTypeInfoDoc.append(classDoc + "\n".join(allFuncDoc))
	if funcName:
		return "Documentation not found for %s"%(funcName,)
	else:
		return "\n".join(allTypeInfoDoc)

# 2 ways to call this one: without a function name it retrieves doc for all functions
Application.LogMessage(GetDocumentation(Application))
# And with a name, it tries to find the doc for that function:
Application.LogMessage(GetDocumentation(Application, "StatusBar"))
Application.LogMessage(GetDocumentation(Application, "NotThere"))

Testing if an SDK object supports a particular Property or Method

First option is to use getattr. This example tests if the selected object supports the Properties property.

oObj = Application.Selection(0)
if getattr(oObj, "Properties", None):
	Application.LogMessage("Yea")
else:
	Application.LogMessage("Nay")

Second option is to use exception handling to continue smoothly even if the Property or Method is not supported.

Unwrap a Python Variable

The return value of a Python based custom command is always sanitized into a ActiveX safe object type.

However, if you are calling the Python based command from another Python script, you can use the unwrap function in win32com.server, to get back the pure Python object.

Creating your own Modules

This is a large topic, so it has its own page: Python Custom Modules (XSISDK)

Exposing a Python Object to XSI

Your Python Custom Command can return a Python object, with all its functions and attributes (e.g. Methods and Properties) available to Python, VBScript and JScript. This is a great way to write your own sophisticated libraries of script code.

The steps are:

  • Set some attributes at the class definition level to define which methods and attributes are exposed:
    • _public_methods_ -> List of public functions
    • _public_attrs_ -> List of public attributes (both read-only and read-write)
    • _readonly_attrs_ -> List of read only attributes
  • wrap the instance of the class being returned:
    • Use the win32com.server.util.wrap( ) function
  • The custom command creates the object and returns it

The following self-installing plug-in returns a Python object usable from jscript and vbscript:

# This class is going to be exported to VB and JScript
class TestPython:
	 # Declare list of exported functions:
	_public_methods_ = ['GetAnswer']
	 # Declare list of exported attributes
	_public_attrs_ = ['exclamation', 'answer']
	 # Declare list of exported read-only attributes:
	_readonly_attrs_ = ['answer']

	 # Class init:
	def __init__(self):
		 # Initialize exported attributes:
		self.exclamation = 1
		self.answer = 42
		 # Perfectly legal to have other non exported attributes

	 # Exported function
	def GetAnswer(self, question):
		return "The answer to " + str(question) + " is " + str(self.answer) + "!"*self.exclamation
		
	 # Perfectly legal to have other non exported functions.
		
true = 1

 # Traditionnal Plugin installation:
def XSILoadPlugin( in_reg ):
	in_reg.Author = "Command Wizard User"
	in_reg.Name = "TestPython Plug-in"
	in_reg.Major = 1
	in_reg.Minor = 0
	in_reg.RegisterCommand( "TestPython","TestPython" )
	return true

def TestPython_Init( io_Context ):
	oCmd = io_Context.Source
	oCmd.ReturnValue = true
	return true

def TestPython_Execute(  ):
	oClass = TestPython()
	import win32com.server
	 # Class MUST be wrapped before being returned:
	return win32com.server.util.wrap(oClass)

This test VBScript works just fine using the Python object:


 VB script begin 

set a = TestPython()
'INFO : TestPython_Execute called
LogMessage a.GetAnswer("life, the universe, everything")
'INFO : The answer to life, the universe, everything is 42!
a.exclamation = 10
LogMessage a.GetAnswer("life, the universe, everything")
'INFO : The answer to life, the universe, everything is 42!!!!!!!!!!

 VB script end

Note: You have the choice in TestPython_Execute() whether to create a new object each time (as shown above), or to share a single instance of the object. The second technique is called a "Singleton". The single instance of the object is stored as a global variable in the plug-in and all scripts can share the data inside it.

Learning more about Win32 (COM) and Python

Chapter 12 of "Python programming on Win32" is the only chapter you actually need from this book and it also happens to the sample chapter from O'Reilley web site. Python programming on Win32 - Chapter 12 (http://www.oreilly.com/catalog/pythonwin32/chapter/ch12.html) All the important info free for the taking. Also includes how to wrap a Python class in COM to return it directly from scripting.

Reduced Typing

If you don't like having to type "Application." in front of every command you could consider storing the global Application object in a variable with a shorter name.

xsi = Application
xsi.GetPrim("Null", "", "", "")
xsi.logmessage( "test" )

You can also get to often used methods by using helpers with shorter names that point to the original methods. For example:

log = Application.LogMessage
log('test')

Viewing XSI Type libraries in Python GUI

Normally you can't load the si3dobjectmodel.tlb file in Python because XSI doesn't save the full path in the Windows Registry (too allow multiple versions to be installed on the same machine).

You can use this method if you don't want to constantly modify your registry:

  1. Create a folder for the TLB files:
    C:\> mkdir C:\Python24\TLBStore
  2. Copy all Softimage TLB files there:
    C:\> copy C:\Softimage\XSI_4.2\Application\bin\*.tlb C:\Python24\TLBStore
  3. Start PythonWin in that folder:
    C:\> cd \Python23\TLBStore
    C:\Python23\TLBStore> E:\Python24\Lib\site-packages\pythonwin\Pythonwin.exe

PythonWin will look in the current folder for the TLB files and will find them, allowing you to MakePy and browse at will.

You will probably want to create a new shortcut for PythonWin\Soft in the start menu and specify C:\Python23\TLBStore for the Start in folder.

Please do not copy the TLB files to any location in your %PATH% environment variable (especially winnt\system32). This will lead to very serious and hard to debug problems the next time you install a version of XSI because stale tlb files will get loaded instead of the correct ones.

Redirecting 'print' to Application.Logmessage

applies to: V6.5

You're probably like me and like the simplicity of 'print' and don't like typing "application.logmessage' and maybe you'd like to see the result of 'o._print_details_()

if __name__=='__ax_main__':
	import sys
 
	class WritableObject:
	    def __init__(self):
			self.content = ""
	    def write(self, string):
	    	if (string=='\n'):
				Application.Logmessage(self.content,32)
				self.content=""
	    	else:
				self.content+=string
 
	# example with redirection of sys.stdout
	sys.stdout = WritableObject()

# go ooo...
print Application._print_details_()

what to do when __str__ and _print_details_ don't work as expected

applies to: V6.5

The implementation of __str__ (C:\Python24\Lib\site-packages\win32com\client\dynamic.py) depends on the object finding the correct typeinfo for the com object and whether the object implements default method (DISPID0); this works with object such as XSIUtils, XSIFactory, XSIMath (oops), XSIUIToolkit but not for XSI objects. XSI 3d objects implement the default method to return a string representation of a object, normally the 3dobject's path.

The normal script objects return the typename provide by their type info, this exists for XSIUtils and such but not XSI 3d objects. This is due to the underlying scripting implemtation that doesn't implement the GetTypeInfo() method but does for XSIFactory and such because they are built using a more standard way.

See the above tip for a handy way of redirecting stdout so that you can use 'print' and o._print_details() and have it logged to the script history window.

This illustrates the problem:

# object with typeinfo
o = XSIFactory
print "o.__str__:" + o.__str__()
print "o._olerepr_.typename: " + o._olerepr_.typename
o._print_details_()

# object with no typeinfo
o = Application.Selection
print "o.__str__:" + o.__str__()
print "o._olerepr_.typename: " + o._olerepr_.typename
o._print_details_()

You can fix up the typeinfo using the following trick:

def dispFixTypeInfo(o):
	if o._olerepr_.typename is "LazyDispatchItem":
		import pythoncom
		pIDispatch = getattr(o, "_oleobj_", o)
		pITypeInfo = pIDispatch .GetTypeInfo()
		o._username_ = pITypeInfo.GetDocumentation(-1)[0]
		o._olerepr_= win32com.client.dynamic.MakeOleRepr(o,pITypeInfo,None)
	return o

o = dispFixTypeInfo(Application.Selection)
o._print_details_()

Advanced

Debugging Python

Python and PyWin32 compile very cleanly with Microsoft Visual Studio dotNet, this means you can compile a debug version (or a ship version with PDB) and actually run it in XSI, allowing more detailed investigations of Python scripting problems. Having the source also help understand how Python communicates with COM.

Avoiding pywin32

Native Python support (without PyWin32) is possible using SWIG (http://www.swig.org/) on the C++ API headers to generate the code for Python to talk to XSI. So, rather than using the COM Scripting API, your Python code would communicate with XSI via the C++ API.



This page was last modified 06:00, 18 Sep 2008.
This page has been accessed 28285 times.