Browse Source

Added ObjectMgrBase class so game-specific ObjectMgr could be inherited from

Gyedo Jeon 15 years ago
parent
commit
bd9e67ceda

+ 2 - 0
direct/src/leveleditor/LevelEditor.py

@@ -6,6 +6,7 @@ to be game specific.
 """
 """
 
 
 from LevelEditorBase import *
 from LevelEditorBase import *
+from ObjectMgr import *
 from ObjectHandler import *
 from ObjectHandler import *
 from ObjectPalette import *
 from ObjectPalette import *
 from LevelEditorUI import *
 from LevelEditorUI import *
@@ -21,6 +22,7 @@ class LevelEditor(LevelEditorBase):
 
 
         # If you have your own ObjectPalette and ObjectHandler
         # If you have your own ObjectPalette and ObjectHandler
         # connect them in your own LevelEditor class
         # connect them in your own LevelEditor class
+        self.objectMgr = ObjectMgr(self)
         self.objectPalette = ObjectPalette()
         self.objectPalette = ObjectPalette()
         self.objectHandler = ObjectHandler(self)
         self.objectHandler = ObjectHandler(self)
         self.protoPalette = ProtoPalette()
         self.protoPalette = ProtoPalette()

+ 1 - 2
direct/src/leveleditor/LevelEditorBase.py

@@ -14,7 +14,6 @@ from direct.gui.DirectGui import *
 
 
 base = ShowBase(False)
 base = ShowBase(False)
 
 
-from ObjectMgr import *
 from FileMgr import *
 from FileMgr import *
 from ActionMgr import *
 from ActionMgr import *
 from MayaConverter import *
 from MayaConverter import *
@@ -26,7 +25,7 @@ class LevelEditorBase(DirectObject):
         self.currentFile = None
         self.currentFile = None
         self.fNeedToSave = False
         self.fNeedToSave = False
         self.actionEvents = []
         self.actionEvents = []
-        self.objectMgr = ObjectMgr(self)
+        #self.objectMgr = ObjectMgr(self)
         self.fileMgr = FileMgr(self)
         self.fileMgr = FileMgr(self)
         self.actionMgr = ActionMgr()
         self.actionMgr = ActionMgr()
 
 

+ 2 - 0
direct/src/leveleditor/ObjectGlobals.py

@@ -18,6 +18,7 @@ PROP_UI_RADIO = '_PropUIRadio'
 PROP_UI_CHECK = '_PropUICheckBox'
 PROP_UI_CHECK = '_PropUICheckBox'
 PROP_UI_SLIDE = '_PropUISlider'
 PROP_UI_SLIDE = '_PropUISlider'
 PROP_UI_SPIN = '_PropUISpinner'
 PROP_UI_SPIN = '_PropUISpinner'
+PROP_UI_BLIND = '_PropUIBlind'
 
 
 # index for property definition
 # index for property definition
 PROP_TYPE = 0
 PROP_TYPE = 0
@@ -40,6 +41,7 @@ PROP_INT = 0 # int type value
 PROP_BOOL = 1 # bool type value
 PROP_BOOL = 1 # bool type value
 PROP_FLOAT = 2 # float type value
 PROP_FLOAT = 2 # float type value
 PROP_STR = 3 # string type value
 PROP_STR = 3 # string type value
+PROP_BLIND = 4 # blind type value
 
 
 TYPE_CONV = {PROP_INT: int, PROP_BOOL: bool, PROP_FLOAT: float, PROP_STR: str}
 TYPE_CONV = {PROP_INT: int, PROP_BOOL: bool, PROP_FLOAT: float, PROP_STR: str}
 
 

+ 3 - 663
direct/src/leveleditor/ObjectMgr.py

@@ -1,670 +1,10 @@
 """
 """
 Defines ObjectMgr
 Defines ObjectMgr
 """
 """
+from ObjectMgrBase import *
 
 
-import os, time, wx, types
-
-from direct.task import Task
-from direct.actor.Actor import Actor
-from pandac.PandaModules import *
-from ActionMgr import *
-import ObjectGlobals as OG
-from ObjectPaletteBase import ObjectGen
-class ObjectMgr:
+class ObjectMgr(ObjectMgrBase):
     """ ObjectMgr will create, manage, update objects in the scene """
     """ ObjectMgr will create, manage, update objects in the scene """
     
     
     def __init__(self, editor):
     def __init__(self, editor):
