""" Usage: This module should only contain functionality related to working and preforming operations by namespace. Author: Ross George """ import re import unbind import pyfbsdk as mobu from RS import Globals from RS.Utils.Scene import Component def Delete(targetNamespaces, deleteNamespaces=False, additionalComponents=()): """ Deletes everything inside the passed namespace (if it exists). The MoBu version has a tendency to crash Arguments: targetNamespaces(list[string, etc.]): FBNamespace or the name of the namespace whose contents should be deleted deleteNamespace (boolean): Delete the namespace along with it's contents additionalComponents (list): other components to include in the delete """ if not isinstance(targetNamespaces, (list, tuple, set)): targetNamespaces = [targetNamespaces] components = [] components.extend(additionalComponents) for namespace in targetNamespaces: if isinstance(namespace, basestring) and not Globals.Scene.NamespaceExist(namespace): continue elif isinstance(namespace, mobu.FBNamespace) and Component.IsDestroyed(namespace): continue elif isinstance(namespace, mobu.FBNamespace): fbnamespace = namespace namespace = namespace.LongName elif isinstance(namespace, basestring): fbnamespace = Globals.Scene.NamespaceGet(namespace) for constraints in GetContentsByType(fbnamespace, mobu.FBConstraint): constraints.Active = False # Get all components under the namespace if deleteNamespaces: Globals.Scene.NamespaceDelete(namespace) if not Component.IsDestroyed(fbnamespace) and not fbnamespace.HasObjectFlags(mobu.FBObjectFlag.kFBFlagDeletable): fbnamespace.EnableObjectFlags(mobu.FBObjectFlag.kFBFlagDeletable) fbnamespace.FBDelete() Globals.Scene.NamespaceCleanup() else: Globals.Scene.NamespaceDeleteContent(namespace) if additionalComponents: Component.Delete(components, deleteNamespaces=deleteNamespaces) def Rename(targetNamespace, newNamespace): """ Renames a namespace. If the target namespace does not exist or the target Arguments: targetNamespace (string): name of the old namespace newNamespace (string): new name to give to the old namespace Return: bool """ # get namespace components targetFBNamespace = GetFBNamespace(targetNamespace) if targetFBNamespace is None or targetNamespace == newNamespace: return False # There is a bug in mobu where setting the namespace of the same object with different casing does not # get picked up. The work around is to rename the object something else first. elif targetNamespace.lower() == newNamespace.lower(): targetFBNamespace.LongName = "{}__temp__".format(targetNamespace) # now set the final correct namespace targetFBNamespace.LongName = newNamespace return targetFBNamespace.LongName == newNamespace def Merge(targetNamespace, sourceNamespaces): """ Merges the contents of the source namespaces into the target namespace. This will skip any components that already share the same name & type. The source namespaces are then deleted along with their remaining components. Arguments: targetNamespace (pyfbsdk.FBNamespace): namespace to merge other namespaces into sourceNamespaces (list): list of namespaces whose contents should be merged """ if isinstance(targetNamespace, basestring): targetNamespace = Globals.Scene.NamespaceGet(targetNamespace) if not isinstance(sourceNamespaces, (list, tuple)): sourceNamespaces = [sourceNamespaces] if not targetNamespace: return False # Map the contents of the namespace by their names contents = {} for content in GetContents(targetNamespace): types = contents.setdefault(content.Name, []) types.append(content) deleteNamespaces = [] for sourceNamespace in sourceNamespaces: if isinstance(sourceNamespace, basestring): sourceNamespace = Globals.Scene.NamespaceGet(sourceNamespace) if not sourceNamespace or sourceNamespace == targetNamespace or Component.IsDestroyed(sourceNamespace): continue Add(targetNamespace, GetAllContents(sourceNamespace), contents) deleteNamespaces.append(sourceNamespace) # Delete the namespaces and any extra components that couldn't be shared Delete(deleteNamespaces, deleteNamespaces=True) return True def Add(namespace, components, contents=None, flat=True): """ Adds components to a namespace. Prevents duplicates objects to be added in the namespace. Arguments: namespace (pyfbsdk.FBNamespace): namespace to add objects to components (list): list of FBComponents to add to the namespace contents (dictionary): dictionary of the names of contents of the namespace. Keys are the name and the values are objects of different types that share the same name. flat (boolean): ignore nested namespaces and add all objects to the top namespace when True. """ if contents is None: contents = {} for content in GetContents(namespace): types = contents.setdefault(content.Name, []) types.append(content) for component in components: if component is None or Component.IsDestroyed(component): continue elif isinstance(component, mobu.FBNamespace) and flat: continue sourceNamespace = "" if ":" in component.LongName: sourceNamespace = GetNamespace(component) # Components of different types can have the same name # here we attempt to check if any component of the same types with the same name exist types = contents.get(component.Name, []) if not types or not any([isinstance(content, type(component)) for content in types]): component.ProcessObjectNamespace(mobu.FBNamespaceAction.kFBReplaceNamespace, sourceNamespace, namespace.LongName ) def LiteRename(targetNamespace, newNamespace): """ Deprecated Arguments: targetNamespace (string): name of the old namespace newNamespace (string): new name to give to the old namespace """ Rename(targetNamespace, newNamespace) def GetComponentMatchList(SourceNamespace, TargetNamespace, TypeList=[mobu.FBPlug], DerivedTypes=True, IncludeRegex=None, ExcludeRegex=None, TypeMatch=True): """ Returns a list of component matches between components from the source and target namespaces. Matches are based off the name of the component and type. Exact type matching can be turned off. Keyword Arguments: Namespace: The namespace to search in. None will search all components. TypeList: The type to match against. DerivedTypes: True if we are also interest in the derived types of the types in the TypeList IncludeRegex: Regular expression to match to for inclusion. ExcludeRegex: Regular expression to match to for exclusion. Returns: A list of ComponentMatchObjects which pairs matched components together. """ # Get all the components in the namespaces sourceNamespaceComponents = Component.GetComponents(SourceNamespace, TypeList, DerivedTypes, IncludeRegex, ExcludeRegex) targetNamespaceComponents = Component.GetComponents(TargetNamespace, TypeList, DerivedTypes, IncludeRegex, ExcludeRegex) return Component.GetComponentMatchList(sourceNamespaceComponents, targetNamespaceComponents, TypeMatch) def CopyData(SourceNamespace, TargetNamespace, TypeList=[mobu.FBPlug], DerivedTypes=True, IncludeRegex=None, ExcludeRegex=None, TypeMatch=True): """ Copies all property data """ # Get component matches between the namespaces componentMatchList = GetComponentMatchList(SourceNamespace, TargetNamespace, TypeList, DerivedTypes, IncludeRegex, ExcludeRegex, TypeMatch ) for componentMatch in componentMatchList: # Protect against pose mismatch error # Copying data between poses crashes mobu sometimes componentMatch.CopyData() def CopyAnimationData(SourceNamespace, TargetNamespace, TypeList=[mobu.FBPlug], DerivedTypes=True, IncludeRegex=None, ExcludeRegex=None, TypeMatch=True): """ Copies all the animation data from one namespace to another on all takes and layers """ # Get component matches between the namespaces componentMatchList = GetComponentMatchList(SourceNamespace, TargetNamespace, TypeList, DerivedTypes, IncludeRegex, ExcludeRegex, TypeMatch ) # Record the current take so we can return state initialTake = Globals.System.CurrentTake # Then we'll go through each of the takes for take in Globals.Scene.Takes: Globals.System.CurrentTake = take # Get initial layer so we can return state initialLayerIndex = take.GetCurrentLayer() # Then each layer for layerIndex in range( take.GetLayerCount() ): take.SetCurrentLayer(layerIndex) # Go through each of our component matches and excute a copy for componentMatch in componentMatchList: componentMatch.CopyAnimationData() # Go back to previous layer take.SetCurrentLayer(initialLayerIndex) # Go back to previous take Globals.System.CurrentTake = initialTake class StateLocker(object): """ Locks a namespace ensuring that nothing can get added and nothing can get removed from it. Experimental - WIP. """ def __init__(self): self._namespaceMonitorSet = set() self._componentRenameDict = dict() self._revertAction = False def _OnConnectionStateNotify(self, source, event): if not self._revertAction: if event.Action is mobu.FBConnectionAction.kFBPrefixRename: nameSplit = event.Plug.LongName.rsplit(':',1) if nameSplit > 1: namespace = nameSplit[0] if namespace in self._namespaceMonitorSet: self._componentRenameDict[event.Plug] = event.Plug.LongName return if event.Action is mobu.FBConnectionAction.kFBPrefixRenamed: # Check if we should revert the rename action because the object was previously in a protected namespace if event.Plug in self._componentRenameDict: self._revertAction = True event.Plug.LongName = self._componentRenameDict[event.Plug] del self._componentRenameDict[event.Plug] self._revertAction = False return # Check if we should revert the rename action because the object was has just been put into a # protected namespace nameSplit = event.Plug.LongName.rsplit(':',1) if nameSplit > 1: namespace = nameSplit[0] if namespace in self._namespaceMonitorSet: self._revertAction = True objectName = nameSplit[1] event.Plug.LongName = objectName self._revertAction = False return def Deactivate(self): """ Deactivates the namespace state locker. """ Globals.System.OnConnectionStateNotify.Remove(self._OnConnectionStateNotify) def Activate(self): """ Activates the namespace state locker. """ Globals.System.OnConnectionStateNotify.Add(self._OnConnectionStateNotify) def Lock(self, namespace): """ Locks the passed namespace. """ self._namespaceMonitorSet.add(namespace) def Unlock(self, namespace): """ Unlocks the passed namespace. """ self._namespaceMonitorSet.remove(namespace) StateLocker = StateLocker() def GetNamespace(character): """ Gets the Namespace of the provided character Arguments: character (pyfbsdk.FBComponent): The character whose namespace you want Return: string """ if character is not None: return ":".join(character.LongName.split(":")[:-1]) def GetFBNamespace(name): return Globals.Scene.NamespaceGet(name) def GetContents(namespace): """ Gets all the contents of a namespace Arguments: namespace (pyfbsdk.FBNamespace): namespace to use Return: list """ if namespace is None or Component.IsDestroyed(namespace): return [] contentList = mobu.FBComponentList() namespace.GetContentList(contentList) return contentList def GetContentsByType(namespace, objectType=None, exactType=True): """ Gets the contents of a namespace that are of an exact type Arguments: namespace (pyfbsdk.FBNamespace): namespace to use objectType (pyfbsdk.FBComponent): class to filter contents by exactType (boolean): search for exact type Return: list """ if namespace is None or Component.IsDestroyed(namespace): return [] componentList = mobu.FBComponentList() namespace.GetContentList(componentList, mobu.FBPlugModificationFlag.kFBPlugAllContent, True, objectType.TypeInfo, exactType) return list(componentList) def GetAllContents(namespace, depth=0): """ Gets all the contents of a namespace and any child namespace it may contain Arguments: namespace (pyfbsdk.FBNamespace): namespace to use depth (int): depth of recursion Return: list """ children = [] if namespace is not None: for index in xrange(namespace.GetContentCount()): component = namespace.GetContent(index) if isinstance(component, mobu.FBNamespace): children[0:0] = GetAllContents(component, depth=depth + 1) children.append(component) return children def FindComponentInNamespace(namespace, name=None, ignoreCase=False, objectType=None, exactType=True): """ Finds a component within the namespace Arguments: namespace (pyfbsdk.FBNamespace): namespace to look under name (string): name of the object to find ignoreCase (boolean): do a case insensitive search Return: pyfbsdk.FBComponent or None """ if name is not None and exactType is None: for component in GetContents(namespace): if re.match(name, component.Name, (0, re.I)[ignoreCase]): return component elif name is not None and objectType: for component in GetContentsByType(namespace, objectType=objectType, exactType=exactType): if re.match(name, component.Name, (0, re.I)[ignoreCase]): return component def RemoveNamespaceByType(namespaceString, componentType=None): """ strip namespace from any poses found url:bugstar:4494564 """ # namespace = GetFBNamespace(namespaceString) if namespace is None or Component.IsDestroyed(namespace): return [] namespaceContent = GetContentsByType(namespace, componentType, exactType=True) for content in namespaceContent: content.ProcessObjectNamespace(mobu.FBNamespaceAction.kFBRemoveAllNamespace, '', '')