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

New in Softimage 2013: Filtering XSICollections


In Softimage 2013, the XSICollection object now has a Filter method, so you can filter XSICollections by type, family, or path name.

oFilteredCollection = XSICollection.Filter( [Type], [Families], [Path] )

I updated my find all cameras script to use XSICollection.Filter, and here’s the timing results (form the same scene, but in 2013).

# INFO : getCameras_FindObjects finished in 0.033000 seconds
# INFO : Found 8301 cameras
# INFO : getCameras_FindObjects_w_Filter finished in 0.269000 seconds
# INFO : Found 24 cameras
# INFO : getCameras_FindObjects_w_SIFilter finished in 0.044000 seconds
# INFO : Found 24 cameras
# INFO : getCameras_FindObjects2 finished in 0.001000 seconds
# INFO : Found 49 cameras
# INFO : getCameras_FindObjects2_w_Filter finished in 0.003000 seconds
# INFO : Found 24 cameras
# INFO : getCameras_FindChildren2 finished in 0.149000 seconds
# INFO : Found 24 cameras
# INFO : getCameras_SelectAllUsingFilter finished in 0.035000 seconds
# INFO : Found 24 cameras
  • FindObjects2 is fastest, with SelectAllUsingFilter a mildly surprising second (again note that I’m calling SelectAllUsingFilter with AffectSelectionList=False, so I’m not actually selecting anything).
    FindObjects2_w_Filter		0.003000 seconds
    SelectAllUsingFilter		0.035000 seconds
    FindObjects_w_SIFilter		0.044000 seconds
    FindChildren2			0.149000 seconds
    FindObjects_w_Filter		0.269000 seconds
    
  • FindObjects finds a lot more than just cameras; it finds lots of nulls too because that GUID isn’t unique to cameras
  • In this context, SIFilter is faster than the XSICollection.Filter method

Here’s the updated script for Softimage 2013:

import time
si = Application
log = si.LogMessage
from win32com.client import constants as C

import win32com.client
oCameraColl = win32com.client.Dispatch( "XSI.Collection" )

si.SetValue("preferences.scripting.cmdlog", False, "")


def timeExecution(func):
    def closure(*args, **kwargs):
        startTime = time.time()
        try:
            ret = func(*args, **kwargs)
        except Exception, e:
            delta = time.time() - startTime
            log('Failed in %f seconds' % delta)
            raise
        delta = time.time() - startTime
        log('%s finished in %f seconds' % (func.__name__, delta))
        return ret
    return closure 

@timeExecution
def getCameras_FindObjects():
	oCameraColl = Application.FindObjects( "", "{5FC0CCAE-3DC8-11D0-9449-00AA006D3165}" )
	return oCameraColl.Count

@timeExecution
def getCameras_FindObjects_w_Filter():
	oCameraColl = Application.FindObjects( "", "{5FC0CCAE-3DC8-11D0-9449-00AA006D3165}" )
	oCameraColl = oCameraColl.Filter( "camera" )
	oCameraColl.RemoveItems( oCameraColl.Filter( "", "", "CopyPaste*" ) )
	oCameraColl.RemoveItems( oCameraColl.Filter( "", "", "View*" ) )
	return oCameraColl.Count

@timeExecution
def getCameras_FindObjects_w_SIFilter():
	oCameraColl = Application.FindObjects( "", "{5FC0CCAE-3DC8-11D0-9449-00AA006D3165}" )
	oCameraColl = si.SIFilter( oCameraColl, "camera" )
	oCameraColl.RemoveItems( oCameraColl.Filter( "", "", "CopyPaste*" ) )
	oCameraColl.RemoveItems( oCameraColl.Filter( "", "", "View*" ) )
	return oCameraColl.Count


@timeExecution
def getCameras_FindObjects2():
	c = si.FindObjects2( C.siCameraID )
	return c.Count

@timeExecution
def getCameras_FindObjects2_w_Filter():
	cams = si.FindObjects2( C.siCameraID )
	oCameraColl.Items = cams
	oCameraColl.RemoveItems( cams.Filter( "", "", "CopyPaste*" ) )
	oCameraColl.RemoveItems( cams.Filter( "", "", "View*" ) )
	return oCameraColl.Count


@timeExecution
def getCameras_FindChildren2():
	cams = si.ActiveSceneRoot.FindChildren2("", "camera")
	return cams.Count

@timeExecution
def getCameras_SelectAllUsingFilter():
	cams = si.SelectAllUsingFilter("Camera", "siIgnoreComponentVisibility", False, "")
	return cams.Count



@timeExecution
def getCameras_Model_FindObjects():
	cams = si.ActiveSceneRoot.FindObjects( C.siCameraID )
	return cams.Count


log( 'Found %d cameras' % getCameras_FindObjects() )
log( 'Found %d cameras' % getCameras_FindObjects_w_Filter() )
log( 'Found %d cameras' % getCameras_FindObjects_w_SIFilter() )
log( 'Found %d cameras' % getCameras_FindObjects2() )
log( 'Found %d cameras' % getCameras_FindObjects2_w_Filter() )
log( 'Found %d cameras' % getCameras_FindChildren2() )
log( 'Found %d cameras' % getCameras_SelectAllUsingFilter() )