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

This commit is contained in:
Gyedo Jeon 2010-02-05 01:12:21 +00:00
parent c62725336f
commit f8fe9ef9f6
8 changed files with 329 additions and 40 deletions

View File

@ -1,8 +1,20 @@
import ObjectGlobals as OG
class ActionBase(Functor): class ActionBase(Functor):
""" Base class for user actions """ """ Base class for user actions """
def __init__(self, function, *args, **kargs): def __init__(self, function, *args, **kargs):
Functor.__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): def undo(self):
print "undo method is not defined for this action" print "undo method is not defined for this action"
@ -43,19 +55,92 @@ class ActionMgr:
class ActionAddNewObj(ActionBase): class ActionAddNewObj(ActionBase):
""" Action class for adding new object """ """ 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) 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): def undo(self):
if self.np is None: if self.result is None:
print "Can't undo this" print "Can't undo this"
else: else:
base.direct.removeAllSelected() base.direct.deselect(self.result)
base.le.objectMgr.deselectAll() base.direct.removeNodePath(self.result)
base.direct.removeNodePath(self.np) self.result = None
self.np = 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 len(self.hierarchy.keys()) == 0 or\
len(self.objInfos.keys()) == 0:
print "Can't undo this"
else:
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 = {}

View File

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

View File

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

View File

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

View File

@ -58,7 +58,7 @@ class ObjectMgr:
self.lastUidMod = 0 self.lastUidMod = 0
return newUid 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 """ """ function to add new obj to the scene """
if parent is None: if parent is None:
parent = render parent = render
@ -105,7 +105,6 @@ class ObjectMgr:
except: except:
newobj = loader.loadModel(Filename.fromOsSpecific(model).getFullpath()) newobj = loader.loadModel(Filename.fromOsSpecific(model).getFullpath())
anim = ''
i = 0 i = 0
for i in range(len(objDef.anims)): for i in range(len(objDef.anims)):
animFile = objDef.anims[i] animFile = objDef.anims[i]
@ -114,9 +113,14 @@ class ObjectMgr:
if i < len(objDef.animNames): if i < len(objDef.animNames):
animName = objDef.animNames[i] animName = objDef.animNames[i]
newAnim = newobj.loadAnims({animName:animFile}) newAnim = newobj.loadAnims({animName:animFile})
if i == 0:
anim = animFile if anim:
newobj.loop(animName) if anim == animFile:
newobj.loop(animName)
else:
if i == 0:
anim = animFile
newobj.loop(animName)
if newobj is None: if newobj is None:
return None return None
@ -151,6 +155,11 @@ class ObjectMgr:
del self.objects[uid] del self.objects[uid]
del self.npIndex[nodePath] del self.npIndex[nodePath]
# remove children also
for child in nodePath.getChildren():
if child.hasTag('OBJRoot'):
self.removeObjectByNodePath(child)
def findObjectById(self, uid): def findObjectById(self, uid):
return self.objects.get(uid) return self.objects.get(uid)
@ -290,7 +299,8 @@ class ObjectMgr:
base.direct.deselectAll() base.direct.deselectAll()
objNP = obj[OG.OBJ_NP] objNP = obj[OG.OBJ_NP]
objRGBA = obj[OG.OBJ_RGBA]
# load new model # load new model
newobj = loader.loadModel(model) newobj = loader.loadModel(model)
newobj.setTag('OBJRoot','1') newobj.setTag('OBJRoot','1')
@ -306,6 +316,9 @@ class ObjectMgr:
newobj.setHpr(objNP.getHpr()) newobj.setHpr(objNP.getHpr())
newobj.setScale(objNP.getScale()) newobj.setScale(objNP.getScale())
# copy RGBA data
self.updateObjectColor(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], newobj)
# delete old geom # delete old geom
del self.npIndex[NodePath(objNP)] del self.npIndex[NodePath(objNP)]
objNP.removeNode() objNP.removeNode()
@ -474,6 +487,7 @@ class ObjectMgr:
np = obj[OG.OBJ_NP] np = obj[OG.OBJ_NP]
objDef = obj[OG.OBJ_DEF] objDef = obj[OG.OBJ_DEF]
objModel = obj[OG.OBJ_MODEL] objModel = obj[OG.OBJ_MODEL]
objAnim = obj[OG.OBJ_ANIM]
objProp = obj[OG.OBJ_PROP] objProp = obj[OG.OBJ_PROP]
objRGBA = obj[OG.OBJ_RGBA] objRGBA = obj[OG.OBJ_RGBA]
@ -482,10 +496,17 @@ class ObjectMgr:
else: else:
parentStr = "None" parentStr = "None"
if objModel is None: if objModel:
self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', None, %s)"%(uid, objDef.name, uid, parentStr)) modelStr = "'%s'"%objModel
else: else:
self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', '%s', %s)"%(uid, objDef.name, uid, objModel, parentStr)) modelStr = "None"
if objAnim:
animStr = "'%s'"%objAnim
else:
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("if objects['%s']:"%uid)
self.saveData.append(" objects['%s'].setPos(%s)"%(uid, np.getPos())) 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'].setHpr(%s)"%(uid, np.getHpr()))
@ -505,6 +526,10 @@ class ObjectMgr:
if obj is None: if obj is None:
return None return None
objDef = obj[OG.OBJ_DEF] objDef = obj[OG.OBJ_DEF]
objModel = obj[OG.OBJ_MODEL]
objAnim = obj[OG.OBJ_ANIM]
objRGBA = obj[OG.OBJ_RGBA]
if parent is None: if parent is None:
parent = nodePath.getParent() parent = nodePath.getParent()
@ -521,6 +546,9 @@ class ObjectMgr:
# copy model info # copy model info
self.updateObjectModel(obj[OG.OBJ_MODEL], newObj, fSelectObject=False) self.updateObjectModel(obj[OG.OBJ_MODEL], newObj, fSelectObject=False)
# copy anim info
self.updateObjectAnim(obj[OG.OBJ_ANIM], newObj, fSelectObject=False)
# copy other properties # copy other properties
for key in obj[OG.OBJ_PROP]: for key in obj[OG.OBJ_PROP]:
self.updateObjectPropValue(newObj, key, obj[OG.OBJ_PROP][key]) self.updateObjectPropValue(newObj, key, obj[OG.OBJ_PROP][key])

View File

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

View File

@ -29,6 +29,11 @@ class AnimFileDrop(wx.FileDropTarget):
for filename in filenames: for filename in filenames:
name = os.path.basename(filename) name = os.path.basename(filename)
animName = Filename.fromOsSpecific(filename).getFullpath() 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: if animName not in objDef.anims:
objDef.anims.append(animName) objDef.anims.append(animName)

View File

@ -21,6 +21,10 @@ class FileDrop(wx.FileDropTarget):
return return
modelname = Filename.fromOsSpecific(filename).getFullpath() 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) itemData = ObjectBase(name=name, model=modelname, actor=True)
self.editor.protoPalette.add(itemData) self.editor.protoPalette.add(itemData)