Selaa lähdekoodia

Added feature to import Maya file, undo/redo for object deletion

Gyedo Jeon 16 vuotta sitten
vanhempi
sitoutus
f8fe9ef9f6

+ 95 - 10
direct/src/leveleditor/ActionMgr.py

@@ -1,8 +1,20 @@
+import ObjectGlobals as OG
+
 class ActionBase(Functor):
     """ Base class for user actions """
 
     def __init__(self, function, *args, **kargs):
         Functor.__init__(self, function, *args, **kargs)
+        self.result = None
+
+    def _do__call__(self, *args, **kargs):
+        self.saveStatus()
+        self.result = Functor._do__call__(self, *args, **kargs)
+        return self.result
+
+    def saveStatus(self):
+        # save object status for undo here
+        pass
         
     def undo(self):
         print "undo method is not defined for this action"
@@ -43,19 +55,92 @@ class ActionMgr:
 class ActionAddNewObj(ActionBase):
     """ Action class for adding new object """
     
-    def __init__(self, function, *args, **kargs):
+    def __init__(self, editor, *args, **kargs):
+        self.editor = editor
+        function = self.editor.objectMgr.addNewObject
         ActionBase.__init__(self, function, *args, **kargs)
-        self.np = None
 
-    def _do__call__(self, *args, **kargs):
-        self.np = ActionBase._do__call__(self, *args, **kargs)
-        return self.np
+    def undo(self):
+        if self.result is None:
+            print "Can't undo this"
+        else:
+            base.direct.deselect(self.result)
+            base.direct.removeNodePath(self.result)
+            self.result = None
+
+class ActionDeleteObj(ActionBase):
+    """ Action class for deleting object """
+
+    def __init__(self, editor, *args, **kargs):
+        self.editor = editor
+        function = base.direct.removeAllSelected
+        ActionBase.__init__(self, function, *args, **kargs)
+        self.selectedUIDs = []
+        self.hierarchy = {}
+        self.objInfos = {}
+
+    def saveStatus(self):
+        selectedNPs = base.direct.selected.getSelectedAsList()
+        def saveObjStatus(np, isRecursive=True):
+            obj = self.editor.objectMgr.findObjectByNodePath(np)
+            if obj:
+                uid = obj[OG.OBJ_UID]
+                if not isRecursive:
+                    self.selectedUIDs.append(uid)
+                objNP = obj[OG.OBJ_NP]
+                self.objInfos[uid] = obj
+                parentNP = objNP.getParent()
+                if parentNP == render:
+                    self.hierarchy[uid] = None
+                else:
+                    parentObj = self.editor.objectMgr.findObjectByNodePath(parentNP)
+                    if parentObj:
+                        self.hierarchy[uid] = parentObj[OG.OBJ_UID]
+
+                for child in np.getChildren():
+                    if child.hasTag('OBJRoot'):
+                        saveObjStatus(child)
+
+        for np in selectedNPs:
+            saveObjStatus(np, False)
 
     def undo(self):
-        if self.np is None:
+        if len(self.hierarchy.keys()) == 0 or\
+           len(self.objInfos.keys()) == 0:
             print "Can't undo this"
         else:
-            base.direct.removeAllSelected()
-            base.le.objectMgr.deselectAll()
-            base.direct.removeNodePath(self.np)
-            self.np = None
+            def restoreObject(uid, parentNP):
+                obj = self.objInfos[uid]
+                objDef = obj[OG.OBJ_DEF]
+                objModel = obj[OG.OBJ_MODEL]
+                objProp = obj[OG.OBJ_PROP]
+                objRGBA = obj[OG.OBJ_RGBA]
+                self.editor.objectMgr.addNewObject(objDef.name,
+                                                   uid,
+                                                   obj[OG.OBJ_MODEL],
+                                                   parentNP)
+                self.editor.objectMgr.updateObjectColor(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], uid)
+                self.editor.objectMgr.updateObjectProperties(uid, objProp)
+
+            while (len(self.hierarchy.keys()) > 0):
+                for uid in self.hierarchy.keys():
+                    if self.hierarchy[uid] is None:
+                        parentNP = None
+                        restoreObject(uid, parentNP)
+                        del self.hierarchy[uid]
+                    else:
+                        parentObj = self.editor.objectMgr.findObjectById(self.hierarchy[uid])
+                        if parentObj:
+                            parentNP = parentObj[OG.OBJ_NP]
+                            restoreObject(uid, parentNP)
+                            del self.hierarchy[uid]
+
+            base.direct.deselectAll()
+            for uid in self.selectedUIDs:
+                obj = self.editor.objectMgr.findObjectById(uid)
+                if obj:
+                    base.direct.select(obj[OG.OBJ_NP], fMultiSelect=1)
+
+            self.selecteUIDs = []
+            self.hierarchy = {}
+            self.objInfos = {}            

