Exploded View


#1

Hey guys,

So I’ve always thought Houdini’s exploded-view node was pretty awesome, and I wanted to try and recreate it inside of Maya (not as a node but a quick python script).

I’ve come very close to getting it to do what I want (see attached images), but there’s a strange offset that’s happening (see third image).

It’s likely a very simple fix which is frustrating :banghead:

Below is the code:

import pymel.core as pmc

sel = pmc.ls(sl=True)
for piece in sel:
    cpom = pmc.createNode('closestPointOnMesh')
    localPos = pmc.xform(piece, q=1, ws=1, rp=1)
    
    # add localTranslation attributes to connect the cpom to
    try:
        pmc.addAttr(piece, ln='localPos', at='double3')
        attrs = ['localPosX', 'localPosY', 'localPosZ']
        for attr in attrs:
            pmc.addAttr(piece, ln=attr, at='double', p='localPos')   
    except: pass
    
    pmc.connectAttr('%s.outMesh'%piece.getShape(), '%s.inMesh'%cpom)
    
    pmc.setAttr('%s.localPosX' % piece, localPos[0])
    pmc.setAttr('%s.localPosY' % piece, localPos[1])
    pmc.setAttr('%s.localPosZ' % piece, localPos[2])

    pmc.connectAttr('%s.localPosX'%piece, '%s.inPositionX'%cpom)
    pmc.connectAttr('%s.localPosY'%piece, '%s.inPositionY'%cpom)
    pmc.connectAttr('%s.localPosZ'%piece, '%s.inPositionZ'%cpom)
    
    unitVector = pmc.getAttr('%s.inPosition'%cpom)
    newPos = []
    
    for i in range(len(unitVector)):
        newPos.append(unitVector[i])
    
    piece.translate.set(newPos)
    

# cleanup all closest point on mesh nodes
for node in pmc.ls('closest*'):
    pmc.delete(node)

I should mention: the reason why I added the localPos attributes was to take into consideration that the individual pieces might have their transformations frozen, and we want the world-space positions, but the closestPointOnMesh node wants the local translation values.

Let me know if you figure this out.

__
cw


#2

After looking at the code, it got really screwy (it’s getting late…). But what I was initially trying to do was to create a reference sphere and position it in the center of all the selected pieces.

Then use the closestPointOnMesh node to multiply the local position of each piece by the face normal of the closest face on the sphere. That would (should) uniformly offset each piece in a spherical direction.

Any ideas how to create that?


#3

After some tinkering around, I think I got it - and it’s much more simplified :applause:

The first chunk of code (below) randomly disperses locators spherically around the selected object (if no selection exists, it uses the origin). Variables are clearly marked to adjust the radius and the number of objects (you can alter the script obviously to include whatever object you’d like).

import math as m
from random import uniform
import pymel.core as pmc

r = 5.0          # radius (n is PyNode scale of sphere)
try:
    c = pmc.xform(pmc.ls(sl=True)[0], q=1, ws=1, rp=1)    # center of objects
except: c=[0,0,0]
n = 1250

# randomly position objects along sphere
for i in range(n):
    s = pmc.spaceLocator()
    s.scale.set(.1,.1,.1)
    
    theta = 2*m.pi*uniform(0,1)
    phi = 1/(m.cos(2*uniform(0,1))-1)
    
    x = c[0] + (m.sqrt(1-(m.cos(phi)**2)))*m.cos(theta) * r
    y = c[1] + (m.sqrt(1-(m.cos(phi)**2)))*m.sin(theta) * r
    z =c[2] +  m.cos(phi) * r
    
    s.translate.set(x,y,z)
pmc.select(d=True)

The next chunk of code is a function that will take two vectors (simple python lists in this case) and return a third vector by subtraction:

def vectSub(vec1, vec2):
    '''Subtract vectors using standard python lists.'''
    
    vec3 = []
    for item in range(len(vec1)):
        vec3.append(vec2[item] - vec1[item])
    return vec3

This next chunk is what explodes the objects (you can think of voronoi-shattered pieces):


# exploded view
sel = pmc.ls(sl=True)
grp = pmc.group(n='explodedView')
groupCenter = pmc.xform(grp, q=1, ws=1, rp=1)
pieceDict = dict()

for piece in sel:
    piecePos = pmc.xform(piece, q=1, ws=1, rp=1) 
    pieceDict[piece] = piecePos
    pmc.makeIdentity(piece, apply=True, t=1, r=0, s=0, n=0)
    
# transform pieces by mult factor      
for piece in sel:
    piecePos = pmc.xform(piece, q=1, ws=1, rp=1) 
    mult = 1.5

    newPos = vectSub(groupCenter, piecePos)  
    x = newPos[0] * mult
    y = newPos[1] * mult
    z = newPos[2] * mult
    
    piece.translate.set(x,y,z)
    

A simple CTRL+Z will undo the explosion, OR you can run the code below (if the objects allow for local transformations):

# reset transformations
for item in pmc.listRelatives(grp, children=True):
    pmc.parent(item, world=True)
pmc.delete(grp)
for piece in pieceDict:
    localPos = ['localPositionX','localPositionY','localPositionZ']
    [pmc.setAttr('%s.%s'%(piece, pos), 0) for pos in localPos]    
    piece.translate.set(pieceDict[piece])
    pmc.xform(piece, cp=True)

Hope someone may find this useful! Also, if you know of a better way please let me know.

__
cw