Tyler Thornock
Technical Animator
Home Tutorials Tools Rigs About/Resume
Maya – Automatic Path Redirection

A common issue in production is handling file path changes, this will show you a way you can dynamically change not only the “project root” of a relative path, but also a way to dynamically lookup a path. This will prevent the need for everyone to use the same drive letter, same depot root folders, and massive batches to update renamed or moved paths. This also works for file references, textures, gpu caches, and other things that use paths.

The way this can be accomplished is via an MPxFileResolver plugin. There is one catch, everyone using files with this special pathing will need the plugin loaded on startup, otherwise file paths will fail. However, in a studio environment this is not normally an issue, and even outsourcing commonly has at least a portion of the studio tools.

NOTE: A commonly used alternative to this method is via Environment Variables per needed path, but you may hit limits for how many you define and how long the paths can be. However, you could probably have a before scene open callback that could read which variables were needed from the header and only populate the required variables… worth exploring, but I have not used this yet in production.

from maya import OpenMayaMPx


class PathResolver(OpenMayaMPx.MPxFileResolver):
    # paths that start with root:/// will be sent to the plugin to be resolved
    kPluginURIScheme = 'root'
    # unique name to prevent clashes with multiple resolvers
    kPluginResolverName = 'studio_path_resolver'

    def __init__(self):
        OpenMayaMPx.MPxFileResolver.__init__(self)

    def resolveURI(self, m_uri_path, mode):
        """
        Maya sends certain paths, based on the uriScheme, to this function to be resolved.

        :param OpenMaya.MURI m_uri_path: info maya currently has about the path
        :param int mode: MPxFileResolverMode enum, 0 just returns the path, 1 the path will be checked for existence
        :return: the resolved file path
        :rtype str
        """
        # a uri actually has multiple parts, but I just use getPath to return everything after 'root://'
        # typically, this would mean the rest is the relative path to your project path
        relative_path = m_uri_path.getPath()
        # if you want the full path, you can just do this... which could be a safer option
        # relative_path = m_uri_path.asString()

        # get your project root from your desired method... I would recommend using an environment variable
        project_root = os.environ.get('STUDIO_PROJECT_ROOT')
        if not project_root:
            # just give up and try the relative path, user isn't setup correctly or your method failed
            return relative_path

        # instead of a file path, you can use some sort of identifier that you can use to lookup the desired path
        # in this case I used /studio_asset_idx-1
        if relative_path.startswith('/studio_asset_idx-'):
            # get the asset idx
            idx = relative_path.rpartition('-')[2]
            if idx != '':
                asset_path = lookup_asset_path(idx)
                if asset_path is not None:
                    relative_path = asset_path

        # add in the project root and make sure slashes are the expected direction 
        resolved_path = os.path.join(project_root, relative_path).replace('\\', '/')

        return resolved_path

    def uriScheme(self):
        return self.kPluginURIScheme

    @staticmethod
    def creator():
        # Creator for the proxy instance 
        return OpenMayaMPx.asMPxPtr(PathResolver())
      

This placeholder “lookup_asset_path” function just needs to find the expected path, commonly from a json data file with all your assets that can be updated at any time and automatically be reflected everywhere.

def lookup_asset_path(idx, key='file_path'):
    # your assets could be defined in a json data file, where the dictionary key is the asset idx and the value is
    # another dictionary with all data about that asset
    all_assets = {'1': {'file_path': 'props/rifle/rig.mb'},
                  '2': {'file_path': 'props/pistol/rig.mb'},
                  }
    
    # if you read a json file, it will need dictionary keys as a string
    str_idx = str(idx)
    
    # look for the data or default to nothing
    data = all_assets.get(str_idx, {})
    
    # look for the key or default to None
    path = data.get(key, None)
    
    return path
    

Standard register/deregister of the plugin.

def initializePlugin(plugin):
    plugin_fn = OpenMayaMPx.MFnPlugin(plugin)
    try:
        plugin_fn.registerURIFileResolver(
            PathResolver.kPluginResolverName,
            PathResolver.kPluginURIScheme,
            PathResolver.creator
        )
    except:
        sys.stderr.write('Failed to initialize studio file resolver: {}\n'.format(PathResolver.kPluginResolverName))
        raise


def uninitializePlugin(plugin):
    plugin_fn = OpenMayaMPx.MFnPlugin(plugin)
    try:
        plugin_fn.deregisterURIFileResolver(PathResolver.kPluginResolverName)
    except:
        sys.stderr.write('Failed to un-initialize studio file resolver: {}\n'.format(PathResolver.kPluginResolverName))
        raise

File type changes should be handled automatically, but are worth thoroughly validating after this process as results may vary depending on your Maya version.

For example, references may also need a MSceneMessage.kBeforeLoadReferenceCheck callback to reresolve the file type.

def before_load_reference_check(file_object, data=None):
    resolved_path = file_object.resolvedFullName()
    file_object.overrideResolvedFullName(resolved_path, reresolveType=True)
    

Overall, this method is one of the most powerful I have found that is already built-in and (mostly) good to go. You could even go as far as managing texture paths, gpu cache paths, or any other path.