"""
Defines ObjectMgrBase
"""

import os, time, wx, types, copy

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

# python wrapper around a panda.NodePath object
class PythonNodePath(NodePath):
    def __init__(self,node):
        NodePath.__init__(self,node)

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
        
        self.Actor = []
        self.findActors(render)
        self.Nodes = []
        self.findNodes(render)

    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 = []
        self.Actor = []
        self.Nodes = []

    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 addNewCurveFromFile(self, curveInfo, degree, uid=None, parent=None, fSelectObject=True, nodePath=None):
        """ function to add new curve to the scene from file"""
        curve = []
        curveControl = []
        
        #transfer the curve information from simple positions into control nodes
        for item in curveInfo:
            controler = render.attachNewNode("controler")
            controler = loader.loadModel('models/misc/smiley')
            controlerPathname = 'controler%d' % item[0]
            controler.setName(controlerPathname)
            controler.setPos(item[1])
            controler.setColor(0, 0, 0, 1)
            controler.setScale(0.2)
            controler.reparentTo(render)
            controler.setTag('OBJRoot','1')
            controler.setTag('Controller','1') 
            curve.append((None, item[1]))
            curveControl.append((item[0], controler))
        
        self.editor.curveEditor.degree = degree
        self.editor.curveEditor.ropeUpdate (curve)
        #add new curve to the scene
        curveObjNP = self.addNewCurve(curveControl, degree, uid, parent, fSelectObject, nodePath = self.editor.curveEditor.currentRope)
        curveObj = self.findObjectByNodePath(curveObjNP)
        self.editor.objectMgr.updateObjectPropValue(curveObj, 'Degree', degree, fSelectObject=False, fUndo=False)
        
        for item in curveControl:
            item[1].reparentTo(curveObjNP)
            item[1].hide()
        
        curveControl = []
        curve = []
        self.editor.curveEditor.currentRope = None
        
        return curveObjNP

    def addNewCurve(self, curveInfo, degree, uid=None, parent=None, fSelectObject=True, nodePath=None):
        """ function to add new curve to the scene"""
        if parent is None:
            parent = self.editor.NPParent
        
        if uid is None:
            uid = self.genUniqueId()

        if self.editor:
            objDef = self.editor.objectPalette.findItem('__Curve__')
        
        if nodePath is None:
            # we need to create curve
            # and then create newobj with newly created curve
            pass
        else:
            newobj = nodePath

        newobj.reparentTo(parent)
        newobj.setTag('OBJRoot','1')

        # populate obj data using default values
        properties = {}
        for key in objDef.properties.keys():
            properties[key] = objDef.properties[key][OG.PROP_DEFAULT]

        properties['Degree'] = degree
        properties['curveInfo'] = curveInfo
            
        # insert obj data to main repository
        self.objects[uid] = [uid, newobj, objDef, None, None, 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, parent)
            self.editor.fNeedToSave = True

        return newobj

    def addNewObject(self, typeName, uid = None, model = None, parent=None, anim = None, fSelectObject=True, nodePath=None, nameStr=None):
        """ function to add new obj to the scene """
        if parent is None:
            parent = self.editor.NPParent

        if uid is None:
            uid = self.genUniqueId()

        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 not hasattr(objDef, 'createFunction'):
                return newobj
            if nodePath is None:
                if objDef.createFunction:
                    funcName = objDef.createFunction[OG.FUNC_NAME]
                    funcArgs = copy.deepcopy(objDef.createFunction[OG.FUNC_ARGS])

                    for pair in funcArgs.items():
                        if pair[1] == OG.ARG_NAME:
                            funcArgs[pair[0]] = nameStr
                        elif pair[1] == OG.ARG_PARENT:
                            funcArgs[pair[0]] = parent

                    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())
                    if hasattr(objDef, 'animDict') and objDef.animDict != {}:
                        objDef.anims = objDef.animDict.get(model)
                        
                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:
                        newobjModel = loader.loadModel(model)
                    except:
                        newobjModel = loader.loadModel(Filename.fromOsSpecific(model).getFullpath(), okMissing=True)
                    if newobjModel:
                        self.flatten(newobjModel, model, objDef, uid)
                        newobj = PythonNodePath(newobjModel)
                    else:
                        newobj = None
                        
                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')

            # 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, parent)
                self.editor.fNeedToSave = True
        return newobj

    def removeObjectById(self, uid):
        obj = self.findObjectById(uid)
        nodePath = obj[OG.OBJ_NP]
        
        for i in range(0,len(self.Actor)):
            if self.Actor[i] == obj:
                del self.Actor[i]
                break
        for i in range(0,len(self.Nodes)):
            if self.Nodes[i][OG.OBJ_UID] == uid:
                del self.Nodes[i]
                break
        self.editor.animMgr.removeAnimInfo(obj[OG.OBJ_UID])
        
        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:
            for i in range(0,len(self.Actor)):
                if self.Actor[i][OG.OBJ_UID] == uid:
                    del self.Actor[i]
                    break
            for i in range(0,len(self.Nodes)):
                if self.Nodes[i][OG.OBJ_UID] == uid:
                    del self.Nodes[i]
                    break
            self.editor.animMgr.removeAnimInfo(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 findObjectByNodePathBelow(self, nodePath):
        for ancestor in nodePath.getAncestors():
            if ancestor.hasTag('OBJRoot'):
                return self.findObjectByNodePath(ancestor)

        return None

    def findObjectsByTypeName(self, typeName):
        results = []
        for uid in self.objects.keys():
            obj = self.objects[uid]
            if obj[OG.OBJ_DEF].name == typeName:
                results.append(obj)

        return results

    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.selectObjectCB(obj, fLEPane)

    def selectObjectCB(self, obj, fLEPane):
        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\
               not child.hasTag('_le_sys') 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]
            objDef = obj[OG.OBJ_DEF]
            objRGBA = obj[OG.OBJ_RGBA]
            uid = obj[OG.OBJ_UID]
            
            # load new model
            if objDef.actor:
                try:
                    newobj = Actor(model)
                except:
                    newobj = Actor(Filename.fromOsSpecific(model).getFullpath())
            else:
                newobjModel = loader.loadModel(model, okMissing=True)
                if newobjModel is None:
                    print "Can't load model %s"%model
                    return
                self.flatten(newobjModel, model, objDef, uid)
                newobj = PythonNodePath(newobjModel)
            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]

            # update scene graph label
            self.editor.ui.sceneGraphUI.changeLabel(obj[OG.OBJ_UID], newobj.getName())

            self.editor.fNeedToSave = True
            # update anim if necessary
            animList = obj[OG.OBJ_DEF].animDict.get(model)
            if animList:
                self.updateObjectAnim(animList[0], obj, fSelectObject=fSelectObject)
            else:
                if fSelectObject:
                    base.direct.select(newobj, fUndo=0)        

    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()

        elif propType == OG.PROP_UI_COMBO_DYNAMIC:
            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, fUndo=True):
        """
        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
                    elif funcArgs[key] == OG.ARG_NOLOADING:
                        kwargs[key] = fSelectObject
                        undoKwargs[key] = fSelectObject
                    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)
        if fUndo:
            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 updateCurve(self, val, obj):
        curve = obj[OG.OBJ_NP]
        degree = int(val)
        curveNode = obj[OG.OBJ_PROP]['curveInfo']
        curveInfor = []
        for item in curveNode:
                curveInfor.append((None, item[1].getPos()))
        curve.setup(degree, curveInfor)

    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') and not child.hasTag('Controller'):
                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"

                    if objDef.named:
                        nameStr = "'%s'"%np.getName()
                    else:
                        nameStr = "None"

                    if objDef.name == '__Curve__':
                        #transfer the curve information from control nodes into simple positions for file save
                        objCurveInfo = obj[OG.OBJ_PROP]['curveInfo']
                        self.objDegree = obj[OG.OBJ_PROP]['Degree']
                        newobjCurveInfo = []
                        for item in objCurveInfo:
                            newobjCurveInfo.append((item[0], item[1].getPos()))
                            
                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewCurveFromFile(%s, %s, '%s', %s, False, None)"%(uid, newobjCurveInfo, self.objDegree, uid, parentStr))    
                    else:
                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', %s, %s, %s, False, None, %s)"%(uid, objDef.name, uid, modelStr, parentStr, animStr, nameStr))
                    
                    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))
                    
                    if objDef.name == '__Curve__':
                        pass
                    else:
                        self.saveData.append("    objectMgr.updateObjectProperties(objects['%s'], %s)"%(uid,objProp))
                    
                self.traverse(child, uid)

    def getSaveData(self):
        self.saveData = []
        self.getPreSaveData()
        self.traverse(render)
        self.getPostSaveData()
        return self.saveData

    def getPreSaveData(self):
        """
        if there are additional data to be saved before main data
        you can override this function to populate data
        """
        pass

    def getPostSaveData(self):
        """
        if there are additional data to be saved after main data
        you can override this function to populate data
        """
        pass

    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)

    def replaceObjectWithTypeName(self, obj, typeName):
        uid = obj[OG.OBJ_UID]
        objNP = obj[OG.OBJ_NP]
        mat = objNP.getMat()
        parentObj = self.findObjectByNodePath(objNP.getParent())
        if parentObj:
            parentNP = parentObj[OG.OBJ_NP]
        else:
            parentNP = None
        self.removeObjectById(uid)
        self.editor.ui.sceneGraphUI.delete(uid)
        newobj = self.addNewObject(typeName, uid, parent=parentNP, fSelectObject=False)
        newobj.setMat(mat)

    def flatten(self, newobjModel, model, objDef, uid):
        # override this to flatten models
        pass
        
    def findActors(self, parent):
        for child in parent.getChildren():
            if child.hasTag('OBJRoot') and not child.hasTag('Controller'):
                obj = self.findObjectByNodePath(child)

                if obj:
                    if isinstance(obj[OG.OBJ_NP],Actor):
                        self.Actor.append(obj)
                    
                self.findActors(child)

        
    def findNodes(self, parent):
        for child in parent.getChildren():
            if child.hasTag('OBJRoot') and not child.hasTag('Controller'):
                obj = self.findObjectByNodePath(child)

                if obj:
                    self.Nodes.append(obj)
                    
                self.findActors(child)
            
                    
