一半君的总结纸

听话只听一半君

#118 常见的Maya里context manager和decorator的用法

有些操作常常需要先执行A,然后执行操作,再执行B

比如长时间频繁修改key,此时可以考虑把undo queue暂时禁止,或者用undo chunk功能,或者把viewport刷新禁止,不然可能会由于maya界面想刷新,造成卡顿,又比如bake之前可以考虑把viewport替换成outliner等没有3d的内容以加快速度

比如如果你想再频繁运行很多处理anim curve的命令时,把他们的undo放到一起,只按undo一次就可以撤销所有几百步操作,那可以使用undo chunk功能,一般你也许会这样用 (这是老版本的maya中的用法,因为python functions are not by default undoable with a single undo. 但是新版的Maya中似乎已经不是这样了)

import maya.cmds as mc

mc.undoInfo(openChunk=True)
# do something
mc.undoInfo(closeChunk=True)

但是每次这么写会很麻烦,而且会把这段代码贴的到处都是,所以可以这样,

  • 基本用法: context manager 方法1
    class disableundo(object):
        
        def __enter__(self):
            mc.undoInfo(stateWithoutFlush=False)
            print 'now undo is off'
    
        def __exit__(self,typ, val, traceback):
            mc.undoInfo(stateWithoutFlush=True)
            print 'now undo is on'
    
    with disableundo():
        print "do something"
    
  • 基本用法: context manager 方法2
    from contextlib import contextmanager
    
    @contextmanager
    def disableundo():
        mc.undoInfo(stateWithoutFlush=False)
        print 'now undo is off'
        yield
        mc.undoInfo(stateWithoutFlush=True)
        print 'now undo is on'
    
    with disableundo():
        print "do something"
    

    context manager只能用在代码内部,不能对整个function进行,关undo,操作,开undo的操作

  • 基本用法: decorator法

    此方法只能用在function的外面,不能用在代码内部

    from functools import wraps
    
    
    def disableundo(f):
        @wraps(f)
        def wrapper(*args, **kw):
            try:
                mc.undoInfo(stateWithoutFlush=False)
                print 'now undo is off'
                r = f(*args, **kw)
                mc.undoInfo(stateWithoutFlush=True)
                print 'now undo is on'
                return r
            except:
                raise
    
        return wrapper
    
    @disableundo
    def func():
        print "do something"
    
    func()
    
  • 综合法: context manager + decorator 二合一 方法1

    这是假设你没法装其他module,而且还是用python2.7的情况,对于用maya的我们,即使是2017了,也还是Python2.7
    用undo chunk举例,因为可以体现rollback的意义

    from functools import wraps
    import time
    import maya.cmds as mc
    
    class ContextDecorator(object):
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
    
        def __enter__(self):
            # Note: Returning self means that in "with ... as x", x will be self
            return self
    
        def __exit__(self, typ, val, traceback):
            pass
    
        def __call__(self, f, rollback=False):
            @wraps(f)
            def wrapper(*args, **kw):
                with self:
                    try:
                        return f(*args, **kw)
                    except:
                        if rollback:
                            mc.undo()
                        raise
            return wrapper
    
    class undochunk(ContextDecorator):
        def __enter__(self):
            mc.undoInfo(openChunk=True)
            return self
        def __exit__(self, typ, val, traceback):
            # Note: typ, val and traceback will only be not None
            # If an exception occured
            mc.undoInfo(closeChunk=True)
    
    # 用的时候
    with undochunk():
        mc.polyCube()
        mc.polyCube()
    
    # 或者
    
    @undochunk()
    def my_func():
        for i in range(3):
            mc.polyCube()
    
    

    似乎这样的操作在新版maya中已经不需要了,但是这种写法和下面的其他例子是一样的

  • 综合法: context manager + decorator 二合一 方法2

    context decorator是python 3.2中新加入的功能,有好几个module把他backport到了python2.7,比如contextlib2,这样就既能当context manager用,又能当decorator用

    from contextlib2 import ContextDecorator
    
    class disableundo(ContextDecorator):
    
        def __enter__(self):
            mc.undoInfo(stateWithoutFlush=False)
            print 'now undo is off'
    
        def __exit__(self, typ, val, traceback):
            mc.undoInfo(stateWithoutFlush=True)
            print 'now undo is on'
    
    
    @disableundo()
    def func():
        print "do something"
    
    with disableundo():
        print "do something"
    
  • 综合法: context manager + decorator 二合一 方法3

    奥义法,不改已经写好的用了@contextmanager的旧代码,出自这里

    from decorator import decorator, FunctionMaker
    from contextlib import GeneratorContextManager
    
    class GeneratorCM(GeneratorContextManager):
        def __call__(self, func):
            return FunctionMaker.create(
            func, "with _cm_: return _func_(%(shortsignature)s)",
            dict(_cm_=self, _func_=func), __wrapped__=func)
    
    @decorator
    def contextmanager(func, *args, **kwds):
        return GeneratorCM(func(*args, **kwds))
    
    
    @contextmanager
    def before_after():
        print 'before' 
        yield
        print 'after' 
    
    
    @before_after()
    def hello(user):
        print 'hello', user
    
    
    def hello(user):
        with before_after():
            print 'hello', user
    

