Exporting with Referenced Namespaces

Simplistically, a namespace in Maya is a string prefix added to a node name, used to partition objects into virtual containers, defining a named collection. See the official docs for the nitty-gritty.

Referenced scenes leverage namespacing not only to isolate scene contents, but to avoid potential name clashes against existing scene nodes. These partitions allow entire scenes to be loaded, unloaded, reloaded, hot swapped, and other sorts of file wizardry.


A Namespace Refresher

Maya's root namespace is specified as :, is assumed and always present, but not displayed. All nodes in a scene belong to the root namespace, period. Newly created namespaces are child of the root namespace, and are accessed using a colin selector to identify nesting levels. IE: :nesting1:nesting2:nestingN with the initial colin being the root.

A locator created in the root namespace is identified by its full path of :locator1, which is synonymous for the abridged path, or "shortName", locator1. If the same locator was created inside a user-generated namespace of "superAwesome", the locator would be identified as :superAwesome:locator1, and displayed as superAwesome:locator1. Moving superAwesome:locator1 to the root is as simple as renaming the node to :locator1, which then displays as locator. Our superAwesome: namespace still exists within the scene, and can be deleted if empty or used to partition additional nodes.


The Problem

My experience dealing with many third party exporters is a general lack of namespace removal support. Scenes containing references and exported with namespaces in tact typically result in data containing unexpected prefix strings. Depending on the engine and implementation, your data may be completely unusable in this current state, and someone will have to resolve naming conflicts.

Unless you work for a company having access to exporter source code, you will need to implement a pre-export process to strip namespaces from nodes before passing them along to the exporter. Otherwise its a simple process of requesting a new exporter feature.

Assuming this burden is on you, why not just move referenced nodes to the root namespace by renaming as outlined above? Because Maya flags referenced nodes read-only.


Common Workarounds

So how do we by-pass the inherent read-only restriction accompanying references?

One workaround is to import scene references, permitting write access to the associated namespace. Then strip namespaces through the renaming process outlined above or by completely removing the namespace altogether. This is certainly valid, however:

  • Importing scene references is a permanent and destructive process which cannot be undone
  • The time spent in the save/process/reload loop greatly reduces rapid iteration
  • Complex scenes become a burden to export, deterring artistic experimentation

Another workaround is to generate a dummy hierarchy from the original source, used only for exporting, placed directly in the root namespace. I dislike this approach even greater:

  • Debugging bad exports now involves validation of the export hierarchy and creation process
  • Creating mock environments eliminates the notion of "what you see is what you get"
  • Implementations can become a technical burden to scale and maintain

My Solution

Once again we call upon OpenMaya to save the day. Maya API rules differ from the scripted environment, granting permission to rename those pesky read-only nodes. Armed with knowledge and an a simple context manager, temporary namespace removal becomes a one line wrapper above an export call. This solution is retains the original integrity of the scene, and doesn't require file reloading to undo changes, encouraging rapid iteration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import maya.cmds as cmds
import maya.api.OpenMaya as api2


class StripNamespace(object):
    """
    Context manager use to temporarily strip a namespace from all dependency nodes within a namespace.

    This allows nodes to masquerade as if they never had namespace, including those considered read-only
    due to file referencing.

    Usage:

        with StripNamespace('someNamespace') as stripped_nodes:
            print cmds.ls(stripped_nodes)
    """

    @classmethod
    def as_name(cls, uuid):
        """
        Convenience method to extract the name from uuid

        :type uuid: basestring
        :rtype: unicode|None
        """
        names = cmds.ls(uuid)
        return names[0] if names else None

    def __init__(self, namespace):
        if cmds.namespace(exists=namespace):
            self.original_names = {}  # (UUID, name_within_namespace)
            self.namespace = cmds.namespaceInfo(namespace, fn=True)
        else:
            raise ValueError('Could not locate supplied namespace, "{0}"'.format(namespace))

    def __enter__(self):
        for absolute_name in cmds.namespaceInfo(self.namespace, listOnlyDependencyNodes=True, fullName=True):

            # Ensure node was *not* auto-renamed (IE: shape nodes)
            if cmds.objExists(absolute_name):

                # get an api handle to the node
                api_obj = api2.MGlobal.getSelectionListByName(absolute_name).getDependNode(0)
                api_node = api2.MFnDependencyNode(api_obj)

                # remember the original name to return upon exit
                uuid = api_node.uuid().asString()
                self.original_names[uuid] = api_node.name()

                # strip namespace by renaming via api, bypassing read-only restrictions
                without_namespace = api_node.name().replace(self.namespace, '')
                api_node.setName(without_namespace)

        return [self.as_name(uuid) for uuid in self.original_names]

    def __exit__(self, exc_type, exc_val, exc_tb):
        for uuid, original_name in self.original_names.iteritems():
            current_name = self.as_name(uuid)
            api_obj = api2.MGlobal.getSelectionListByName(current_name).getDependNode(0)
            api_node = api2.MFnDependencyNode(api_obj)
            api_node.setName(original_name)