+ 31 - 15
direct/src/leveleditor/LevelEditorBase.py

@@ -16,6 +16,7 @@ from ObjectMgr import *
 from FileMgr import *
 from ActionMgr import *
 from ProtoPalette import *
+from MayaConverter import *
 
 class LevelEditorBase(DirectObject):
     """ Base Class for Panda3D LevelEditor """ 
@@ -110,6 +111,7 @@ class LevelEditorBase(DirectObject):
             ('DIRECT-delete', self.handleDelete),
             ('preRemoveNodePath', self.removeNodePathHook),
             ('DIRECT_selectedNodePath_fMulti_fTag', self.selectedNodePathHook),
+            ('DIRECT_deselectedNodePath', self.deselectAll),
             ('DIRECT_deselectAll', self.deselectAll),
             ('LE-Undo', self.actionMgr.undo),
             ('LE-Redo', self.actionMgr.redo),
@@ -140,20 +142,22 @@ class LevelEditorBase(DirectObject):
             base.direct.selected.last = None
 
     def handleDelete(self):
-        reply = wx.MessageBox("Do you want to delete selected?", "Delete?",
-                              wx.YES_NO | wx.ICON_QUESTION)
-        if reply == wx.YES:
-            base.direct.removeAllSelected()
-            self.objectMgr.deselectAll()
-        else:
-            # need to reset COA
-            dnp = base.direct.selected.last
-            # Update camera controls coa to this point
-            # Coa2Camera = Coa2Dnp * Dnp2Camera
-            mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(base.direct.camera)
-            row = mCoa2Camera.getRow(3)
-            coa = Vec3(row[0], row[1], row[2])
-            base.direct.cameraControl.updateCoa(coa)
+        action = ActionDeleteObj(self)
+        self.actionMgr.push(action)
+        action()
+##         reply = wx.MessageBox("Do you want to delete selected?", "Delete?",
+##                               wx.YES_NO | wx.ICON_QUESTION)
+##         if reply == wx.YES:
+##             base.direct.removeAllSelected()
+##         else:
+##             # need to reset COA
+##             dnp = base.direct.selected.last
+##             # Update camera controls coa to this point
+##             # Coa2Camera = Coa2Dnp * Dnp2Camera
+##             mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(base.direct.camera)
+##             row = mCoa2Camera.getRow(3)
+##             coa = Vec3(row[0], row[1], row[2])
+##             base.direct.cameraControl.updateCoa(coa)
 
     def selectedNodePathHook(self, nodePath, fMultiSelect = 0, fSelectTag = 1):
         # handle unpickable nodepath
@@ -163,7 +167,7 @@ class LevelEditorBase(DirectObject):
 
         self.objectMgr.selectObject(nodePath)
 
-    def deselectAll(self):
+    def deselectAll(self, np=None):
         self.objectMgr.deselectAll()
 
     def reset(self):
@@ -221,3 +225,15 @@ class LevelEditorBase(DirectObject):
         except:
             pass
 
+
+    def convertMaya(self, modelname, obj=None, isAnim=False):
+        if obj and isAnim:
+            mayaConverter = MayaConverter(self.ui, self, modelname, obj, isAnim)
+        else:
+            reply = wx.MessageBox("Is it an animation file?", "Animation?",
+                              wx.YES_NO | wx.ICON_QUESTION)
+            if reply == wx.YES:
+                mayaConverter = MayaConverter(self.ui, self, modelname, None, True)
+            else:        
+                mayaConverter = MayaConverter(self.ui, self, modelname, None, False)
+        mayaConverter.Show()

