diff --git a/direct/src/level/AttribDesc.py b/direct/src/level/AttribDesc.py new file mode 100755 index 0000000000..41e3fc56fc --- /dev/null +++ b/direct/src/level/AttribDesc.py @@ -0,0 +1,15 @@ +"""AttribDesc.py module: contains the AttribDesc class""" + +class AttribDesc: + """ + Entity attribute descriptor + name == name of attribute + """ + def __init__(self, name): + self.name = name + def getName(self): + return self.name + def __str__(self): + return self.name + def __repr__(self): + return "AttribDesc('%s')" % self.name diff --git a/direct/src/level/BasicEntities.py b/direct/src/level/BasicEntities.py index 71c0b96d85..4a9d1da87f 100755 --- a/direct/src/level/BasicEntities.py +++ b/direct/src/level/BasicEntities.py @@ -9,9 +9,6 @@ class NodePathAttribs: """Derive from this class to give an entity the behavior of a NodePath, without necessarily deriving from NodePath. Derived class must implement getNodePath().""" - __attribs__ = ( - 'parent', 'pos', 'hpr', - ) def initNodePathAttribs(self, doReparent=1): """Call this after the entity has been initialized""" self.callSetters('pos','x','y','z', @@ -38,9 +35,6 @@ class NodePathAttribs: def setSz(self, *args): self.getNodePath().setSz(*args) class privNodePathImpl(NodePath.NodePath): - __attribs__ = ( - 'parent', 'pos', 'hpr', - ) def __init__(self, name): node = hidden.attachNewNode(name) NodePath.NodePath.__init__(self, node) diff --git a/direct/src/level/Entity.py b/direct/src/level/Entity.py index 57d8eb9edd..04ac9281cf 100755 --- a/direct/src/level/Entity.py +++ b/direct/src/level/Entity.py @@ -10,12 +10,6 @@ class Entity(DirectObject): and can be edited with the LevelEditor.""" notify = DirectNotifyGlobal.directNotify.newCategory('Entity') - __attribs__ = ( - 'type', - 'name', - 'comment', - ) - def __init__(self, level=None, entId=None): self.initializeEntity(level, entId) @@ -71,117 +65,6 @@ class Entity(DirectObject): self.__dict__[attrib] = value if __debug__: - def getAttribDescriptors(entClass): - """pass in an Entity class""" - # lazy compilation - if not entClass.__dict__.has_key('_attribDescs'): - entClass.compileAttribDescs(entClass) - return entClass.__dict__['_attribDescs'] - getAttribDescriptors = staticmethod(getAttribDescriptors) - - def compileAttribDescs(entClass): - Entity.notify.debug('compiling attrib descriptors for %s' % - entClass.__name__) - # create a complete list of attribute descriptors, pulling in - # the attribs from the entire class heirarchy - def getClassList(obj): - """returns list, ordered from most-derived to base classes, - depth-first. Multiple inheritance base classes that do not - derive from Entity are listed before those that do. - """ - assert (type(obj) == types.ClassType) - classList = [obj] - - # no need to go below Entity - if obj == Entity: - return classList - - # explore the base classes - entityBases = [] - nonEntityBases = [] - for base in obj.__bases__: - l = getClassList(base) - if Entity in l: - entityBases.extend(l) - else: - nonEntityBases.extend(l) - # put bases that derive from Entity last - classList = classList + nonEntityBases + entityBases - return classList - - def getUniqueClassList(obj): - classList = getClassList(obj) - # remove duplicates, leaving the last instance - uniqueList = [] - for i in range(len(classList)): - if classList[i] not in classList[(i+1):]: - uniqueList.append(classList[i]) - return uniqueList - - classList = getUniqueClassList(entClass) - - # work backwards, through the class list, from Entity to the - # most-derived class, aggregating attribute descriptors. - allAttribs = [] - - def isDistObjAI(obj): - # util func: is this class a DistributedObjectAI? - lineage = getClassLineage(obj) - for item in lineage: - if type(item) == types.ClassType: - if item.__name__ == 'DistributedObjectAI': - return 1 - return 0 - - while len(classList): - cl = classList.pop() - Entity.notify.debug('looking for attribs on %s' % cl.__name__) - - def getClassAttr(cl, name): - """grab an attribute, such as __attribs__, off of a class""" - if cl.__dict__.has_key(name): - return cl.__dict__[name] - elif isDistObjAI(cl): - # It's a distributed AI class. - # Check the client-side class - globals = {} - locals = {} - ccn = cl.__name__[:-2] # clientClassName - Entity.notify.debug('importing client class %s' % ccn) - try: - exec 'import %s' % ccn in globals, locals - except: - print 'could not import %s' % ccn - return None - exec 'attr = %s.%s.__dict__.get("%s")' % ( - ccn, ccn, name) in globals, locals - return locals['attr'] - else: - return None - - # delete some attribs? - delAttribs = getClassAttr(cl, '__delAttribs__') - if delAttribs is not None: - assert type(delAttribs) in (types.TupleType, types.ListType) - Entity.notify.debug('delAttribs: %s' % list(delAttribs)) - for attrib in delAttribs: - if attrib in allAttribs: - allAttribs.remove(attrib) - - attribs = getClassAttr(cl, '__attribs__') - if attribs is not None: - assert type(attribs) in (types.TupleType, types.ListType) - Entity.notify.debug('attribs: %s' % list(attribs)) - for attrib in attribs: - if attrib not in allAttribs: - allAttribs.append(attrib) - - # we now have an ordered list of all of the attribute descriptors - # for this class. Cache it on the class object - Entity.notify.debug('all attribs: %s' % allAttribs) - entClass.__dict__['_attribDescs'] = allAttribs - compileAttribDescs = staticmethod(compileAttribDescs) - # support for level editing def handleAttribChange(self, attrib, value): # call callback function if it exists diff --git a/direct/src/level/EntityTypeRegistry.py b/direct/src/level/EntityTypeRegistry.py new file mode 100755 index 0000000000..02f69b6813 --- /dev/null +++ b/direct/src/level/EntityTypeRegistry.py @@ -0,0 +1,99 @@ +"""EntityTypeRegistry module: contains the EntityTypeRegistry class""" + +import DirectNotifyGlobal +import types +import AttribDesc +import EntityTypes +from PythonUtil import mostDerivedLast + +class EntityTypeRegistry: + notify = DirectNotifyGlobal.directNotify.newCategory('EntityTypeRegistry') + + def __init__(self, entityTypeModule=EntityTypes): + """pass in a module that contains EntityType classes""" + # maps entity typename to type class + self.name2typeClass = {} + + # get a list of the entity type classes in the type module + classes = [] + for key, value in entityTypeModule.__dict__.items(): + if type(value) is types.ClassType: + if issubclass(value, EntityTypes.Entity): + classes.append(value) + + # put derived classes after their bases + mostDerivedLast(classes) + + # scrub through the class heirarchy and compile complete + # attribute descriptor lists for each concrete Entity type class + for c in classes: + # if this is a concrete Entity type, add it to the dict + if c.__dict__.has_key('name'): + if self.name2typeClass.has_key(c.name): + EntityTypeRegistry.notify.debug( + "replacing %s with %s for type '%s'" % + (self.name2typeClass[c.name], c, c.name)) + self.name2typeClass[c.name] = c + + self.privCompileAttribDescs(c) + + def getAttributeDescriptors(self, entityTypeName): + return self.name2typeClass[entityTypeName]._attribDescs + + def privCompileAttribDescs(self, entTypeClass): + # has someone already compiled the info? + if entTypeClass.__dict__.has_key('_attribDescs'): + return + + c = entTypeClass + EntityTypeRegistry.notify.debug('compiling attrib descriptors for %s' % + c.__name__) + + # make sure all of our base classes have their complete list of + # attribDescs + for base in c.__bases__: + self.privCompileAttribDescs(base) + + # aggregate the attribute descriptors from our direct base classes + delAttribs = c.__dict__.get('delAttribs', []) + baseADs = [] + + bases = list(c.__bases__) + # make sure that Entity comes first + if EntityTypes.Entity in bases: + bases.remove(EntityTypes.Entity) + bases = [EntityTypes.Entity] + bases + + for base in bases: + for desc in base._attribDescs: + # are we blocking this attribute? + if desc.getName() in delAttribs: + continue + + # make sure we haven't already picked up this attribute + # from an earlier base class + for d in baseADs: + if desc.getName() == d.getName(): + break + else: + baseADs.append(desc) + + # now that we have all of the descriptors from our base classes, + # add the descriptors from this class + attribDescs = [] + if c.__dict__.has_key('attribs'): + for attrib in c.attribs: + desc = AttribDesc.AttribDesc(attrib) + + # if we picked up an attribute with the same name from a base + # class, this overrides it + for ad in baseADs: + if ad.getName() == desc.getName(): + baseADs.remove(ad) + # there ought to be no more than one desc with + # this name from the base classes + break + + attribDescs.append(desc) + + c._attribDescs = baseADs + attribDescs diff --git a/direct/src/level/EntityTypes.py b/direct/src/level/EntityTypes.py new file mode 100755 index 0000000000..be3b6fd929 --- /dev/null +++ b/direct/src/level/EntityTypes.py @@ -0,0 +1,208 @@ +"""EntityTypes module: contains classes that describe Entity types""" + +class Entity: + attribs = ( + 'type', + 'name', + 'comment', + ) + +class LevelMgr(Entity): + name = 'levelMgr' + attribs = ( + 'cogLevel', + 'cogTrack', + 'modelFilename', + ) + +class LogicGate(Entity): + name = 'logicGate' + attribs = ( + 'input_input1_bool', + 'input_input2_bool', + 'isInput1', + 'isInput2', + 'logicType', + 'output', + ) + +class NodepathImpl: + attribs = ( + 'parent', + 'pos', + 'hpr', + ) + +# Note: this covers Nodepath and DistributedNodepath +class Nodepath(Entity, NodepathImpl): + name = 'nodepath' + +class NodepathAttribs: + attribs = ( + 'parent', + 'pos', + 'hpr', + ) + +class Zone(Entity, NodepathAttribs): + name = 'zone' + delAttribs = ( + 'parent', + 'pos', + 'hpr', + ) + attribs = ( + 'description', + 'modelZoneNum', + ) + +class BarrelBase(Nodepath): + delAttribs = ( + 'hpr', + ) + attribs = ( + 'h', + ) + +class BeanBarrel(BarrelBase): + name = 'beanBarrel' + +class GagBarrel(BarrelBase): + name = 'gagBarrel' + attribs = ( + 'gagLevel', + 'gagTrack', + ) + +class Switch(Entity, NodepathImpl): + attribs = ( + 'scale', + 'color', + 'model', + 'input_isOn_bool', + 'isOn', + 'output', + 'secondsOn', + ) + +class Button(Switch): + name = 'button' + +class Trigger(Switch): + name = 'trigger' + +class Crate(Nodepath): + name = 'crate' + delAttribs = ( + 'hpr', + ) + attribs = ( + 'scale', + 'gridId', + 'pushable', + ) + +class Door(Entity): + name = 'door' + attribs = ( + 'parent', + 'pos', + 'hpr', + 'scale', + 'color', + 'model', + 'doorwayNum', + 'input_Lock0_bool', + 'input_Lock1_bool', + 'input_Lock2_bool', + 'input_Lock3_bool', + 'input_isOpen_bool', + 'isLock0Unlocked', + 'isLock1Unlocked', + 'isLock2Unlocked', + 'isLock3Unlocked', + 'isOpen', + 'output', + 'secondsOpen', + ) + +class Grid(Nodepath): + name = 'grid' + delAttribs = ( + 'hpr', + ) + attribs = ( + 'cellSize', + 'numCol', + 'numRow', + ) + +class Lift(Nodepath): + name = 'lift' + attribs = ( + 'duration', + 'startPos', + 'endPos', + 'modelPath', + 'floorName', + 'modelScale', + ) + +class Platform(Nodepath): + name = 'platform' + delAttribs = ( + 'pos', + ) + attribs = ( + 'startPos', + 'endPos', + 'speed', + 'waitDur', + 'phaseShift', + ) + +class SinkingPlatform(Nodepath): + name = 'sinkingPlatform' + delAttribs = ( + 'pos', + ) + attribs = ( + 'endPos', + 'phaseShift', + 'startPos', + 'verticalRange', + 'sinkRate', + 'riseRate', + 'speed', + 'waitDur', + ) + +class Stomper(Nodepath): + name = 'stomper' + attribs = ( + 'headScale', + 'motion', + 'period', + 'phaseShift', + 'range', + 'shaftScale', + 'soundLen', + 'soundOn', + 'style', + 'zOffset', + ) + +class StomperPair(Nodepath): + name = 'stomperPair' + attribs = ( + 'headScale', + 'motion', + 'period', + 'phaseShift', + 'range', + 'shaftScale', + 'soundLen', + 'soundOn', + 'stomperIds', + 'style', + ) diff --git a/direct/src/level/LevelMgr.py b/direct/src/level/LevelMgr.py index 259928e1ea..7eeba0cac0 100755 --- a/direct/src/level/LevelMgr.py +++ b/direct/src/level/LevelMgr.py @@ -6,12 +6,6 @@ import LevelMgrBase class LevelMgr(LevelMgrBase.LevelMgrBase): """This class manages editable client-side level attributes""" - __attribs__ = ( - 'cogLevel', - 'cogTrack', - 'modelFilename', - ) - def __init__(self, level, entId): LevelMgrBase.LevelMgrBase.__init__(self, level, entId) diff --git a/direct/src/level/LogicGateAI.py b/direct/src/level/LogicGateAI.py index b4c2fc8e2a..761e7d68f3 100755 --- a/direct/src/level/LogicGateAI.py +++ b/direct/src/level/LogicGateAI.py @@ -69,10 +69,6 @@ class LogicGateAI(Entity.Entity, PandaObject.PandaObject): if __debug__: notify = DirectNotifyGlobal.directNotify.newCategory( 'LogicGateAI') - __attribs__ = ( - 'input_input1_bool', 'input_input2_bool', - 'isInput1', 'isInput2', 'logicType', 'output', - ) logicTests={ "and": andTest, "or": orTest, diff --git a/direct/src/level/ZoneEntity.py b/direct/src/level/ZoneEntity.py index c40ff1a684..ca13636032 100755 --- a/direct/src/level/ZoneEntity.py +++ b/direct/src/level/ZoneEntity.py @@ -4,13 +4,6 @@ import ZoneEntityBase import BasicEntities class ZoneEntity(ZoneEntityBase.ZoneEntityBase, BasicEntities.NodePathAttribs): - __delAttribs__ = ( - 'parent', 'pos', 'hpr', - ) - __attribs__ = ( - 'description', 'modelZoneNum', - ) - def __init__(self, level, entId): ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId) diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index cb25c72344..cc2b5339a9 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -757,3 +757,13 @@ class PureVirtual: pure-virtual methods. """ raise 'error: derived class must implement %s' % callerInfo()[2] +def mostDerivedLast(classList): + """pass in list of classes. sorts list in-place, with derived classes + appearing after their bases""" + def compare(a,b): + if a is b: + return 0 + if issubclass(a,b): + return 1 + return -1 + classList.sort(compare)