Scripting FBX export and import


You can use the FBXExport command to create the ExportFBXOptions property, and then use either the OM or SetValue to set the FBX options. For example, this Python snippet creates the FBX property by calling FBXExport( “option” ), and then sets the FBX SDK version (for which there is no separate FBXSetExport command).

si = Application
si.FBXExport( "option" )
o = si.Dictionary.GetObject( "ExportFBXOptions" )
o.Parameters( "FBXSDKVersion" ).Value = "FBX201300"

You can do the same thing for FBXImport with FBXImport( “option” ), but I always got an error, even if the property was created.

si = Application

try:
	si.FBXImport( "option" )
except:
	o = si.Dictionary.GetObject( "ImportFBXOptions" )
	
si.LogMessage( o )

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).

Debugging interfaces not available – debugging is disabled..


I recently installed a new build of Softimage, and right from the start I was getting Debugging interfaces not available – debugging is disabled.. messages in the script history.

(Note that I use the Python installed with Softimage.)

I tracked the message down to the SetScriptSite function in
%XSI_HOME%\Application\python\Lib\site-packages\win32comext\axscript\client\framework.py

	#
	# IActiveScript
	def SetScriptSite(self, site):
		# We should still work with an existing site (or so MSXML believes 🙂
		self.scriptSite = site
		if self.debugManager is not None:
			self.debugManager.Close()
		import traceback
		try:
			import win32com.axdebug.axdebug # see if the core exists.
			import debug
			self.debugManager = debug.DebugManager(self)
		except pythoncom.com_error:
			# COM errors will occur if the debugger interface has never been
			# seen on the target system
			trace("Debugging interfaces not available - debugging is disabled..")
			self.debugManager = None
		except ImportError:
			#trace("Debugging extensions (axdebug) module does not exist - debugging is disabled..")
			self.debugManager = None
		except:
			traceback.print_exc()
			trace("*** Debugger Manager could not initialize - %s: %s" % (sys.exc_info()[0],sys.exc_info()[1]))
			self.debugManager = None

		try:
			self.lcid = site.GetLCID() 
		except pythoncom.com_error:
			self.lcid = win32api.GetUserDefaultLCID()
		self.Reset()

Normally (in all my other Softimage installs on this computer), line 10 fails and fires an ImportError. This is invisible because the trace() on line 19 is commented out. So basically, the Softimage install anticipates that win32com.axdebug.axdebug won’t be found.

However, if win32com.axdebug.axdebug is found, then SetScriptSite tries to import debug, and when that fails, you get that Debugging interfaces not available – debugging is disabled.. message. That’s what was happening on my machine.

As usual, my friend Process Monitor was helpful in figuring this out.

My workaround, for now, is to comment out that trace() call on line 16.

Finding shader nodes with no connections


Here’s a JScript snippet that finds render tree nodes that are not connected to anything in the render tree.

LogMessage( isConnected( Selection(0) ) );

function isConnected( o )
{
	var oDR = XSIUtils.DataRepository ;

	var strOpInfo = oDR.GetConnectionStackInfo( o )
//	LogMessage( strOpInfo );
	var oTopNode = ParseXML( strOpInfo ) ;

	var oConnections = oTopNode.childNodes ;
	if ( oConnections.length == 0 )
	{
		return false;
	}
	return true;
}

function ParseXML( strXML )
{
	var oXMLParser = new ActiveXObject("Microsoft.XMLDOM") 
	oXMLParser.async = false	
	oXMLParser.loadXML( strXML ) ;

	if (oXMLParser.parseError.errorCode != 0) 
	{
		logmessage( "Invalid XML " + oXMLParser.parseError.reason , siError ) ;	
		return null ;
	}

	// the xsi_file node
	// If this is NULL we must have failed to load the XML
	var oTopNode = oXMLParser.documentElement ;

	return oTopNode ;
}

Most of this JScript came from the SDK Explorer code in $XSI_HOME\Addons\sdkui\Application\Plugins\SDKExplorer.js, because I noticed that disconnected shaders would have an empty connection stack, and I didn’t want to go through all the parameters individually looking for connections.
SDK_Explorer_Connection_stack_is_empty

Here’s a Python version that does a little more: it follows the output connections to check whether or not the shader is ultimately connected the material.

# Python Code
import xml.etree.ElementTree as etree

def is_connected( o ):
	if not o.IsClassOf( 52 ):
		print "Input is not a shader"
		return False
		
	sXML = XSIUtils.DataRepository.GetConnectionStackInfo( o )
	root = etree.fromstring( sXML )
	for connection in root:
		if connection.find('type').text == 'out':
			x = Application.Dictionary.GetObject( connection.find('object').text )
			return True if x.IsClassOf( 64 ) else is_connected( x )
				
	return False

