|
@@ -21,6 +21,9 @@ class Actor(DirectObject, NodePath):
|
|
|
animLoaderOptions = LoaderOptions(LoaderOptions.LFSearch |
|
|
animLoaderOptions = LoaderOptions(LoaderOptions.LFSearch |
|
|
|
LoaderOptions.LFReportErrors |
|
|
LoaderOptions.LFReportErrors |
|
|
|
LoaderOptions.LFConvertAnim)
|
|
LoaderOptions.LFConvertAnim)
|
|
|
|
|
+
|
|
|
|
|
+ validateSubparts = ConfigVariableBool('validate-subparts', True)
|
|
|
|
|
+
|
|
|
class PartDef:
|
|
class PartDef:
|
|
|
|
|
|
|
|
"""Instances of this class are stored within the
|
|
"""Instances of this class are stored within the
|
|
@@ -1078,18 +1081,89 @@ class Actor(DirectObject, NodePath):
|
|
|
else:
|
|
else:
|
|
|
Actor.notify.warning("no joint named %s!" % (jointName))
|
|
Actor.notify.warning("no joint named %s!" % (jointName))
|
|
|
|
|
|
|
|
- def getJoints(self, jointName):
|
|
|
|
|
|
|
+ def getJoints(self, partName = None, jointName = '*', lodName = None):
|
|
|
|
|
+ """ Returns the list of all joints, from the named part or
|
|
|
|
|
+ from all parts, that match the indicated jointName. The
|
|
|
|
|
+ jointName may include pattern characters like *. """
|
|
|
|
|
+
|
|
|
joints=[]
|
|
joints=[]
|
|
|
- for lod in self.__partBundleDict.values():
|
|
|
|
|
- for part in lod.values():
|
|
|
|
|
- partBundle=part.getBundle()
|
|
|
|
|
- joint=partBundle.findChild(jointName)
|
|
|
|
|
- if(joint):
|
|
|
|
|
- joints.append(joint)
|
|
|
|
|
|
|
+ pattern = GlobPattern(jointName)
|
|
|
|
|
+
|
|
|
|
|
+ if lodName == None:
|
|
|
|
|
+ # Get all LOD's
|
|
|
|
|
+ partBundleDicts = self.__partBundleDict.values()
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Get one LOD.
|
|
|
|
|
+ partBundleDict = self.__partBundleDict.get(lodName)
|
|
|
|
|
+ if not partBundleDict:
|
|
|
|
|
+ Actor.notify.warning("couldn't find lod: %s" % (lodName))
|
|
|
|
|
+ return []
|
|
|
|
|
+ partBundleDicts = [partBundleDict]
|
|
|
|
|
+
|
|
|
|
|
+ for partBundleDict in partBundleDicts:
|
|
|
|
|
+ parts = []
|
|
|
|
|
+ if partName:
|
|
|
|
|
+ subpartDef = self.__subpartDict.get(partName, None)
|
|
|
|
|
+ if not subpartDef:
|
|
|
|
|
+ # Whole part
|
|
|
|
|
+ subset = None
|
|
|
|
|
+ partDef = partBundleDict.get(partName)
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Sub-part
|
|
|
|
|
+ subset = subpartDef.subset
|
|
|
|
|
+ partDef = partBundleDict.get(subpartDef.truePartName)
|
|
|
|
|
+ if not partDef:
|
|
|
|
|
+ Actor.notify.warning("no part named %s!" % (partName))
|
|
|
|
|
+ return []
|
|
|
|
|
+ parts = [partDef]
|
|
|
|
|
+ else:
|
|
|
|
|
+ subset = None
|
|
|
|
|
+ parts = partBundleDict.values()
|
|
|
|
|
+
|
|
|
|
|
+ for partData in parts:
|
|
|
|
|
+ partBundle = partData.getBundle()
|
|
|
|
|
+
|
|
|
|
|
+ if not pattern.hasGlobCharacters() and not subset:
|
|
|
|
|
+ # The simple case.
|
|
|
|
|
+ joint = partBundle.findChild(jointName)
|
|
|
|
|
+ if joint:
|
|
|
|
|
+ joints.append(joint)
|
|
|
|
|
+ else:
|
|
|
|
|
+ # The more complex case.
|
|
|
|
|
+ isIncluded = True
|
|
|
|
|
+ if subset:
|
|
|
|
|
+ isIncluded = subset.isIncludeEmpty()
|
|
|
|
|
+ self.__getPartJoints(joints, pattern, partBundle, subset, isIncluded)
|
|
|
|
|
|
|
|
return joints
|
|
return joints
|
|
|
|
|
+
|
|
|
|
|
+ def getOverlappingJoints(self, partNameA, partNameB, jointName = '*', lodName = None):
|
|
|
|
|
+ """ Returns the set of joints, matching jointName, that are
|
|
|
|
|
+ shared between partNameA and partNameB. """
|
|
|
|
|
+ jointsA = set(self.getJoints(partName = partNameA, jointName = jointName, lodName = lodName))
|
|
|
|
|
+ jointsB = set(self.getJoints(partName = partNameB, jointName = jointName, lodName = lodName))
|
|
|
|
|
+
|
|
|
|
|
+ return jointsA & jointsB
|
|
|
|
|
+
|
|
|
|
|
+ def __getPartJoints(self, joints, pattern, partNode, subset, isIncluded):
|
|
|
|
|
+ """ Recursively walks the joint hierarchy to look for matching
|
|
|
|
|
+ joint names, implementing getJoints(). """
|
|
|
|
|
+
|
|
|
|
|
+ name = partNode.getName()
|
|
|
|
|
+ if subset:
|
|
|
|
|
+ # Constrain the traversal just to the named subset.
|
|
|
|
|
+ if subset.matchesInclude(name):
|
|
|
|
|
+ isIncluded = True
|
|
|
|
|
+ elif subset.matchesExclude(name):
|
|
|
|
|
+ isIncluded = False
|
|
|
|
|
+
|
|
|
|
|
+ if isIncluded and pattern.matches(name):
|
|
|
|
|
+ joints.append(partNode)
|
|
|
|
|
+
|
|
|
|
|
+ for child in partNode.getChildren():
|
|
|
|
|
+ self.__getPartJoints(joints, pattern, child, subset, isIncluded)
|
|
|
|
|
|
|
|
- def getJointTransform(self,partName, jointName, lodName='lodRoot'):
|
|
|
|
|
|
|
+ def getJointTransform(self, partName, jointName, lodName='lodRoot'):
|
|
|
partBundleDict=self.__partBundleDict.get(lodName)
|
|
partBundleDict=self.__partBundleDict.get(lodName)
|
|
|
if not partBundleDict:
|
|
if not partBundleDict:
|
|
|
Actor.notify.warning("no lod named: %s" % (lodName))
|
|
Actor.notify.warning("no lod named: %s" % (lodName))
|
|
@@ -1845,7 +1919,7 @@ class Actor(DirectObject, NodePath):
|
|
|
|
|
|
|
|
|
|
|
|
|
def makeSubpart(self, partName, includeJoints, excludeJoints = [],
|
|
def makeSubpart(self, partName, includeJoints, excludeJoints = [],
|
|
|
- parent="modelRoot"):
|
|
|
|
|
|
|
+ parent="modelRoot", overlapping = False):
|
|
|
|
|
|
|
|
"""Defines a new "part" of the Actor that corresponds to the
|
|
"""Defines a new "part" of the Actor that corresponds to the
|
|
|
same geometry as the named parent part, but animates only a
|
|
same geometry as the named parent part, but animates only a
|
|
@@ -1868,6 +1942,10 @@ class Actor(DirectObject, NodePath):
|
|
|
of its descendents), even if a parent joint was named by
|
|
of its descendents), even if a parent joint was named by
|
|
|
includeJoints.
|
|
includeJoints.
|
|
|
|
|
|
|
|
|
|
+ if overlapping is False, an error is raised (in the dev build)
|
|
|
|
|
+ if this subpart shares joints with any other subparts. If
|
|
|
|
|
+ overlapping is True, no such error is raised.
|
|
|
|
|
+
|
|
|
parent is the actual partName that this subpart is based
|
|
parent is the actual partName that this subpart is based
|
|
|
on."""
|
|
on."""
|
|
|
|
|
|
|
@@ -1883,6 +1961,15 @@ class Actor(DirectObject, NodePath):
|
|
|
|
|
|
|
|
self.__subpartDict[partName] = Actor.SubpartDef(parent, subset)
|
|
self.__subpartDict[partName] = Actor.SubpartDef(parent, subset)
|
|
|
|
|
|
|
|
|
|
+ if __dev__ and not overlapping and self.validateSubparts.getValue():
|
|
|
|
|
+ # Without the overlapping flag True, we're not allowed to
|
|
|
|
|
+ # define overlapping sub-parts. Verify that we haven't.
|
|
|
|
|
+ for otherPartName, otherPartDef in self.__subpartDict.items():
|
|
|
|
|
+ if otherPartName != partName and otherPartDef.truePartName == parent:
|
|
|
|
|
+ joints = self.getOverlappingJoints(partName, otherPartName)
|
|
|
|
|
+ if joints:
|
|
|
|
|
+ raise StandardError, 'Overlapping joints: %s and %s' % (partName, otherPartName)
|
|
|
|
|
+
|
|
|
def setSubpartsComplete(self, flag):
|
|
def setSubpartsComplete(self, flag):
|
|
|
|
|
|
|
|
"""Sets the subpartsComplete flag. This affects the behavior
|
|
"""Sets the subpartsComplete flag. This affects the behavior
|
|
@@ -1907,11 +1994,49 @@ class Actor(DirectObject, NodePath):
|
|
|
|
|
|
|
|
self.__subpartsComplete = flag
|
|
self.__subpartsComplete = flag
|
|
|
|
|
|
|
|
|
|
+ if __dev__ and self.__subpartsComplete and self.validateSubparts.getValue():
|
|
|
|
|
+ # If we've specified any parts at all so far, make sure we've
|
|
|
|
|
+ # specified all of them.
|
|
|
|
|
+ if self.__subpartDict:
|
|
|
|
|
+ self.verifySubpartsComplete()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def getSubpartsComplete(self):
|
|
def getSubpartsComplete(self):
|
|
|
"""See setSubpartsComplete()."""
|
|
"""See setSubpartsComplete()."""
|
|
|
|
|
|
|
|
return self.__subpartsComplete
|
|
return self.__subpartsComplete
|
|
|
|
|
|
|
|
|
|
+ def verifySubpartsComplete(self, partName = None, lodName = None):
|
|
|
|
|
+ """ Ensures that each joint is defined by at least one
|
|
|
|
|
+ subPart. Prints a warning if this is not the case. """
|
|
|
|
|
+
|
|
|
|
|
+ if partName:
|
|
|
|
|
+ assert partName not in self.__subpartDict
|
|
|
|
|
+ partNames = [partName]
|
|
|
|
|
+ else:
|
|
|
|
|
+ if lodName:
|
|
|
|
|
+ partNames = self.__partBundleDict[lodName].keys()
|
|
|
|
|
+ else:
|
|
|
|
|
+ partNames = self.__partBundleDict.values()[0].keys()
|
|
|
|
|
+
|
|
|
|
|
+ for partName in partNames:
|
|
|
|
|
+ subJoints = set()
|
|
|
|
|
+ for subPartName, subPartDef in self.__subpartDict.items():
|
|
|
|
|
+ if subPartName != partName and subPartDef.truePartName == partName:
|
|
|
|
|
+ subJoints |= set(self.getJoints(partName = subPartName, lodName = lodName))
|
|
|
|
|
+
|
|
|
|
|
+ allJoints = set(self.getJoints(partName = partName, lodName = lodName))
|
|
|
|
|
+ diff = allJoints.difference(subJoints)
|
|
|
|
|
+ for joint in list(diff):
|
|
|
|
|
+ if joint.getName() == '<skeleton>' or joint.getName() == 'morph' or isinstance(joint, PartBundle):
|
|
|
|
|
+ # We'll allow these special-case joints, which are
|
|
|
|
|
+ # usually unanimated root nodes, to remain
|
|
|
|
|
+ # uncovered without complaining.
|
|
|
|
|
+ diff.remove(joint)
|
|
|
|
|
+
|
|
|
|
|
+ if diff:
|
|
|
|
|
+ self.notify.warning('Uncovered joints: %s' % (list(diff)))
|
|
|
|
|
+
|
|
|
def loadAnims(self, anims, partName="modelRoot", lodName="lodRoot"):
|
|
def loadAnims(self, anims, partName="modelRoot", lodName="lodRoot"):
|
|
|
"""loadAnims(self, string:string{}, string='modelRoot',
|
|
"""loadAnims(self, string:string{}, string='modelRoot',
|
|
|
string='lodRoot')
|
|
string='lodRoot')
|