Extrait de code Maya

Récupérer la position de chaque vertex d’un nœud de mesh

Il s’agit d’une méthode extremenent rapide :

import maya.cmds as mc

raw_pos = mc.xform('pCube1.vtx[*]', query = True, worldSpace = True, translation = True)
vtx_pos = zip(raw_pos[0::3], raw_pos[1::3], raw_pos[2::3])

Récupérer l’index du vertex le plus proche d’une position donnée

import maya.cmds as mc

def sqrt_dst(p1,p2):
    """return square distance between `p1` and `p2`

    This assume `p1` and `p2` are tuple of three component each.
    """
    return (p1[0]-p2[0])**2+(p1[1]-p2[1])**2+(p1[2]-p2[2])**2

def closest(org_pos, node):
    """Return vertex indice of given node wich is the closest of given world space `org_pos`"""
    # get every world space position of given node vertices
    raw_pos = mc.xform(node+'.vtx[*]', query = True, worldSpace = True, translation = True)

    # convert raw list to list of three-component-tuple
    pt_pos = zip(raw_pos[0::3], raw_pos[1::3], raw_pos[2::3])

    pt_sqrt_dst = [sqrt_dst(org_pos, pt) for pt in pt_pos]

    return pt_sqrt_dst.index(min(pt_sqrt_dst))

# get world position of the locator
loc_pos = mc.xform('locator1', query = True, worldSpace = True, translation = True)

print closest(org_pos = loc_pos, node = 'pCube1')

Itérer sur les top nodes de la scène

import maya.cmds as mc

# to avoid to get default cameras, we create a set with default nodes
defaultNodes = set(mc.ls(defaultNodes=True))

def top_nodes():

    # iter over every top transform nodes
    for node in mc.ls(assemblies = True):

        # skip default nodes and nodes having parent
        if node in defaultNodes:
            continue

        yield node

Créez un groupe dans une scène vide (ctrl+g) :

Maya empty group

print list(top_nodes())
# [u'null1']

Iterate over nodes having non-ascii characters in their names

This snippet run though every node in the current scene and detect if there is non ascii characters in node names.

import maya.cmds as mc

def non_ascii_named_nodes():

    # iterate over every node of the current scene
    for node in mc.ls('*'):

        # try to read node name in ascii...
        try:
            node.decode('ascii')
        except UnicodeEncodeError:
            # ...and return the node if its fail
            yield node

Create a null node and rename it "pâté" then run the command:

print list(non_ascii_named_nodes())
# [u'p\xe2t\xe9']

Maya can print correct values like this:

for node in non_ascii_named_nodes():
    print node
# pâté

Iterate over nodes having invalid characters in their names

import string
import maya.cmds as mc

# store every valid characters for later check
_valid_chars = set(string.ascii_letters + string.digits + '_')

def invalid_named_nodes():

    return (n for n in mc.ls('*')  # return each node
            if any((c not in _valid_chars  # having a invalid character
                    for c in n)))

And use it like this:

for invalid_node in invalid_named_nodes():
    print invalid_node
# pâté

Convert non-ascii node names to valid ascii

Following previous snippet, this one will rename every node to ensure it has a ascii complient name.

import unicodedata

import maya.cmds as mc

has_ascii = True

while has_ascii:

    for node in mc.ls('*'):
        try:
            node.decode('ascii')
        except UnicodeEncodeError:
            new_name = unicodedata.normalize('NFKD', node).encode('ascii', 'ignore')
            print "Rename non-ASCII node: ", node, "->", new_name
            mc.rename(node, new_name)
            break
    else:  # no break
        has_ascii = False

# Rename non-ASCII node:  pâté -> pate

You have to be careful as new name can potentially clash with another node.

This snippet is inspired from here.

Add a new menu

Maya add_menu

import maya.mel as mel

# get main window menu
mainMayaWindow = mel.eval('$nothing = $gMainWindow')

# top menu
menu = mc.menu('Coucou!', parent = mainMayaWindow)

mc.menuItem(label = "Another Manager", command = "print 'another manager'", parent = menu)

# optionnal: add another entry in the menu with a function instead of a string
def do_something(arg):
    print "Do something"

mc.menuItem(label = "Another Menu", command = do_something, parent = menu)

Retrive currently selected camera

It looks like there is two way to retrive currently focused camera.

import maya.cmds as mc

# method 1: get focused panel
focus_pan = mc.getPanel(withFocus=True)