-        self.editor = editor
-
-        # main obj repository of objects in the scene
-        self.objects = {}
-        self.npIndex = {}
-        self.saveData = []
-        self.objectsLastXform = {}
-
-        self.lastUid = ''
-        self.lastUidMode = 0
-        self.currNodePath = None   
-
-        self.currLiveNP = None
-
-    def reset(self):
-        base.direct.deselectAllCB()
-
-        for id in self.objects.keys():
-            try:
-                self.objects[id][OG.OBJ_NP].removeNode()
-            except:
-                pass
-            del self.objects[id]
-
-        for np in self.npIndex.keys():
-            del self.npIndex[np]
-               
-        self.objects = {}
-        self.npIndex = {}
-        self.saveData = []
-
-    def genUniqueId(self):
-        # [gjeon] to solve the problem of unproper $USERNAME
-        userId = os.path.basename(os.path.expandvars('$USERNAME'))
-        if userId == '':
-            userId = base.config.GetString("le-user-id")
-        if userId == '':
-            userId = 'unknown'
-        newUid = str(time.time()) + userId
-        # prevent duplicates from being generated in the same frame (this can
-        # happen when creating several new objects at once)
-        if (self.lastUid == newUid):
-            # append a value to the end to uniquify the id
-            newUid = newUid + str(self.lastUidMod)
-            self.lastUidMod = self.lastUidMod + 1
-        else:
-            self.lastUid = newUid
-            self.lastUidMod = 0
-        return newUid
-
-    def addNewObject(self, typeName, uid = None, model = None, parent=None, anim = None, fSelectObject=True, nodePath=None):
-        """ function to add new obj to the scene """
-        if parent is None:
-            parent = self.editor.NPParent
-
-        if self.editor:
-            objDef = self.editor.objectPalette.findItem(typeName)
-            if objDef is None:
-                objDef = self.editor.protoPalette.findItem(typeName)
-        else: # when loaded outside of LE
-            objDef = base.objectPalette.findItem(typeName)
-            if objDef is None:
-                objDef = base.protoPalette.findItem(typeName)
-        newobj = None
-        if objDef and type(objDef) != dict:
-            if nodePath is None:
-                if objDef.createFunction:
-                    funcName = objDef.createFunction[OG.FUNC_NAME]
-                    funcArgs = objDef.createFunction[OG.FUNC_ARGS]
-                    if type(funcName) == types.StringType:
-                        if funcName.startswith('.'):
-                            # when it's using default objectHandler
-                            if self.editor:
-                                func = Functor(eval("self.editor.objectHandler%s"%funcName))
-                            else: # when loaded outside of LE
-                                func = Functor(eval("base.objectHandler%s"%funcName))                        
-                        else:
-                            # when it's not using default objectHandler, whole name of the handling obj
-                            # should be included in function name
-                            func = Functor(eval(funcName))
-                    else:
-                        func = funcName
-                    # create new obj using function and keyword arguments defined in ObjectPalette
-                    newobj = func(**funcArgs)
-                elif objDef.actor:
-                    if model is None:
-                        model = objDef.model
-                    try:
-                        newobj = Actor(model)
-                    except:
-                        newobj = Actor(Filename.fromOsSpecific(model).getFullpath())
-                elif objDef.model is not None:
-                    # since this obj is simple model let's load the model
-                    if model is None:
-                        model = objDef.model
-                    try:
-                        newobj = loader.loadModel(model)
-                    except:
-                        newobj = loader.loadModel(Filename.fromOsSpecific(model).getFullpath())
-                else:
-                    newobj = hidden.attachNewNode(objDef.name)
-            else:
-                newobj = nodePath
-            i = 0
-            for i in range(len(objDef.anims)):
-                animFile = objDef.anims[i]
-                # load new anim
-                animName = os.path.basename(animFile)
-                if i < len(objDef.animNames):
-                    animName = objDef.animNames[i]
-                newAnim = newobj.loadAnims({animName:animFile})
-
-                if anim:
-                    if anim == animFile:
-                        newobj.loop(animName)
-                else:
-                    if i == 0:
-                        anim = animFile
-                        newobj.loop(animName)
-
-            if newobj is None:
-                return None
-
-            newobj.reparentTo(parent)
-            newobj.setTag('OBJRoot','1')
-
-            if uid is None:
-                uid = self.genUniqueId()
-
-            # populate obj data using default values
-            properties = {}
-            for key in objDef.properties.keys():
-                properties[key] = objDef.properties[key][OG.PROP_DEFAULT]
-
-            # insert obj data to main repository
-            self.objects[uid] = [uid, newobj, objDef, model, anim, properties, (1,1,1,1)]
-            self.npIndex[NodePath(newobj)] = uid
-
-            if self.editor:
-                if fSelectObject:
-                    self.editor.select(newobj, fUndo=0)
-                self.editor.ui.sceneGraphUI.add(newobj)
-                self.editor.fNeedToSave = True
-        return newobj
-
-    def removeObjectById(self, uid):
-        obj = self.findObjectById(uid)
-        nodePath = obj[OG.OBJ_NP]
-        del self.objects[uid]
-        del self.npIndex[nodePath]
-
-        # remove children also
-        for child in nodePath.getChildren():
-            if child.hasTag('OBJRoot'):
-                self.removeObjectByNodePath(child)
-        nodePath.remove()
-
-        self.editor.fNeedToSave = True        
-
-    def removeObjectByNodePath(self, nodePath):
-        uid = self.npIndex.get(nodePath)
-        if uid:
-            del self.objects[uid]
-            del self.npIndex[nodePath]
-
-        # remove children also
-        for child in nodePath.getChildren():
-            if child.hasTag('OBJRoot'):
-                self.removeObjectByNodePath(child)
-        self.editor.fNeedToSave = True
-
-    def findObjectById(self, uid):
-        return self.objects.get(uid)
-
-    def findObjectByNodePath(self, nodePath):
-        uid = self.npIndex.get(NodePath(nodePath))
-        if uid is None:
-            return None
-        else:
-            return self.objects[uid]
-
-    def deselectAll(self):
-        self.currNodePath = None
-        taskMgr.remove('_le_updateObjectUITask')
-        self.editor.ui.objectPropertyUI.clearPropUI()
-        self.editor.ui.sceneGraphUI.tree.UnselectAll()
-
-    def selectObject(self, nodePath, fLEPane=0):
-        obj = self.findObjectByNodePath(nodePath)
-        if obj is None:
-            return
-
-        self.currNodePath = obj[OG.OBJ_NP]
-        self.objectsLastXform[obj[OG.OBJ_UID]] = Mat4(self.currNodePath.getMat())
-        # [gjeon] to connect transform UI with nodepath's transform
-        self.spawnUpdateObjectUITask()
-        self.updateObjectPropertyUI(obj)
-        #import pdb;pdb.set_trace()
-        if fLEPane == 0:
-           self.editor.ui.sceneGraphUI.select(obj[OG.OBJ_UID])
-
-        if not obj[OG.OBJ_DEF].movable:
-            if base.direct.widget.fActive:
-                base.direct.widget.toggleWidget()
-
-            
-
-    def updateObjectPropertyUI(self, obj):
-        objDef = obj[OG.OBJ_DEF]
-        objProp = obj[OG.OBJ_PROP]
-        self.editor.ui.objectPropertyUI.updateProps(obj, objDef.movable)
-        self.editor.fNeedToSave = True
-        
-    def onEnterObjectPropUI(self, event):
-        taskMgr.remove('_le_updateObjectUITask')        
-        self.editor.ui.bindKeyEvents(False)
-
-    def onLeaveObjectPropUI(self, event):
-        self.spawnUpdateObjectUITask()
-        self.editor.ui.bindKeyEvents(True)
-
-    def spawnUpdateObjectUITask(self):
-        if self.currNodePath is None:
-            return
-
-        taskMgr.remove('_le_updateObjectUITask')
-        t = Task.Task(self.updateObjectUITask)
-        t.np = self.currNodePath
-        taskMgr.add(t, '_le_updateObjectUITask')
-        
-    def updateObjectUITask(self, state):
-        self.editor.ui.objectPropertyUI.propX.setValue(state.np.getX())
-        self.editor.ui.objectPropertyUI.propY.setValue(state.np.getY())
-        self.editor.ui.objectPropertyUI.propZ.setValue(state.np.getZ())
-
-        h = state.np.getH()
-        while h < 0:
-            h = h + 360.0
-
-        while h > 360:
-            h = h - 360.0
-
-        p = state.np.getP()
-        while p < 0:
-            p = p + 360.0
-
-        while p > 360:
-            p = p - 360.0
-
-        r = state.np.getR()
-        while r < 0:
-            r = r + 360.0
-
-        while r > 360:
-            r = r - 360.0 
-            
-        self.editor.ui.objectPropertyUI.propH.setValue(h)
-        self.editor.ui.objectPropertyUI.propP.setValue(p)
-        self.editor.ui.objectPropertyUI.propR.setValue(r)        
-
-        self.editor.ui.objectPropertyUI.propSX.setValue(state.np.getSx())
-        self.editor.ui.objectPropertyUI.propSY.setValue(state.np.getSy())
-        self.editor.ui.objectPropertyUI.propSZ.setValue(state.np.getSz())
-        
-        return Task.cont
-        
-    def updateObjectTransform(self, event):
-        if self.currNodePath is None:
-            return
-
-        np = hidden.attachNewNode('temp')
-        np.setX(float(self.editor.ui.objectPropertyUI.propX.getValue()))
-        np.setY(float(self.editor.ui.objectPropertyUI.propY.getValue()))
-        np.setZ(float(self.editor.ui.objectPropertyUI.propZ.getValue()))
-
-        h = float(self.editor.ui.objectPropertyUI.propH.getValue())
-        while h < 0:
-            h = h + 360.0
-
-        while h > 360:
-            h = h - 360.0
-
-        p = float(self.editor.ui.objectPropertyUI.propP.getValue())
-        while p < 0:
-            p = p + 360.0
-
-        while p > 360:
-            p = p - 360.0
-
-        r = float(self.editor.ui.objectPropertyUI.propR.getValue())
-        while r < 0:
-            r = r + 360.0
-
-        while r > 360:
-            r = r - 360.0 
-            
-        np.setH(h)
-        np.setP(p)
-        np.setR(r)
-
-        np.setSx(float(self.editor.ui.objectPropertyUI.propSX.getValue()))
-        np.setSy(float(self.editor.ui.objectPropertyUI.propSY.getValue()))
-        np.setSz(float(self.editor.ui.objectPropertyUI.propSZ.getValue()))        
-
-        obj = self.findObjectByNodePath(self.currNodePath)
-        action = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat()))
-        self.editor.actionMgr.push(action)
-        np.remove()
-        action()
-        self.editor.fNeedToSave = True
-        
-    def setObjectTransform(self, uid, xformMat):
-        obj = self.findObjectById(uid)
-        if obj:
-            obj[OG.OBJ_NP].setMat(xformMat)
-        self.editor.fNeedToSave = True
-        
-    def updateObjectColor(self, r, g, b, a, np=None):
-        if np is None:
-            np = self.currNodePath
-
-        obj = self.findObjectByNodePath(np)
-        if not obj:
-            return
-        obj[OG.OBJ_RGBA] = (r,g,b,a)
-        for child in np.getChildren():
-            if not child.hasTag('OBJRoot') and\
-               child.getName() != 'bboxLines':
-                child.setTransparency(1)
-                child.setColorScale(r, g, b, a)
-        self.editor.fNeedToSave = True
-        
-    def updateObjectModel(self, model, obj, fSelectObject=True):
-        """ replace object's model """
-        if obj[OG.OBJ_MODEL] != model:
-            base.direct.deselectAllCB()
-
-            objNP = obj[OG.OBJ_NP]
-            objRGBA = obj[OG.OBJ_RGBA]
-            
-            # load new model
-            newobj = loader.loadModel(model)
-            newobj.setTag('OBJRoot','1')
-
-            # reparent children
-            objNP.findAllMatches("=OBJRoot").reparentTo(newobj)
-            
-            # reparent to parent
-            newobj.reparentTo(objNP.getParent())
-
-            # copy transform
-            newobj.setPos(objNP.getPos())
-            newobj.setHpr(objNP.getHpr())
-            newobj.setScale(objNP.getScale())
-
-            # copy RGBA data
-            self.updateObjectColor(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], newobj)
-
-            # delete old geom
-            del self.npIndex[NodePath(objNP)]
-            objNP.removeNode()
-
-            # register new geom
-            obj[OG.OBJ_NP] = newobj
-            obj[OG.OBJ_MODEL] = model
-            self.npIndex[NodePath(newobj)] = obj[OG.OBJ_UID]
-
-            if fSelectObject:
-                base.direct.select(newobj, fUndo=0)        
-
-            self.editor.fNeedToSave = True
-
-    def updateObjectAnim(self, anim, obj, fSelectObject=True):
-        """ replace object's anim """
-        if obj[OG.OBJ_ANIM] != anim:
-            base.direct.deselectAllCB()
-            objNP = obj[OG.OBJ_NP]
-
-            # load new anim
-            animName = os.path.basename(anim)
-            newAnim = objNP.loadAnims({animName:anim})
-            objNP.loop(animName)
-            obj[OG.OBJ_ANIM] = anim
-            if fSelectObject:
-                base.direct.select(objNP, fUndo=0)
-
-            self.editor.fNeedToSave = True
-
-    def updateObjectModelFromUI(self, event, obj):
-        """ replace object's model with one selected from UI """
-        model = event.GetString()
-        if model is not None:
-            self.updateObjectModel(model, obj)
-
-    def updateObjectAnimFromUI(self, event, obj):
-        """ replace object's anim with one selected from UI """
-        anim = event.GetString()
-        if anim is not None:
-            self.updateObjectAnim(anim, obj)
-
-    def updateObjectProperty(self, event, obj, propName):
-        """
-        When an obj's property is updated in UI,
-        this will update it's value in data structure.
-        And call update function if defined.        
-        """
-        
-        objDef = obj[OG.OBJ_DEF]
-        objProp = obj[OG.OBJ_PROP]
-        
-        propDef = objDef.properties[propName]
-        if propDef is None:
-            return
-
-        propType = propDef[OG.PROP_TYPE]
-        propDataType = propDef[OG.PROP_DATATYPE]
-        
-        if propType == OG.PROP_UI_SLIDE:
-            if len(propDef) <= OG.PROP_RANGE:
-                return
-
-            strVal = event.GetString()
-            if strVal == '':
-                min = float(propDef[OG.PROP_RANGE][OG.RANGE_MIN])
-                max = float(propDef[OG.PROP_RANGE][OG.RANGE_MAX])
-                intVal = event.GetInt()
-                if intVal is None:
-                    return
-                val = intVal / 100.0 * (max - min) + min
-            else:
-                val = strVal
-
-        elif propType == OG.PROP_UI_ENTRY:
-            val = event.GetString()
-
-        elif propType == OG.PROP_UI_SPIN:
-            val = event.GetInt()
-
-        elif propType == OG.PROP_UI_CHECK:
-            if event.GetInt():
-                val = True
-            else:
-                val = False
-
-        elif propType == OG.PROP_UI_RADIO:
-            val = event.GetString()
-
-        elif propType == OG.PROP_UI_COMBO:
-            val = event.GetString()
-
-        else:
-            # unsupported property type
-            return
-
-        # now update object prop value and call update function
-        self.updateObjectPropValue(obj, propName, val, \
-                                   fSelectObject=(propType != OG.PROP_UI_SLIDE)
-                                   )
-
-    def updateObjectPropValue(self, obj, propName, val, fSelectObject=False):
-        """
-        Update object property value and
-        call update function if defined.         
-        """
-        
-        objDef = obj[OG.OBJ_DEF]
-        objProp = obj[OG.OBJ_PROP]
-        
-        propDef = objDef.properties[propName]
-        propDataType = propDef[OG.PROP_DATATYPE]
-
-        val = OG.TYPE_CONV[propDataType](val)
-        oldVal = objProp[propName]
-        #objProp[propName] = val
-        
-        if propDef[OG.PROP_FUNC] is None:
-            func = None
-            undoFunc = None
-        else:
-            funcName = propDef[OG.PROP_FUNC][OG.FUNC_NAME]
-            funcArgs = propDef[OG.PROP_FUNC][OG.FUNC_ARGS]
-
-            # populate keyword arguments
-            kwargs = {}
-            undoKwargs = {}
-            for key in funcArgs.keys():
-                if funcArgs[key] == OG.ARG_VAL:
-                    kwargs[key] = val
-                    undoKwargs[key] = oldVal
-                elif funcArgs[key] == OG.ARG_OBJ:
-                    undoKwargs[key] = obj
-                    objProp[propName] = val
-                    kwargs[key] = obj
-                else:
-                    kwargs[key] = funcArgs[key]
-                    undoKwargs[key] = funcArgs[key]
-
-            if type(funcName) == types.StringType:
-                if funcName.startswith('.'):
-                    if self.editor:
-                        func = Functor(eval("self.editor.objectHandler%s"%funcName), **kwargs)
-                        undoFunc = Functor(eval("self.editor.objectHandler%s"%funcName), **undoKwargs)
-                    else: # when loaded outside of LE
-                        func = Functor(eval("base.objectHandler%s"%funcName), **kwargs)
-                        undoFunc = Functor(eval("base.objectHandler%s"%funcName), **undoKwargs)                    
-                else:
-                    func = Functor(eval(funcName), **kwargs)
-                    undoFunc = Functor(eval(funcName), **undoKwargs)
-            else:
-                func = Functor(funcName, **kwargs)
-                undoFunc = Functor(funcName, **undoKwargs)
-                
-            # finally call update function
-            #func(**kwargs)
-        action = ActionUpdateObjectProp(self.editor, fSelectObject, obj, propName, val, oldVal, func, undoFunc)
-        self.editor.actionMgr.push(action)
-        action()
-
-        if self.editor:
-            self.editor.fNeedToSave = True
-            if fSelectObject:
-                base.direct.select(obj[OG.OBJ_NP], fUndo=0)
-
-    def updateObjectProperties(self, nodePath, propValues):
-        """
-        When a saved level is loaded,
-        update an object's properties
-        And call update function if defined.
-        """
-
-        obj = self.findObjectByNodePath(nodePath)
-        if obj:
-            for propName in propValues:
-                self.updateObjectPropValue(obj, propName, propValues[propName])
-
-    def traverse(self, parent, parentId = None):
-        """
-        Trasverse scene graph to gather data for saving
-        """
-        
-        for child in parent.getChildren():
-            if child.hasTag('OBJRoot'):
-                obj = self.findObjectByNodePath(child)
-
-                if obj:
-                    uid = obj[OG.OBJ_UID]
-                    np = obj[OG.OBJ_NP]
-                    objDef = obj[OG.OBJ_DEF]
-                    objModel = obj[OG.OBJ_MODEL]
-                    objAnim = obj[OG.OBJ_ANIM]
-                    objProp = obj[OG.OBJ_PROP]
-                    objRGBA = obj[OG.OBJ_RGBA]
-
-                    if parentId:
-                        parentStr = "objects['%s']"%parentId
-                    else:
-                        parentStr = "None"
-
-                    if objModel:
-                        modelStr = "'%s'"%objModel
-                    else:
-                        modelStr = "None"
-
-                    if objAnim:
-                        animStr = "'%s'"%objAnim
-                    else:
-                        animStr = "None"
-
-                    self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', %s, %s, %s, fSelectObject=False)"%(uid, objDef.name, uid, modelStr, parentStr, animStr))
-                    self.saveData.append("if objects['%s']:"%uid)
-                    self.saveData.append("    objects['%s'].setPos(%s)"%(uid, np.getPos()))
-                    self.saveData.append("    objects['%s'].setHpr(%s)"%(uid, np.getHpr()))
-                    self.saveData.append("    objects['%s'].setScale(%s)"%(uid, np.getScale()))
-                    self.saveData.append("    objectMgr.updateObjectColor(%f, %f, %f, %f, objects['%s'])"%(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], uid))
-                    self.saveData.append("    objectMgr.updateObjectProperties(objects['%s'], %s)"%(uid,objProp))
-                    
-                self.traverse(child, uid)
-
-    def getSaveData(self):
-        self.saveData = []
-        self.traverse(render)
-        return self.saveData
-
-    def duplicateObject(self, nodePath, parent=None):
-        obj = self.findObjectByNodePath(nodePath)
-        if obj is None:
-            return None
-        objDef = obj[OG.OBJ_DEF]
-        objModel = obj[OG.OBJ_MODEL]
-        objAnim = obj[OG.OBJ_ANIM]
-        objRGBA = obj[OG.OBJ_RGBA]
-
-        if parent is None:
-            parentNP = nodePath.getParent()
-            parentObj = self.findObjectByNodePath(parentNP)
-            if parentObj is None:
-                parent = parentNP
-            else:
-                parent = parentObj[OG.OBJ_NP]
-
-        newObjNP = self.addNewObject(objDef.name, parent=parent, fSelectObject = False)
-
-        # copy transform data
-        newObjNP.setPos(obj[OG.OBJ_NP].getPos())
-        newObjNP.setHpr(obj[OG.OBJ_NP].getHpr())
-        newObjNP.setScale(obj[OG.OBJ_NP].getScale())
-
-        newObj = self.findObjectByNodePath(NodePath(newObjNP))
-        if newObj is None:
-            return None
-        # copy model info
-        self.updateObjectModel(obj[OG.OBJ_MODEL], newObj, fSelectObject=False)
-
-        # copy anim info
-        self.updateObjectAnim(obj[OG.OBJ_ANIM], newObj, fSelectObject=False)
-
-        # copy other properties
-        for key in obj[OG.OBJ_PROP]:
-            self.updateObjectPropValue(newObj, key, obj[OG.OBJ_PROP][key])
-        return newObjNP
-
-    def duplicateChild(self, nodePath, parent):
-        children = nodePath.findAllMatches('=OBJRoot')
-        for childNP in children:
-            newChildObjNP = self.duplicateObject(childNP, parent)
-            if newChildObjNP is not None:
-                self.duplicateChild(childNP, newChildObjNP)
-    
-    def duplicateSelected(self):
-        selectedNPs = base.direct.selected.getSelectedAsList()
-        duplicatedNPs = []
-        for nodePath in selectedNPs:
-            newObjNP = self.duplicateObject(nodePath)
-            if newObjNP is not None:
-                self.duplicateChild(nodePath, newObjNP)
-                duplicatedNPs.append(newObjNP)
-
-        base.direct.deselectAllCB()
-        for newNodePath in duplicatedNPs:
-            base.direct.select(newNodePath, fMultiSelect = 1, fUndo=0)
-
-        self.editor.fNeedToSave = True
-
-    def makeSelectedLive(self):
-        obj = self.findObjectByNodePath(base.direct.selected.last)
-        if obj:
-            if self.currLiveNP:
-                self.currLiveNP.clearColorScale()
-                if self.currLiveNP == obj[OG.OBJ_NP]:
-                    self.currLiveNP = None
-                    return
-
-            self.currLiveNP = obj[OG.OBJ_NP]
-            self.currLiveNP.setColorScale(0, 1, 0, 1)
+        ObjectMgrBase.__init__(self, editor)