print is_connected( Application.Selection(0) )

Saturday Snippet: How many operators in Softimage?


Dictionary.Info gives you a list of all members in a particular family.

ops = Application.Dictionary.Info( '',"Operators").split(',')
print len(ops)
for op in ops:
	print op

For example, there are 592 operators in 2013 SP1 (and 585 in 2012 SP1).

# 592
#  RaiseNurbsCrvDegree
#  ExtrusionAlongAxis
#  4DVectorTo3DVectorNode
#  CreatePolygon
#  GetSetSumNode
#  deformbyspineop2
#  weighmapdeformbyspineop2
#  GetDistanceBetweenNode
#  MarkHardEdgeVertex
#  FCurveNode
#  BooleanSrfTopo
#  OldGenerateSampleSetNode
#  HipIcon
#  WeightPainter
#  DirectionToRotationNode
#  PropWeightMapBySpineOp
#  BaseRetargetOp
#  SkeletonUpVector
#  SkeletonPrefAxis
#  ReferenceToStringNode
#  BooleanSrfOp
#  RemoveNurbsCrvKnot
#  DeleteParticleOp
#  MergeDisjointVerticesNode
#  CenterManip
#  FillHole
#  SubtractNode
#  BlasterOp
#  NegateNode
#  CrvOffset
#  FourSided
#  DissolveComponent
#  Cap
#  3DVectorTo4DVectorNode
#  GetClosestPointsNode
#  FilterEdge
#  SubdivideEdge
#  MergePolygonNode
#  AnimationMarker
#  ExponentNode
#  Fixed Length Operator
#  AddEdge
#  SrfSwap
#  SrfDump
#  OffsetPolygons
#  ExtractPolygonsOp
#  SurfaceUVControl
#  Copy Shape Op
#  2DVectorNode
#  GetArraySizeNode
#  SortArrayNode
#  UpVectorDefiner
#  HairCutOp
#  DetachPolygonsOp
#  RotationToScalarNode
#  Birail2Contours
#  ToeGuide
#  GetSetMedianNode
#  4x4MatrixNode
#  Primitive Grid
#  Primitive Circle
#  Primitive Arc
#  Primitive Spiral
#  IntegerNode
#  NurbsCrvDeletePoint
#  HairScaleOp
#  HSVAToColorNode
#  ICETree
#  HLSAToColorNode
#  Primitive Cylinder
#  AreParallelNode
#  ManCharEyeOp
#  AddNode
#  VectorTo4x4MatrixNode
#  CrvDeform
#  ScalarNode
#  HairResampleOp
#  CompoundNode
#  SimulationStepNode
#  GetSetSizeNode
#  Primitive Square
#  Primitive Octahedron Operator
#  3x3MatrixNode
#  SRTToMatrixNode
#  MeshSubdivideWithCenter
#  PrimitivePreSimulationMarker
#  SrfOffset
#  SpeechBlendOp
#  TextToCurveListOp
#  SimpleIKRetargetOp
#  TextureOp
#  Quadrangulate
#  FrameStepNode
#  4x4MatrixToVectorNode
#  ChestBone
#  CopyGeo
#  PopFromArrayNode
#  SelectCaseNode
#  ColorNode
#  InvertPolygonNode
#  VertexColorChangeDatatype
#  MotionDerivator
#  FRBulgeNode
#  ExtractSubCrvOp
#  BipedRigSteppingAngle
#  ShapeNode
#  UndefinedLocationNode
#  GetClosestLocationNode
#  SplitPolygon
#  SetPositionNode
#  FingerBladeOp
#  CurrentTimeNode
#  SubdividePolygon
#  Surface curve adaptor
#  QStretch
#  3DVectorTo4x4MatrixNode
#  Primitive Disc
#  MatrixToSRTNode
#  RelaxUVW
#  UVToLocationNode
#  CrvPass
#  CrvExtract
#  SrfCrvShift
#  ReinterpretLocationToNewGeometryNode
#  CrvReparam
#  TangentOp_cpp
#  SubtractColorNode
#  SrfFit
#  PrimitiveMeshNode
#  MaximumNode
#  HairPopOp
#  ColorToGrayscaleNode
#  BooleanNode
#  MoveComponent
#  SCMFixer
#  SubdivideLocallyNode
#  MoveComponentProportional
#  BlobEncodeTestNode
#  PropVolume
#  GetDeterminantNode
#  CrvExtractSeg
#  GenerateSampleSetNode
#  MultiplyByScalarNode
#  CollapseEdgeNode
#  ScalarTo3DVectorNode
#  SplitEdge
#  HairMergeOp
#  SquaredLengthNode
#  TrigonometryNode
#  SparksOp
#  GetDataNode
#  SpringOp
#  Primitive Dodecahedron Operator
#  LengthNode
#  Loft
#  WeldPoints
#  QuaternionToAxisAndAngleNode
#  SortArrayWithKeyNode
#  TestBackcompNode
#  RemoveFromArrayNode
#  Loft
#  Extrusion
#  FourSided
#  PolyMeshPNet
#  CrvOffset
#  SrfOffset
#  SrfFillet
#  SrfIntersect
#  SrfMerge
#  FillHole
#  CrvFit
#  CrvExtract
#  Cap
#  CrvProject
#  CurveCreation
#  CrvNet
#  Birail
#  CrvFillet
#  CrvStitch
#  CrvMerge
#  CrvExtractSeg
#  ExtrusionTwoProfiles
#  SCMTopology
#  Revolution
#  BlendCrv
#  BlendSrf
#  CopyOp
#  GetArrayProductNode
#  FlexibleEnvelope
#  4x4MatrixTo3DVectorNode
#  SrfSubdivision
#  TangentOp2_cpp
#  ModuloNode
#  BooleanToIntegerNode
#  ClusterSimpleShapeCombinerOp
#  CollisionAvoidanceNode
#  HairGenOp
#  HairDynamicsOp
#  3x3MatrixToVectorNode
#  CrvSnap
#  TrimByProjection
#  SrfStitch
#  FRTugNode
#  VectorTo3x3MatrixNode
#  CurrentFrameNode
#  SCMFixer2
#  Lattice
#  RevolutionAlongAxis
#  FilterNode
#  Bulge
#  SetData
#  DelaySetDataNode
#  ImageCropOp
#  MultiplyVectorByMatrixNode
#  CrvPassByPoint
#  IsSingularNode
#  IntegerToBooleanNode
#  SetUserDataMapValueOp
#  SplitEdgeNode
#  BlobDecodeTestNode
#  CopyOp
#  LockEnvelopeWeights
#  CrvExtendTo
#  SrfClean
#  CrvFillet
#  CrvStitchToSrf
#  CrvStitch
#  CrvMerge
#  ExtendCrvToPoint
#  Surface To Surface Curve Adaptor
#  DicePolygons
#  AddVertexNode
#  CurveToCurvePointAdaptor
#  Modify Envelope Weight Prop Op
#  Skeleton Controller
#  BevelComponent
#  DeformBySpine
#  SimulateBulletRigidBodiesNode
#  RotationNode
#  First Bone Roll Division
#  Rotation Order
#  ConstrainCurveIsopoint
#  Revolution
#  SrfInverse
#  HairLockOp
#  CurveToCurveBoundaryAdaptor
#  LimitEnvelopeDeformersOp
#  HairSplitOp
#  GetArrayAverageNode
#  SetOneDataNode
#  InsertSrfKnot
#  RemoveSrfKnot
#  CrvProject
#  BlendNode
#  LogarithmNode
#  SrfFillet
#  EdgeDelete
#  ExtrudeComponent
#  AddEdgeNode
#  SimulateRigidBodiesNode
#  Spine Curve Operator
#  GetDataAtPreviousFrameNode
#  TriangulatePolygonNode
#  IntegerToScalarNode
#  Gator
#  SCMTopology
#  GetClosestPointToLineSegmentNode
#  First Bone Roll Division
#  MergeTopoArrayNode
#  ExtrudePolygonIslandNode
#  IsOnGeometryNode
#  ScalarToRotationNode
#  ExclusiveOrNode
#  DeleteComponent
#  ResizeArrayNode
#  DeletePointNode
#  GetAngleBetweenNode
#  SteppingAngle30Op
#  SymmetryMap
#  FlexibleEnvelopeAutoAssign
#  PropWeightMapBySpineOp
#  Snip
#  EditPolygon
#  GroupGeometryNode
#  CreateTopoNode
#  NurbsToMesh
#  Shear
#  GradientNode
#  HyperFlowOp
#  CameraTxt
#  PolyUVContourStretchingOnTopOp
#  BlendCrv
#  CustomOperator
#  ColorToHLSANode
#  QuaternionToRotationNode
#  FRLiftNode
#  SplineGenOp
#  PolygonReduction
#  ImageSourceOp
#  AddPolygonNode
#  IfNode
#  SimulationMarker
#  ScalarTo2DVectorNode
#  GetMinimumInSetNode
#  InsertInArrayNode
#  LinearInterpolateNode
#  ArcTan2Node
#  StringFilePathNode
#  SnapFace
#  HairPuffOp
#  PrimToNonLocalMatCls
#  GetElementIndicesNode
#  SetClusterPropSizeOp
#  RepeatNode
#  DisconnectComponentNode
#  ParTypeClsGenOp
#  FindInArrayNode
#  Fold
#  OldGetGeometrySampleNode
#  PushOnArrayNode
#  Birail
#  TextureOp
#  SurfacePQ
#  Cluster
#  MotionIntegrator
#  PassThroughNode
#  Multiply Rotation Operator
#  Spring
#  Collapse
#  QuaternionNode
#  BridgePolygon
#  HairUnlockOp
#  SymmetrizePolygon
#  FlipUVW
#  SetUVPropPinInfoOp
#  ImageFXOp
#  GetSetOrNode
#  TurbulenceNode
#  RGBAToColorNode
#  ClonePointNode
#  GetSetAndNode
#  RotationToEulerNode
#  SimMotionDerivOp
#  CompareNode
#  PointIndexToLocation
#  MergeTopoNode
#  MapCompOp2
#  DisconnectComponent
#  BlendTransformNode
#  Copy Shape Op
#  GetGeometrySampleNode
#  MatchUVW
#  NXDynamicsOp
#  FlowFieldRenderOp
#  Push
#  SoftBodyOp
#  RaycastNode
#  FlexibleEnvelopeAutoAssign
#  OldNurbsCrvDeletePoint
#  SlicePolygons
#  VertexColorPainter
#  PolyMeshPNet
#  BuildArrayFromConstantNode
#  AbsoluteValueNode
#  Cls Shape Combiner Op
#  ModelingMarker
#  CrvInverse
#  SrfShift
#  CrvShift
#  OffsetComponents
#  ExtractEdgeLoopOp
#  TurnEdgeOp
#  Fore Arm Roll Division
#  LogValuesNode
#  AddPointNode
#  IDToLocation
#  ExtrudeComponentAxis
#  SetEdgeCreaseValueOp
#  GetSetAverageNode
#  Primitive Tetrahedron Operator
#  SrfOpenClose
#  MixerOp
#  CloneTopoNode
#  Bend
#  Taper
#  ReserveArrayNode
#  Surface To Boundary Curve Adaptor
#  NormalizeNode
#  Fracture
#  Primitive Text
#  InvertNode
#  RotationToQuaternionNode
#  InitializeNode
#  SlicePolygonNode
#  PrimitivePostSimulationMarker
#  DeletePolygonNode
#  ClothOp
#  ShapeJitter
#  DotProductNode
#  SrfPass
#  MapAdaptOp
#  Spin
#  SplineDynamicsOp
#  Primitive Icosahedron Operator
#  Primitive Sphere
#  Extrusion
#  DivideByScalarNode
#  FilterPolygon
#  OLD Cache On File --REPLACEME
#  TestNode
#  InvertPolygon
#  Primitive Cone
#  Primitive Cube
#  QuaternionToScalarNode
#  CacheDataNode
#  PrimaryShapeMarker
#  MeshMerge
#  ICETreeComment
#  3DVectorNode
#  PaintWeight
#  WeightMap
#  SrfMerge
#  AddPointToMiddleOfCurve
#  GetArrayMaximumNode
#  Connector_Head
#  DieNode
#  DogLeg
#  CombineLogicNode
#  4DVectorNode
#  CrvClean
#  CrvOpenClose
#  PostSimulationMarker
#  Smooth
#  WeldEdges
#  CrossProductNode
#  Spine PointAt Operator
#  SrfIntersect
#  ShrinkWrap
#  QuadrupedGuideOp
#  WMCnxOp
#  DeformHairAdaptor
#  Revolution
#  Extrusion
#  ProjectVectorNode
#  SrfDeform
#  TransposeNode
#  Connector_L
#  InsertNurbsCrvKnot
#  BlowUpOp
#  ClusterCenter
#  NotNode
#  CurveKnotAlignTangent
#  CurveListToMesh
#  AddColorNode
#  ScalarTo4DVectorNode
#  CrvFit
#  MapCompOp
#  SrfCrvInverse
#  Softimage
#  FacialSoftTissueOp
#  GetArraySubIndicesNode
#  EvolveOp
#  HairClumpOp
#  HairAttenuateOp
#  BlendSrf
#  ScriptedOpHost
#  AddParticleOp
#  QuaternionToEulerNode
#  Heal
#  MinimumNode
#  MeshSubdivide
#  SrfReparam
#  FRSkullUVsToPtLocsNode
#  CustomNode
#  Proportional
#  Connector_T
#  PointInVolumeNode
#  ArePerpendicularNode
#  RotateVectorNode
#  BBoxCns
#  ColorToRGBANode
#  PolyUVContourStretchingOp
#  SpinEdge
#  CompoundNode
#  Connector_Box
#  LocalTextureOp
#  GetClosestPointToLineNode
#  PropertyXferOp
#  3DVectorToScalarNode
#  StringFilePathSequenceNode
#  ScalarToQuaternionNode
#  HairCombOp
#  PrimitiveSimulationMarker
#  BasicCollideNode
#  ExecuteNode
#  CurveCreation
#  SecondaryShapeMarker
#  CrvNet
#  SetInArrayNode
#  MultiplyColorByScalarNode
#  PropWeightMapBySpineOp
#  ClsKeyWeightMapOp
#  EulerToQuaternionNode
#  QuatSpineOp
#  WhileNode
#  Weight Maps Mixer Op
#  GetActionSourceAtFrameNode
#  Noise
#  Wave
#  BallisticSim
#  VertexColor
#  Cage Deform Auto Assign
#  CycleUVW
#  Proportional
#  GetArraySumNode
#  InstanceShapeNode
#  DynamicsOp
#  EdgeAdd
#  FlowFieldOp
#  BuildArrayNode
#  PolyUVPackingOp
#  ExtrudeComponentNormal
#  SelectInArrayNode
#  RescaleNode
#  CacheOnFileNode
#  RotationToAxisAndAngleNode
#  DeleteVertexNode
#  ColorToHSVANode
#  AddToClsOp
#  Twist
#  IslandHealUVWOp
#  SimulateParticlesNode
#  MultiplyNode
#  AxisAndAngleToQuaternionNode
#  HealUVW
#  2DVectorToScalarNode
#  FirstValidNode
#  SwitchContextNode
#  TangentOp2_cpp
#  StringNode
#  GetArrayMinimumNode
#  ClusterToObj
#  RoundNode
#  GetMaximumInSetNode
#  Primitive Torus
#  HairRotateOp
#  FilterPoints
#  FRSkinNode
#  RandomValueNode
#  CrvDump
#  DeleteTrim
#  PolygonAdd
#  ClampNode
#  Randomize
#  CageDeform
#  MeshLocalSubdivision
#  
#  MeshSrfDeletePoint
#  TopoTransformNode
#  CopyUVW
#  SquareRootNode
#  HairShatterOp
#  SetNurbsCrvKnotMultiplicity
#  HairRecombOp
#  AxisAndAngleToRotationNode
#  EulerToRotationNode
#  BuildArrayFromSetNode
#  ColorToBrightnessNode
#  BlendColorNode
#  4DVectorToScalarNode
#  QuaternionInterpolateNode
#  syCollidePla
#  syZipper
#  POXSI
#  syWind
#  syProperties
#  syCollide
#  syMimic
#  syCache
#  syVolume
#  syTurbulence
#  sySpring
#  syPin
#  sySelfCollide
#  syPropertiesFlesh
#  syGravity
#  syDamp
#  syCloth
#  syPropertiesSkin
#  syNail
#  syAir
#  syCollideSph

Finding degenerate polygons by area


Degenerate polygons are usually zero-area polygons.

Here’s a script that uses the ICE attribute PolygonArea to find polygons with area less than a specified epsilon:

si = Application
epsilon = 0.00001

# Get PolygonArea DataArray (which is a tuple)
attr = si.Selection(0).ActivePrimitive.GetICEAttributeFromName( "PolygonArea" )
areaData = attr.DataArray

#
# Find the indices of the bad polys
#
bad = [ x for x,y in enumerate( areaData ) if y < epsilon]

# Select the degenerates with a string like 'cube.poly[112,114,155]'
si.SelectGeometryComponents( 'cube.poly[%s]' % ','.join(str(i) for i in bad) )


### OR ###

#
# Get the actual Polygon objects
#
polys = si.Selection(0).ActivePrimitive.Geometry.Polygons
bad = []
for i in range( len(areaData) ):
	if areaData[i] < epsilon:
		bad.append( polys(i) )

si.SelectObj( polys )

Saturday snippet: Python classes, __getattr_, lambda, and-or trick, and getattr


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' )

Getting selected text from the script history


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

Saturday Snippet: Selecting a range of polygons by polygon index


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 )