ufe  1.0
Universal Front End is a DCC-agnostic component that will allow a DCC to browse and edit data in multiple data models
UFE Concepts and Architecture

The design goal of the UFE project is to develop an independent component that allows a Digital Content Creation (DCC) application to operate on objects from different plugin run-times.

An example is to allow Maya to view and edit objects not just from its native DG (Dependency Graph), but also from plugin run-times such as Bifrost and USD.

Fundamental Concepts

  • UFE works on scene items: a scene item is a path in the scene and the associated run-time object it represents.
  • UFE is interface-based: given a scene item, we can try to create an interface from it. Interfaces provide semantics to a scene item, such as a hierarchy interface, to navigate the scene hierarchy, a scene item operations interface, to operate on an individual scene item, or a 3D transform interface", to view and edit the 3D transformation of a scene item. If a scene item does not support an interface, the resulting interface object will be null.
  • UFE is DCC-independent: the UFE component is standalone. A DCC application links against UFE. As such, UFE supports, but does not implement, application-centric services like undo / redo, which must be provided by the DCC app using UFE services.
  • A UFE-enabled DCC creates UFE interface objects on UFE scene items, then calls methods on the interface object.

    Maya API programmers will see a clear analog with MFn function sets, which are interfaces on underlying objects.

  • The implementation of the interface is provided by a plugin to the DCC. The plugin registers its UFE implementation to the Ufe::RunTimeMgr , on startup.

Architecture

UFE scene items are represented as a path through the scene graph, and the path is component of one or more path segments. Each path segment corresponds to a single run-time. Each path segment is composed of one or more path components.

  • Ufe::SceneItem : describes a run time object. In the base class, a scene item stores a path. Scene items must be implemented as derived classes by run times. Ufe::Selection and interfaces work with scene items.
  • Ufe::Path : names objects in a hierarchical way. Composed of path segments. Each path segment is in a single run-time. Viewed as a list of path components.
  • Ufe::PathSegment : that part of a UFE path that lies within a single run-time. A list of path components.
  • Ufe::PathComponent : describes the name of an object in the scene graph.
  • Ufe::Selection : an ordered list of scene items that allows for O(m) retrieval of a selection item, given a path of m components. The UFE selection has a hierarchical acceleration structure for fast path-based querying.
  • Ufe::Scene : a singleton that is a conceptual "container" of all scene items. It provides notification services when items are added or removed.

UFE interfaces are bound to UFE scene items, one scene item at a time. The expected usage of a UFE interface object is to create one, bind it to a scene item, then operate on the scene item through the interface until the operation is complete. The UFE interface can then be discarded, or bound onto another scene item. This avoids the memory-consuming "one proxy object per scene object" approach.

A scene item may optionally represent a property associated with a given scene object. A property represents data associated with a single node or object. In different run-times, it is variously known as an attribute, a component, or an underworld path, but is always associated with a single node or object. At time of writing (24-Apr-2018), UFE only supports querying the existence of such data on a scene item, using Ufe::SceneItem::isProperty() .

Implementation

The UFE standalone component defines interfaces that DCC plugin run-times must implement. When the DCC plugin run-time loads into the DCC, it registers its implementation of the UFE interfaces with the UFE run-time manager. From that point on, the UFE run-time manager will know how to create interface objects for that run-time.

  • UFE object lifescope: managed through shared pointers. This also makes Python bindings easier, as Python also uses reference counting to manage lifescope.
  • UFE implementation: in C++. UFE provides source code compatibility only, and requires that the UFE component and all components that depend on it (the DCC, the DCC run-time plugins) be compiled with the same C++ run-time and compilation options. Non binary-compatible changes in UFE require recompilation of the DCC and its run-time plugins. In practice, this is already a DCC requirement, at each major version.
  • UFE provides no thread safeness.
  • UFE scene singleton: provided by the DCC application, at initialization time, using Ufe::Scene::initializeInstance() . This allows the DCC to implement scene hooks defined in the Ufe::Scene base class, e.g. to refresh its user interface. There is no Python bindings for Ufe::Scene::initializeInstance() .

Run Time Interfaces

The UFE component defines abstract interfaces. These define semantics that concrete UFE derived class concrete interfaces must implement.

The concrete implementation of one or more interfaces for a given run-time can be either built into the DCC, or provided by a plugin to that DCC. Providing UFE support through a DCC plugin has several advantages, all related to the fact that the plugin is a separate software component:

  • It provides extensibility to support new data models in the DCC through UFE, without changing the DCC itself.
  • It reduces risk because the DCC code remains unchanged.
  • It provides flexibility in delivering the plugin or fixes to the plugin separately from the DCC.

The sequence of steps, and the software components that participate in implementing this scheme, are the following. Let us call the plugin that provides UFE support to the DCC the UFE plugin. Since this is the most flexible way of providing UFE support in a DCC, we will base our description on this approach, though DCC builtin code can follow exactly the same procedure.

  • At plugin load time, the UFE plugin registers its interface implementations with the UFE run-time manager, using Ufe::RunTimeMgr::register_() . The plugin registers one handler per interface it supports. Each handler is a singleton that will handle all interface object creation for the interface it handles. If no handler is registered by a run-time for a given interface, requesting an interface object for any scene item from that run-time will return a null pointer.
  • To view or edit an object from a UFE-enabled run-time, the DCC creates an interface object on a scene item. If the scene item supports the interface, a pointer to a valid interface object is returned. If the scene item does not support the interface, a null pointer is returned.
    Ufe::Selection selection;
    // Fill in the selection here.
    for (const auto& item : selection) {
    auto hierarchy = Ufe::Hierarchy::hierarchy(item);
    if (!hierarchy) {
    continue;
    }
    auto parent = hierarchy->parent();
    // Perform operations on parent here.
    }
  • Internally, UFE uses the handler for the requested interface to create, or bind an existing interface object to the scene item. The run-time that is used is the run-time corresponding to the ID of the final segment in the path. The run-time manager accesses the interface handler for that run-time, and asks the interface handler to return the interface object. This is hidden from the caller, but must be understood by the run-time implementer.

The following component diagram illustrates the responsibilities and dependencies of the different objects.

componentDiagram.png
Component Diagram

The following sequence diagram illustrates the flow of control between the different components and objects.

sequenceDiagram.png
Sequence Diagram

Notifications

UFE supports the Observer Pattern to allow Views in the Model-view-controller Pattern to update on changes.

Observable objects in UFE (Subjects, in Observer Pattern terminology) include an Ufe::ObservableSelection, derived from the Ufe::Selection base class, to be notified of selection changes, as well as scene items that support the Ufe::Transform3d interface, to be notified of 3D transform changes on those scene objects. To receive notifications, clients must create an Ufe::Observer object, and add it to the Subject to be observed.

It is the intention of UFE to encapsulate native run-time notifications so that UFE clients do not have to use the native run-time API to receive notifications, and can rely on UFE alone.

At time of writing (13-Mar-2018), UFE does not support notification grouping, so that multiple fine-grained notifications can be delivered to observers in a single transaction. However, this support is desired and intended to be present in a future version of UFE.

Selection

The Ufe::Selection class provides the following services and performance characteristics. Assuming a scene item with a path of m components, and a selection list that already has n scene items:

  • Order preserving: items in the list are iterated on according to their append order.
  • Uniqueness: a Ufe::SceneItem can be present in the list only once, as keyed by its Ufe::Path .
  • O(m) append: appending a scene item to the list is on average linear on the number of Ufe::PathComponent in the scene item's path.
  • O(m) remove: similar to append.
  • O(m) search: similar to append.
  • O(n) clear.
  • O(m) queries for contains ancestor or contains descendant.

Internally, each selection maintains a Trie of scene items to accelerate lookup.

Global Selection

UFE clients can create any number of Ufe::Selection objects for their needs. UFE supports the concept of a Ufe::GlobalSelection singleton, which contains an observable selection. This is intended to match DCCs which use a single, global selection as the workflow mechanism to determine what a user will view or edit. The DCC can set the Ufe::GlobalSelection observable selection using Ufe::GlobalSelection::initializeInstance() .

Undo / Redo

UFE provides support for DCC services like undo / redo. In all UFE interfaces that modify objects, each operation has two calls, one with undo capability, and one without. Providing undo capability typically involves code complexity, and using undo capability incurs run-time cost in processing and memory. Therefore, non-interactive use of an interface should use calls without undo capability.

The UFE interface calls that provide undo capability do so by supporting the Command Pattern, to allow the host application to execute and unexecute the operation.

Versioning

UFE uses the familiar semantic versioning scheme. This implies binary backwardly compatible minor versions, so that plugins and clients will be able to pickup a new UFE minor version without needing to recompile.

UFE will attempt to have mostly source code backwardly compatible major versions, so that only minor changes to existing code are necessary when updating to a new major version.

The UFE API is namespaced using the major version number. For ease of use, UFE uses a C++ using namespace declaration to present a familiar un-versioned Ufe namespace to client code, while keeping the actual symbols in the shared library versioned.

UFE provides the Ufe::VersionInfo class to retrieve versioning information. It is possible to obtain major, minor, and patch versions, as well as build number, commit SHA, branch name, and build date, both from C++ and from Python.