+ 674 - 0
direct/src/leveleditor/ObjectMgrBase.py

@@ -0,0 +1,674 @@
+"""
+Defines ObjectMgrBase
+"""
+
+import os, time, wx, types
+
+from direct.task import Task
+from direct.actor.Actor import Actor
+from pandac.PandaModules import *
+from ActionMgr import *
+import ObjectGlobals as OG
+from ObjectPaletteBase import ObjectGen
+
+class ObjectMgrBase:
+    """ ObjectMgr will create, manage, update objects in the scene """
+    
+    def __init__(self, editor):
+        self.editor = editor
+
+        # main obj repository of objects in the scene
+        self.objects = {}
+        self.npIndex = {}
+        self.saveData = []
+        self.objectsLastXform = {}
+
+        self.lastUid = ''
+        self.lastUidMode = 0
+        self.currNodePath = None   
+
+        self.currLiveNP = None
+
+    def reset(self):
+        base.direct.deselectAllCB()
+
+        for id in self.objects.keys():
+            try:
+                self.objects[id][OG.OBJ_NP].removeNode()
+            except:
+                pass
+            del self.objects[id]
+
+        for np in self.npIndex.keys():
+            del self.npIndex[np]
+               
+        self.objects = {}
+        self.npIndex = {}
+        self.saveData = []
+
+    def genUniqueId(self):
+        # [gjeon] to solve the problem of unproper $USERNAME
+        userId = os.path.basename(os.path.expandvars('$USERNAME'))
+        if userId == '':
+            userId = base.config.GetString("le-user-id")
+        if userId == '':
+            userId = 'unknown'
+        newUid = str(time.time()) + userId
+        # prevent duplicates from being generated in the same frame (this can
+        # happen when creating several new objects at once)
+        if (self.lastUid == newUid):
+            # append a value to the end to uniquify the id
+            newUid = newUid + str(self.lastUidMod)
+            self.lastUidMod = self.lastUidMod + 1
+        else:
+            self.lastUid = newUid
+            self.lastUidMod = 0
+        return newUid
+
+    def addNewObject(self, typeName, uid = None, model = None, parent=None, anim = None, fSelectObject=True, nodePath=None):
+        """ function to add new obj to the scene """
+        if parent is None:
+            parent = self.editor.NPParent
+
+        if self.editor:
+            objDef = self.editor.objectPalette.findItem(typeName)
+            if objDef is None:
+                objDef = self.editor.protoPalette.findItem(typeName)
+        else: # when loaded outside of LE
+            objDef = base.objectPalette.findItem(typeName)
+            if objDef is None:
+                objDef = base.protoPalette.findItem(typeName)
+        newobj = None
+        if objDef and type(objDef) != dict:
+            if nodePath is None:
+                if objDef.createFunction:
+                    funcName = objDef.createFunction[OG.FUNC_NAME]
+                    funcArgs = objDef.createFunction[OG.FUNC_ARGS]
+                    if type(funcName) == types.StringType:
+                        if funcName.startswith('.'):
+                            # when it's using default objectHandler
+                            if self.editor:
+                                func = Functor(eval("self.editor.objectHandler%s"%funcName))
+                            else: # when loaded outside of LE
+                                func = Functor(eval("base.objectHandler%s"%funcName))                        
+                        else:
+                            # when it's not using default objectHandler, whole name of the handling obj
+                            # should be included in function name
+                            func = Functor(eval(funcName))
+                    else:
+                        func = funcName
+                    # create new obj using function and keyword arguments defined in ObjectPalette
+                    newobj = func(**funcArgs)
+                elif objDef.actor:
+                    if model is None:
+                        model = objDef.model
+                    try:
+                        newobj = Actor(model)
+                    except:
+                        newobj = Actor(Filename.fromOsSpecific(model).getFullpath())
+                elif objDef.model is not None:
+                    # since this obj is simple model let's load the model
+                    if model is None:
+                        model = objDef.model
+                    try:
+                        newobj = loader.loadModel(model)
+                    except:
+                        newobj = loader.loadModel(Filename.fromOsSpecific(model).getFullpath())
+                else:
+                    newobj = hidden.attachNewNode(objDef.name)
+            else:
+                newobj = nodePath
+            i = 0
+            for i in range(len(objDef.anims)):
+                animFile = objDef.anims[i]
+                # load new anim
+                animName = os.path.basename(animFile)
+                if i < len(objDef.animNames):
+                    animName = objDef.animNames[i]
+                newAnim = newobj.loadAnims({animName:animFile})
+
+                if anim:
+                    if anim == animFile:
+                        newobj.loop(animName)
+                else:
+                    if i == 0:
+                        anim = animFile
+                        newobj.loop(animName)
+
+            if newobj is None:
+                return None
+
+            newobj.reparentTo(parent)
+            newobj.setTag('OBJRoot','1')
+
+            if uid is None:
+                uid = self.genUniqueId()
+
+            # populate obj data using default values
+            properties = {}
+            for key in objDef.properties.keys():
+                properties[key] = objDef.properties[key][OG.PROP_DEFAULT]
+
+            # insert obj data to main repository
+            self.objects[uid] = [uid, newobj, objDef, model, anim, properties, (1,1,1,1)]
+            self.npIndex[NodePath(newobj)] = uid
+
+            if self.editor:
+                if fSelectObject:
+                    self.editor.select(newobj, fUndo=0)
+                self.editor.ui.sceneGraphUI.add(newobj)
+                self.editor.fNeedToSave = True
+        return newobj
+
+    def removeObjectById(self, uid):
+        obj = self.findObjectById(uid)
+        nodePath = obj[OG.OBJ_NP]
+        del self.objects[uid]
+        del self.npIndex[nodePath]
+
+        # remove children also
+        for child in nodePath.getChildren():
+            if child.hasTag('OBJRoot'):
+                self.removeObjectByNodePath(child)
+        nodePath.remove()
+
+        self.editor.fNeedToSave = True        
+
+    def removeObjectByNodePath(self, nodePath):
+        uid = self.npIndex.get(nodePath)
+        if uid:
+            del self.objects[uid]
+            del self.npIndex[nodePath]
+
+        # remove children also
+        for child in nodePath.getChildren():
+            if child.hasTag('OBJRoot'):
+                self.removeObjectByNodePath(child)
+        self.editor.fNeedToSave = True
+
+    def findObjectById(self, uid):
+        return self.objects.get(uid)
+
+    def findObjectByNodePath(self, nodePath):
+        uid = self.npIndex.get(NodePath(nodePath))
+        if uid is None:
+            return None
+        else:
+            return self.objects[uid]
+
+    def deselectAll(self):
+        self.currNodePath = None
+        taskMgr.remove('_le_updateObjectUITask')
+        self.editor.ui.objectPropertyUI.clearPropUI()
+        self.editor.ui.sceneGraphUI.tree.UnselectAll()
+
+    def selectObject(self, nodePath, fLEPane=0):
+        obj = self.findObjectByNodePath(nodePath)
+        if obj is None:
+            return
+
+        self.currNodePath = obj[OG.OBJ_NP]
+        self.objectsLastXform[obj[OG.OBJ_UID]] = Mat4(self.currNodePath.getMat())
+        # [gjeon] to connect transform UI with nodepath's transform
+        self.spawnUpdateObjectUITask()
+        self.updateObjectPropertyUI(obj)
+        #import pdb;pdb.set_trace()
+        if fLEPane == 0:
+           self.editor.ui.sceneGraphUI.select(obj[OG.OBJ_UID])
+
+        if not obj[OG.OBJ_DEF].movable:
+            if base.direct.widget.fActive:
+                base.direct.widget.toggleWidget()
+
+            
+
+    def updateObjectPropertyUI(self, obj):
+        objDef = obj[OG.OBJ_DEF]
+        objProp = obj[OG.OBJ_PROP]
+        self.editor.ui.objectPropertyUI.updateProps(obj, objDef.movable)
+        self.editor.fNeedToSave = True
+        
+    def onEnterObjectPropUI(self, event):
+        taskMgr.remove('_le_updateObjectUITask')        
+        self.editor.ui.bindKeyEvents(False)
+
+    def onLeaveObjectPropUI(self, event):
+        self.spawnUpdateObjectUITask()
+        self.editor.ui.bindKeyEvents(True)
+
+    def spawnUpdateObjectUITask(self):
+        if self.currNodePath is None:
+            return
+
+        taskMgr.remove('_le_updateObjectUITask')
+        t = Task.Task(self.updateObjectUITask)
+        t.np = self.currNodePath
+        taskMgr.add(t, '_le_updateObjectUITask')
+        
+    def updateObjectUITask(self, state):
+        self.editor.ui.objectPropertyUI.propX.setValue(state.np.getX())
+        self.editor.ui.objectPropertyUI.propY.setValue(state.np.getY())
+        self.editor.ui.objectPropertyUI.propZ.setValue(state.np.getZ())
+
+        h = state.np.getH()
+        while h < 0:
+            h = h + 360.0
+
+        while h > 360:
+            h = h - 360.0
+
+        p = state.np.getP()
+        while p < 0:
+            p = p + 360.0
+
+        while p > 360:
+            p = p - 360.0
+
+        r = state.np.getR()
+        while r < 0:
+            r = r + 360.0
+
+        while r > 360:
+            r = r - 360.0 
+            
+        self.editor.ui.objectPropertyUI.propH.setValue(h)
+        self.editor.ui.objectPropertyUI.propP.setValue(p)
+        self.editor.ui.objectPropertyUI.propR.setValue(r)        
+
+        self.editor.ui.objectPropertyUI.propSX.setValue(state.np.getSx())
+        self.editor.ui.objectPropertyUI.propSY.setValue(state.np.getSy())
+        self.editor.ui.objectPropertyUI.propSZ.setValue(state.np.getSz())
+        
+        return Task.cont
+        
+    def updateObjectTransform(self, event):
+        if self.currNodePath is None:
+            return
+
+        np = hidden.attachNewNode('temp')
+        np.setX(float(self.editor.ui.objectPropertyUI.propX.getValue()))
+        np.setY(float(self.editor.ui.objectPropertyUI.propY.getValue()))
+        np.setZ(float(self.editor.ui.objectPropertyUI.propZ.getValue()))
+
+        h = float(self.editor.ui.objectPropertyUI.propH.getValue())
+        while h < 0:
+            h = h + 360.0
+
+        while h > 360:
+            h = h - 360.0
+
+        p = float(self.editor.ui.objectPropertyUI.propP.getValue())
+        while p < 0:
+            p = p + 360.0
+
+        while p > 360:
+            p = p - 360.0
+
+        r = float(self.editor.ui.objectPropertyUI.propR.getValue())
+        while r < 0:
+            r = r + 360.0
+
+        while r > 360:
+            r = r - 360.0 
+            
+        np.setH(h)
+        np.setP(p)
+        np.setR(r)
+
+        np.setSx(float(self.editor.ui.objectPropertyUI.propSX.getValue()))
+        np.setSy(float(self.editor.ui.objectPropertyUI.propSY.getValue()))
+        np.setSz(float(self.editor.ui.objectPropertyUI.propSZ.getValue()))        
+
+        obj = self.findObjectByNodePath(self.currNodePath)
+        action = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat()))
+        self.editor.actionMgr.push(action)
+        np.remove()
+        action()
+        self.editor.fNeedToSave = True
+        
+    def setObjectTransform(self, uid, xformMat):
+        obj = self.findObjectById(uid)
+        if obj:
+            obj[OG.OBJ_NP].setMat(xformMat)
+        self.editor.fNeedToSave = True
+        
+    def updateObjectColor(self, r, g, b, a, np=None):
+        if np is None:
+            np = self.currNodePath
+
+        obj = self.findObjectByNodePath(np)
+        if not obj:
+            return
+        obj[OG.OBJ_RGBA] = (r,g,b,a)
+        for child in np.getChildren():
+            if not child.hasTag('OBJRoot') and\
+               child.getName() != 'bboxLines':
+                child.setTransparency(1)
+                child.setColorScale(r, g, b, a)
+        self.editor.fNeedToSave = True
+        
+    def updateObjectModel(self, model, obj, fSelectObject=True):
+        """ replace object's model """
+        if obj[OG.OBJ_MODEL] != model:
+            base.direct.deselectAllCB()
+
+            objNP = obj[OG.OBJ_NP]
+            objRGBA = obj[OG.OBJ_RGBA]
+            
+            # load new model
+            newobj = loader.loadModel(model)
+            newobj.setTag('OBJRoot','1')
+
+            # reparent children
+            objNP.findAllMatches("=OBJRoot").reparentTo(newobj)
+            
+            # reparent to parent
+            newobj.reparentTo(objNP.getParent())
+
+            # copy transform
+            newobj.setPos(objNP.getPos())
+            newobj.setHpr(objNP.getHpr())
+            newobj.setScale(objNP.getScale())
+
+            # copy RGBA data
+            self.updateObjectColor(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], newobj)
+
+            # delete old geom
+            del self.npIndex[NodePath(objNP)]
+            objNP.removeNode()
+
+            # register new geom
+            obj[OG.OBJ_NP] = newobj
+            obj[OG.OBJ_MODEL] = model
+            self.npIndex[NodePath(newobj)] = obj[OG.OBJ_UID]
+
+            if fSelectObject:
+                base.direct.select(newobj, fUndo=0)        
+
+            self.editor.fNeedToSave = True
+
+    def updateObjectAnim(self, anim, obj, fSelectObject=True):
+        """ replace object's anim """
+        if obj[OG.OBJ_ANIM] != anim:
+            base.direct.deselectAllCB()
+            objNP = obj[OG.OBJ_NP]
+
+            # load new anim
+            animName = os.path.basename(anim)
+            newAnim = objNP.loadAnims({animName:anim})
+            objNP.loop(animName)
+            obj[OG.OBJ_ANIM] = anim
+            if fSelectObject:
+                base.direct.select(objNP, fUndo=0)
+
+            self.editor.fNeedToSave = True
+
+    def updateObjectModelFromUI(self, event, obj):
+        """ replace object's model with one selected from UI """
+        model = event.GetString()
+        if model is not None:
+            self.updateObjectModel(model, obj)
+
+    def updateObjectAnimFromUI(self, event, obj):
+        """ replace object's anim with one selected from UI """
+        anim = event.GetString()
+        if anim is not None:
+            self.updateObjectAnim(anim, obj)
+
+    def updateObjectProperty(self, event, obj, propName):
+        """
+        When an obj's property is updated in UI,
+        this will update it's value in data structure.
+        And call update function if defined.        
+        """
+        
+        objDef = obj[OG.OBJ_DEF]
+        objProp = obj[OG.OBJ_PROP]
+        
+        propDef = objDef.properties[propName]
+        if propDef is None:
+            return
+
+        propType = propDef[OG.PROP_TYPE]
+        propDataType = propDef[OG.PROP_DATATYPE]
+        
+        if propType == OG.PROP_UI_SLIDE:
+            if len(propDef) <= OG.PROP_RANGE:
+                return
+
+            strVal = event.GetString()
+            if strVal == '':
+                min = float(propDef[OG.PROP_RANGE][OG.RANGE_MIN])
+                max = float(propDef[OG.PROP_RANGE][OG.RANGE_MAX])
+                intVal = event.GetInt()
+                if intVal is None:
+                    return
+                val = intVal / 100.0 * (max - min) + min
+            else:
+                val = strVal
+
+        elif propType == OG.PROP_UI_ENTRY:
+            val = event.GetString()
+
+        elif propType == OG.PROP_UI_SPIN:
+            val = event.GetInt()
+
+        elif propType == OG.PROP_UI_CHECK:
+            if event.GetInt():
+                val = True
+            else:
+                val = False
+
+        elif propType == OG.PROP_UI_RADIO:
+            val = event.GetString()
+
+        elif propType == OG.PROP_UI_COMBO:
+            val = event.GetString()
+
+        else:
+            # unsupported property type
+            return
+
+        # now update object prop value and call update function
+        self.updateObjectPropValue(obj, propName, val, \
+                                   fSelectObject=(propType != OG.PROP_UI_SLIDE)
+                                   )
+
+    def updateObjectPropValue(self, obj, propName, val, fSelectObject=False):
+        """
+        Update object property value and
+        call update function if defined.         
+        """
+        objDef = obj[OG.OBJ_DEF]
+        objProp = obj[OG.OBJ_PROP]
+        
+        propDef = objDef.properties[propName]
+        propDataType = propDef[OG.PROP_DATATYPE]
+
+        if propDataType != OG.PROP_BLIND:
+            val = OG.TYPE_CONV[propDataType](val)
+            oldVal = objProp[propName]
+
+            if propDef[OG.PROP_FUNC] is None:
+                func = None
+                undoFunc = None
+            else:
+                funcName = propDef[OG.PROP_FUNC][OG.FUNC_NAME]
+                funcArgs = propDef[OG.PROP_FUNC][OG.FUNC_ARGS]
+
+                # populate keyword arguments
+                kwargs = {}
+                undoKwargs = {}
+                for key in funcArgs.keys():
+                    if funcArgs[key] == OG.ARG_VAL:
+                        kwargs[key] = val
+                        undoKwargs[key] = oldVal
+                    elif funcArgs[key] == OG.ARG_OBJ:
+                        undoKwargs[key] = obj
+                        objProp[propName] = val
+                        kwargs[key] = obj
+                    else:
+                        kwargs[key] = funcArgs[key]
+                        undoKwargs[key] = funcArgs[key]
+
+                if type(funcName) == types.StringType:
+                    if funcName.startswith('.'):
+                        if self.editor:
+                            func = Functor(eval("self.editor.objectHandler%s"%funcName), **kwargs)
+                            undoFunc = Functor(eval("self.editor.objectHandler%s"%funcName), **undoKwargs)
+                        else: # when loaded outside of LE
+                            func = Functor(eval("base.objectHandler%s"%funcName), **kwargs)
+                            undoFunc = Functor(eval("base.objectHandler%s"%funcName), **undoKwargs)                    
+                    else:
+                        func = Functor(eval(funcName), **kwargs)
+                        undoFunc = Functor(eval(funcName), **undoKwargs)
+                else:
+                    func = Functor(funcName, **kwargs)
+                    undoFunc = Functor(funcName, **undoKwargs)
+
+                # finally call update function
+                #func(**kwargs)
+        else:
+            oldVal = objProp[propName]            
+            func = None
+            undoFunc = None
+        action = ActionUpdateObjectProp(self.editor, fSelectObject, obj, propName, val, oldVal, func, undoFunc)
+        self.editor.actionMgr.push(action)
+        action()
+
+        if self.editor:
+            self.editor.fNeedToSave = True
+            if fSelectObject:
+                base.direct.select(obj[OG.OBJ_NP], fUndo=0)
+
+    def updateObjectProperties(self, nodePath, propValues):
+        """
+        When a saved level is loaded,
+        update an object's properties
+        And call update function if defined.
+        """
+
+        obj = self.findObjectByNodePath(nodePath)
+        if obj:
+            for propName in propValues:
+                self.updateObjectPropValue(obj, propName, propValues[propName])
+
+    def traverse(self, parent, parentId = None):
+        """
+        Trasverse scene graph to gather data for saving
+        """
+        
+        for child in parent.getChildren():
+            if child.hasTag('OBJRoot'):
+                obj = self.findObjectByNodePath(child)
+
+                if obj:
+                    uid = obj[OG.OBJ_UID]
+                    np = obj[OG.OBJ_NP]
+                    objDef = obj[OG.OBJ_DEF]
+                    objModel = obj[OG.OBJ_MODEL]
+                    objAnim = obj[OG.OBJ_ANIM]
+                    objProp = obj[OG.OBJ_PROP]
+                    objRGBA = obj[OG.OBJ_RGBA]
+
+                    if parentId:
+                        parentStr = "objects['%s']"%parentId
+                    else:
+                        parentStr = "None"
+
+                    if objModel:
+                        modelStr = "'%s'"%objModel
+                    else:
+                        modelStr = "None"
+
+                    if objAnim:
+                        animStr = "'%s'"%objAnim
+                    else:
+                        animStr = "None"
+
+                    self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', %s, %s, %s, fSelectObject=False)"%(uid, objDef.name, uid, modelStr, parentStr, animStr))
+                    self.saveData.append("if objects['%s']:"%uid)
+                    self.saveData.append("    objects['%s'].setPos(%s)"%(uid, np.getPos()))
+                    self.saveData.append("    objects['%s'].setHpr(%s)"%(uid, np.getHpr()))
+                    self.saveData.append("    objects['%s'].setScale(%s)"%(uid, np.getScale()))
+                    self.saveData.append("    objectMgr.updateObjectColor(%f, %f, %f, %f, objects['%s'])"%(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], uid))
+                    self.saveData.append("    objectMgr.updateObjectProperties(objects['%s'], %s)"%(uid,objProp))
+                    
+                self.traverse(child, uid)
+
+    def getSaveData(self):
+        self.saveData = []
+        self.traverse(render)
+        return self.saveData
+
+    def duplicateObject(self, nodePath, parent=None):
+        obj = self.findObjectByNodePath(nodePath)
+        if obj is None:
+            return None
+        objDef = obj[OG.OBJ_DEF]
+        objModel = obj[OG.OBJ_MODEL]
+        objAnim = obj[OG.OBJ_ANIM]
+        objRGBA = obj[OG.OBJ_RGBA]
+
+        if parent is None:
+            parentNP = nodePath.getParent()
+            parentObj = self.findObjectByNodePath(parentNP)
+            if parentObj is None:
+                parent = parentNP
+            else:
+                parent = parentObj[OG.OBJ_NP]
+
+        newObjNP = self.addNewObject(objDef.name, parent=parent, fSelectObject = False)
+
+        # copy transform data
+        newObjNP.setPos(obj[OG.OBJ_NP].getPos())
+        newObjNP.setHpr(obj[OG.OBJ_NP].getHpr())
+        newObjNP.setScale(obj[OG.OBJ_NP].getScale())
+
+        newObj = self.findObjectByNodePath(NodePath(newObjNP))
+        if newObj is None:
+            return None
+        # copy model info
+        self.updateObjectModel(obj[OG.OBJ_MODEL], newObj, fSelectObject=False)
+
+        # copy anim info
+        self.updateObjectAnim(obj[OG.OBJ_ANIM], newObj, fSelectObject=False)
+
+        # copy other properties
+        for key in obj[OG.OBJ_PROP]:
+            self.updateObjectPropValue(newObj, key, obj[OG.OBJ_PROP][key])
+        return newObjNP
+
+    def duplicateChild(self, nodePath, parent):
+        children = nodePath.findAllMatches('=OBJRoot')
+        for childNP in children:
+            newChildObjNP = self.duplicateObject(childNP, parent)
+            if newChildObjNP is not None:
+                self.duplicateChild(childNP, newChildObjNP)
+    
+    def duplicateSelected(self):
+        selectedNPs = base.direct.selected.getSelectedAsList()
+        duplicatedNPs = []
+        for nodePath in selectedNPs:
+            newObjNP = self.duplicateObject(nodePath)
+            if newObjNP is not None:
+                self.duplicateChild(nodePath, newObjNP)
+                duplicatedNPs.append(newObjNP)
+
+        base.direct.deselectAllCB()
+        for newNodePath in duplicatedNPs:
+            base.direct.select(newNodePath, fMultiSelect = 1, fUndo=0)
+
+        self.editor.fNeedToSave = True
+
+    def makeSelectedLive(self):
+        obj = self.findObjectByNodePath(base.direct.selected.last)
+        if obj:
+            if self.currLiveNP:
+                self.currLiveNP.clearColorScale()
+                if self.currLiveNP == obj[OG.OBJ_NP]:
+                    self.currLiveNP = None
+                    return
+
+            self.currLiveNP = obj[OG.OBJ_NP]
+            self.currLiveNP.setColorScale(0, 1, 0, 1)