Added undo for selection and object transform

This commit is contained in:
Gyedo Jeon 2010-02-06 02:24:34 +00:00
parent c3b70b120d
commit fd7bf04577
5 changed files with 190 additions and 43 deletions

View File

@ -1,24 +1,6 @@
from pandac.PandaModules import *
import ObjectGlobals as OG 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"
class ActionMgr: class ActionMgr:
def __init__(self): def __init__(self):
self.undoList = [] self.undoList = []
@ -35,6 +17,7 @@ class ActionMgr:
def push(self, action): def push(self, action):
self.undoList.append(action) self.undoList.append(action)
print 'current undoList', self.undoList
def undo(self): def undo(self):
if len(self.undoList) < 1: if len(self.undoList) < 1:
@ -43,6 +26,7 @@ class ActionMgr:
action = self.undoList.pop() action = self.undoList.pop()
self.redoList.append(action) self.redoList.append(action)
action.undo() action.undo()
print 'current redoList', self.redoList
def redo(self): def redo(self):
if len(self.redoList) < 1: if len(self.redoList) < 1:
@ -50,7 +34,31 @@ class ActionMgr:
else: else:
action = self.redoList.pop() action = self.redoList.pop()
self.undoList.append(action) self.undoList.append(action)
action() action.redo()
print 'current undoList', self.undoList
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 redo(self):
self.result = self._do__call__()
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"
class ActionAddNewObj(ActionBase): class ActionAddNewObj(ActionBase):
""" Action class for adding new object """ """ Action class for adding new object """
@ -59,11 +67,23 @@ class ActionAddNewObj(ActionBase):
self.editor = editor self.editor = editor
function = self.editor.objectMgr.addNewObject function = self.editor.objectMgr.addNewObject
ActionBase.__init__(self, function, *args, **kargs) ActionBase.__init__(self, function, *args, **kargs)
self.uid = None
def redo(self):
if self.uid is None:
print "Can't redo this add"
else:
self.result = self._do__call__(uid=self.uid)
return self.result
def undo(self): def undo(self):
if self.result is None: if self.result is None:
print "Can't undo this" print "Can't undo this add"
else: else:
print "Undo: addNewObject"
obj = self.editor.objectMgr.findObjectByNodePath(self.result)
self.uid = obj[OG.OBJ_UID]
self.editor.ui.sceneGraphUI.delete(self.uid)
base.direct.deselect(self.result) base.direct.deselect(self.result)
base.direct.removeNodePath(self.result) base.direct.removeNodePath(self.result)
self.result = None self.result = None
@ -78,6 +98,7 @@ class ActionDeleteObj(ActionBase):
self.selectedUIDs = [] self.selectedUIDs = []
self.hierarchy = {} self.hierarchy = {}
self.objInfos = {} self.objInfos = {}
self.objTransforms = {}
def saveStatus(self): def saveStatus(self):
selectedNPs = base.direct.selected.getSelectedAsList() selectedNPs = base.direct.selected.getSelectedAsList()
@ -89,6 +110,7 @@ class ActionDeleteObj(ActionBase):
self.selectedUIDs.append(uid) self.selectedUIDs.append(uid)
objNP = obj[OG.OBJ_NP] objNP = obj[OG.OBJ_NP]
self.objInfos[uid] = obj self.objInfos[uid] = obj
self.objTransforms[uid] = objNP.getMat()
parentNP = objNP.getParent() parentNP = objNP.getParent()
if parentNP == render: if parentNP == render:
self.hierarchy[uid] = None self.hierarchy[uid] = None
@ -107,10 +129,12 @@ class ActionDeleteObj(ActionBase):
def undo(self): def undo(self):
if len(self.hierarchy.keys()) == 0 or\ if len(self.hierarchy.keys()) == 0 or\
len(self.objInfos.keys()) == 0: len(self.objInfos.keys()) == 0:
print "Can't undo this" print "Can't undo this deletion"
else: else:
print "Undo: deleteObject"
def restoreObject(uid, parentNP): def restoreObject(uid, parentNP):
obj = self.objInfos[uid] obj = self.objInfos[uid]
objNP = obj[OG.OBJ_NP]
objDef = obj[OG.OBJ_DEF] objDef = obj[OG.OBJ_DEF]
objModel = obj[OG.OBJ_MODEL] objModel = obj[OG.OBJ_MODEL]
objProp = obj[OG.OBJ_PROP] objProp = obj[OG.OBJ_PROP]
@ -121,6 +145,7 @@ class ActionDeleteObj(ActionBase):
parentNP) parentNP)
self.editor.objectMgr.updateObjectColor(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], uid) self.editor.objectMgr.updateObjectColor(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], uid)
self.editor.objectMgr.updateObjectProperties(uid, objProp) self.editor.objectMgr.updateObjectProperties(uid, objProp)
objNP.setMat(self.objTransforms[uid])
while (len(self.hierarchy.keys()) > 0): while (len(self.hierarchy.keys()) > 0):
for uid in self.hierarchy.keys(): for uid in self.hierarchy.keys():
@ -135,12 +160,98 @@ class ActionDeleteObj(ActionBase):
restoreObject(uid, parentNP) restoreObject(uid, parentNP)
del self.hierarchy[uid] del self.hierarchy[uid]
base.direct.deselectAll() base.direct.deselectAllCB()
for uid in self.selectedUIDs: for uid in self.selectedUIDs:
obj = self.editor.objectMgr.findObjectById(uid) obj = self.editor.objectMgr.findObjectById(uid)
if obj: if obj:
base.direct.select(obj[OG.OBJ_NP], fMultiSelect=1) self.editor.select(obj[OG.OBJ_NP], fMultiSelect=1, fUndo=0)
self.selecteUIDs = [] self.selecteUIDs = []
self.hierarchy = {} self.hierarchy = {}
self.objInfos = {} self.objInfos = {}
class ActionSelectObj(ActionBase):
""" Action class for adding new object """
def __init__(self, editor, *args, **kargs):
self.editor = editor
function = base.direct.selectCB
ActionBase.__init__(self, function, *args, **kargs)
self.selectedUIDs = []
def saveStatus(self):
selectedNPs = base.direct.selected.getSelectedAsList()
for np in selectedNPs:
obj = self.editor.objectMgr.findObjectByNodePath(np)
if obj:
uid = obj[OG.OBJ_UID]
self.selectedUIDs.append(uid)
def undo(self):
print "Undo : selectObject"
base.direct.deselectAllCB()
for uid in self.selectedUIDs:
obj = self.editor.objectMgr.findObjectById(uid)
if obj:
self.editor.select(obj[OG.OBJ_NP], fMultiSelect=1, fUndo=0)
self.selectedUIDs = []
class ActionTransformObj(ActionBase):
""" Action class for object transformation """
def __init__(self, editor, *args, **kargs):
self.editor = editor
function = self.editor.objectMgr.setObjectTransform
ActionBase.__init__(self, function, *args, **kargs)
self.uid = args[0]
#self.xformMat = Mat4(args[1])
self.origMat = None
def saveStatus(self):
obj = self.editor.objectMgr.findObjectById(self.uid)
if obj:
self.origMat = Mat4(obj[OG.OBJ_NP].getMat())
def redo(self):
if self.uid is None:
print "Can't redo this add"
else:
self.result = self._do__call__()#uid=self.uid, xformMat=self.xformMat)
return self.result
def undo(self):
if self.origMat is None:
print "Can't undo this transform"
else:
print "Undo: transformObject"
obj = self.editor.objectMgr.findObjectById(self.uid)
if obj:
obj[OG.OBJ_NP].setMat(self.origMat)
del self.origMat
self.origMat = None
class ActionDeselectAll(ActionBase):
""" Action class for adding new object """
def __init__(self, editor, *args, **kargs):
self.editor = editor
function = base.direct.deselectAllCB
ActionBase.__init__(self, function, *args, **kargs)
self.selectedUIDs = []
def saveStatus(self):
selectedNPs = base.direct.selected.getSelectedAsList()
for np in selectedNPs:
obj = self.editor.objectMgr.findObjectByNodePath(np)
if obj:
uid = obj[OG.OBJ_UID]
self.selectedUIDs.append(uid)
def undo(self):
print "Undo : deselectAll"
base.direct.deselectAllCB()
for uid in self.selectedUIDs:
obj = self.editor.objectMgr.findObjectById(uid)
if obj:
self.editor.select(obj[OG.OBJ_NP], fMultiSelect=1, fUndo=0)
self.selectedUIDs = []