也许经常会用到的context manager们

  • 禁止undo,进行操作,再打开undo
    class disableundo(ContextDecorator):
        def __enter__(self):
            mc.undoInfo(stateWithoutFlush=False)
            return self
        def __exit__(self, typ, val, traceback):
            mc.undoInfo(stateWithoutFlush=True)
    
  • 禁止刷新
    class disablerefresh(ContextDecorator):
        def __enter__(self):
            mc.refresh(suspend=True)
            return self
        def __exit__(self, typ, val, traceback):
            mc.refresh(suspend=False)
            # force refresh
            mc.refresh(suspend=False)
    
  • 禁止viewport,比如换成outliner或者node editor这种不费资源的
    class disableviewport(ContextDecorator):
        def __enter__(self):
            mm.eval('setNamedPanelLayout "Single Perspective View";')
            model_panel = next( ( x for x in mc.getPanel(vis=1) if x.startswith('modelPanel')),None)
            mm.eval('outlinerPanel -e -rp %s outlinerPanel1;' % model_panel)
            return self
        def __exit__(self, typ, val, traceback):
            mm.eval('PreviousViewArrangement;')
    
  • 保存当前选择
    class saveselection(ContextDecorator):
        def __enter__(self):
            self._sel = mc.ls(sl=1)
            return self
        def __exit__(self, typ, val, traceback):
            mc.select(self._sel,r=1)
    
  • 进行操作前后启用和禁用relative namepsace
    class relativenamespace(ContextDecorator):
        def __enter__(self):
            if not mc.namespace(ex=self.namespace):
                mc.namespace(add=self.namespace)
            mc.namespace(set=self.namespace)
            mc.namespace(rel=1)
            return self
        def __exit__(self, typ, val, traceback):
            mc.namespace(set=':')
            mc.namespace(rel=0)
    
  • 得到import文件的时候所有新创建的节点,这个啥时候会用到? 比如动画组为了保持镜头的连续性,经常从别的shot import做好了的char rig,一般的做法是先放到一个临时的namespace里,再看有没有重名的,没有重就去掉namespace, 根据具体命令规范,把import进来的东西放到不同的组下面去,这个如果又要自动,又要有扩展性,不是那么容易写好的,但想写类似这个工具的第一步是得知道哪些nodes是新import进来的对吧.
    class capturenewnodes(ContextDecorator):
    
        def __init__(self, **kwargs):
            super(capturenewnodes, self).__init__(**kwargs)
            self._id = None
            self._nodes = []
    
        def __enter__(self):
            if not self._id:
                self._id = OM.MDGMessage.addNodeAddedCallback(
                    self.node_added, 'dependNode')
            return self
    
        def __exit__(self, typ, val, traceback):
            if self._id:
                OM.MDGMessage.removeCallback(self._id)
                self._id = None
    
        def node_added(self, node, *args):
            self._nodes.append(OM.MObjectHandle(node))
    
        def get_nodes(self, handle=False, full_path=False):
            self._nodes = filter(lambda x: x.isValid(), self._nodes)
            nodes = self._nodes
            if not handle:
                nodes = []
                for n in self._nodes:
                    mObj = n.object()
                    if mObj.hasFn(OM.MFn.kDagNode):
                        fnNode = OM.MFnDagNode(mObj)
    
                        if full_path:
                            path = fnNode.fullPathName()
                        else:
                            path = fnNode.partialPathName()
    
                    elif mObj.hasFn(OM.MFn.kDependencyNode):
                        path = OM.MFnDependencyNode(mObj).name()
    
                    nodes.append(path)
    
            return nodes
    

考虑到把上面的decorators放在一个package里

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

上面__init__.py放成这样的好处是,如果你把排在sys.path后面路径里的package里的部分文件放到一个排在sys.path前面的路径里,依然可以正常使用,举个例子,有个公司通用的package ABC, 在show xyz里我们要改他里面的某个文件,此时不用把整个package全拷到show的路径里,只需要拷想改的module即可

update:把常用的几个放到了github,在这里 ogTools
参考:
Python in Maya – How to make commands repeatable and undoable
Introduction to the Maya API
Safely using destructors in Python
Python Decorators vs. Context Managers: Have your cake and eat it!
A better contextlib.contextmanager

Advertisements

One response to “#118 常见的Maya里context manager和decorator的用法

  1. 上官云恒 六月 9, 2016 @ 6:09 下午

    屌 有点看不懂

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: