# Scripting – Shuffling a collection to randomly select by percentage

Via 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

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

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

```