Saturday Snippet: russian roulette


I commented out the things that would actually “shoot a bullet”.

CrashXSI() is an actual Softimage command, but it doesn’t do anything in a release build.

import random

# bullets
def crashxsi():
	Application.LogMessage( "Bang!" )
	Application.CrashXSI()
	
def blank():
	Application.LogMessage( "Try again" )
	
def losework():
	Application.LogMessage( 'NewScene( "", False )' )
	#Application.NewScene( "", False )
	
def quitxsi():
	Application.LogMessage( "Application.Quit()" )
	#Application.Quit()


#load chambers
chambers = {1: blank,
			2: crashxsi,
			3: quitxsi,
			4: blank,
			5: blank,
			6: losework
			}
	
#spin and shoot
chambers[ random.randint(1,6) ]()

Writing a python script for processing scenes with xsibatch


Here’s the basic skeleton of a Python script that calls xsibatch -processing -script on all scene files in a given folder.

Python command line:
The python script takes two arguments: the root folder for the location of the scene files, and the name of the script file to run with xsibatch -script.

python process_scenes.pys --dir ""C:\Program Files\Autodesk\Softimage 2013 SP1\Data\XSI_SAMPLES\Scenes" --script "test.pys"

process_scenes.pys:
The python script takes care of finding all the scene files, and then running xsibatch -processing -script on each .scn file.

import os
import fnmatch
import subprocess
import sys
import getopt

XSI_BINDIR=r"C:\\Program Files\\Autodesk\\Softimage 2013 SP1\\Application\\bin"

opts, extraparams = getopt.getopt(sys.argv[1:], "d:s:", ["dir=","script="]) 

#
# Get root directory to scan for scene files
#
SCENES_DIR="C:\\Program Files\\Autodesk\\Softimage 2013 SP1\\Data\\XSI_SAMPLES\\Scenes\\OLD"
SCRIPT="test1.pys"

for o,p in opts:
  if o in ['-d','--dir']:
     SCENES_DIR = p
  elif o in ['-s','--script']:
     SCRIPT = p

#
# Generator function for finding files
#
def find_files(directory, pattern):
     for root, dirs, files in os.walk(directory):
         for basename in files:
             if fnmatch.fnmatch(basename, pattern):
                 filename = os.path.join(root, basename)
                 yield filename

#
# Open each scene file and run the specified script
#
for scn in find_files(SCENES_DIR, '*.scn'):
	sXsiBatch = "%s\\xsibatch" % XSI_BINDIR
	subprocess.call( [ sXsiBatch, '-processing', '-script', SCRIPT, '-args', '-sSceneName', scn ] )

Softimage script test.pys:
A simple test script to run with xsibatch. Note that because the function is named “main”, I don’t have to specify that on the xsibatch command line. I just have to specify the arguments.

def main( sSceneName ):
	LogMessage( sSceneName )	

Saturday Snippet – Building a cross-platform path


…and exporting the selected objects to an ASS file.

from win32com.client import constants as C

filename = XSIUtils.BuildPath( 
	Application.InstallationPath( C.siProjectPath ),
	'Arnold_Scenes',
	'[Scene].[Frame].ass' )
Application.SITOA_ExportScene( 1,1,1,True,True,filename)
# Application.SITOA_ExportScene(1, 1, 1, True, True, "C:\\Users\\Steve\\My Project\\Arnold_Scenes\\[Scene].[Frame].ass")

Saturday snippet – Launching a command-line utility and viewing its output


Here’s a snippet that shows how to launch a command-line program in a command prompt window, and keep the command prompt window open so you can see the output. Note that does not block Softimage.

#sKick = "C:/Users/SOLIDANGLE/Documents/Workgroups/sitoa-2.5.0-2013/Addons/SItoA/Application/bin/nt-x86-64/kick.exe"
sKick = XSIUtils.BuildPath( Application.InstallationPath( 2 ), "Addons", "SItoA", "Application", "bin", XSIUtils.Environment("XSI_CPU_OPT"), "kick.exe" )
XSIUtils.LaunchProcess( "cmd /C start cmd /K %s -licensecheck" % sKick )

Basically, what this does is launch a cmd.exe process, and in that process, run the command “start cmd /K kick -licensecheck”. The second “cmd” is required to open a command prompt, where you’ll see the output of “kick -licensecheck”.

In JScript, it would be something similar:

sKick = XSIUtils.BuildPath( Application.InstallationPath( 2 ), "Addons", "SItoA", "Application", "bin", XSIUtils.Environment("XSI_CPU_OPT"), "kick.exe" )
XSIUtils.LaunchProcess( "cmd /C start cmd /K " + sKick + " -licensecheck" )

In Python, you could also do this:

from subprocess import call
call(["cmd", "/K", sKick, "-licensecheck"])

The above would open a non-blocking command prompt window (eg you could go back to Softimage), but this next snippet would prevent Softmage from responding until you closed the command prompt:

import os
os.system("cmd /k %s -licensecheck" % sKick)

Scripting – Shuffling a collection to randomly select by percentage


randompercentageVia a tech-artists.org tweet, I came across some MAXScript for randomly selecting a specified percentage of mesh elements (for example, give me a random selection that includes 35% of all vertices).

I converted it to Python in Softimage. Note that I don’t really shuffle a collection: you can’t set items in a collection, so there’s no way to swap items. Instead, I put the collection in a list and shuffle the list.