# method 2: get what the playblast command consider as the active editor
focus_pan = mc.playblast(activeEditor=True)

# and finally get camera name from editor
cam = mc.modelPanel(focus_pan, query=True, camera=True)

If the focused panel is not a modelEditor, method 1 will bring to a RuntimeError when trying to retrieve camera name using modelPanel. Method 2 seems to be more reliable.

Get materials connected to a list of shapes

You maybe know hyperShade() command to select materials connected to selected objects:

>>> import maya.cmds as mc
>>> mc.hyperShade(mc.hyperShade(shaderNetworksSelectMaterialNodes=True)
>>> mc.ls(selection=True)
[u'lambert1']

But this command need UI.

Here is a version relying on connections:

>>> mshs = ['pSphereShape1']
>>> sgs = mc.listConnections(mshs, type='shadingEngine')
>>> sg_inputs = mc.listConnections(sgs, destination=False)
>>> mc.ls(sg_inputs, materials=True)
[u'lambert1']

Point to Camera space (PyMEL)

import pymel.core.datatypes as dt
import pymel.core as pm

cam = pm.PyNode("persp")
target = pm.PyNode("locator1")

# generate view projection matrix
cam_world_mtx = cam.getMatrix(worldSpace=True)
cam_world_inv_mtx = cam_world_mtx.inverse()

cam_proj_mtx = cam.projectionMatrix()

cam_vp_mtx = cam_world_inv_mtx * cam_proj_mtx

# get world space target position
target_pt = dt.Point(target.getTranslation(space='world'))

# compute projected target point
proj_pt = target_pt * cam_vp_mtx

# put in image space
proj_pt_x = (proj_pt.x / proj_pt.w) / 2.0 + 0.5
proj_pt_y = (proj_pt.y / proj_pt.w) / 2.0 + 0.5

print proj_pt_x, proj_pt_y

print proj_pt.w  # distance from camera

Get used UDIMs on given mesh node

def udim_from_meshes(meshes):

    udims = set()

    for mesh in meshes:

        try:
            uvs = cmds.getAttr('{}.uvpt[:]'.format(mesh))
        except ValueError:
            continue  # No UV on this mesh.

        # Generate UDIM number and store in to the set.
        udims |= {int(1000+(math.floor(u)+1)+(math.floor(v)*10))
                  for u, v in uvs}

    return udims

Find optionVar differences.

This code can be useful to find specific option var name:

# Gather all values.
d[v] = {k: cmds.optionVar(q=k)
        for k in cmds.optionVar(list=True)}

# ...edit global preferences...

# List different ones.
for v in cmds.optionVar(list=True):
    t = cmds.optionVar(q=v)
    if t != d[v]:
        print v, "old:", d[v], "new:", t

Maya Python Idioms

Those are idioms to do various things in Maya.

Get node name

If you want to get node name from a given node path, you can use split('|')[-1] and be sure to get the node name.

>>> '|path|to|node'.split('|')[-1]  # full node path
'node'
>>> 'path|to|node'.split('|')[-1]  # relative node path
'node'
>>> 'node'.split('|')[-1]  # even node name works
'node'

Get parent name from absolute node path

A safe way to get parent is to do:

>>> mc.listRelatives('|path|to|node', parent=True, fullPath=True)[0]
'|path|to'

The listRelatives() command return a list of parent node paths. A node can have multiple parent in case of instance (one shape, multiple parent transform nodes).

But listRelatives() is costly. If you have the garanty there is no instance in your scene and the input node path is absolute you can rely on string manipulation and use rsplit('|', 1) to cut the string on the right and get parent node:

>>> '|path|to|node'.rsplit('|', 1)[0]
'|path|to'

Get absolute node path from relative node path

Some commands can't return absolute node path. The way to get absolute node path from relative node path is:

>>> mc.ls('to|node', long=True)[0]
'|path|to|node'

Progress message during Alembic exports

# compute the number of time the current time will change (here: 100)
_time_changed_max = 100.0
_time_changed_count = 0

def _progress_msg():

    global _time_changed_count

    _time_changed_count += 1

    # compute percent value
    percent = (_time_changed_count/_time_changed_max)*100.0

    # round two decimal after the comma
    print "{:.2f}%".format(percent)

job_num = mc.scriptJob(event=['timeChanged', _progress_msg])

# ...do your export here...

mc.scriptJob(kill=job_num, force=True)

Dernière mise à jour : mar. 22 novembre 2022