ufe  4.2
Universal Front End is a DCC-agnostic component that will allow a DCC to browse and edit data in multiple data models
code wrapper to control the execution of composite commands

Introduction

This service provides UFE interfaces to allow any UFE runtime to inject code to be run before an operation and after an operation. In this design, this injection is called "wrapping" and the injected code are called the "prelude" and "cleanup".

The concrete, original purpose of this UFE design, as far as Maya and MayaUSD are concerned, is to allow edit routing (selecting in which USD layer edits are written) for composite UFE commands created by Maya.

For example, the "group" Maya command, when applied to USD prims, which are seen as UFE scene items by Maya. Such grouping in Maya is a multi-steps operation involving multiple UFE command grouped inside a UFE composite command. For USD, we want to allow the user, via MayaUSD, to control the target layer used by this grouping operation.

The following classes are used in collaboration to implement the service:

  • CodeWrapper
  • CodeWrapperContainer
  • CodeWrapperContext
  • CodeWrapperHandler
  • BatchCompositeCommand

Code Wrapper Classes

CodeWrapper is an interface that wraps the execution of some other function with arbitrary prelude and cleanup virtual functions. For example, it can be used to surround the execute, undo and redo functions of a command. A concrete implementation in a derived class would choose what to do in its prelude and cleanup. A concrete CodeWrapper is created by a concrete CodeWrapperHandler.

The CodeWrapperContainer is a concrete class that hold multiple CodeWrapper, one per UFE runtime that is involved in an operation. We determine which runtimes are involved by examining the UFE selection used for the operation and by requesting a CodeWrapper from the CodeWrapperHandler of those runtimes.

These CodeWrappers that the CodeWrapperContainer holds are created by the CodeWrapperHandler. Only the CodeWrapperContainer is able to create the code wrappers. This ensures proper usage of the handler and the code wrappers it creates.

CodeWrapperContext is a RAII class: it execute code in its constructor and in its destructor. The methods that get executed are the preludes and cleanups of the code wrappers held by a CodeWrapperContainer. The CodeWrapperContext wraps the execution of other code by calling the prelude and cleanup functions of the CodeWrappers for the sub-operation. The prelude function is called in the constructor and the cleanup is called in the destructor. For example it could wrap the "undo" of a "group" command.

CodeWrapperHandler provides services to create a concrete CodeWrapper for a given runtime. The runtime receives the UFE selection that will be affected and the operation name.

The CodeWrapperHandler derives from BatchOpsHandler to allow compatibility with UFE v4 and because it affects operations on multiple scene items.

All three of these classes are entirely general and are not tied to composite commands. They receive a UFE selection plus an "operation name" and "sub-operation". They pass these information to the CodeWrapperHandler, which creates the CodeWrapper.

Composite Command Wrapping

The BatchCompositeCommand is a concrete user of the preceding three code wrapping classes. It creates CodeWrapperContext in its execute(), undo() and redo() functions to allow any runtime to add a prelude and cleanup to the composite command.

In the USD runtime, the CodeWrapperHandler creates a CodeWrapper that does edit routing in its prelude and restores the edit target in its cleanup.

The necessity of exposing both the BatchCompositeCommand and the CodeWrapperContext is to support the two coding patterns when creating a composite command:

  • Creating all sub-commands and adding them to the composite without executing them, and then executing the whole composite command.
  • Creating all sub-commands and executing them immediately before adding them to the composite command and not executing the composite command.

So, if the sub-commands are all created but not executed and are added to the composite command, then the composite command will automatically do the prelude and cleanup and thus, with this coding pattern, the context class is not needed. In pseudo-code:

auto composite = new BatchCompositeCommand(selection, "op-name")
for each sub-command:
auto subCmd = new SomeOtherUfeCommand();
composite->append(subCmd);
composite->execute()

In contrast, if the individual sub-commands are executed before adding them to the composite command, then the context class must be used in order for these individually-executed commands to be properly surrounded by the intended prelude and cleanup code. In pseudo-code, the pattern this supports is:

auto composite = new BatchCompositeCommand(selection, "op-name")
CodeWrapperContext ctx(composite->codeWrappers(), "execute");
for each sub-command:
auto subCmd = new SomeOtherUfeCommand();
subCmd->execute()
composite->append(subCmd);

Testing

Some unit test in both C++ and Python verify that the interfaces work as intended. The unit tests exercise the following classes in both C++ and Python:

  • CodeWrapper.
  • CodeWrapperContainer.
  • CodeWrapperContext.
  • CodeWrapperHandler.
  • BatchCompositeCommand.