+ 1 - 1
direct/src/leveleditor/LevelEditorUI.py

@@ -20,7 +20,7 @@ class PandaTextDropTarget(wx.TextDropTarget):
         self.editor = editor
 
     def OnDropText(self, x, y, text):
-        action = ActionAddNewObj(self.editor.objectMgr.addNewObject, text)
+        action = ActionAddNewObj(self.editor, text)
         self.editor.actionMgr.push(action)
         action()
 

+ 154 - 0
direct/src/leveleditor/MayaConverter.py

@@ -0,0 +1,154 @@
+from direct.wxwidgets.WxAppShell import *
+import os, re, shutil
+
+from ObjectPaletteBase import *
+
+CLOSE_STDIN = "<CLOSE STDIN>"
+
+class Process:
+    def __init__(self, parent, cmd, end_callback):
+        self.process = wx.Process(parent)
+        self.process.Redirect()
+        self.process.pid = wx.Execute(cmd, wx.EXEC_ASYNC|wx.EXEC_MAKE_GROUP_LEADER, self.process)
+        self.b = []
+        if self.process.pid:
+            #what was up with wx.Process.Get*Stream names?
+            self.process._stdin_ = self.process.GetOutputStream()
+            self.process._stdout_ = self.process.GetInputStream()
+            self.process._stderr_ = self.process.GetErrorStream()
+            self.process.Bind(wx.EVT_END_PROCESS, end_callback)
+            return
+        raise StartupError
+            
+    def Poll(self, input=''):
+        if (input or self.b) and self.process and self.process._stdin_:
+            if self.b or len(input) > 512:
+                if input:
+                    #if we don't chop up our input into resonably sized chunks,
+                    #some platforms (like Windows) will send some small number
+                    #of bytes per .write() call (sometimes 2 in the case of
+                    #Windows).
+                    self.b.extend([input[i:i+512] for i in xrange(0, len(input), 512)])
+                input = self.b.pop(0)
+            self.process._stdin_.write(input)
+            if hasattr(self.process._stdin_, "LastWrite"):
+                y = self.process._stdin_.LastWrite()
+                if y != len(input):
+                    self.b.insert(0, input[y:])
+        x = []
+        for s in (self.process._stderr_, self.process._stdout_):
+            if s and s.CanRead():
+                x.append(s.read())
+            else:
+                x.append('')
+        return x
+        
+    def CloseInp(self):
+        if self.process and self.process._stdin_:
+            self.process.CloseOutput()
+            self.process._stdin_ = None
+    
+    def Kill(self, ks='SIGKILL'):
+        errors = {wx.KILL_BAD_SIGNAL: "KILL_BAD_SIGNAL",
+                  wx.KILL_ACCESS_DENIED: "KILL_ACCESS_DENIED",
+                  wx.KILL_ERROR: "KILL_ERROR"}
+        if self.process:
+            if ks == CLOSE_STDIN:
+                self.CloseInp()
+                return 1, None
+            elif wx.Process.Exists(self.process.pid):
+                signal = getattr(wx, ks)
+                r = wx.Process.Kill(self.process.pid, signal, flags=wx.KILL_CHILDREN)
+            else:
+                r = 65535
+                self.CloseInp()
+                return 1, None
+            
+            if r not in (wx.KILL_OK, wx.KILL_NO_PROCESS, 65535):
+                return 0, (self.process.pid, signal, errors.get(r, "UNKNOWN_KILL_ERROR %s"%r))
+            else:
+                return 1, None
+
+class MayaConverter(wx.Dialog):
+    def __init__(self, parent, editor, mayaFile, obj = None, isAnim=False):
+        wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Maya Converter",
+                           pos=wx.DefaultPosition, size=(300, 200))
+
+        self.editor = editor
+        self.obj = obj
+        self.isAnim = isAnim
+
+        self.mainPanel = wx.Panel(self, -1)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self.mainPanel, 1, wx.EXPAND, 0)
+        self.SetSizer(sizer)
+
+        self.output = wx.TextCtrl(self.mainPanel, -1, style = wx.TE_MULTILINE, pos = (0, 0), size = (100, 400))
+        sizer2 = wx.BoxSizer(wx.VERTICAL)
+        sizer2.Add(self.output, 1, wx.EXPAND, 0)
+        self.mainPanel.SetSizer(sizer2)
+
+        if self.isAnim:
+            if self.obj:
+                command = 'maya2egg -a chan %s -o %s.anim.egg'%(mayaFile, mayaFile)
+                self.process = Process(self, command, lambda p0=None, p1=mayaFile: self.onProcessEnded(p0, p1))
+            else:
+                command = 'maya2egg -a model %s -o %s.model.egg'%(mayaFile, mayaFile)
+                self.process = Process(self, command, lambda p0=None, p1=mayaFile: self.onModelProcessEnded(p0, p1))
+        else:
+            command = 'maya2egg %s -o %s.egg'%(mayaFile, mayaFile)
+            self.process = Process(self, command, lambda p0=None, p1=mayaFile: self.onProcessEnded(p0, p1))
+
+        self.timer = wx.Timer(self, -1)
+        self.Bind(wx.EVT_TIMER, self.onPoll, self.timer)
+        self.timer.Start(100)
+        
+    def onPoll(self, evt):
+        if self.process:
+            for i in self.process.Poll():
+                self.output.AppendText(i)
+
+    def onModelProcessEnded(self, evt, mayaFile):
+        self.process.CloseInp()
+        for i in self.process.Poll():
+            self.output.AppendText(i)        
+        self.process = None
+        command = 'maya2egg -a chan %s -o %s.anim.egg'%(mayaFile, mayaFile)
+        self.process = Process(self, command, lambda p0 = None, p1=mayaFile: self.onProcessEnded(p0, p1))
+
+    def onProcessEnded(self, evt, mayaFile):
+        self.process.CloseInp()
+        for i in self.process.Poll():
+            self.output.AppendText(i)
+
+        self.output.AppendText('Converting %s is finished\n'%mayaFile)
+        self.process = None
+
+        name = os.path.basename(mayaFile)
+        if self.isAnim:
+            if self.obj:
+                objDef = self.obj[OG.OBJ_DEF]
+                objNP = self.obj[OG.OBJ_NP]
+                animName = "%s.anim.egg"%mayaFile                
+                if animName not in objDef.anims:
+                    objDef.anims.append(animName)
+                name = os.path.basename(animName)
+                objNP.loadAnims({name:animName})
+                objNP.loop(name)
+                self.obj[OG.OBJ_ANIM] = animName
+                self.editor.ui.objectPropertyUI.updateProps(self.obj)
+                return
+            else:
+                modelName = "%s.model.egg"%mayaFile
+                animName = "%s.anim.egg"%mayaFile
+                itemData = ObjectBase(name=name, model=modelName, anims=[animName], actor=True)
+        else:
+            modelName = "%s.egg"%mayaFile
+            itemData = ObjectBase(name=name, model=modelName, actor=False)
+
+        self.editor.protoPalette.add(itemData)
+
+        newItem = self.editor.ui.protoPaletteUI.tree.AppendItem(self.editor.ui.protoPaletteUI.root, name)
+        self.editor.ui.protoPaletteUI.tree.SetItemPyData(newItem, itemData)
+        self.editor.ui.protoPaletteUI.tree.ScrollTo(newItem)
+            

