[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”

Getting the selected FxTree nodes


You can use the undocumented selectednodes attribute, which was added in Softimage 2014.

views = Application.Desktop.ActiveLayout.Views
v = views.Find("Fx Tree") or views.Find("View Manager").Views.Find("Fx Tree")
if v:
    print v.GetAttributeValue("selectednodes")  # None

The FxTree view also has a targetcontent attribute, which is documented.

* hat tips to csaez (code) and luceric (attribute)

Adding ReferenceWidgets to PPGs?


It used to be possible to add a ReferenceWidget by calling oLayout.AddItem( “Param”, “ref”, “ReferenceWidget” ) in your DefineLayout. But that was four or five years ago, and it no longer works.

def MyProperty3333_DefineLayout( in_ctxt ):
	oLayout = in_ctxt.Source
	oLayout.Clear()

	# Test some other control types
	oLayout.AddItem( "Param", "txt", "Texture Space" )
	oLayout.AddItem( "Param", "scnref", "SceneReferenceWidget" )

	# Doesn't work
	oLayout.AddItem( "Param", "ref", "ReferenceWidget" )

	# Doesn't work
	oPPGItem = oLayout.AddItem( "Param", "", "dscontrol" ) ;
	oPPGItem.SetAttribute( "UIType", "ReferenceWidget" );

For reference 😉 this is a ReferenceWidget:
ReferenceWidget

And this is how a ReferenceWidget is added in SPDL:

	Parameter "inst_source" input
	{
		title = "Instance source";
		guid = "{5620730D-AEFC-4C4A-B1DF-9377E808E27B}";
		type = reference;
	}
...
	inst_source
	{
		Name = "Source Object";
		UIType = "ReferenceWidget.ReferenceWidget.1"
		{
			filter = "{6B579D20-3DC5-11D0-9449-00AA006D3165}";
			mode = "single";
		}
	}

Scripting: Getting the StrandPosition arrays


StrandPosition is an array of arrays: one array of positions for each strand.

Here’s a Python snippet:

from win32com.client import constants
xsi = Application

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

attr = xsi.Selection(0).ActivePrimitive.Geometry.ICEAttributes( "StrandPosition" )
dataType = attr.DataType
data2D = attr.DataArray2D
for data in data2D:
   for elem in data:
      elem = dispFix(elem)
      xsi.LogMessage( "Vector3: " + str(elem.X) + ":" + str(elem.Y) + ":" + str(elem.Z) )

And here’s a JScript snippet:

a = Selection(0).ActivePrimitive.Geometry.ICEAttributes( "StrandPosition" );
LogMessage( ClassName(a) );

x = a.DataArray2D.toArray();
LogMessage( x.length );
for ( var i = 0; i < x.length; i++ )
{
   y = x[i].toArray();
   LogMessage( "=======================" );
   for ( var j = 0; j < y.length; j++ )
   {
      LogMessage( y[j].X + ", " + y[j].Y + ", " + y[j].Z );
   }
}

Getting values and fcurves for the port parameters of an ICE node


ICE nodes have ports, and the ports have parameters. It’s the parameters that you work with in an ICE node PPG.

For simple types such as float, integer and boolean, you can access the port parameter value through ICENodeInputPort.Value. However, for more complex types, like a 3D vector, you need to go through the ICENodeInputPort.Parameters.

In either case (simple or complex types), to get an Fcurve, you get a parameter and then use Parameter.Source.

For example, suppose you have a Scalar node:
ports_params_Scalar
To get the value from a Scalar node, you’d do this:

si = Application
node = si.Dictionary.GetObject( "pointcloud.pointcloud.ICETree.ScalarNode" )
port = node.InputPorts(0)

# For scalars, you can just use the port.Value property
print port.Value

# Or you could go through the Parameters
print port.Parameters(0).Value
# 0.428628623486
# 0.428628623486

# It's a little confusing because the port and the parameter have the same name:
print port.FullName
print port.Parameters(0).FullName
# pointcloud.pointcloud.ICETree.ScalarNode.value
# pointcloud.pointcloud.ICETree.ScalarNode.value


# Now get the Fcurve
fcv = param.Source

Now consider the case of a 3D Vector node, where you have one port (value) and three parameters (value_x, value_y, and value_z):
ports_params_3DVector
In this case, you cannot use ICENodeInputPort.Value, so you have to go through the parameters collection:

si = Application
node = si.Dictionary.GetObject( "pointcloud.pointcloud.ICETree.3DVectorNode" )
port = node.InputPorts(0)
print port.FullName # pointcloud.pointcloud.ICETree.3DVectorNode.value
param = port.Parameters( 0 )
print param.FullName # pointcloud.pointcloud.ICETree.3DVectorNode.value_x
print param.Value # 0.933080613613

# Now get the Fcurve
fcv = param.Source

hat tip: Alan Fregtman

Scripting: Toggling the constraint compensation mode


Here’s one way, using the not operator.

si = Application
si.SetUserPref( "SI3D_CONSTRAINT_COMPENSATION_MODE", not si.GetUserPref("SI3D_CONSTRAINT_COMPENSATION_MODE") )

The “problem” with this approach is that you’re toggling between 0 and -1, not between 0 and 1 (when you click the CnsComp button in the UI, you set the pref to either 0 or 1). The -1 happens because not 0 is -1.

Application.LogMessage( True == 1 )
Application.LogMessage( False == 0 )
Application.LogMessage( not False == -1 )
# INFO : True
# INFO : True
# INFO : True

So here’s a couple of other ways to toggle the preference value:

si = Application
si.SetUserPref( "SI3D_CONSTRAINT_COMPENSATION_MODE", 0 if si.GetUserPref("SI3D_CONSTRAINT_COMPENSATION_MODE") else 1 )
si = Application
toggle = [1,0]
si.SetUserPref( "SI3D_CONSTRAINT_COMPENSATION_MODE", toggle[si.GetUserPref("SI3D_CONSTRAINT_COMPENSATION_MODE")] )

Objects and default properties


Here’s a scripting question from the mailing list:

Why do I get “None” when I print ChainRoot.Children and
ChainRoot.Bones?

si = Application
chainRoot = si.Create2DSkeleton(0, 0, 0, 10, 0, 0, -90, 0, 0, 4)

print chainRoot
print chainRoot.Children # Not working
print chainRoot.Bones # Not working
print chainRoot.Effector

# root1
# None
# None
# eff1

The answer is that ChainRoot.Children and ChainRoot.Bones are objects that don’t have a default property, so (in Python) you get the string representation of those objects, which is “None”.

ChainRoot and ChainRoot.Effector, being types of SIObject, and they do have a default property: the Name property.

ChainRoot.Children is an X3DObjectCollection, and ChainRoot.Bones is a ChainBoneCollection, and for whatever reason, those two collections don’t have Item as their default property (for some collections, like ISIVTCollection and LinktabRuleCollection, the Item property is the default property).

So the above Python is equivalent to this:

si = Application
chainRoot = si.Create2DSkeleton(0, 0, 0, 10, 0, 0, -90, 0, 0, 4)

print chainRoot.Name
print str( chainRoot.Children )
print chainRoot.Bones.__str__()
print chainRoot.Effector.Name

# root1
# None
# None
# eff1

Note that in JScript and VBScript, you’d get a “Type mismatch” error instead of the string “None” (in the original snippet).