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