Monday, July 27, 2015

How to turn instanced nparticles models into geometry

If you need your instanced nparticles to become actual geometry with keyframes that you can work with and modify here is the way to go about it.

This is an amazing little tool here is more info about it by it's creator.
http://www.sigillarium.com/blog/726/


Save this script to a .py file

Place the file in your maya scripts folder

maya>2016>scripts


from sag_instancerToGeometry import *
sag_instancerToGeometry()


#-----------------------------------------------------------------------------------------------maya-
# file: sag_instancerToGeometry.py
# version: 1.4
# date: 2012.05.05
# author: Arkadiy Demchenko (sagroth@sigillarium.com)
#----------------------------------------------------------------------------------------------------
# Converts instancer into keyframed objects:
# Put the file into maya scripts dir and run:
#
# from sag_instancerToGeometry import *
# sag_instancerToGeometry()
#----------------------------------------------------------------------------------------------------
# 2012.05.05 (v1.4) - corrected GUI for older maya versions
# 2012.04.06 (v1.3) - doesn't set visibility to off prior to the starting frame of conversion
#                   - doesn't pay attention to 'start from current frame' if custom range is defined
#
# 2011.06.19 (v1.2) - reworked GUI
# - uses long names correctly (no problems with objects of the same name anymore)
# - doesn't freeze source objects' rotations or error if channels have keyframes
# - keeps input connections for instances also
# - works with different rotation orders of source objects and instancer itself
# - each baked object is inside it's own group which actually gets all keyframes
# - works with any linear units of the scene (switches to cm and back, actually)
#
# 2010.06.11 (v1.1) - duplicates now maintain original input connections
# - only translate, rotate, scale and visibility are keyframed now
#
# 2009.11.14 (v1.0) - main release
#----------------------------------------------------------------------------------------------------

from maya.cmds import *

# GUI
def sag_instancerToGeometry():
if window( 'sag_instancerToGeometry_win', exists = True ):
deleteUI( 'sag_instancerToGeometry_win' )

window( 'sag_instancerToGeometry_win', title = 'Instancer to Geo', sizeable = False )

columnLayout( adj = True )

radioButtonGrp( 'sag_instancerToGeometry_win__dupOrInst_RBG',
labelArray2 = ['Make Duplicates', 'Make Instances'],
numberOfRadioButtons = 2,
select = 2 )

separator( style = 'in' )

checkBox( 'sag_instancerToGeometry_win__fromCurFrame_ChB',
align = 'left',
label = 'Start from Current Frame',
value = 1 )

radioButtonGrp( 'sag_instancerToGeometry_win__range_RBG',
labelArray2 = ['Playback Range', 'Custom Range'],
numberOfRadioButtons = 2,
select = 1,
onCommand1 = 'intFieldGrp( "sag_instancerToGeometry_win__range_IFG", edit = True, enable1 = False, enable2 = False )',
onCommand2 = 'intFieldGrp( "sag_instancerToGeometry_win__range_IFG", edit = True, enable1 = True, enable2 = True )' )

intFieldGrp( 'sag_instancerToGeometry_win__range_IFG',
label = '',
numberOfFields = 2,
columnWidth = (1, 24),
value1 = playbackOptions( q = True, min = True ),
value2 = playbackOptions( q = True, max = True ),
enable1 = False,
enable2 = False )

sep2 = separator( style = 'in' )

rowLayout( numberOfColumns = 2, columnWidth2 = [ 172, 40 ], columnAlign2 = [ 'center', 'center' ] )

button( label = 'Convert',
width = 172,
command = 'sag_instancerToGeometry_cmd()' )

button( label = 'Help',
width = 48,
command = 'showHelp( "http://www.sigillarium.com/blog/726/", absolute = True )' )

showWindow( 'sag_instancerToGeometry_win' )


# RUN MAIN PROCEDURE WITH SETTINGS FROM GUI
def sag_instancerToGeometry_cmd():
dupOrInst = radioButtonGrp( 'sag_instancerToGeometry_win__dupOrInst_RBG', q = True, select = True ) - 1
fromCurFrame = checkBox( 'sag_instancerToGeometry_win__fromCurFrame_ChB', q = True, value = True )
rangeSpecified = radioButtonGrp( 'sag_instancerToGeometry_win__range_RBG', q = True, select = True ) - 1
start = intFieldGrp( 'sag_instancerToGeometry_win__range_IFG', q = True, value1 = True )
end = intFieldGrp( 'sag_instancerToGeometry_win__range_IFG', q = True, value2 = True )

sag_instancerToGeometry_do( dupOrInst, fromCurFrame, rangeSpecified, start, end )


# MAIN PROCEDURE
def sag_instancerToGeometry_do( dupOrInst, fromCurFrame, rangeSpecified, start, end ):

# RANGE OPTIONS
currentFrame = currentTime( q = True )
startFrame = playbackOptions( q = True, min = True )
endFrame = playbackOptions( q = True, max = True )

if rangeSpecified > 0:
startFrame = start
endFrame = end
elif fromCurFrame > 0:
startFrame = currentFrame

# MAKE A LIST OF ALL SELECTED INSTANCERS
instList = []

selList = ls( selection = True )

for each in selList:
if objectType( each ) == 'instancer':
instList.append( each )

if instList == []:
print 'No instancers selected!'
return

# FIND GEOMETRY, PARTICLE OBJECTS AND MAPPED ATTRIBUTES FOR INSTANCERS
geoList = []
ptList = []
iamList = []

blankInsts = []
for each in instList:
# MAKE A LIST OF INPUT OBJECTS
instGeo = listConnections( each + '.inputHierarchy', source = True, destination = False, connections = False, plugs = False )

if instGeo == None:
print 'No geometry connected to instancer ' + each + '!'
blankInsts.append( each )

else:
# MAKE A LIST OF PARTICLE OBJECTS AND THEIR INSTANCER MAPPED ATTRIBUTES
conn = listConnections( each + '.inputPoints', source = True, destination = False, connections = False, plugs = True )

if conn == None:
print 'No particles connected to instancer ' + each + '!'
blankInsts.append( each )
else:
geoList.append( instGeo )
ptList.append( conn[0][:conn[0].find( '.' )] )
iamList.append( getAttr( conn[0][:conn[0].rfind( '.' )] + '.instanceAttributeMapping' ) )

# REMOVE INSTANCERS WITH NO GEOMETRY OR PARTICLES ATTACHED FROM THE LIST
for each in blankInsts:
instList.remove( each )

# QUIT IF NO REASONABLE INSTANCERS LEFT
if instList == []:
return

# SET UNITS TO CM (SINCE THAT'S WHAT PARTICLE VALUES USING NO MATTER WHAT)
origUnits = currentUnit( query = True, linear = True )
currentUnit( linear = 'cm' )

# LISTS FOR STORING CONVERTED IDS AND CREATED DUPLICATES
pids = []
dups = []
for inst in instList:
pids.append( [] )
dups.append( [] )

# MAIN CONVERSION LOOP
for t in xrange( int( startFrame ), int( endFrame ) + 1 ):
currentTime( t, update = True )

for inst in instList:
instInd = instList.index( inst )
instGeo = geoList[instInd]
instPt = ptList[instInd]
instIam = iamList[instInd]

# GET INSTANCER ROTATION ORDER AND CONVERT IT INTO GEOMETRY ROTATION ORDER
instRodOrig = getAttr( inst + '.rotationOrder' )
instRodConv = { 0:0, 1:3, 2:4, 3:1, 4:2, 5:5 }
instRod = instRodConv[ instRodOrig ]

deadPids = pids[instList.index(inst)][:]
instNum = getAttr( inst + '.instanceCount' )
for i in xrange( 0, instNum ):
# GET GENERAL OPTIONS VALUES
pid = int(particle( instPt, q = True, at = 'particleId', order = i )[0])
pos = particle( instPt, q = True, at = 'worldPosition', order = i )
scl = (1,1,1)
shr = (0,0,0)
vis = 1.0
idx = 0.0
if 'scale' in instIam:
scl = particle( instPt, q = True, at = instIam[instIam.index( 'scale' )+1], order = i )
if 'shear' in instIam:
shr = particle( instPt, q = True, at = instIam[instIam.index( 'shear' )+1], order = i )
if 'visibility' in instIam:
vis = particle( instPt, q = True, at = instIam[instIam.index( 'visibility' )+1], order = i )[0]
if 'objectIndex' in instIam:
idx = particle( instPt, q = True, at = instIam[instIam.index( 'objectIndex' )+1], order = i )[0]

