Maya external file references across branches

just query the attribute containing the path, for a Texture file the path to the external file is saved as a string attribute on the file node. the attribute name is “fileTextureName”.

Hi again. So today this went from fantasy to implementation. I though I would share the love and give back some code.

What I’m doing is adding callbacks that fire a script every time the scene is saved or exported. This script will find the current project (this code is not included since I find our internal project definition and not Mayas, though they would effectively be the same) and then strips out every occurrence of that path in the .ma file. This effectively converts all absolute paths in the Maya file, that are relative to the current project, to relative paths. All other paths are not touched. Simple and über fast!

Again, had I changed the paths through Maya while the scene was loaded, I would have had to wait for the external files to re-load… Not quite as fast :slight_smile:

I’ll post the two modules involved in the next posts.

Thanks again for all your input!

Updated:

import re
import paths

def make_file_references_relative(path):
    """
    Make external file references project relative, in the last saved file

    The Maya file has to be an ASCII file and the external file
    references have to be within the current Maya project/workspace.

    D:/P4/My_Project/dev/sprint_tech//Construction/texture.tga -> Construction/texture.tga
    """

    if path:
        # Here we must check that the file is type ascii
        if path.lower().endswith(".ma"):
            try:
                f = open(path, "r")
                text = f.read()
            finally:
                f.close()
            try:
                text = remove_old_project_paths(text)
                text = remove_project_path(text)
            except:
                print "make_file_references_relative: Failed to replace paths in file!"
            try:
                f = open(path, "w")
                f.write(text)
            finally:
                f.close()

def remove_old_project_paths(text):
    """
    Remove all valid project paths in input text. Also change Maya .mb type file references to .ma type
    """
    removeDict = dict()

    # Remove old project paths (not current
    pattern = re.compile('"[^"]*"')
    for m in re.finditer(pattern, text):
        for token in ['/Construction/', '/Assembly/', '"Construction/', '"Assembly/']:
            if re.search(token, m.group()):
                tokenIndex = m.group().find(token)
                replaceString = '"' + m.group()[tokenIndex+1:].replace('.mb', '.ma').replace('.Mb', '.ma').replace('.MB', '.ma')
                if not removeDict.has_key(m.group()):
                    removeDict[str(m.group())] = replaceString
                break

    for key in removeDict.iterkeys():
        text = text.replace(key, removeDict[key])

    return text

def remove_project_path(text):
    """
    Return current project path from input text.
    """

    # Remove current project project
    projectPath = paths.project()
    removeString = '//'.join((projectPath.replace("\\", "/"), ""))
    text = text.replace(removeString, "")

    return text

Updated:

import maya.OpenMaya as OpenMaya
import repath

userCallbacks = []

class _Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Callback:
    __metaclass__ = _Singleton   
    
    def __init__(self):
        self.idList = []
        
    def add(self):
        try:
            if self not in userCallbacks:
                userCallbacks.append(self)
        except:
            pass
        
    def remove(self):
        for _id in self.idList:
            OpenMaya.MMessage.removeCallback(_id)
        try:
            userCallbacks.remove(self)
        except:
            pass
        
class Save_Relative_Callback(Callback):
    
    def __init__(self):
        Callback.__init__(self)
        self.create_callbacks()
        
    def create_callbacks(self):
        try:
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kAfterSave, make_file_references_relative_save))
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kAfterExport, make_file_references_relative_export))
        except:
            print "Failed to create callback"
        
class Last_Save_Callback(Callback):
    lastSave = None
    lastExport = None
    def __init__(self):
        Callback.__init__(self)
        self.create_callbacks()
            
    def create_callbacks(self):
        try:
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kBeforeSave, store_last_save))
            self.idList.append(OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kBeforeExport, store_last_export))
        except:
            print "Failed to create callback"
        
        
def make_file_references_relative_save(*args):
    if Last_Save_Callback.lastSave:
        repath.make_file_references_relative(Last_Save_Callback.lastSave)
    
def make_file_references_relative_export(*args):
    if Last_Save_Callback.lastExport:
        repath.make_file_references_relative(Last_Save_Callback.lastExport)

def store_last_save(*args):
    Last_Save_Callback.lastSave = OpenMaya.MFileIO_beforeSaveFilename()

def store_last_export(*args):
    Last_Save_Callback.lastExport = OpenMaya.MFileIO_beforeExportFilename()

def add(callback):
    callback.add()

Updated:

Now add something like this to your Maya start up script:

import callback
callback.add(callback.Last_Save_Callback())
callback.add(callback.Save_Relative_Callback())

That’s it for now. Pretty rough and untested code, but it should get the idea across.

I am having a similar issue with DX11 Shaders in 2013.5. The path to the .FX file is saved as an absolute path, so we are having pathing headaches getting the files to open on machines with slightly different configurations.

to make matters worse, the Technique is lost when the FX file can’t be found on load, so fixing the path up on load doesn’t restore the correct Technique (not a problem when the FX file only has one technique).

to make things even worse, the Shader will not retain an environment variable in it’s path attribute - the path is auto-expanded unlike File Nodes, which can save an environment variable to a texture file.

seems like my only option is post-save, edit the .MA replacing all shader paths with “$DX11SHADER_ROOT”

Hi,

I realized that the solution I had posed originally was rather flawed, pretty much right away. But I did not get around to updating it until now… It worked great for us until our Maya aspirations were crushed by project re-scope :slight_smile:

So to limit mis-information, please find the updated solution in the original posts.

Thanks

[QUOTE=rgkovach123;20698]I am having a similar issue with DX11 Shaders in 2013.5. The path to the .FX file is saved as an absolute path, so we are having pathing headaches getting the files to open on machines with slightly different configurations.

to make matters worse, the Technique is lost when the FX file can’t be found on load, so fixing the path up on load doesn’t restore the correct Technique (not a problem when the FX file only has one technique).

to make things even worse, the Shader will not retain an environment variable in it’s path attribute - the path is auto-expanded unlike File Nodes, which can save an environment variable to a texture file.

seems like my only option is post-save, edit the .MA replacing all shader paths with “$DX11SHADER_ROOT”[/QUOTE]

Consider using the subst command to create a virtual drive. This drive letter can be pointed at the correct location per user. This is how we deal with texture and rig references in Maya on different machines, and for different sandboxes etc.