+ 37 - 9
direct/src/leveleditor/ObjectMgr.py

@@ -58,7 +58,7 @@ class ObjectMgr:
             self.lastUidMod = 0
         return newUid
 
-    def addNewObject(self, typeName, uid = None, model = None, parent=None, fSelectObject=True):
+    def addNewObject(self, typeName, uid = None, model = None, parent=None, anim = None, fSelectObject=True):
         """ function to add new obj to the scene """
         if parent is None:
             parent = render
@@ -105,7 +105,6 @@ class ObjectMgr:
                 except:
                     newobj = loader.loadModel(Filename.fromOsSpecific(model).getFullpath())
 
-            anim = ''
             i = 0
             for i in range(len(objDef.anims)):
                 animFile = objDef.anims[i]
@@ -114,9 +113,14 @@ class ObjectMgr:
                 if i < len(objDef.animNames):
                     animName = objDef.animNames[i]
                 newAnim = newobj.loadAnims({animName:animFile})
-                if i == 0:
-                    anim = animFile
-                    newobj.loop(animName)
+
+                if anim:
+                    if anim == animFile:
+                        newobj.loop(animName)
+                else:
+                    if i == 0:
+                        anim = animFile
+                        newobj.loop(animName)
 
             if newobj is None:
                 return None
@@ -151,6 +155,11 @@ class ObjectMgr:
             del self.objects[uid]
             del self.npIndex[nodePath]
 
