Circa 1995. The last time I wrote any perlscript in XSI, as it was then known.
$array = $Application->GetMarking(); for $item (@$array) { $Application.LogMessage("Marking: $item"); } #INFO : Marking: kine.local.pos
Circa 1995. The last time I wrote any perlscript in XSI, as it was then known.
$array = $Application->GetMarking(); for $item (@$array) { $Application.LogMessage("Marking: $item"); } #INFO : Marking: kine.local.pos
XSICollections know how to handle (aka parse) string expressions like ‘cube.pnt[2,4,LAST]’
si = Application import win32com.client c = win32com.client.Dispatch( "XSI.Collection" ) #c.SetAsText( 'cube.pnt[2,4,LAST]' ) c.Items = 'cube.pnt[2,4,LAST]' print c.Count print si.ClassName( c(0) ) print c(0).SubComponent.ComponentCollection.Count print si.ClassName( c(0).SubComponent.ComponentCollection(0) ) print c(0).SubComponent.ComponentCollection(2).Index # 1 # CollectionItem # 3 # Vertex # 7
Back in 1999, this code looked something like this:
CreatePrim "Cube", "MeshSurface" set list = GetCollection( "cube.pnt[2,3,6,LAST]" ) if not typename(list) = "Nothing" then logmessage list end if function GetCollection( in_str ) Dim l_myList set GetCollection = CreateObject( "Sumatra.Collection" ) On Error Resume Next GetCollection.items = in_str if GetCollection.Count = 0 then set GetCollection = Nothing end if end function
Here’s a snippet of some python posted on the Softimage mailing list this week.
from win32com.client import constants class Softimage: __getattr__ = lambda x, a: getattr(Application, a, False) or getattr(constants, a) softimage = Softimage()
Let’s break this down…
First, this snippet defines a class named Softimage (lines 1 and 2) and then instantiates an instance of that class (line 4).
Once you have an instance of a class, you can access the attributes of the class. Now this Softimage class doesn’t define any attributes, but it does provide a __getattr__ method. For example, if you ask for softimage.GetValue, then that __getattr__ method gives you back the function object Application.GetValue. If you ask for softimage.ActiveSceneRoot, you get back the value of the Application.ActiveSceneRoot property. And if you ask for softimage.siX3DObjectID, you get back constants.siX3DObjectID.
So, how does that work?
__getattr__ is a method that is called when an attribute isn’t found on a class instance.
If you define __getattr__ without using lambda or the and-or trick, it could look something like this:
from win32com.client import constants class Softimage: # __getattr__ = lambda x, a: getattr(Application, a, False) or getattr(constants, a) def __getattr__( self, name ): a = getattr(Application, name, False) if not a: a = getattr(constants, name) return a
getattr is a built-in Python function that gets an attribute on a specified object. Here, we’re getting an attribute of the Softimage Application object. For example:
# Get the scene root a = getattr(Application, "ActiveSceneRoot", False) print a print a.Parameters.Count # Get the selection with the GetValue command a = getattr(Application, "GetValue", False) print a print a( 'SelectionList' )
Here’s how to get the selection from the script history. This is how the Repeat command works.
def GetScriptHistoryView(): oLayout = Application.Desktop.ActiveLayout # See if the script history is a view of it's own oViews = oLayout.Views.Filter( "Script History" ) if oViews.Count == 0: oViews = oLayout.Views.Filter( "Script Editor" ) return oViews(0) oLog = GetScriptHistoryView() text = oLog.GetAttributeValue( "historyline" ) if oLog else "<Nothing>" print text
Given a selected polygon, add the next 5 polygons to the selection. For example, if polygon 22 is selected, then add polygons 23,24,25,26, and 27 to the selection.
You can do this without using any loops, by using a string expression for the polygon components.
Here’s some JScript that shows two ways to do it. Method 1 uses a string expression. Method 2 is a loop, which can either use the Polygons collection or a string expression.
var o = Selection(0).SubComponent.Parent3DObject var p = Selection(0).SubComponent.ComponentCollection(0) var LAST = o.ActivePrimitive.Geometry.Polygons.Count; // Method 1 // SelectGeometryComponents("grid.poly[22-27]", null, null); var sPoly = ".poly["+p.Index+"-"+Math.min( p.Index+5,LAST-1 )+"]" //SelectGeometryComponents( o.FullName + sPoly ); //Selection.SetAsText( o.FullName + sPoly ); // Method 2 //for ( var i = p.Index+1 ; i < Math.min( p.Index+5,LAST ) ; i++ ) //{ // ToggleSelection( o.ActivePrimitive.Geometry.Polygons(i) ); // -or- // ToggleSelection( o.FullName + ".poly["+i+"]" ) //}
Here’s some Python that does something similar but different:
si = Application o = si.Selection(0).SubComponent.Parent3DObject p = si.Selection(0).SubComponent.ComponentCollection(0) LAST = o.ActivePrimitive.Geometry.Polygons.Count s = ".poly[%s-%s]" % ( p.Index, min( p.Index+5,LAST-1 ) ) si.SelectGeometryComponents( s )
For most context menus, the Target attribute contains both the selected objects (if any) and the object under the mouse.
def MyCustomMenu_Init( in_ctxt ): oMenu = in_ctxt.Source oMenu.Filter = "camera" oMenu.AddCallbackitem("Custom Camera Tool","MyCameraTool") return true def MyCameraTool(in_ctxt): obj = in_ctxt.GetAttibute("Target") Application.LogMessage(obj)
I’m pretty sure I’m the one who dug up that info and put it in the docs (on the Menu Item Callback page). I did not, however, write this bit, which to my ear does not sound good at all:
If your menu is attached to a contextual menu, the currently selected objects are passed in to your callback. The target object under the cursor is also passed in as part of the selected objects. However if no objects are selected, then only the target is passed in. The objects can be retrieved through the Context.GetAttribute method with “Target” specified as the value for the AttributeName parameter. The selected/target objects are not passed in to the callback of a custom menu item attached to a regular menu.
I prefer something more like this:
For context menus, the callback gets the currently selected objects and the object under the mouse. You can get these target objects from the Target attribute with Context.GetAttribute.
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) ]()
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 )
…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")
Custom properties and the PPG object provide shortcuts for direct access to parameters.
For custom properties, that means you can type the shortcut oProp.SomeParam instead of oProp.Parameters(“SomeParam”). Note that that in this case, the default property is Name: if you print oProp.SomeParam, you get the name of the parameter.
from win32com.client import constants as C si = Application p = si.ActiveProject.ActiveScene.Root.AddCustomProperty( "Test" ) x = p.AddParameter2("X",C.siString,"a;b;c;d",None,None,None,None,C.siClassifUnknown,C.siPersistable) print p.X print p.X.IsEqualTo( p.Parameters("X") ) print x.IsEqualTo( p.X ) # Test.X # True # True
For the PPG object, the shortcut is even more of a convenience. To access a parameter named “Param”, you can type PPG.Param instead of PPG.Inspected(0).Param. And when you use the PPG.Param shortcut, the default property is Value.
So you can do this:
PPG.Param = 'hello;world'
There’s no need to type “PPG.Param.Value”, unless you’re doing something like this:
list = PPG.Param.Value.split(';')