[Scripting] Opening a page in a web browser


Here’s a couple of ways to open a URL in a web browser from inside Softimage. Unfortunately, these worked on Windows only. On Linux, the JScript can’t create that ActiveX object, and the Python didn’t do anything.

JScript:

// Open a web page in the default browser
var objShell = new ActiveXObject("shell.application");
objShell.ShellExecute("http://support.solidangle.com", "", "", "open", 1);

Python:

import webbrowser
webbrowser.open( 'http://support.solidangle.com' )

How to check if an object exists with no error handling


Yes, this old chestnut…I thought I had posted this ages ago, but I don’t see in the archives, so:

If you want to check if an object exists, and you don’t want to deal with any error handling, then do it this way:

from sipyutils import disp		# win32com.client.Dispatch

def objExists( name ):
    c = disp( "XSI.Collection" )
    c.Items = name
    return not( c.Count == 0 )

print 'Object does exist' if objExists( "XSI_Man.geom" ) else 'Does not exist'

Getting the plugin path


You can use the OriginPath property to get the location of a plugin, but OriginPath is available in the scope of a plugin callback only.

__sifile__ and __sipath__, however, can be used in the global scope.

import win32com.client
from win32com.client import constants

import sipyutils

Application.LogMessage( "Global: __sipath__=%s" % __sipath__ )
Application.LogMessage( "Global: __sifile__=%s" % __sifile__ )

null = None
false = 0
true = 1

def XSILoadPlugin( in_reg ):
	in_reg.Author = "SOLIDANGLE"
	in_reg.Name = "TestPlugin"
	in_reg.Major = 1
	in_reg.Minor = 0

	#Register plugin items

	Application.LogMessage( "XSILoadPlugin: __sipath__=%s" % __sipath__ )
	Application.LogMessage( "XSILoadPlugin: __sifile__=%s" % __sifile__ )
	Application.LogMessage( "XSILoadPlugin: in_reg.OriginPath=%s" % in_reg.OriginPath )

	return true

The above will output something like the following:

# INFO : Global: __sipath__=C:\Users\SOLIDANGLE\Autodesk\Softimage_2015\Application\Plugins
# INFO : Global: __sifile__=C:\Users\SOLIDANGLE\Autodesk\Softimage_2015\Application\Plugins\TestPlugin.py
# INFO : XSILoadPlugin: __sipath__=C:\Users\SOLIDANGLE\Autodesk\Softimage_2015\Application\Plugins
# INFO : XSILoadPlugin: __sifile__=C:\Users\SOLIDANGLE\Autodesk\Softimage_2015\Application\Plugins\TestPlugin.py
# INFO : XSILoadPlugin: in_reg.OriginPath=C:\Users\SOLIDANGLE\Autodesk\Softimage_2015\Application\Plugins\

Getting all shaders under a light


Given something like this:
light_disconnected shaders
Here’s how you get all shaders under a light, even the disconnected ones:

from sipyutils import si		# win32com.client.Dispatch('XSI.Application')
from sipyutils import log		# LogMessage
from sipyutils import C			# win32com.client.constants
from sipyutils import disp		# win32com.client.Dispatch

si = si()

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

oLight = si.Selection(0)


#import win32com.client
oDisconnected = disp( "XSI.Collection" )

if oLight.IsClassOf( C.siLightID ):
	for oShader in oLight.GetAllShaders():
		oOut = dispFix( oShader.Parameters( "out" ) )
		if oOut.Targets.Count == 0:
			oDisconnected.Add( oShader )
			
log( oDisconnected.GetAsText() )

Hat tip: Matt Lind, who provided the GetAllShaders answer to the question “how to get all shaders in a light, even the disconnected ones”

Scripting – Creating an empty polygon mesh


There’s several ways to create an empty polygon mesh with the Object Model.

from sipyutils import si			# win32com.client.Dispatch('XSI.Application')
from sipyutils import log		# LogMessage
from sipyutils import C			# win32com.client.constants

si = si()
o = si.Selection(0)

if o.IsClassOf( C.siX3DObjectID ):
	o.AddPolygonMesh()
	o.AddPrimitive( "EmptyPolygonMesh"  )
	o.AddGeometry( "EmptyPolygonMesh" )

AddPolygonMesh() courtesy of Vladimir

PPG callbacks and variable scope


The scope of variable b is this snippet. That is, the identifier b exists and has the value 1 only while this snippet is running. After that, it doesn’t exist unless some other code defines it.

Consequently, the OnClicked callback is going to fail with a ‘b’ is undefined error.

var myPset = ActiveSceneRoot.AddProperty("CustomProperty",false,"Mytest");
var myLayout = myPset.PPGLayout;

var b = 1;

function a_OnClicked(){
	LogMessage( b );	
}

myLayout.AddRow()
myLayout.AddButton("a","button");
myLayout.EndRow() 

myLayout.Logic=OnInit.toString() +  a_OnClicked.toString();
myLayout.Language = "JScript" ;

InspectObj(myPset);

The way it works is that Softimage creates a new instance of the JScript ActiveX scripting engine each time it needs to execute some fragment of code. So, the above snippet runs in one instances of the scripting engine (which is destroyed after the code is executed). Then later, when the button is clicked, the OnClicked callback runs in a new and different instances of the scripting engine. And that new instance of the scripting engine, there is no variable named b.

One way around this would be to add b as a parameter:

