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.