2017-08-08

Python in Maya #3: Context Manager


Building on top of Python in Maya #1: Decorators because
from contextlib import contextmanager

import pymel.core as pm


# this is the decorator from the old post
def working_unit_linear_cm(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        scene_unit = pm.currentUnit(query=True, linear=True)
        if scene_unit != 'cm':
            pm.currentUnit(linear='cm')
        try:
            result = func(*args, **kwargs)
        finally:
            if scene_unit != 'cm':
                pm.currentUnit(linear=scene_unit)
        return result
    return wrapper


# context manager version
@contextmanager
def working_unit_linear(value='cm'):
    scene_value = pm.currentUnit(query=True, linear=True)
    if scene_value != value:
        pm.currentUnit(linear=value)
    yield
    if scene_value != value:
        pm.currentUnit(linear=scene_value)


# rewriting the decorator using the new context manager
def working_unit_linear_cm(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        with working_unit_linear():
            result = func(*args, **kwargs)
        return result


# generalized currentUnit context manager
@contextmanager
def current_unit(**flags):
    different_scene_values = {}
    for k, value in flags.iteritems():
        scene_value = pm.currentUnit(query=True, **{k: True})
        if scene_value != value:
            pm.currentUnit(**{k: value})
            different_scene_values[k] = scene_value
    yield
    for k, value in different_scene_values.iteritems():
        pm.currentUnit(**{k: value})


# rewriting the first context manager
@contextmanager
def current_unit_linear(value='cm'):
    with current_unit(linear=value):
        yield
Notes
  • Only use current_unit?
  • Generalize current_unit to any command?
  • Decorator with argument examples?
  • Name working_units or current_unit?
  • Procedural programming paradigm is the "opposite", by extracting/reusing the middle code section (vs. the wrapping part)
  • The ContextDecorator can be used as decorator and context manager: https://docs.python.org/3/library/contextlib.html#contextlib.ContextDecorator

"""
More contextmanager examples
"""
from contextlib import contextmanager

import pymel.core as pm


@contextmanager
def unlocked_node(node):
    node = pm.PyNode(node)
    is_locked = node.isLocked()
    if is_locked:
        node.setLocked(False)
    yield node
    if is_locked and node.exists():
        node.setLocked(True)


@contextmanager
def unlocked_attribute(attribute):
    attribute = pm.PyNode(attribute)
    with unlocked_node(attribute.node()):
        is_locked = attribute.isLocked()
        if is_locked:
            attribute.setLocked(False)
        yield attribute
        if is_locked and attribute.exists():
            attribute.setLocked(True)


@contextmanager
def attribute_value(attribute, value):
    attribute = pm.PyNode(attribute)
    start_value = attribute.get()
    if start_value == value:
        yield attribute
        return
    with unlocked_attribute(attribute) as attr:
        if start_value != value:
            attr.set(value)
        yield attr
        if start_value != value:
            attr.set(start_value)


# TODO: generalized unlock context manager that detects attr/node?

2017-03-03

Python in Maya #2: list comprehension, izip, ...

Examples for:
  • List comprehension
  • List slicing
  • Iterate over strings
  • izip (zip iterator)

1. Basic example

"""
transform parent / child association
"""

from itertools import izip

import pymel.core as pm


# 1. PyMEL + list comprehension
transforms = ['joint1', 'joint2', 'joint3', 'joint4']
# convert strings to PyNodes
transforms = [pm.PyNode(transform) for transform in transforms]
# create list of parents
parents = [transform.getParent() for transform in transforms]
for parent, child in izip(parents, transforms):
    print('%s > %s' % (parent, child))
# None > joint1
# joint1 > joint2
# joint2 > joint3
# joint3 > joint4


# 2. Strings + list slicing
transforms = ['joint1', 'joint2', 'joint3', 'joint4']
for parent, child in izip(transforms[:-1], transforms[1:]):
    print('%s > %s' % (parent, child))
# joint1 > joint2
# joint2 > joint3
# joint3 > joint4

2. Bad example (Thanks to Viktoras Makauskas for pointing it out)

"""
Essential attribute connections for 
transform driver, driven relationship.
"""

import pymel.core as pm
driver, driven = pm.ls(selection=True, type='transform')

# 1. list comprehension 
for attribute in [at+ax for at in 'trs' for ax in 'xyz']+['ro']:
    driver.attr(attribute) >> driven.attr(attribute)

# 2. constant is better here, because it is:
#    - more readable
#    - almost same character count
transform_attributes = ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz', 'ro']
for attribute in transform_attributes:
    driver.attr(attribute) >> driven.attr(attribute)

# 3. using parent attr
# transform_attributes = ['translate', 'rotate', 'scale', 'rotateOrder']
transform_attributes = ['t', 'r', 's', 'ro']
for attribute in transform_attributes:
    driver.attr(attribute) >> driven.attr(attribute)

2017-03-06: updated

2016-11-08

Python in Maya #1: Decorators

Two examples of simple Python decorators for Maya. There are many resources online that explain decorators, so I don't want to add yet another one. I just want to demonstrate their usefulness to Maya coders that are not using them yet.

1. Preserve Maya selection (maya.cmds)

"""
Some Maya commands automatically modify the selection. 
This is often unwanted behavior. 
Sometimes there is a flag to disable that behavior. 
Other times it is possible to avoid those commands. 
But if neither is an option (or you don't want to worry about it) 
this decorator will always disable it.
"""

from functools import wraps
import maya.cmds as mc


def preserve_selection(func):
    """prevent Maya selection changes"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        selection = mc.ls(orderedSelection=True)
        result = func(*args, **kwargs)
        mc.select(clear=True)
        for each in selection:
            if mc.objExists(each):
                mc.select(each, add=True)
        return result

    return wrapper


@preserve_selection
def create_cube():
    return mc.polyCube()


create_cube()
# cube was not selected on creation

2. Run in "Settings > Working Units > Linear > centimeter" (PyMEL)

"""
Some commands do not run properly in scene working units other 
than the default centimeter. This decorator is a simple 
workaround for that problem, by temporarily setting it to 
centimeter for the duration of the wrapped function/method.
"""

from functools import wraps
import pymel.core as pm


def working_unit_linear_cm(func):
    """temporarily set working unit to cm"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        scene_unit = pm.currentUnit(query=True, linear=True)
        if scene_unit != 'cm':
            pm.currentUnit(linear='cm')
        try:
            result = func(*args, **kwargs)
        finally:
            if scene_unit != 'cm':
                pm.currentUnit(linear=scene_unit)
        return result

    return wrapper


@working_unit_linear_cm
def snap_vertex(driven_vertex, driver_vertex):
    driven_vertex = pm.PyNode(driven_vertex)
    driver_vertex = pm.PyNode(driver_vertex)
    driven_vertex.setPosition(driver_vertex.getPosition())


snap_vertex('pCube1.vtx[2]', 'pCube1.vtx[3]')
# snapped properly with non 'cm' scene_unit

Notes

  • Both examples show the good practice of using the optional, standard library, convenience function functools.wraps to not lose/overwrite the wrapped functions func.__name__, func.__doc__, ... docs: https://docs.python.org/2/library/functools.html#functools.wraps
  • Theoretically both examples would be good cases for context managers. But practically it does not seem like a good fit to me (except for unit tests): 1. preserve_selection: You should never write scripts that rely on selection changes during their execution because it makes the code harder to understand and reuse (macro style copy/paste code). 2. Working units only ever make problems when they are something other than the default cm (in my experience).
  • 2017-01-31: preserve_selection now works when a selected object gets deleted

2014-10-01

Harald rig breakdown


Harald rig breakdown from Parzival Roethlein on Vimeo.

In 2011 I worked on my last student short at Filmakademie called Harald: https://www.facebook.com/haraldfilm

I was responsible for the main character rigs (including blendshape and some meshflow modeling), animation scripts/support and animated one shot.

Final meshflow

At first I updated my PyMEL autorigger from 2010 and rigged the bodies with it. Both spine and the bendy limbs are modified ribbon setups (see older post: Joint chain rigging techniques). There are no (corrective-) blendshapes for the body, only skinning.
The faces use a standard blendshape + joint combination to match the expression sheet and allow for customization. Reverse wrap deformer for eye bulge in eyelid. Eyelid joints sliding on geometry. Rig performance had some priority and the finished rig showing the final deformation (one subdivision level) ran at over 20 fps.

I created some animation scripts for the project Maya shelf including a UI (first time using Qt) requested by the animators, which was supposed to be similar to the one they used in a previous Filmakademie short: Der Besuch

And finally I animated one shot because I always wanted to do that, even thou I am not an animator. My previous experience was very limited, so I had to learn most animation principles for the first time and it certainly was a fun exercise to use my own rigs.

2014-09-22

BabyDragon walkcycle rig breakdown


Baby Dragon walkcycle rig breakdown from Parzival Roethlein on Vimeo.

This is a personal project from early 2013.
When I saw a turntable of the dragon on Vimeo (https://vimeo.com/52581835), I contacted the modeler. And after I was done with the rig a co-worker / fellow student did the animation.

For the neck / spine / tail I used the curve/up-curve setup, described in an older post of mine, with one joint for each spike:
Joint chain rigging techniques

Credits:
Modeller/Surfacing: Angel Navarro angelnavarroart.com/
Character TD: Parzival Roethlein
Animator: Alexander Dietrich cargocollective.com/alexanderdietrich



Unrelated to this post, I recently updated an older post:
Maya naming conventions

2013-06-12

Harald Trailer

Here is the trailer for the last student short I worked on at Filmakademie. I was the Character/Animation TD and animated one shot.



Facebook page for the short:
https://www.facebook.com/haraldfilm

It recently got the SIGGRAPH 2013 Best Student Runners-Up award, yay!
https://siggraphmediablog.blogspot.de/2013/06/european-directors-win-majority-of.html
The Best Student award also went to a Filmakademie project called Rollin' Safari, all clips are online at: https://www.youtube.com/user/rollinsafari/videos

2013-05-02

Maya wrap deformer tips


Attributes:
  • After creating a wrap deformer the driver surface gets a dropoff and smoothness attribute. Usually the user can expect these attributes to be in the deformer ChannelBox/AttributeEditor. This has been changed for the wrap deformer for a case were there is one driver on multiple shapes, so the smoothness attribute gets connected to each wrap deformer. The smoothness only works with Falloff Mode: Volume and Exclusive Bind: off. As the name says it can help create smooth deformations. On the downside it moves unaffected neighbors of deformed vertices in the opposite direction, so that may be a reason not to use smoothness for some cases.
  • When enabled Exclusive Bind improves performance a lot, but may lead to bad deformation. Works especially well when driver and driven have a similar resolution. I used this setting a lot in the past.
  • I usually use Falloff Mode: Surface for smooth results, probably because my driver and driven object usually have a similar shape. The smoothness attribute requires Volume mode thou.
  • maxDistance 0.0 disables maxDistance
Use cases:
  • Deform highres geometry with easier to rig/cloth-simulate low-res geometry. For this case my tip is to output the lowres mesh into a separate mesh node and smooth/subdivide that and then use this highres version of the lowres mesh as wrap deformer driver on the actual highres mesh. The result is surprisingly fast when using exclusive bind etc and allows for much better deformation than using the wrap deformer the normal way and trying to adjust the wrap deformer attributes, which can never work properly (it's a non-barycentric binding) and gets really slow. As with many deformers in Maya, they are not very functional, but they are fast, so when making these procedural deformations you can compensate for the lack of features.
  • When the driving mesh just has a skinCluster I prefer to copy the skinCluster and weights to the highres, because it calculates faster than a wrap deformer and you also have the option of tweaking the weights further.
  • Use as partial blendshape, to be able to work on local area and with those patches drive the final one-piece mesh. Edit membership has to be used to avoid double transformation. Not a user friendly workflow. Exclusive Bind can be used to greatly improve speed (if meshflow is similar).
Example:


2013-04-23

Kool-Aid commercial: Rig breakdown


Kool-Aid commercial: Rig breakdown from Parzival Roethlein on Vimeo.

The complete spots:
https://vimeo.com/64086798
https://vimeo.com/64086797

The node order of the face, starting with the low resolution geometry:
-> blendShape
 -> skinCluster (for the mouth I put the joints on a curve with motion paths, to tweak the curve cvs without getting intersecting loops, see old post: Joint chain rigging techniques)
 -> bend deformer (To roughly match the body shape. Works for this range of motion, for more range I would recommend using a surface constraint / rivet on the nurbs surface and connect translation to UV values)
[lowres for animators]
 -> smooth (subdivide)
 -> sculpt deformer (project on nurbs surface with the same shape as the glass)
[highres, final shape]

And the highres geometry was used as a mask in compositing.

2013-04-03

Adventure Time - A Glitch is a Glitch

Yesterday aired the Adventure Time episode "A Glitch is a Glitch" (AT S5E15 AGIAG).
It was a special 3D episode (usually the show is 2D) and directed by David OReilly.
The rigging was done by Mark Feller and me at Studio Soi in July 2012. My main responsibility were the faces and spines. The faces were made by hand, some parts I could import and adjust after the first one was done. The mouth joints were sliding on a nurbs surface, that had the shape of the face polygons. For the spines I wrote a script (ribbon based. See older post: Joint chain rigging techniques).

This is a clip from the episode: