Blog

How to group actions into a single Undo Chunk in Maya.

When working with Maya scripts, there are times when we want to group multiple operations into a single undoable action. This allows users to undo a series of related changes in one step, providing a more intuitive and efficient workflow. Maya offers the cmds.undoInfo command for managing undo chunks, but improper use of the openChunk and closeChunk flags can leave the undo queue in a bad state. We always want to ensure that we properly close an undo chunk after opening it.

One inefficient way to accomplish this, is to wrap our code within a try/except/finally block to ensure that the undo chunk is always closed, even if an exception is raised. However, to further simplify the process and make our code more readable, we can create something called a "context manager" or "decorator" to handle the undo chunk management for us. The ContextDecorator class in Python allows us to use the same code as both a context manager and a decorator, providing flexibility in how we apply it to our code.

from maya import cmds
from contextlib import ContextDecorator

class Undo(ContextDecorator):
    """A context manager for managing undo chunks in Maya. When entered, it opens
    an undo chunk, and when exited, it closes the chunk. This ensures that all
    operations performed within the context are grouped into a single undoable
    action.

    Args:
        name (str, optional): The name of the undo chunk. Defaults to None."""

    def __init__(self, name=None):
        self.name = name

    def __enter__(self):
        cmds.undoInfo(openChunk=True, infinity=True, chunkName=self.name)

    def __exit__(self, exc_type, exc_value, traceback):
        cmds.undoInfo(closeChunk=True)

To use our Undo as a context manager, we can wrap our code within a with statement. This is useful if we want to wrap a block of code within an undo chunk.

def func():
    with Undo():
    ...

If you want to apply the Undo to an entire function, you can simply use as a decorator.

@Undo()
def func():
    ...