Writing particle point positions to a CSV file


One of the nice things about Python is the convenience of modules like CSV.

A point cloud is an X3DObject, so you could just get the point positions the same way you would for a mesh. But the recommended way to do it is with ICEAttribute.GetDataArrayChunk or, for small point clouds, ICEAttribute.DataArray.

import csv
from siutils import si		# Application
from siutils import sidict	# Dictionary
from siutils import log		# LogMessage


# Get a CSV writer object
# http://docs.python.org/release/2.5.2/lib/module-csv.html
csvWriter = csv.writer(open('cloud1.csv', 'wb'), delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)



# PointCloud is an X3DObject, so you can simply get points at current frame
# For large point clouds, use the ICEAttribute object instead
points = sidict.GetObject( "pointcloud" ).ActivePrimitive.Geometry.Points;
for p in points:
	#print "(%s, %s, %s)" % ( p.Position.X, p.Position.Y, p.Position.Z )
	csvWriter.writerow([p.Position.X, p.Position.Y, p.Position.Z])


# Use the ICEAttribute.DataArray for PointPositions
# For large point clouds, use ICEAttribute.GetDataArrayChunk
points = sidict.GetObject( "pointcloud" ).ActivePrimitive.Geometry.ICEAttributes("PointPosition").DataArray
for p in points:
	log( "%s, %s, %s" % (p.X, p.Y, p.Z) )
	csvWriter.writerow([p.X, p.Y, p.Z])

Update: The above script will write the CSV file in %XSI_BINDIR%. Changing the output location is an exercise left to the reader 😉 as is using GetDataArrayChunck (but for that you can copy the example in the docs).

Python PolygonMesh.Set example


This simple example shows how to pass in the vertex and polygon data in Python.

Application.CreatePrim("Cube", "MeshSurface", "", "")
oCube = Application.Selection(0)

# tuple of tuples
# one tuple for the X coordinate, one for the Y, and one for the Z
verts = ((-0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 5.0), (-0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.0), (-0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -18.0))

# tuple of polygon data
polys = (4, 0, 2, 3, 1, 4, 0, 1, 5, 4, 4, 0, 4, 6, 2, 4, 1, 3, 7, 5, 4, 2, 6, 7, 3, 4, 4, 5, 7, 6)

Application.FreezeObj(oCube)

oCube.ActivePrimitive.Geometry.Set(verts,polys)

To help understand the vertex and polyon data, consider this simple polygon mesh:

Given the above polygon mesh, this snippet:

oCube = Application.Selection(0)

data = oCube.ActivePrimitive.Geometry.Get2()
verts = data[0]
polys = data[1]

print verts
print polys

would print this:

#  ((-3.0, 4.0, 1.0, -2.0), (0.0, -4.0, 0.0, 0.0), (1.0, -4.0, 5.0, 3.0))
# (4, 0, 1, 2, 3)

A brief history of the Python multi-dispatch issue


For the most part, this is no longer a problem, although there may be a few cases where you have to use win32com.client.dynamic.Dispatch. A huge effort was put in to XSI 6.0 to get rid of this “dispatch” issue.

The main problem was that XSI used a technique called “Multi-Dispatch” rather than normal inheritance to organize the interfaces supported by each object in the Scripting Object Model. In the original SDK, this was designed to maintain binary ompatibility for the now-obsolete COM C++ API.

The problem was that because Python used optimizations to read and compile the contents of typelibs rather than communicating with XSI directly via IDispatch, objects did not always appear to have all the methods and properties that they are supposed to.

For XSI 6.0, the OM hierarchy was overhauled to use derivation and inheritance instead of the multi-dispatch mechanism.

Prior to XSI 6.0, the workaround was the __init__.py file hack, as described in this 2005 post to the XSI Mailing List by Jerry Gamache:

—–Original Message—–
From: owner-xsi@Softimage.COM [mailto:owner-xsi@Softimage.COM] On Behalf Of Jerry Gamache
Sent: March-17-05 2:23 PM
To: XSI@Softimage.COM
Subject: RE: avoiding using get value with python

XSI sometimes return objects with an incorrectly set (as per PythonWin standards) multi-dispatch interface.

The way to work around that is to re-wrap the object in a dynamic dispatch. This allows you to skip the Application.GetValue:

def dispFix( badDispatch ):
	import win32com.client.dynamic
	# Re-Wraps a bad dispatch into a working one:
	return win32com.client.dynamic.Dispatch(badDispatch)

# Let's see if this works:
Application.CreatePrim("Sphere", "MeshSurface", "", "")
Application.ApplyHairOp("sphere", "")
oHair = Application.GetValue("hair").ActivePrimitive.ConstructionHistory.Find("HairGenOp")
try:
	Application.LogMessage(oHair.Parameters['EmitterMeshSubdlevel'].Value)
except:
	Application.LogMessage("Incorrect dispatch pointer")
oHair = dispFix(oHair)
Application.LogMessage(oHair.Parameters['EmitterMeshSubdlevel'].Value)

# or, as a single long line:

dispFix(Application.GetValue("hair")  
	   .ActivePrimitive.ConstructionHistory 
	   .Find("HairGenOp")
	 ).Parameters['EmitterMeshSubdlevel'].Value = 1