# IF OBJECT INDEX IS HIGHER OR LOWER THAN AVAILABLE NUMBER OF INSTANCE OBJECTS - CLAMP TO THE CLOSEST POSSIBLE VALUE
if idx > (len( instGeo ) - 1):
idx = (len( instGeo ) - 1)
elif idx < 0:
idx = 0

# IF SCALE ATTRIBUTE IS FLOAT INSTEAD OF VECTOR - FORCE VECTOR
if len( scl ) < 3:
scl = [scl[0], scl[0], scl[0]]

# GET ROTATION OPTIONS VALUES
rot = (0,0,0)
if 'rotation' in instIam:
rot = particle( instPt, q = True, at = instIam[instIam.index( 'rotation' )+1], order = i )

# IF THE PARTICLE IS NEWBORN MAKE A DUPLICATE
newBorn = 0

dupName = inst.replace( '|', '_' ) + '_' + instGeo[int(idx)].replace( '|', '_' ) + '_id_' + str(pid)

if pid not in pids[instList.index(inst)]:
pids[instList.index(inst)].append( pid )

# IF OBJECT WITH THE SAME NAME ALREADY EXISTS, ADD _# SUFFIX
if objExists( dupName ):
z = 1
dupName += '_' + str( z )
while objExists( dupName ):
z += 1
dupName = dupName[:dupName.rfind( '_' )+1] + str( z )

if dupOrInst > 0:
dup = instance( instGeo[int(idx)], name = dupName )[0]
trsConns = listConnections( instGeo[int(idx)], s = True, d = False, c = True, p = True )
if trsConns != None:
for y in xrange( 0, len( trsConns ), 2 ):
if not isConnected( trsConns[y+1], dup + trsConns[y][trsConns[y].rfind( '.' ):] ):
connectAttr( trsConns[y+1], dup + trsConns[y][trsConns[y].rfind( '.' ):] )
else:
dup = duplicate( instGeo[int(idx)], name = dupName, inputConnections = True )[0]

# CREATE A GROUP FOR A DUPLICATE
dupGrp = group( em = True, name = dup + '_grp' )
parent( dupGrp, dup )
setAttr( dupGrp + '.translate', 0, 0, 0, type = 'double3' )
parent( dupGrp, world = True )
parent( dup, dupGrp )

dup = dupGrp

dups[instList.index(inst)].append( dup )

if t != int( startFrame ):
newBorn = 1
else:
# IF OBJECT WITH THE SAME NAME EXISTS FROM PREVIOUS BAKE, FIND THE SUFFIXED NAME FROM THIS BAKE
if not dupName + '_grp' in dups[instList.index(inst)]:
z = 1
dupName += '_' + str( z )
while not dupName + '_grp' in dups[instList.index(inst)]:
z += 1
dupName = dupName[:dupName.rfind( '_' )+1] + str( z )

dup = dupName + '_grp'
if pid in deadPids:
deadPids.remove( pid )

# TRANSFORM THE DUPLICATE
setAttr( dup + '.translate', pos[0], pos[1], pos[2], type = 'double3' )
setAttr( dup + '.scale', scl[0], scl[1], scl[2], type = 'double3' )
setAttr( dup + '.visibility', vis )

setAttr( dup + '.rotateOrder', instRod )
setAttr( dup + '.rotate', rot[0], rot[1], rot[2], type = 'double3' )

# SET KEYFRAMES
setKeyframe( dup, inTangentType = 'linear', outTangentType = 'linear', attribute = ( 'translate', 'rotate', 'scale', 'visibility' ) )
if newBorn > 0:
setKeyframe( dup + '.visibility', time = currentTime( q = True ) - 1, value = 0 )

# MAKE DEAD INSTANCES INVISIBLE
for dead in deadPids:
setKeyframe( dups[instList.index(inst)][pids[instList.index(inst)].index(dead)] + '.visibility', value = 0 )

# GROUP DUPLICATES, DELETE STATIC CHANNELS AND APPLY EULER FILTER
for inst in instList:
group( dups[instList.index(inst)], name = inst + '_geo_grp', world = True )
for obj in dups[instList.index(inst)]:
delete( obj, staticChannels = True, unitlessAnimationCurves = False, hierarchy = 'none', controlPoints = False, shape = False )
animCurves = listConnections( obj, source = True, destination = False, connections = False, plugs = False )
filterCurve( animCurves )

# RESTORING ORIGINAL UNITS
currentUnit( linear = origUnits )