474 lines
17 KiB
Python
Executable File
474 lines
17 KiB
Python
Executable File
"""
|
|
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, '', '') |