View File

@ -34,7 +34,7 @@ class LevelEditorBase(DirectObject):
def initialize(self): def initialize(self):
""" You should call this in your __init__ method of inherited LevelEditor class """ """ You should call this in your __init__ method of inherited LevelEditor class """
fTk = base.config.GetBool('want-tk', 0) fTk = 0
fWx = 0 fWx = 0
base.startDirect(fWantTk = fTk, fWantWx = fWx) base.startDirect(fWantTk = fTk, fWantWx = fWx)
base.closeWindow(base.win) base.closeWindow(base.win)
@ -97,8 +97,10 @@ class LevelEditorBase(DirectObject):
widget.setBin('gui-popup', 0) widget.setBin('gui-popup', 0)
widget.setDepthTest(0) widget.setDepthTest(0)
# [gjeon] to handle delete here first # [gjeon] to intercept messages here
base.direct.ignore('DIRECT-delete') base.direct.ignore('DIRECT-delete')
base.direct.ignore('DIRECT-select')
base.direct.ignore('DIRECT-preDeselectAll')
# [gjeon] do not use the old way of finding current DR # [gjeon] do not use the old way of finding current DR
base.direct.drList.tryToGetCurrentDr = False base.direct.drList.tryToGetCurrentDr = False
@ -108,10 +110,13 @@ class LevelEditorBase(DirectObject):
self.actionEvents.extend([ self.actionEvents.extend([
# Node path events # Node path events
('DIRECT-select', self.select),
('DIRECT-delete', self.handleDelete), ('DIRECT-delete', self.handleDelete),
('DIRECT-preDeselectAll', self.deselectAll),
('DIRECT_deselectAll', self.deselectAllCB),
('preRemoveNodePath', self.removeNodePathHook), ('preRemoveNodePath', self.removeNodePathHook),
('DIRECT_deselectedNodePath', self.deselectAllCB),
('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', self.selectedNodePathHook), ('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', 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),
@ -165,6 +170,20 @@ class LevelEditorBase(DirectObject):
## coa = Vec3(row[0], row[1], row[2]) ## coa = Vec3(row[0], row[1], row[2])
## base.direct.cameraControl.updateCoa(coa) ## base.direct.cameraControl.updateCoa(coa)
def select(self, nodePath, fMultiSelect=0, fSelectTag=1, fResetAncestry=1, fLEPane=0, fUndo=1):
if fUndo:
# Select tagged object if present
if fSelectTag:
for tag in base.direct.selected.tagList:
if nodePath.hasNetTag(tag):
nodePath = nodePath.findNetTag(tag)
break
action = ActionSelectObj(self, nodePath, fMultiSelect)
self.actionMgr.push(action)
action()
else:
base.direct.selectCB(nodePath, fMultiSelect, fSelectTag, fResetAncestry, fLEPane, fUndo)
def selectedNodePathHook(self, nodePath, fMultiSelect = 0, fSelectTag = 1, fLEPane = 0): def selectedNodePathHook(self, nodePath, fMultiSelect = 0, fSelectTag = 1, fLEPane = 0):
# handle unpickable nodepath # handle unpickable nodepath
if nodePath.getName() in base.direct.iRay.unpickable: if nodePath.getName() in base.direct.iRay.unpickable:
@ -180,6 +199,13 @@ class LevelEditorBase(DirectObject):
self.objectMgr.selectObject(nodePath, fLEPane) self.objectMgr.selectObject(nodePath, fLEPane)
def deselectAll(self, np=None): def deselectAll(self, np=None):
if len(base.direct.selected.getSelectedAsList()) ==0:
return
action = ActionDeselectAll(self)
self.actionMgr.push(action)
action()
def deselectAllCB(self, dnp=None):
self.objectMgr.deselectAll() self.objectMgr.deselectAll()
def reset(self): def reset(self):

View File

@ -46,7 +46,7 @@ class ObjectHandler:
def updateSmiley(self, val, obj): def updateSmiley(self, val, obj):
objNP = obj[OG.OBJ_NP] objNP = obj[OG.OBJ_NP]
if base.direct: if base.direct:
base.direct.deselectAll() base.direct.deselectAllCB()
for child in objNP.findAllMatches("+GeomNode"): for child in objNP.findAllMatches("+GeomNode"):
child.removeNode() child.removeNode()

View File

@ -7,7 +7,7 @@ import os, time, wx
from direct.task import Task from direct.task import Task
from direct.actor.Actor import Actor from direct.actor.Actor import Actor
from pandac.PandaModules import * from pandac.PandaModules import *
from ActionMgr import *
import ObjectGlobals as OG import ObjectGlobals as OG
class ObjectMgr: class ObjectMgr:
@ -26,7 +26,7 @@ class ObjectMgr:
self.currNodePath = None self.currNodePath = None
def reset(self): def reset(self):
self.deselectAll() self.deselectAllCB()
for id in self.objects.keys(): for id in self.objects.keys():
self.objects[id][OG.OBJ_NP].removeNode() self.objects[id][OG.OBJ_NP].removeNode()
@ -130,8 +130,6 @@ class ObjectMgr:
if uid is None: if uid is None:
uid = self.genUniqueId() uid = self.genUniqueId()
else:
fSelectObject = False
# populate obj data using default values # populate obj data using default values
properties = {} properties = {}
@ -144,7 +142,7 @@ class ObjectMgr:
if self.editor: if self.editor:
if fSelectObject: if fSelectObject:
base.direct.select(newobj) self.editor.select(newobj, fUndo=0)
self.editor.ui.sceneGraphUI.add(newobj) self.editor.ui.sceneGraphUI.add(newobj)
return newobj return newobj
@ -249,7 +247,7 @@ class ObjectMgr:
if self.currNodePath is None: if self.currNodePath is None:
return return
np = self.currNodePath np = hidden.attachNewNode('temp')
np.setX(float(self.editor.ui.objectPropertyUI.propX.getValue())) np.setX(float(self.editor.ui.objectPropertyUI.propX.getValue()))
np.setY(float(self.editor.ui.objectPropertyUI.propY.getValue())) np.setY(float(self.editor.ui.objectPropertyUI.propY.getValue()))
np.setZ(float(self.editor.ui.objectPropertyUI.propZ.getValue())) np.setZ(float(self.editor.ui.objectPropertyUI.propZ.getValue()))
@ -283,6 +281,18 @@ class ObjectMgr:
np.setSy(float(self.editor.ui.objectPropertyUI.propSY.getValue())) np.setSy(float(self.editor.ui.objectPropertyUI.propSY.getValue()))
np.setSz(float(self.editor.ui.objectPropertyUI.propSZ.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()
def setObjectTransform(self, uid, xformMat):
obj = self.findObjectById(uid)
if obj:
print obj[OG.OBJ_NP], xformMat
obj[OG.OBJ_NP].setMat(xformMat)
def updateObjectColor(self, r, g, b, a, np=None): def updateObjectColor(self, r, g, b, a, np=None):
if np is None: if np is None:
np = self.currNodePath np = self.currNodePath
@ -300,7 +310,7 @@ class ObjectMgr:
def updateObjectModel(self, model, obj, fSelectObject=True): def updateObjectModel(self, model, obj, fSelectObject=True):
""" replace object's model """ """ replace object's model """
if obj[OG.OBJ_MODEL] != model: if obj[OG.OBJ_MODEL] != model:
base.direct.deselectAll() base.direct.deselectAllCB()
objNP = obj[OG.OBJ_NP] objNP = obj[OG.OBJ_NP]
objRGBA = obj[OG.OBJ_RGBA] objRGBA = obj[OG.OBJ_RGBA]
@ -333,12 +343,12 @@ class ObjectMgr:
self.npIndex[NodePath(newobj)] = obj[OG.OBJ_UID] self.npIndex[NodePath(newobj)] = obj[OG.OBJ_UID]
if fSelectObject: if fSelectObject:
base.direct.select(newobj) base.direct.select(newobj, fUndo=0)
def updateObjectAnim(self, anim, obj, fSelectObject=True): def updateObjectAnim(self, anim, obj, fSelectObject=True):
""" replace object's anim """ """ replace object's anim """
if obj[OG.OBJ_ANIM] != anim: if obj[OG.OBJ_ANIM] != anim:
base.direct.deselectAll() base.direct.deselectAllCB()
objNP = obj[OG.OBJ_NP] objNP = obj[OG.OBJ_NP]
# load new anim # load new anim
@ -347,7 +357,7 @@ class ObjectMgr:
objNP.loop(animName) objNP.loop(animName)
obj[OG.OBJ_ANIM] = anim obj[OG.OBJ_ANIM] = anim
if fSelectObject: if fSelectObject:
base.direct.select(objNP) base.direct.select(objNP, fUndo=0)
def updateObjectModelFromUI(self, event, obj): def updateObjectModelFromUI(self, event, obj):
""" replace object's model with one selected from UI """ """ replace object's model with one selected from UI """
@ -463,7 +473,7 @@ class ObjectMgr:
func(**kwargs) func(**kwargs)
if self.editor and fSelectObject: if self.editor and fSelectObject:
base.direct.select(obj[OG.OBJ_NP]) base.direct.select(obj[OG.OBJ_NP], fUndo=0)
def updateObjectProperties(self, nodePath, propValues): def updateObjectProperties(self, nodePath, propValues):
""" """
@ -510,7 +520,7 @@ class ObjectMgr:
else: else:
animStr = "None" animStr = "None"
self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', %s, %s, %s)"%(uid, objDef.name, uid, modelStr, parentStr, animStr)) 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("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()))
@ -575,7 +585,7 @@ class ObjectMgr:
self.duplicateChild(nodePath, newObjNP) self.duplicateChild(nodePath, newObjNP)
duplicatedNPs.append(newObjNP) duplicatedNPs.append(newObjNP)
base.direct.deselectAll() base.direct.deselectAllCB()
print duplicatedNPs print duplicatedNPs
for newNodePath in duplicatedNPs: for newNodePath in duplicatedNPs:
base.direct.select(newNodePath, fMultiSelect = 1) base.direct.select(newNodePath, fMultiSelect = 1, fUndo=0)