Shuffling actually makes this harder than it has to be. Check out Alan’s nice script for a better way to do this.

si = Application
log = si.LogMessage
sisel = si.Selection
#http://creativescratchpad.blogspot.ca/2011/06/select-random-elements-by-percentage.html

import random

#
# Return a list that includes a randomly selected
# percentage of the items in a collection
#
def get_random_percentage( collection, percentage ):
	v = [x for x in collection]

	# random shuffle
	for i in range( len(v) ):
		j = random.randint( i, len(v)-1 )
		v[i], v[j] = v[j], v[i]

	# select by percentage
	step = 100/percentage
	w = []
	for i in xrange( 0, len(v), step ):
		w.append( v[i] )

#	print len(w)
#	print (percentage/100.0) * len(v)
	
	return w


Application.SelectObj("torus", "", True)
# Select a random 50% of the vertices
x = get_random_percentage( sisel(0).ActivePrimitive.Geometry.Vertices, 50 )
si.SelectObj(x)

print sisel(0).SubComponent.ComponentCollection.Count

# Suppose you had 2000 cubes.
# Select a random 25% of those 2000...
Application.SelectObj("cube*", "", True)
x = get_random_percentage( sisel, 25 )
si.SelectObj(x)

I learned a couple of things about MAXScript:

  • MAXScript arrays are 1-based
  • In the MaxScript docs, there aren’t any function/method reference pages. You have to go to a “value” page (eg Number Values) and there you’ll find all the methods. That’s fine once you know, but it was confusing at first when I didn’t see anything in the TOC for Functions or Methods.

Saturday Snippet – XSICollections and CollectionItems


When you stick something, like say a Vertex, into an XSICollection, you get a CollectionItem. But you can get back to the Vertex if you know how (via the SubComponent
).

si = Application
log = si.LogMessage
sisel = si.Selection
import win32com.client

oColl = win32com.client.Dispatch( "XSI.Collection" )
o = sisel(0)

print si.ClassName( o.ActivePrimitive.Geometry.Vertices(0) )
# Vertex

#oColl.Add( o.ActivePrimitive.Geometry.Vertices(0) )
oColl.AddItems( o.ActivePrimitive.Geometry.Vertices )
print si.ClassName( oColl(0) )
# CollectionItem

print si.ClassName( oColl(0).SubComponent.ComponentCollection(0) )
# Vertex

a = o.ActivePrimitive.Geometry.Vertices(0)
b = oColl(0).SubComponent.ComponentCollection(0)
print a.IsEqualTo(b)
# True
print b.IsEqualTo(a)
# True

Saturday snippet – simple example of Python list comprehensions


Here’s something I was trying to do in ICE (without using any Repeats).

Given an array like

a = [ 5, 2 ,3 ]

create an array like

b = [ 0, 0, 0, 0, 0, 1, 1, 2, 2, 2 ]

See the pattern? (a[0] is 5, so array b has five elements with value 0).

In Python, using list comprehension, you can do it like this:

a = [ 5, 2, 3 ]

print [ i for i in range( len(a) ) for j in range( a[i] )]

The list comprehension is the equivalent of:

for i in range( len(a) ):
	for j in range( a[i] ):
		print i

Checking the environment of a running program


Sometimes when you’re troubleshooting, it’s a good idea to check the environment in which Softimage is running.
You can check specific environment variables in the script editor like this:

import os
print os.getenv( "XSI_USERHOME" )
print os.getenv( "TEMP" )

or like this:

print XSIUtils.Environment("XSI_BINDIR")

But I would typically use Process Explorer to see the full environment:
ProcessExplorer_Environment
or Process Monitor (in Process Monitor, you just have to find the Process Start operation for XSI.exe and double-click it).
ProcessMonitor_Environment

Saturday snippet – Wrapping an Undo


Two ways…

OneUndo decorater by Cesar Saez

from functools import wraps  
    
def OneUndo(function):  
   @wraps(function)  
   def _inner(*args, **kwargs):  
     try:  
       Application.BeginUndo()  
       f = function(*args, **kwargs)  
     finally:  
       Application.EndUndo()  
       return f  
   return _inner
#
# And a basic example...
# 
@OneUndo 
def CreateNulls(p_iCounter=100):
     lNulls = []
     for i in range(p_iCounter):
       lNulls.append( Application.ActiveSceneRoot.AddNull() )
     return lNulls

CreateNulls()

Undo with statement by ethivierge

from win32com.client import constants as c
from win32com.client.dynamic import Dispatch as d
 
xsi = Application
log = xsi.LogMessage
collSel = xsi.Selection
 
 
class xsiUndo():
    def __enter__(self):
        xsi.BeginUndo()
 
    def __exit__(self, type, value, traceback):
        xsi.EndUndo()
 
 
def testFunc():
    log("running test")
 
 
with xsiUndo():
    testFunc()

Saturday Snippet: Finding the Python and PyWin version


Courtesy of Eric Thivierge:

import sys
import os
import distutils
import distutils.sysconfig
site_packages = distutils.sysconfig.get_python_lib(plat_specific=1)
build_no = open(os.path.join(site_packages, "pywin32.version.txt")).read().strip()
print "Python version:   " + sys.version
print "PyWin32 version:  " + build_no

See also Supported versions of Python and PyWin