+        # remove children also
+        for child in nodePath.getChildren():
+            if child.hasTag('OBJRoot'):
+                self.removeObjectByNodePath(child)
+
     def findObjectById(self, uid):
         return self.objects.get(uid)
 
@@ -290,7 +299,8 @@ class ObjectMgr:
             base.direct.deselectAll()
 
             objNP = obj[OG.OBJ_NP]
-
+            objRGBA = obj[OG.OBJ_RGBA]
+            
             # load new model
             newobj = loader.loadModel(model)
             newobj.setTag('OBJRoot','1')
@@ -306,6 +316,9 @@ class ObjectMgr:
             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()
@@ -474,6 +487,7 @@ class ObjectMgr:
                     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]
 
@@ -482,10 +496,17 @@ class ObjectMgr:
                     else:
                         parentStr = "None"
 
-                    if objModel is None:
-                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', None, %s)"%(uid, objDef.name, uid, parentStr))
+                    if objModel:
+                        modelStr = "'%s'"%objModel
+                    else:
+                        modelStr = "None"
+
+                    if objAnim:
+                        animStr = "'%s'"%objAnim
                     else:
-                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', '%s', %s)"%(uid, objDef.name, uid, objModel, parentStr))
+                        animStr = "None"
+
+                    self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', %s, %s, %s)"%(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()))
@@ -505,6 +526,10 @@ class ObjectMgr:
         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:
             parent = nodePath.getParent()
 
@@ -521,6 +546,9 @@ class ObjectMgr:
         # 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])

+ 1 - 4
direct/src/leveleditor/ObjectPaletteBase.py

@@ -38,11 +38,8 @@ class ObjectPaletteBase:
         """
         You can insert item to obj palette tree.
 
-        'item' is the name to be inserted, it can be either a group or obj.
-        If item is a group 'obj' will be None.
+        'item' is the object to be inserted, it can be either a group or obj.
         'parentName' is the name of parent under where this item will be inserted.
-        'data' is used in recursive searching.
-
         """
         if type(self.data) != dict:
            return None

+ 5 - 0
direct/src/leveleditor/ObjectPropertyUI.py

@@ -29,6 +29,11 @@ class AnimFileDrop(wx.FileDropTarget):
         for filename in filenames:
             name = os.path.basename(filename)
             animName = Filename.fromOsSpecific(filename).getFullpath()
+            if name.endswith('.mb') or\
+               name.endswith('.ma'):
+                self.editor.convertMaya(animName, obj, isAnim=True)
+                return
+
             if animName not in objDef.anims:
                 objDef.anims.append(animName)
 

+ 4 - 0
direct/src/leveleditor/ProtoPaletteUI.py

@@ -21,6 +21,10 @@ class FileDrop(wx.FileDropTarget):
                return
 
             modelname = Filename.fromOsSpecific(filename).getFullpath()
+            if modelname.endswith('.mb') or\
+               modelname.endswith('.ma'):
+                self.editor.convertMaya(modelname)
+                return
             itemData = ObjectBase(name=name, model=modelname, actor=True)
             self.editor.protoPalette.add(itemData)