var myPset=ActiveSceneRoot.AddProperty("CustomProperty",false,"Mytest");
var b =	myPset.AddParameter2("b",siInt4,1,0,100,0,100,siClassifUnknown,siPersistable | siKeyable);

var myLayout=myPset.PPGLayout;

myLayout.AddRow()
myLayout.AddButton("a","button");
myLayout.EndRow() 
myLayout.Logic=OnInit.toString() +  a_OnClicked.toString();
myLayout.Language = "JScript" ;

InspectObj(myPset);

function a_OnClicked(){
	LogMessage(PPG.b);	
}

Another way would be to use a LogicFile, something like this:

// LogicFile for an on-the-fly custom property
var b = 1;
var c = -1;
function OnInit() {
	LogMessage( "OnInit" );
	c = 99;
}
function a_OnClicked(){
	LogMessage( b );	
	LogMessage( c );
}

Then your property would work like this:

var myPset=ActiveSceneRoot.AddProperty("CustomProperty",false,"Mytest");
var myLayout=myPset.PPGLayout;

myLayout.AddRow()
myLayout.AddButton("a","button");
myLayout.SetAttribute( siUILogicFile, "\\some\\path\\LogicFile.js" );

myLayout.EndRow() 
myLayout.Language = "JScript" ;

InspectObj(myPset);

And this would give the following output:

// INFO : OnInit
// INFO : 1
// INFO : 99

Syntax highlighting in the text editor widget


Syntax highlighting works only if you set the siUILanguage to one of cpp, c#, HTML, JScript, PerlScript, Python, VBScript, or XML. Then the language keywords are highlight, plus any keywords you add with the siUIKeywords attribute.

Syntax_highlight

from sipyutils import si			# win32com.client.Dispatch('XSI.Application')
from sipyutils import C			# win32com.client.constants

si=si()

p = XSIFactory.Createobject( 'CustomProperty' )
oParam = p.AddParameter3("TextEditorWidget", C.siString, '')

oLayout = p.PPGLayout
oLayout.Clear()

oItem = oLayout.AddItem( "TextEditorWidget", "Source Code", C.siControlTextEditor)
oItem.SetAttribute( "Language", "JScript" )
oItem.SetAttribute(C.siUIFont, "Courier New")
oItem.SetAttribute(C.siUIKeywords, "foo bar fubar snafu")
oItem.SetAttribute(C.siUIAutoComplete, "fubar");
oItem.SetAttribute(C.siUICommentFont, "Courier New")
oItem.SetAttribute(C.siUICommentColor, 0x75715e)
oItem.SetAttribute(C.siUIPreprocessorColor, 0x808080)
oItem.SetAttribute(C.siUIToolbar, True)
oItem.SetAttribute(C.siUIFontSize, 10)
oItem.SetAttribute(C.siUIHeight, 500)
oItem.SetAttribute(C.siUIBackgroundColor, 0xf8f8f2)
oItem.SetAttribute(C.siUIForegroundColor, 0x272822)
oItem.SetAttribute(C.siUIHorizontalScroll, True)
oItem.SetAttribute(C.siUIVerticalScroll, True)
oItem.SetAttribute(C.siUILineNumbering, True)
oItem.SetAttribute(C.siUILineWrap, False)
oItem.SetAttribute("UseSpacesForTab", True)
oItem.SetAttribute("TabSize", 2) 

si.inspectobj( p )

ISIVTCollections, output arguments, and implicit return values


Commands that don’t explicitly define a return value, but do have output arguments, have an implicit return value: an ISIVTCollection.

The ISIVTCollection is a “special type of collection”, but it may help to think of it as [something like] a dictionary. An ISVTCollection holds a collection of key, value pairs (like a Python dictionary). You use the key names, like “Value”, to extract the associated value.

For example, the PickElement command has three “output arguments”; that is, parameters that return some value. These output arguments are PickedElement, ButtonPressed, and ModifierPressed.

So PickElement returns an ISIVTCollection collection with three key-value pairs. The keys are “PickedElement”, “ButtonPressed”, and “ModifierPressed”.

You can use the ISIVTCollection.Value method to get the value of a key:

from sipyutils import si			# win32com.client.Dispatch('XSI.Application')
from sipyutils import log		# LogMessage
from sipyutils import C			# win32com.client.constants
si = si()

vtcol = si.PickElement(C.siObjectFilter, "Pick deformer")

button = vtcol.Value( "ButtonPressed" )
modkey = vtcol.Value( "ModifierPressed" )
o = vtcol.Value( "PickedElement" )

There’s also an Item property, but like most Item properties it fails in Python:

vtcol = si.PickElement(C.siObjectFilter, "Pick deformer")
o = vtcol.Item( "PickedElement" )
# ERROR : Traceback (most recent call last):
#   File "<Script Block >", line 7, in <module>
#     o = vtcol.Item( "PickedElement" )
# TypeError: 'NoneType' object is not callable

Fortunately, Item is the default property, so you can omit it:

vtcol = si.PickElement(C.siObjectFilter, "Pick deformer")
o = vtcol( "PickedElement" )

You can also use an integer index instead of the key string. It just so happens that ISIVTCollections are sorted by key name (case insensitive), so you can work out which index to use.

# ISIVTCollection is sorted by name (case insensitive)
# 0 = ButtonPressed
# 1 = ModifierPressed
# 2 = PickedElement
o = si.PickElement(C.siObjectFilter, "Pick deformer")(2)

print ( si.ClassName(o) )
# X3DObject