"""Actor module: contains the Actor class""" __all__ = ['Actor'] from pandac.PandaModules import * from direct.showbase.DirectObject import DirectObject from pandac.PandaModules import LODNode import types class Actor(DirectObject, NodePath): """ Actor class: Contains methods for creating, manipulating and playing animations on characters """ notify = directNotify.newCategory("Actor") partPrefix = "__Actor_" modelLoaderOptions = LoaderOptions(LoaderOptions.LFSearch | LoaderOptions.LFReportErrors | LoaderOptions.LFConvertSkeleton) animLoaderOptions = LoaderOptions(LoaderOptions.LFSearch | LoaderOptions.LFReportErrors | LoaderOptions.LFConvertAnim) class PartDef: """Instances of this class are stored within the PartBundleDict to track all of the individual PartBundles associated with the Actor. In general, each separately loaded model file is a different PartBundle. This can include the multiple different LOD's, as well as the multiple different pieces of a multipart Actor. """ def __init__(self, partBundle, partModel): # We also save the ModelRoot node along with the # PartBundle, so that the reference count in the ModelPool # will be accurate. self.partBundle = partBundle self.partModel = partModel def __repr__(self): return 'Actor.PartDef(%s, %s)' % (repr(self.partBundle), repr(self.partModel)) class AnimDef: """Instances of this class are stored within the AnimControlDict to track all of the animations associated with the Actor. This includes animations that have already been bound (these have a valid AnimControl) as well as those that have not yet been bound (for these, self.animControl is None). There is a different AnimDef for each different part or sub-part, times each different animation in the AnimDict. """ def __init__(self, filename): self.filename = filename self.animModel = None self.animControl = None def makeCopy(self): return Actor.AnimDef(self.filename) def __repr__(self): return 'Actor.AnimDef(%s)' % (repr(self.filename)) class SubpartDef: """Instances of this class are stored within the SubpartDict to track the existance of arbitrary sub-parts. These are designed to appear to the user to be identical to true "part" of a multi-part Actor, but in fact each subpart represents a subset of the joints of an existing part (which is accessible via a different name). """ def __init__(self, truePartName, subset = PartSubset()): self.truePartName = truePartName self.subset = subset def makeCopy(self): return Actor.SubpartDef(self.truePartName, PartSubset(self.subset)) def __repr__(self): return 'Actor.SubpartDef(%s, %s)' % (repr(self.truePartName), repr(self.subset)) def __init__(self, models=None, anims=None, other=None, copy=1, lodNode = None): """__init__(self, string | string:string{}, string:string{} | string:(string:string{}){}, Actor=None) Actor constructor: can be used to create single or multipart actors. If another Actor is supplied as an argument this method acts like a copy constructor. Single part actors are created by calling with a model and animation dictionary (animName:animPath{}) as follows: a = Actor("panda-3k.egg", {"walk":"panda-walk.egg" \ "run":"panda-run.egg"}) This could be displayed and animated as such: a.reparentTo(render) a.loop("walk") a.stop() Multipart actors expect a dictionary of parts and a dictionary of animation dictionaries (partName:(animName:animPath{}){}) as below: a = Actor( # part dictionary {"head":"char/dogMM/dogMM_Shorts-head-mod", \ "torso":"char/dogMM/dogMM_Shorts-torso-mod", \ "legs":"char/dogMM/dogMM_Shorts-legs-mod"}, \ # dictionary of anim dictionaries {"head":{"walk":"char/dogMM/dogMM_Shorts-head-walk", \ "run":"char/dogMM/dogMM_Shorts-head-run"}, \ "torso":{"walk":"char/dogMM/dogMM_Shorts-torso-walk", \ "run":"char/dogMM/dogMM_Shorts-torso-run"}, \ "legs":{"walk":"char/dogMM/dogMM_Shorts-legs-walk", \ "run":"char/dogMM/dogMM_Shorts-legs-run"} \ }) In addition multipart actor parts need to be connected together in a meaningful fashion: a.attach("head", "torso", "joint-head") a.attach("torso", "legs", "joint-hips") # # ADD LOD COMMENT HERE! # Other useful Actor class functions: #fix actor eye rendering a.drawInFront("joint-pupil?", "eyes*") #fix bounding volumes - this must be done after drawing #the actor for a few frames, otherwise it has no effect a.fixBounds() """ try: self.Actor_initialized return except: self.Actor_initialized = 1 # initialize our NodePath essence NodePath.__init__(self) self.__autoCopy = copy # create data structures self.__partBundleDict = {} self.__subpartDict = {} self.__sortedLODNames = [] self.__animControlDict = {} self.__controlJoints = {} self.__subpartsComplete = False self.__LODNode = None self.switches = None if (other == None): # act like a normal constructor # create base hierarchy self.gotName = 0 root = ModelNode('actor') root.setPreserveTransform(1) self.assign(NodePath(root)) self.setGeomNode(self.attachNewNode(ModelNode('actorGeom'))) self.__hasLOD = 0 # load models # # four cases: # # models, anims{} = single part actor # models{}, anims{} = single part actor w/ LOD # models{}, anims{}{} = multi-part actor # models{}{}, anims{}{} = multi-part actor w/ LOD # # make sure we have models if (models): # do we have a dictionary of models? if (type(models)==type({})): # if this is a dictionary of dictionaries if (type(models[models.keys()[0]]) == type({})): # then it must be a multipart actor w/LOD self.setLODNode(node = lodNode) # preserve numerical order for lod's # this will make it easier to set ranges sortedKeys = models.keys() sortedKeys.sort() for lodName in sortedKeys: # make a node under the LOD switch # for each lod (just because!) self.addLOD(str(lodName)) # iterate over both dicts for modelName in models[lodName].keys(): self.loadModel(models[lodName][modelName], modelName, lodName, copy = copy) # then if there is a dictionary of dictionaries of anims elif (type(anims[anims.keys()[0]])==type({})): # then this is a multipart actor w/o LOD for partName in models.keys(): # pass in each part self.loadModel(models[partName], partName, copy = copy) else: # it is a single part actor w/LOD self.setLODNode(node = lodNode) # preserve order of LOD's sortedKeys = models.keys() sortedKeys.sort() for lodName in sortedKeys: self.addLOD(str(lodName)) # pass in dictionary of parts self.loadModel(models[lodName], lodName=lodName, copy = copy) else: # else it is a single part actor self.loadModel(models, copy = copy) # load anims # make sure the actor has animations if (anims): if (len(anims) >= 1): # if so, does it have a dictionary of dictionaries? if (type(anims[anims.keys()[0]])==type({})): # are the models a dict of dicts too? if (type(models)==type({})): if (type(models[models.keys()[0]]) == type({})): # then we have a multi-part w/ LOD sortedKeys = models.keys() sortedKeys.sort() for lodName in sortedKeys: # iterate over both dicts for partName in anims.keys(): self.loadAnims( anims[partName], partName, lodName) else: # then it must be multi-part w/o LOD for partName in anims.keys(): self.loadAnims(anims[partName], partName) elif (type(models)==type({})): # then we have single-part w/ LOD sortedKeys = models.keys() sortedKeys.sort() for lodName in sortedKeys: self.loadAnims(anims, lodName=lodName) else: # else it is single-part w/o LOD self.loadAnims(anims) else: self.copyActor(other, True) # overwrite everything # For now, all Actors will by default set their top bounding # volume to be the "final" bounding volume: the bounding # volumes below the top volume will not be tested. If a cull # test passes the top bounding volume, the whole Actor is # rendered. # We do this partly because an Actor is likely to be a fairly # small object relative to the scene, and is pretty much going # to be all onscreen or all offscreen anyway; and partly # because of the Character bug that doesn't update the # bounding volume for pieces that animate away from their # original position. It's disturbing to see someone's hands # disappear; better to cull the whole object or none of it. self.__geomNode.node().setFinal(1) def delete(self): try: self.Actor_deleted return except: self.Actor_deleted = 1 self.cleanup() def copyActor(self, other, overwrite=False): # act like a copy constructor self.gotName = other.gotName # copy the scene graph elements of other if (overwrite): otherCopy = other.copyTo(NodePath()) otherCopy.detachNode() # assign these elements to ourselve (overwrite) self.assign(otherCopy) else: # just copy these to ourselve otherCopy = other.copyTo(self) self.setGeomNode(otherCopy.getChild(0)) # copy the part dictionary from other self.__copyPartBundles(other) self.__copySubpartDict(other) self.__subpartsComplete = other.__subpartsComplete # copy the anim dictionary from other self.__copyAnimControls(other) # copy the switches for lods self.switches = other.switches self.__LODNode = self.find('**/+LODNode') self.__hasLOD = 0 if (not self.__LODNode.isEmpty()): self.__hasLOD = 1 def __cmp__(self, other): # Actor inherits from NodePath, which inherits a definition of # __cmp__ from FFIExternalObject that uses the NodePath's # compareTo() method to compare different NodePaths. But we # don't want this behavior for Actors; Actors should only be # compared pointerwise. A NodePath that happens to reference # the same node is still different from the Actor. if self is other: return 0 else: return 1 def __str__(self): """ Actor print function """ return "Actor %s, parts = %s, LODs = %s, anims = %s" % \ (self.getName(), self.getPartNames(), self.getLODNames(), self.getAnimNames()) def listJoints(self, partName="modelRoot", lodName="lodRoot"): """Handy utility function to list the joint hierarchy of the actor. """ partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.error("no lod named: %s" % (lodName)) subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName)) partDef = partBundleDict.get(subpartDef.truePartName) if partDef == None: Actor.notify.error("no part named: %s" % (partName)) self.__doListJoints(0, partDef.partBundle.node().getBundle(), subset.isIncludeEmpty(), subpartDef.subset) def __doListJoints(self, indentLevel, part, isIncluded, subset): name = part.getName() if subset.matchesInclude(name): isIncluded = True elif subset.matchesExclude(name): isIncluded = False if isIncluded: value = '' if hasattr(part, 'outputValue'): lineStream = LineStream.LineStream() part.outputValue(lineStream) value = lineStream.getLine() print ' ' * indentLevel, part.getName(), value for i in range(part.getNumChildren()): self.__doListJoints(indentLevel + 2, part.getChild(i), isIncluded, subset) def getActorInfo(self): """ Utility function to create a list of information about an actor. Useful for iterating over details of an actor. """ lodInfo = [] for lodName in self.__animControlDict.keys(): partDict = self.__animControlDict[lodName] partInfo = [] for partName in partDict.keys(): truePartName = self.__subpartDict.get(partName, [partName])[0] partBundle = self.__partBundleDict[lodName][truePartName].partBundle animDict = partDict[partName] animInfo = [] for animName in animDict.keys(): file = animDict[animName].filename animControl = animDict[animName].animControl animInfo.append([animName, file, animControl]) partInfo.append([partName, partBundle, animInfo]) lodInfo.append([lodName, partInfo]) return lodInfo def getAnimNames(self): animNames = [] for lodName, lodInfo in self.getActorInfo(): for partName, bundle, animInfo in lodInfo: for animName, file, animControl in animInfo: if animName not in animNames: animNames.append(animName) return animNames def pprint(self): """ Pretty print actor's details """ for lodName, lodInfo in self.getActorInfo(): print 'LOD:', lodName for partName, bundle, animInfo in lodInfo: print ' Part:', partName print ' Bundle:', `bundle` for animName, file, animControl in animInfo: print ' Anim:', animName print ' File:', file if animControl == None: print ' (not loaded)' else: print (' NumFrames: %d PlayRate: %0.2f' % (animControl.getNumFrames(), animControl.getPlayRate())) def cleanup(self): """ Actor cleanup function """ self.stop(None) self.flush() self.__geomNode.removeNode() if not self.isEmpty(): self.removeNode() def flush(self): """ Actor flush function """ self.__partBundleDict = {} self.__subpartDict = {} self.__sortedLODNames = [] self.__animControlDict = {} self.__controlJoints = {} if self.__LODNode and (not self.__LODNode.isEmpty()): self.__LODNode.removeNode() self.__LODNode = None self.__hasLOD = 0 # accessing def getAnimControlDict(self): return self.__animControlDict def getPartBundleDict(self): return self.__partBundleDict def __updateSortedLODNames(self): # Cache the sorted LOD names so we dont have to grab them # and sort them every time somebody asks for the list self.__sortedLODNames = self.__partBundleDict.keys() # Reverse sort the doing a string->int def sortFunc(x, y): if not str(x).isdigit(): smap = {'h':3, 'm':2, 'l':1, 'f':0} """ sx = smap.get(x[0],None) sy = smap.get(y[0],None) if sx is None: self.notify.error('Invalid lodName: %s' % x) if sy is None: self.notify.error('Invalid lodName: %s' % y) """ return cmp(smap[y[0]], smap[x[0]]) else: return cmp (int(y), int(x)) self.__sortedLODNames.sort(sortFunc) def getLODNames(self): """ Return list of Actor LOD names. If not an LOD actor, returns 'lodRoot' Caution - this returns a reference to the list - not your own copy """ return self.__sortedLODNames def getPartNames(self): """ Return list of Actor part names. If not an multipart actor, returns 'modelRoot' NOTE: returns parts of arbitrary LOD """ return self.__partBundleDict.values()[0].keys() + self.__subpartDict.keys() def getGeomNode(self): """ Return the node that contains all actor geometry """ return self.__geomNode def setGeomNode(self, node): """ Set the node that contains all actor geometry """ self.__geomNode = node def getLODNode(self): """ Return the node that switches actor geometry in and out""" return self.__LODNode.node() def setLODNode(self, node=None): """ Set the node that switches actor geometry in and out. If one is not supplied as an argument, make one """ if (node == None): node = LODNode("lod") self.__LODNode = self.__geomNode.attachNewNode(node) self.__hasLOD = 1 self.switches = {} def useLOD(self, lodName): """ Make the Actor ONLY display the given LOD """ # make sure we don't call this twice in a row # and pollute the the switches dictionary sortedKeys = self.switches.keys() sortedKeys.sort() index = sortedKeys.index(lodName) self.__LODNode.node().forceSwitch(index) ## self.resetLOD() ## # store the data in the switches for later use ## sortedKeys = self.switches.keys() ## for eachLod in sortedKeys: ## index = sortedKeys.index(eachLod) ## # set the switches to not display ever ## self.__LODNode.node().setSwitch(index, 0, 10000) ## # turn the given LOD on 'always' ## index = sortedKeys.index(lodName) ## self.__LODNode.node().setSwitch(index, 10000, 0) def printLOD(self): sortedKeys = self.switches.keys() sortedKeys.sort() for eachLod in sortedKeys: print "python switches for %s: in: %d, out %d" % (eachLod, self.switches[eachLod][0], self.switches[eachLod][1]) switchNum = self.__LODNode.node().getNumSwitches() for eachSwitch in range(0, switchNum): print "c++ switches for %d: in: %d, out: %d" % (eachSwitch, self.__LODNode.node().getIn(eachSwitch), self.__LODNode.node().getOut(eachSwitch)) def resetLOD(self): """ Restore all switch distance info (usually after a useLOD call)""" self.__LODNode.node().clearForceSwitch() ## sortedKeys = self.switches.keys() ## sortedKeys.sort() ## for eachLod in sortedKeys: ## index = sortedKeys.index(eachLod) ## self.__LODNode.node().setSwitch(index, self.switches[eachLod][0], ## self.switches[eachLod][1]) def addLOD(self, lodName, inDist=0, outDist=0, center=None): """addLOD(self, string) Add a named node under the LODNode to parent all geometry of a specific LOD under. """ self.__LODNode.attachNewNode(str(lodName)) # save the switch distance info self.switches[lodName] = [inDist, outDist] # add the switch distance info self.__LODNode.node().addSwitch(inDist, outDist) if center != None: self.__LODNode.node().setCenter(center) def setLOD(self, lodName, inDist=0, outDist=0): """setLOD(self, string) Set the switch distance for given LOD """ # save the switch distance info self.switches[lodName] = [inDist, outDist] # add the switch distance info sortedKeys = self.switches.keys() sortedKeys.sort() index = sortedKeys.index(lodName) self.__LODNode.node().setSwitch(index, inDist, outDist) def getLOD(self, lodName): """getLOD(self, string) Get the named node under the LOD to which we parent all LOD specific geometry to. Returns 'None' if not found """ lod = self.__LODNode.find("**/" + str(lodName)) if lod.isEmpty(): return None else: return lod def hasLOD(self): """ Return 1 if the actor has LODs, 0 otherwise """ return self.__hasLOD def setCenter(self, center): if center != None: self.__LODNode.node().setCenter(center) def update(self, lod=0): lodnames = self.getLODNames() if (lod < len(lodnames)): partDefs = self.__partBundleDict[lodnames[lod]].values() for partDef in partDefs: # print "updating: %s" % (partBundle.node()) partDef.partBundle.node().updateToNow() else: self.notify.warning('update() - no lod: %d' % lod) def getFrameRate(self, animName=None, partName=None): """getFrameRate(self, string, string=None) Return actual frame rate of given anim name and given part. If no anim specified, use the currently playing anim. If no part specified, return anim durations of first part. NOTE: returns info only for an arbitrary LOD """ lodName = self.__animControlDict.keys()[0] controls = self.getAnimControls(animName, partName) if len(controls) == 0: return None return controls[0].getFrameRate() def getBaseFrameRate(self, animName=None, partName=None): """getBaseFrameRate(self, string, string=None) Return frame rate of given anim name and given part, unmodified by any play rate in effect. """ lodName = self.__animControlDict.keys()[0] controls = self.getAnimControls(animName, partName) if len(controls) == 0: return None return controls[0].getAnim().getBaseFrameRate() def getPlayRate(self, animName=None, partName=None): """ Return the play rate of given anim for a given part. If no part is given, assume first part in dictionary. If no anim is given, find the current anim for the part. NOTE: Returns info only for an arbitrary LOD """ # use the first lod lodName = self.__animControlDict.keys()[0] controls = self.getAnimControls(animName, partName) if len(controls) == 0: return None return controls[0].getPlayRate() def setPlayRate(self, rate, animName, partName=None): """setPlayRate(self, float, string, string=None) Set the play rate of given anim for a given part. If no part is given, set for all parts in dictionary. It used to be legal to let the animName default to the currently-playing anim, but this was confusing and could lead to the wrong anim's play rate getting set. Better to insist on this parameter. NOTE: sets play rate on all LODs""" for control in self.getAnimControls(animName, partName): control.setPlayRate(rate) def getDuration(self, animName=None, partName=None, fromFrame=None, toFrame=None): """ Return duration of given anim name and given part. If no anim specified, use the currently playing anim. If no part specified, return anim duration of first part. NOTE: returns info for arbitrary LOD """ lodName = self.__animControlDict.keys()[0] controls = self.getAnimControls(animName, partName) if len(controls) == 0: return None animControl = controls[0] if fromFrame is None: fromFrame = 0 if toFrame is None: toFrame = animControl.getNumFrames()-1 return ((toFrame+1)-fromFrame) / animControl.getFrameRate() def getNumFrames(self, animName=None, partName=None): lodName = self.__animControlDict.keys()[0] controls = self.getAnimControls(animName, partName) if len(controls) == 0: return None return controls[0].getNumFrames() def getFrameTime(self, anim, frame, partName=None): numFrames = self.getNumFrames(anim,partName) animTime = self.getDuration(anim,partName) frameTime = animTime * float(frame) / numFrames return frameTime def getCurrentAnim(self, partName=None): """ Return the anim currently playing on the actor. If part not specified return current anim of an arbitrary part in dictionary. NOTE: only returns info for an arbitrary LOD """ lodName, animControlDict = self.__animControlDict.items()[0] if partName == None: partName, animDict = animControlDict.items()[0] else: animDict = animControlDict.get(partName) if animDict == None: # part was not present Actor.notify.warning("couldn't find part: %s" % (partName)) return None # loop through all anims for named part and find if any are playing for animName, anim in animDict.items(): if isinstance(anim.animControl, AnimControl) and anim.animControl.isPlaying(): return animName # we must have found none, or gotten an error return None def getCurrentFrame(self, animName=None, partName=None): """ Return the current frame number of the anim current playing on the actor. If part not specified return current anim of first part in dictionary. NOTE: only returns info for an arbitrary LOD """ lodName, animControlDict = self.__animControlDict.items()[0] if partName == None: partName, animDict = animControlDict.items()[0] else: animDict = animControlDict.get(partName) if animDict == None: # part was not present Actor.notify.warning("couldn't find part: %s" % (partName)) return None # loop through all anims for named part and find if any are playing for animName, anim in animDict.items(): if isinstance(anim.animControl, AnimControl) and anim.animControl.isPlaying(): return anim.animControl.getFrame() # we must have found none, or gotten an error return None # arranging def getPart(self, partName, lodName="lodRoot"): """ Find the named part in the optional named lod and return it, or return None if not present """ partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return None truePartName = self.__subpartDict.get(partName, [partName])[0] partDef = partBundleDict.get(truePartName) if partDef != None: return partDef.partBundle return None def removePart(self, partName, lodName="lodRoot"): """ Remove the geometry and animations of the named part of the optional named lod if present. NOTE: this will remove child geometry also! """ # find the corresponding part bundle dict partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return # remove the part if (partBundleDict.has_key(partName)): partBundleDict[partName].partBundle.removeNode() del(partBundleDict[partName]) # find the corresponding anim control dict partDict = self.__animControlDict.get(lodName) if not partDict: Actor.notify.warning("no lod named: %s" % (lodName)) return # remove the animations if (partDict.has_key(partName)): del(partDict[partName]) def hidePart(self, partName, lodName="lodRoot"): """ Make the given part of the optionally given lod not render, even though still in the tree. NOTE: this will affect child geometry """ partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return partDef = partBundleDict.get(partName) if partDef: partDef.partBundle.hide() else: Actor.notify.warning("no part named %s!" % (partName)) def showPart(self, partName, lodName="lodRoot"): """ Make the given part render while in the tree. NOTE: this will affect child geometry """ partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return partDef = partBundleDict.get(partName) if partDef: partDef.partBundle.show() else: Actor.notify.warning("no part named %s!" % (partName)) def showAllParts(self, partName, lodName="lodRoot"): """ Make the given part and all its children render while in the tree. NOTE: this will affect child geometry """ partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return partDef = partBundleDict.get(partName) if partDef: partDef.partBundle.show() partDef.partBundle.getChildren().show() else: Actor.notify.warning("no part named %s!" % (partName)) def exposeJoint(self, node, partName, jointName, lodName="lodRoot", localTransform = 0): """exposeJoint(self, NodePath, string, string, key="lodRoot") Starts the joint animating the indicated node. As the joint animates, it will transform the node by the corresponding amount. This will replace whatever matrix is on the node each frame. The default is to expose the net transform from the root, but if localTransform is true, only the node's local transform from its parent is exposed.""" partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return None truePartName = self.__subpartDict.get(partName, [partName])[0] partDef = partBundleDict.get(truePartName) if partDef: bundle = partDef.partBundle.node().getBundle() else: Actor.notify.warning("no part named %s!" % (partName)) return None # Get a handle to the joint. joint = bundle.findChild(jointName) if node == None: node = self.attachNewNode(jointName) if (joint): if localTransform: joint.addLocalTransform(node.node()) else: joint.addNetTransform(node.node()) else: Actor.notify.warning("no joint named %s!" % (jointName)) return node def stopJoint(self, partName, jointName, lodName="lodRoot"): """stopJoint(self, string, string, key="lodRoot") Stops the joint from animating external nodes. If the joint is animating a transform on a node, this will permanently stop it. However, this does not affect vertex animations.""" partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return None truePartName = self.__subpartDict.get(partName, [partName])[0] partDef = partBundleDict.get(truePartName) if partDef: bundle = partDef.partBundle.node().getBundle() else: Actor.notify.warning("no part named %s!" % (partName)) return None # Get a handle to the joint. joint = bundle.findChild(jointName) if (joint): joint.clearNetTransforms() joint.clearLocalTransforms() else: Actor.notify.warning("no joint named %s!" % (jointName)) def controlJoint(self, node, partName, jointName, lodName="lodRoot"): """controlJoint(self, NodePath, string, string, key="lodRoot") The converse of exposeJoint: this associates the joint with the indicated node, so that the joint transform will be copied from the node to the joint each frame. This can be used for programmer animation of a particular joint at runtime. This must be called before any animations are played. Once an animation has been loaded and bound to the character, it will be too late to add a new control during that animation. """ partBundleDict = self.__partBundleDict.get(lodName) if not partBundleDict: Actor.notify.warning("no lod named: %s" % (lodName)) return None truePartName = self.__subpartDict.get(partName, [partName])[0] partDef = partBundleDict.get(truePartName) if part: bundle = partDef.partBundle.node().getBundle() else: Actor.notify.warning("no part named %s!" % (partName)) return None joint = bundle.findChild(jointName) if joint == None: Actor.notify.warning("no joint named %s!" % (jointName)) return None if node == None: node = self.attachNewNode(jointName) if joint.getType().isDerivedFrom(MovingPartMatrix.getClassType()): node.setMat(joint.getInitialValue()) # Store a dictionary of jointName: node to list the controls # requested for joints. The controls will actually be applied # later, when we load up the animations in bindAnim(). if self.__controlJoints.has_key(bundle.this): self.__controlJoints[bundle.this][jointName] = node else: self.__controlJoints[bundle.this] = { jointName: node } return node def instance(self, path, partName, jointName, lodName="lodRoot"): """instance(self, NodePath, string, string, key="lodRoot") Instance a nodePath to an actor part at a joint called jointName""" partBundleDict = self.__partBundleDict.get(lodName) if partBundleDict: truePartName = self.__subpartDict.get(partName, [partName])[0] partDef = partBundleDict.get(truePartName) if partDef: joint = partDef.partBundle.find("**/" + jointName) if (joint.isEmpty()): Actor.notify.warning("%s not found!" % (jointName)) else: return path.instanceTo(joint) else: Actor.notify.warning("no part named %s!" % (partName)) else: Actor.notify.warning("no lod named %s!" % (lodName)) def attach(self, partName, anotherPartName, jointName, lodName="lodRoot"): """attach(self, string, string, string, key="lodRoot") Attach one actor part to another at a joint called jointName""" partBundleDict = self.__partBundleDict.get(lodName) if partBundleDict: truePartName = self.__subpartDict.get(partName, [partName])[0] partDef = partBundleDict.get(truePartName) if partDef: anotherPartDef = partBundleDict.get(anotherPartName) if anotherPartDef: joint = anotherPartDef.partBundle.find("**/" + jointName) if (joint.isEmpty()): Actor.notify.warning("%s not found!" % (jointName)) else: partDef.partBundle.reparentTo(joint) else: Actor.notify.warning("no part named %s!" % (anotherPartName)) else: Actor.notify.warning("no part named %s!" % (partName)) else: Actor.notify.warning("no lod named %s!" % (lodName)) def drawInFront(self, frontPartName, backPartName, mode, root=None, lodName=None): """drawInFront(self, string, int, string=None, key=None) Arrange geometry so the frontPart(s) are drawn in front of backPart. If mode == -1, the geometry is simply arranged to be drawn in the correct order, assuming it is already under a direct-render scene graph (like the DirectGui system). That is, frontPart is reparented to backPart, and backPart is reordered to appear first among its siblings. If mode == -2, the geometry is arranged to be drawn in the correct order, and depth test/write is turned off for frontPart. If mode == -3, frontPart is drawn as a decal onto backPart. This assumes that frontPart is mostly coplanar with and does not extend beyond backPart, and that backPart is mostly flat (not self-occluding). If mode > 0, the frontPart geometry is placed in the 'fixed' bin, with the indicated drawing order. This will cause it to be drawn after almost all other geometry. In this case, the backPartName is actually unused. Takes an optional argument root as the start of the search for the given parts. Also takes optional lod name to refine search for the named parts. If root and lod are defined, we search for the given root under the given lod. """ # check to see if we are working within an lod if lodName != None: # find the named lod node lodRoot = self.find("**/" + str(lodName)) if root == None: # no need to look further root = lodRoot else: # look for root under lod root = lodRoot.find("**/" + root) else: # start search from self if no root and no lod given if root == None: root = self frontParts = root.findAllMatches("**/" + frontPartName) if mode > 0: # Use the 'fixed' bin instead of reordering the scene # graph. numFrontParts = frontParts.getNumPaths() for partNum in range(0, numFrontParts): frontParts[partNum].setBin('fixed', mode) return if mode == -2: # Turn off depth test/write on the frontParts. numFrontParts = frontParts.getNumPaths() for partNum in range(0, numFrontParts): frontParts[partNum].setDepthWrite(0) frontParts[partNum].setDepthTest(0) # Find the back part. backPart = root.find("**/" + backPartName) if (backPart.isEmpty()): Actor.notify.warning("no part named %s!" % (backPartName)) return if mode == -3: # Draw as a decal. backPart.node().setEffect(DecalEffect.make()) else: # Reorder the backPart to be the first of its siblings. backPart.reparentTo(backPart.getParent(), -1) #reparent all the front parts to the back part frontParts.reparentTo(backPart) def fixBounds(self, part=None): """fixBounds(self, nodePath=None) Force recomputation of bounding spheres for all geoms in a given part. If no part specified, fix all geoms in this actor """ # if no part name specified fix all parts if (part==None): part = self # update all characters first charNodes = part.findAllMatches("**/+Character") numCharNodes = charNodes.getNumPaths() for charNum in range(0, numCharNodes): (charNodes.getPath(charNum)).node().update() # for each geomNode, iterate through all geoms and force update # of bounding spheres by marking current bounds as stale geomNodes = part.findAllMatches("**/+GeomNode") numGeomNodes = geomNodes.getNumPaths() for nodeNum in range(0, numGeomNodes): thisGeomNode = geomNodes.getPath(nodeNum) numGeoms = thisGeomNode.node().getNumGeoms() for geomNum in range(0, numGeoms): thisGeom = thisGeomNode.node().getGeom(geomNum) thisGeom.markBoundsStale() assert Actor.notify.debug("fixing bounds for node %s, geom %s" % \ (nodeNum, geomNum)) thisGeomNode.node().markInternalBoundsStale() def showAllBounds(self): """ Show the bounds of all actor geoms """ geomNodes = self.__geomNode.findAllMatches("**/+GeomNode") numGeomNodes = geomNodes.getNumPaths() for nodeNum in range(0, numGeomNodes): geomNodes.getPath(nodeNum).showBounds() def hideAllBounds(self): """ Hide the bounds of all actor geoms """ geomNodes = self.__geomNode.findAllMatches("**/+GeomNode") numGeomNodes = geomNodes.getNumPaths() for nodeNum in range(0, numGeomNodes): geomNodes.getPath(nodeNum).hideBounds() # actions def animPanel(self): from direct.showbase import TkGlobal from direct.tkpanels import AnimPanel return AnimPanel.AnimPanel(self) def stop(self, animName=None, partName=None): """stop(self, string=None, string=None) Stop named animation on the given part of the actor. If no name specified then stop all animations on the actor. NOTE: stops all LODs""" for control in self.getAnimControls(animName, partName): control.stop() def play(self, animName, partName=None, fromFrame=None, toFrame=None): """play(self, string, string=None) Play the given animation on the given part of the actor. If no part is specified, try to play on all parts. NOTE: plays over ALL LODs""" if fromFrame == None: for control in self.getAnimControls(animName, partName): control.play() else: for control in self.getAnimControls(animName, partName): if toFrame == None: control.play(fromFrame, control.getNumFrames() - 1) else: control.play(fromFrame, toFrame) def loop(self, animName, restart=1, partName=None, fromFrame=None, toFrame=None): """loop(self, string, int=1, string=None) Loop the given animation on the given part of the actor, restarting at zero frame if requested. If no part name is given then try to loop on all parts. NOTE: loops on all LOD's """ if fromFrame == None: for control in self.getAnimControls(animName, partName): control.loop(restart) else: for control in self.getAnimControls(animName, partName): if toFrame == None: control.loop(restart, fromFrame, control.getNumFrames() - 1) else: control.loop(restart, fromFrame, toFrame) def pingpong(self, animName, restart=1, partName=None, fromFrame=None, toFrame=None): """pingpong(self, string, int=1, string=None) Loop the given animation on the given part of the actor, restarting at zero frame if requested. If no part name is given then try to loop on all parts. NOTE: loops on all LOD's""" if fromFrame == None: fromFrame = 0 for control in self.getAnimControls(animName, partName): if toFrame == None: control.pingpong(restart, fromFrame, control.getNumFrames() - 1) else: control.pingpong(restart, fromFrame, toFrame) def pose(self, animName, frame, partName=None, lodName=None): """pose(self, string, int, string=None) Pose the actor in position found at given frame in the specified animation for the specified part. If no part is specified attempt to apply pose to all parts.""" for control in self.getAnimControls(animName, partName, lodName): control.pose(frame) def setBlend(self, animBlend = None, frameBlend = None, blendType = None, partName = None): """ Changes the way the Actor handles blending of multiple different animations, and/or interpolation between consecutive frames. The animBlend and frameBlend parameters are boolean flags. You may set either or both to True or False. If you do not specify them, they do not change from the previous value. When animBlend is True, multiple different animations may simultaneously be playing on the Actor. This means you may call play(), loop(), or pose() on multiple animations and have all of them contribute to the final pose each frame. In this mode (that is, when animBlend is True), starting a particular animation with play(), loop(), or pose() does not implicitly make the animation visible; you must also call setControlEffect() for each animation you wish to use to indicate how much each animation contributes to the final pose. The frameBlend flag is unrelated to playing multiple animations. It controls whether the Actor smoothly interpolates between consecutive frames of its animation (when the flag is True) or holds each frame until the next one is ready (when the flag is False). The default value of frameBlend is controlled by the interpolate-frames Config.prc variable. In either case, you may also specify blendType, which controls the precise algorithm used to blend two or more different matrix values into a final result. Different skeleton hierarchies may benefit from different algorithms. The default blendType is controlled by the anim-blend-type Config.prc variable. """ bundles = [] for lodName, bundleDict in self.__partBundleDict.items(): if partName == None: for partDef in bundleDict.values(): bundles.append(partDef.partBundle.node().getBundle()) else: truePartName = self.__subpartDict.get(partName, [partName])[0] partDef = bundleDict.get(truePartName) if partDef != None: bundles.append(partDef.partBundle.node().getBundle()) else: Actor.notify.warning("Couldn't find part: %s" % (partName)) for bundle in bundles: if blendType != None: bundle.setBlendType(blendType) if animBlend != None: bundle.setAnimBlendFlag(animBlend) if frameBlend != None: bundle.setFrameBlendFlag(frameBlend) def enableBlend(self, blendType = PartBundle.BTNormalizedLinear, partName = None): """ Enables blending of multiple animations simultaneously. After this is called, you may call play(), loop(), or pose() on multiple animations and have all of them contribute to the final pose each frame. With blending in effect, starting a particular animation with play(), loop(), or pose() does not implicitly make the animation visible; you must also call setControlEffect() for each animation you wish to use to indicate how much each animation contributes to the final pose. This method is deprecated. You should use setBlend() instead. """ self.setBlend(animBlend = True, blendType = blendType, partName = partName) def disableBlend(self, partName = None): """ Restores normal one-animation-at-a-time operation after a previous call to enableBlend(). This method is deprecated. You should use setBlend() instead. """ self.setBlend(animBlend = False, partName = partName) def setControlEffect(self, animName, effect, partName = None, lodName = None): """ Sets the amount by which the named animation contributes to the overall pose. This controls blending of multiple animations; it only makes sense to call this after a previous call to setBlend(animBlend = True). """ for control in self.getAnimControls(animName, partName, lodName): control.getPart().setControlEffect(control, effect) def getAnimControl(self, animName, partName, lodName="lodRoot"): """getAnimControl(self, string, string, string="lodRoot") Search the animControl dictionary indicated by lodName for a given anim and part. Return the animControl if present, or None otherwise """ partDict = self.__animControlDict.get(lodName) # if this assertion fails, named lod was not present assert partDict != None animDict = partDict.get(partName) if animDict == None: # part was not present Actor.notify.warning("couldn't find part: %s" % (partName)) else: anim = animDict.get(animName) if anim == None: # anim was not present assert Actor.notify.debug("couldn't find anim: %s" % (animName)) pass else: # bind the animation first if we need to if not isinstance(anim.animControl, AnimControl): self.__bindAnimToPart(animName, partName, lodName) return anim.animControl return None def getAnimControls(self, animName=None, partName=None, lodName=None): """getAnimControls(self, string, string=None, string=None) Returns a list of the AnimControls that represent the given animation for the given part and the given lod. If animName is omitted, the currently-playing animation (or all currently-playing animations) is returned. If partName is omitted, all parts are returned (or possibly the one overall Actor part, according to the subpartsComplete flag). If lodName is omitted, all LOD's are returned. """ if partName == None and self.__subpartsComplete: # If we have the __subpartsComplete flag, and no partName # is specified, it really means to play the animation on # all subparts, not on the overall Actor. partName = self.__subpartDict.keys() controls = [] # build list of lodNames and corresponding animControlDicts # requested. if lodName == None: # Get all LOD's animControlDictItems = self.__animControlDict.items() else: partDict = self.__animControlDict.get(lodName) if partDict == None: Actor.notify.warning("couldn't find lod: %s" % (lodName)) animControlDictItems = [] else: animControlDictItems = [(lodName, partDict)] for lodName, partDict in animControlDictItems: # Now, build the list of partNames and the corresponding # animDicts. if partName == None: # Get all main parts, but not sub-parts. animDictItems = [] for thisPart, animDict in partDict.items(): if not self.__subpartDict.has_key(thisPart): animDictItems.append((thisPart, animDict)) else: # Get exactly the named part or parts. if isinstance(partName, types.StringTypes): partNameList = [partName] else: partNameList = partName animDictItems = [] for pName in partNameList: animDict = partDict.get(pName) if animDict == None: # Maybe it's a subpart that hasn't been bound yet. subpartDef = self.__subpartDict.get(pName) if subpartDef: animDict = {} partDict[pName] = animDict if animDict == None: # part was not present Actor.notify.warning("couldn't find part: %s" % (pName)) else: animDictItems.append((pName, animDict)) if animName == None: # get all playing animations for thisPart, animDict in animDictItems: for anim in animDict.values(): if isinstance(anim.animControl, AnimControl) and anim.animControl.isPlaying(): controls.append(anim.animControl) else: # get the named animation only. for thisPart, animDict in animDictItems: anim = animDict.get(animName) if anim == None and partName != None: for pName in partNameList: # Maybe it's a subpart that hasn't been bound yet. subpartDef = self.__subpartDict.get(pName) if subpartDef: truePartName = subpartDef.truePartName anim = partDict[truePartName].get(animName) if anim: anim = anim.makeCopy() animDict[animName] = anim if anim == None: # anim was not present assert Actor.notify.debug("couldn't find anim: %s" % (animName)) pass else: # bind the animation first if we need to animControl = anim.animControl if animControl == None: animControl = self.__bindAnimToPart(animName, thisPart, lodName) if animControl: controls.append(animControl) return controls def loadModel(self, modelPath, partName="modelRoot", lodName="lodRoot", copy = 1): """loadModel(self, string, string="modelRoot", string="lodRoot", bool = 0) Actor model loader. Takes a model name (ie file path), a part name(defaults to "modelRoot") and an lod name(defaults to "lodRoot"). If copy is set to 0, do a loadModel instead of a loadModelCopy. """ assert partName not in self.__subpartDict assert Actor.notify.debug("in loadModel: %s, part: %s, lod: %s, copy: %s" % \ (modelPath, partName, lodName, copy)) if isinstance(modelPath, NodePath): # If we got a NodePath instead of a string, use *that* as # the model directly. if (copy): model = modelPath.copyTo(NodePath()) else: model = modelPath else: # otherwise, we got the name of the model to load. loaderOptions = self.modelLoaderOptions if not copy: # If copy = 0, then we should always hit the disk. loaderOptions = LoaderOptions(loaderOptions) loaderOptions.setFlags(loaderOptions.getFlags() & ~LoaderOptions.LFNoRamCache) # Pass loaderOptions to specify that we want to # get the skeleton model. This only matters to model # files (like .mb) for which we can choose to extract # either the skeleton or animation, or neither. model = loader.loadModel(modelPath, loaderOptions = loaderOptions) if (model == None): raise StandardError, "Could not load Actor model %s" % (modelPath) if (model.node().isOfType(PartBundleNode.getClassType())): bundle = model else: bundle = model.find("**/+PartBundleNode") if (bundle.isEmpty()): Actor.notify.warning("%s is not a character!" % (modelPath)) model.reparentTo(self.__geomNode) else: # Maybe the model file also included some animations. If # so, try to bind them immediately and put them into the # animControlDict. acc = AnimControlCollection() autoBind(model.node(), acc, ~0) numAnims = acc.getNumAnims() # Now extract out the PartBundleNode and integrate it with # the Actor. self.__prepareBundle(bundle, model, partName, lodName) if numAnims != 0: # If the model had some animations, store them in the # dict so they can be played. Actor.notify.info("model contains %s animations." % (numAnims)) # make sure this lod is in anim control dict self.__animControlDict.setdefault(lodName, {}) self.__animControlDict[lodName].setdefault(partName, {}) for i in range(numAnims): animControl = acc.getAnim(i) animName = acc.getAnimName(i) # Now we've already bound the animation, but we # have no associated filename. So store the # animControl, but put None in for the filename. self.__animControlDict[lodName][partName][animName] = [None, animControl] def __prepareBundle(self, bundle, model, partName="modelRoot", lodName="lodRoot"): assert partName not in self.__subpartDict # Rename the node at the top of the hierarchy, if we # haven't already, to make it easier to identify this # actor in the scene graph. if not self.gotName: self.node().setName(bundle.node().getName()) self.gotName = 1 # we rename this node to make Actor copying easier bundle.node().setName(Actor.partPrefix + partName) if (self.__partBundleDict.has_key(lodName) == 0): # make a dictionary to store these parts in needsDict = 1 bundleDict = {} else: needsDict = 0 if (lodName!="lodRoot"): # parent to appropriate node under LOD switch bundle.reparentTo(self.__LODNode.find("**/" + str(lodName))) else: bundle.reparentTo(self.__geomNode) if (needsDict): bundleDict[partName] = Actor.PartDef(bundle, model.node()) self.__partBundleDict[lodName] = bundleDict self.__updateSortedLODNames() else: self.__partBundleDict[lodName][partName] = Actor.PartDef(bundle, model.node()) def makeSubpart(self, partName, includeJoints, excludeJoints = [], parent="modelRoot"): """Defines a new "part" of the Actor that corresponds to the same geometry as the named parent part, but animates only a certain subset of the joints. This can be used for partial-body animations, for instance to animate a hand waving while the rest of the body continues to play its walking animation. includeJoints is a list of joint names that are to be animated by the subpart. Each name can include globbing characters like '?' or '*', which will match one or any number of characters, respectively. Including a joint by naming it in includeJoints implicitly includes all of the descendents of that joint as well, except for excludeJoints, below. excludeJoints is a list of joint names that are *not* to be animated by the subpart. As in includeJoints, each name can include globbing characters. If a joint is named by excludeJoints, it will not be included (and neither will any of its descendents), even if a parent joint was named by includeJoints. parent is the actual partName that this subpart is based on.""" assert partName not in self.__subpartDict subpartDef = self.__subpartDict.get(parent, Actor.SubpartDef('')) subset = PartSubset(subpartDef.subset) for name in includeJoints: subset.addIncludeJoint(GlobPattern(name)) for name in excludeJoints: subset.addExcludeJoint(GlobPattern(name)) self.__subpartDict[partName] = Actor.SubpartDef(parent, subset) def setSubpartsComplete(self, flag): """Sets the subpartsComplete flag. This affects the behavior of play(), loop(), stop(), etc., when no explicit parts are specified. When this flag is False (the default), play() with no parts means to play the animation on the overall Actor, which is a separate part that overlaps each of the subparts. If you then play a different animation on a subpart, it may stop the overall animation (in non-blend mode) or blend with it (in blend mode). When this flag is True, play() with no parts means to play the animation on each of the subparts--instead of on the overall Actor. In this case, you may then play a different animation on a subpart, which replaces only that subpart's animation. It makes sense to set this True when the union of all of your subparts completely defines the entire Actor. """ self.__subpartsComplete = flag def getSubpartsComplete(self): """See setSubpartsComplete().""" return self.__subpartsComplete def loadAnims(self, anims, partName="modelRoot", lodName="lodRoot"): """loadAnims(self, string:string{}, string='modelRoot', string='lodRoot') Actor anim loader. Takes an optional partName (defaults to 'modelRoot' for non-multipart actors) and lodName (defaults to 'lodRoot' for non-LOD actors) and dict of corresponding anims in the form animName:animPath{} """ assert Actor.notify.debug("in loadAnims: %s, part: %s, lod: %s" % (anims, partName, lodName)) for animName, filename in anims.items(): # make sure this lod is in anim control dict self.__animControlDict.setdefault(lodName, {}) self.__animControlDict[lodName].setdefault(partName, {}) # store the file path only; we will bind it (and produce # an AnimControl) when it is played self.__animControlDict[lodName][partName][animName] = Actor.AnimDef(filename) def unloadAnims(self, anims, partName="modelRoot", lodName="lodRoot"): """unloadAnims(self, string:string{}, string='modelRoot', string='lodRoot') Actor anim unloader. Takes an optional partName (defaults to 'modelRoot' for non-multipart actors) and lodName (defaults to 'lodRoot' for non-LOD actors) and dict of corresponding anims in the form animName:animPath{}. Deletes the anim control for the given animation and parts/lods. """ assert Actor.notify.debug("in unloadAnims: %s, part: %s, lod: %s" % (anims, partName, lodName)) if (lodName == None): lodNames = self.__animControlDict.keys() else: lodNames = [lodName] if (partName == None): if len(lodNames) > 0: partNames = self.__animControlDict[lodNames[0]].keys() else: partNames = [] else: partNames = [partName] if (anims==None): if len(lodNames) > 0 and len(partNames) > 0: anims = self.__animControlDict[lodNames[0]][partNames[0]].keys() else: anims = [] for lodName in lodNames: for partName in partNames: for animName in anims: # delete the anim control try: animDef = self.__animControlDict[lodName][partName][animName] if animDef.animControl != None: # Try to clear any control effects before we let # our handle on them go. This is especially # important if the anim control was blending # animations. animDef.animControl.getPart().clearControlEffects() animDef.animControl = None except: return def bindAnim(self, animName, partName="modelRoot", lodName="lodRoot"): """bindAnim(self, string, string='modelRoot', string='lodRoot') Bind the named animation to the named part and lod """ if lodName == None: lodNames = self.__animControlDict.keys() else: lodNames = [lodName] # loop over all lods for thisLod in lodNames: if partName == None: partNames = self.__partBundleDict[thisLod].keys() else: partNames = [partName] # loop over all parts for thisPart in partNames: ac = self.__bindAnimToPart(animName, thisPart, thisLod) def __bindAnimToPart(self, animName, partName, lodName): """ for internal use only! """ # make sure this anim is in the dict subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName)) partDict = self.__animControlDict[lodName] animDict = partDict.get(partName) if animDict == None: # It must be a subpart that hasn't been bound yet. animDict = {} partDict[partName] = animDict anim = animDict.get(animName) if anim == None: # It must be a subpart that hasn't been bound yet. anim = partDict[subpartDef.truePartName].get(animName) anim = anim.makeCopy() animDict[animName] = anim if anim == None: Actor.notify.error("actor has no animation %s", animName) # only bind if not already bound! if anim.animControl: return anim.animControl # fetch a copy from the modelPool, or if we weren't careful # enough to preload, fetch from disk animPath = anim.filename loaderOptions = self.animLoaderOptions if not self.__autoCopy: # If copy = 0, then we should always hit the disk. loaderOptions = LoaderOptions(loaderOptions) loaderOptions.setFlags(loaderOptions.getFlags() & ~LoaderOptions.LFNoRamCache) animNode = loader.loadModel(animPath, loaderOptions = loaderOptions) if animNode == None: return None animBundle = (animNode.find("**/+AnimBundleNode").node()).getBundle() bundle = self.__partBundleDict[lodName][subpartDef.truePartName].partBundle.node().getBundle() # Are there any controls requested for joints in this bundle? # If so, apply them. assert Actor.notify.debug('actor bundle %s, %s'% (bundle,bundle.this)) controlDict = self.__controlJoints.get(bundle.this, None) if controlDict: for jointName, node in controlDict.items(): if node: joint = animBundle.makeChildDynamic(jointName) if joint: joint.setValueNode(node.node()) else: Actor.notify.error("controlled joint %s is not present" % jointName) # bind anim animControl = bundle.bindAnim(animBundle, -1, subpartDef.subset) if (animControl == None): Actor.notify.error("Null AnimControl: %s" % (animName)) else: # store the animControl anim.animControl = animControl assert Actor.notify.debug("binding anim: %s to part: %s, lod: %s" % (animName, partName, lodName)) return animControl def __copyPartBundles(self, other): """__copyPartBundles(self, Actor) Copy the part bundle dictionary from another actor as this instance's own. NOTE: this method does not actually copy geometry """ for lodName in other.__partBundleDict.keys(): self.__partBundleDict[lodName] = {} self.__updateSortedLODNames() # find the lod Asad if lodName == 'lodRoot': partLod = self else: partLod = self.find("**/" + lodName) if partLod.isEmpty(): Actor.notify.warning("no lod named: %s" % (lodName)) return None for partName, partDef in other.__partBundleDict[lodName].items(): model = partDef.partModel.copySubgraph() # find the part in our tree partBundle = partLod.find("**/" + Actor.partPrefix + partName) if (partBundle != None): # store the part bundle self.__partBundleDict[lodName][partName] = Actor.PartDef(partBundle, model) else: Actor.notify.error("lod: %s has no matching part: %s" % (lodName, partName)) def __copySubpartDict(self, other): """Copies the subpartDict from another as this instance's own. This makes a deep copy of the map and all of the names and PartSubset objects within it. We can't use copy.deepcopy() because of the included C++ PartSubset objects.""" self.__subpartDict = {} for partName, subpartDef in other.__subpartDict.items(): subpartDefCopy = subpartDef if subpartDef: subpartDef = subpartDef.makeCopy() self.__subpartDict[partName] = subpartDef def __copyAnimControls(self, other): """__copyAnimControls(self, Actor) Get the anims from the anim control's in the anim control dictionary of another actor. Bind these anim's to the part bundles in our part bundle dict that have matching names, and store the resulting anim controls in our own part bundle dict""" for lodName in other.__animControlDict.keys(): self.__animControlDict[lodName] = {} for partName in other.__animControlDict[lodName].keys(): self.__animControlDict[lodName][partName] = {} for animName in other.__animControlDict[lodName][partName].keys(): anim = other.__animControlDict[lodName][partName][animName] anim = anim.makeCopy() self.__animControlDict[lodName][partName][animName] = anim def actorInterval(self, *args, **kw): from direct.interval import ActorInterval return ActorInterval.ActorInterval(self, *args, **kw) def printAnimBlends(self, animName=None, partName=None, lodName=None): out = '' first = True if animName is None: animNames = self.getAnimNames() else: animNames = [animName] for animName in animNames: if animName is 'nothing': continue thisAnim = '%s: ' % animName totalEffect = 0. controls = self.getAnimControls(animName, partName, lodName) for control in controls: part = control.getPart() name = part.getName() effect = part.getControlEffect(control) if effect > 0.: totalEffect += effect thisAnim += ('%s:%.3f, ' % (name, effect)) # don't print anything if this animation is not being played if totalEffect > 0.: if not first: out += '\n' first = False out += thisAnim print out def osdAnimBlends(self, animName=None, partName=None, lodName=None): if not onScreenDebug.enabled: return # puts anim blending info into the on-screen debug panel if animName is None: animNames = self.getAnimNames() else: animNames = [animName] for animName in animNames: if animName is 'nothing': continue thisAnim = '' totalEffect = 0. controls = self.getAnimControls(animName, partName, lodName) for control in controls: part = control.getPart() name = part.getName() effect = part.getControlEffect(control) if effect > 0.: totalEffect += effect thisAnim += ('%s:%.3f, ' % (name, effect)) thisAnim += "\n" for control in controls: part = control.getPart() name = part.getName() rate = control.getPlayRate() thisAnim += ('%s:%.1f, ' % (name, rate)) # don't display anything if this animation is not being played itemName = 'anim %s' % animName if totalEffect > 0.: onScreenDebug.add(itemName, thisAnim) else: if onScreenDebug.has(itemName): onScreenDebug.remove(itemName) # these functions compensate for actors that are modeled facing the viewer but need # to face away from the camera in the game def faceAwayFromViewer(self): self.getGeomNode().setH(180) def faceTowardsViewer(self): self.getGeomNode().setH(0)