If that really really annoys you and you don’t want to dispFix at all, there is a way to make sure everything works, but you need to modify a Python file to always return dynamic dispatches:

Change %PYTHONPATH%\Lib\site-packages\win32com\client\__init__.py

Look for the function called __WrapDispatch

And comment all lines except the last one that begins with “return dynamic.Dispatch( … ”

Of course it is always a good idea to backup files you are about to modify.

Getting the selected ICE nodes


The context for custom menu callbacks gives you access to the current view instance, so you can use the selection view attribute to get the selected ICE nodes. See line 35 in the example.

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 = "My_ICETreeUserTool_Plugin"
	in_reg.Major = 1
	in_reg.Minor = 0

	in_reg.RegisterMenu(constants.siMenuICEViewToolsID,"My_ICETreeUserTool_Menu",false,false)
	#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 My_ICETreeUserTool_Menu_Init( in_ctxt ):
	oMenu = in_ctxt.Source
	oMenu.AddCallbackItem("My ICE Tree User Tool","OnMyICETreeUserTool")
	return true

def OnMyICETreeUserTool( in_ctxt ):
	itv = in_ctxt.GetAttribute("Target")
	
	LogMessage( 'View: ' + itv.Name )
	
	# get the selected nodes
	nodes = itv.GetAttributeValue('selection')
	LogMessage( 'Selected nodes: ' + nodes )
		

Installing Python modules in the Softimage Python site-packages


To get a Python module like PIL to work with the Python installed with Softimage, you will need to edit the registry. I tested this on my system, where I do not have any Python installed, except the Python installed with Softimage.

The PIL installer is looking in the registry for that Python install path, so we must edit the registry to put in the path to the Softimage Python.

You can use regedit to add the registry entry

HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\2.6\InstallPath

Then set the “(Default)” value to

C:\Program Files\Autodesk\Softimage 2011 Subscription Advantage Pack\Application\python

If you do have an external Python installed, you can just temporarily change the “(Default)” value to point to the Softimage install of Python.

Hat Tip: leendert68

Also: mabxsi suggests an alternative approach that avoids editing the registry

Getting the contents of docked views


I was recently asked whether it’s possible to find docked FxTree views and figure out 1) what specific tree is loaded, and 2) what nodes are selected in the Fx tree.

You can find docked Fx Tree views, but unfortunately there’s no way to find out what’s in that view.
Here’s a Python snippet that finds docked Fx Tree views:

# Find docked Fx Tree views
oVM = Application.Desktop.ActiveLayout.Views.Filter( "View Manager" )
oFxTreeViews = oVM(0).Views.Filter( "Fx Tree" )

for view in oFxTreeViews:
    Application.LogMessage(view.Name+": "+view.Type)

In the XSI SDK, you use attributes to access the contents of a view, and there are no attributes defined for Fx Tree views.

For example, here’s how it works for an ICE Tree view:

# Find docked ICE Tree views
oVM = Application.Desktop.ActiveLayout.Views.Filter( "View Manager" )
oICETreeViews = oVM(0).Views.Filter( "ICE Tree" )

# Get the ICE tree that is loaded into the view
Application.LogMessage( oICETreeViews(0).GetAttributeValue( "container" ) );

# Get the selected nodes in the ICE tree
Application.LogMessage( oICETreeViews(0).GetAttributeValue( "selection" ) );

PyQT ImportError: DLL load failed: The specified procedure could not be found


Several customers have hit this error when using PyQT with Softimage.

# INFO : Traceback (most recent call last):
#   File "somefile.py", line X, in <module>
#     from PyQt4 import QtGui
# ImportError: DLL load failed: The specified procedure could not be found.

On Windows, Softimage 2011 includes four Qt DLLs in the %XSI_BINDIR% folder.

  • QtCore4.dll
  • QtGui4.dll
  • QtOpenGL4.dll
  • QtXml4.dll

We don’t use the Qt DLLs. I believe the idea was to ship the Qt dlls for plugins that use Qt, so that the dlls were already in place. So, you can rename the DLLs to .bak to avoid the error.

References:
conflict with QT DLLs inside Softimage 2011.5
Scripting – struggling to get pyQt to work

Python gotcha – Forgetting the parentheses after a method name


Technically, I suppose this is not really a gotcha. But for someone like me who has to switch between scripting languages frequently, it’s a potential “gotcha”.

In Python, functions and methods are legitimate objects. That’s why you can do this:

App = Application
LogMsg = App.LogMessage
ClassName = App.ClassName

but sometimes you might forget the parentheses after a name, and unlike JScript, you won’t get a syntax error (at least not for the missing parentheses). For example, this little snippet will give you a “# AttributeError: ‘NoneType’ object has no attribute ‘AddKey'” error:

param = Application.Dictionary.GetObject( "null.kine.local.rotx" )

if str(param.Source) == 'None':
    param.AddFcurve
    fcurve = param.Source
    fcurve.AddKey( 10, 90 )
    fcurve.AddKey( 20, 45 )

In JScript, you’d get a syntax error for the missing parentheses. I’m not saying that that is better 😉 it’s just a difference you have to be aware of.

param = Dictionary.GetObject( "null.kine.local.rotx" )
param.AddFcurve
// ERROR : Object doesn't support this property or method - [line 3]

Some related links: