| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- """
- Base class for Level Editor
- You should write your own LevelEditor class inheriting this.
- Refer LevelEditor.py for example.
- """
- from direct.showbase.DirectObject import *
- from direct.directtools.DirectUtil import *
- from direct.gui.DirectGui import *
- from CurveEditor import *
- from FileMgr import *
- from ActionMgr import *
- from MayaConverter import *
- class LevelEditorBase(DirectObject):
- """ Base Class for Panda3D LevelEditor """
- def __init__(self):
- #loadPrcFileData('startup', 'window-type none')
- self.currentFile = None
- self.fNeedToSave = False
- self.actionEvents = []
- #self.objectMgr = ObjectMgr(self)
- self.curveEditor = CurveEditor(self)
- self.fileMgr = FileMgr(self)
- self.actionMgr = ActionMgr()
- self.fMoveCamera = False
- self.NPParent = render
- # define your own config file in inherited class
- self.settingsFile = None
- # you can show/hide specific properties by using propertiesMask and this mode
- self.BASE_MODE = BitMask32.bit(0)
- self.CREATE_CURVE_MODE = BitMask32.bit(2)
- self.EDIT_CURVE_MODE = BitMask32.bit(3)
-
- self.mode = self.BASE_MODE
- self.preMode = None
-
- def initialize(self):
- """ You should call this in your __init__ method of inherited LevelEditor class """
- # specifiy what obj can be 'selected' as objects
- base.direct.selected.addTag('OBJRoot')
- self.actionEvents.extend([
- # Node path events
- ('DIRECT-select', self.select),
- ('DIRECT-delete', self.handleDelete),
- ('DIRECT-preDeselectAll', self.deselectAll),
- ('DIRECT_deselectAll', self.deselectAllCB),
- ('preRemoveNodePath', self.removeNodePathHook),
- ('DIRECT_deselectedNodePath', self.deselectAllCB),
- ('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', self.selectedNodePathHook),
- ('DIRECT_deselectAll', self.deselectAll),
- ('LE-Undo', self.actionMgr.undo),
- ('LE-Redo', self.actionMgr.redo),
- ('LE-Duplicate', self.objectMgr.duplicateSelected),
- ('DIRECT_manipulateObjectCleanup', self.cleanUpManipulating),
- ('LE-MakeLive', self.objectMgr.makeSelectedLive),
- ('LE-NewScene', self.ui.onNew),
- ('LE-SaveScene', self.ui.onSave),
- ('LE-OpenScene', self.ui.onOpen),
- ('LE-Quit', self.ui.quit),
- ('DIRECT-mouse1', self.handleMouse1),
- ('DIRECT-mouse1Up', self.handleMouse1Up),
- ('DIRECT-mouse2', self.handleMouse2),
- ('DIRECT-mouse2Up', self.handleMouse2Up),
- ('DIRECT-mouse3', self.handleMouse3),
- ('DIRECT-mouse3Up', self.handleMouse3Up),
- ('DIRECT-toggleWidgetVis', self.toggleWidget),
- ])
- # Add all the action events
- for event in self.actionEvents:
- if len(event) == 3:
- self.accept(event[0], event[1], event[2])
- else:
- self.accept(event[0], event[1])
- # editor state text display such as edit mode
- self.statusReadout = OnscreenText(
- pos = (-1.2, 0.9), bg=Vec4(1,1,1,1),
- scale = 0.05, align = TextNode.ALeft,
- mayChange = 1, font = TextNode.getDefaultFont())
- self.statusReadout.setText("")
- # Make sure readout is never lit or drawn in wireframe
- useDirectRenderStyle(self.statusReadout)
- self.statusReadout.reparentTo(hidden)
- self.statusLines = []
- taskMgr.doMethodLater(5, self.updateStatusReadoutTimeouts, 'updateStatus')
- self.loadSettings()
- self.reset()
-
- def setTitleWithFilename(self, filename=""):
- title = self.ui.appname
- if filename != "":
- filenameshort = os.path.basename(filename)
- title = title + " (%s)"%filenameshort
- self.ui.SetLabel(title)
- def removeNodePathHook(self, nodePath):
- if nodePath is None:
- return
- base.direct.deselect(nodePath)
- self.objectMgr.removeObjectByNodePath(nodePath)
- if (base.direct.selected.last != None and nodePath.compareTo(base.direct.selected.last)==0):
- # if base.direct.selected.last is refering to this
- # removed obj, clear the reference
- if (hasattr(__builtins__,'last')):
- __builtins__.last = None
- else:
- __builtins__['last'] = None
- base.direct.selected.last = None
- def toggleWidget(self):
- if self.objectMgr.currNodePath:
- obj = self.objectMgr.findObjectByNodePath(self.objectMgr.currNodePath)
- if obj and not obj[OG.OBJ_DEF].movable:
- return
- base.direct.toggleWidgetVis()
- def handleMouse1(self, modifiers):
- if base.direct.fAlt or modifiers == 4:
- self.fMoveCamera = True
- return
- if self.mode == self.CREATE_CURVE_MODE :
- self.curveEditor.createCurve()
- def handleMouse1Up(self):
- self.fMoveCamera = False
- def handleMouse2(self, modifiers):
- if base.direct.fAlt or modifiers == 4:
- self.fMoveCamera = True
- return
- def handleMouse2Up(self):
- self.fMoveCamera = False
- def handleMouse3(self, modifiers):
- if base.direct.fAlt or modifiers == 4:
- self.fMoveCamera = True
- return
-
- self.ui.onRightDown()
- def handleMouse3Up(self):
- self.fMoveCamera = False
- def handleDelete(self):
- oldSelectedNPs = base.direct.selected.getSelectedAsList()
- oldUIDs = []
- for oldNP in oldSelectedNPs:
- obj = self.objectMgr.findObjectByNodePath(oldNP)
- if obj:
- oldUIDs.append(obj[OG.OBJ_UID])
- action = ActionDeleteObj(self)
- self.actionMgr.push(action)
- action()
- for uid in oldUIDs:
- self.ui.sceneGraphUI.delete(uid)
- ## 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 cleanUpManipulating(self, selectedNPs):
- for np in selectedNPs:
- obj = self.objectMgr.findObjectByNodePath(np)
- if obj:
- action = ActionTransformObj(self, obj[OG.OBJ_UID], Mat4(np.getMat()))
- self.actionMgr.push(action)
- action()
- 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):
- # handle unpickable nodepath
- if nodePath.getName() in base.direct.iRay.unpickable:
- base.direct.deselect(nodePath)
- return
- if fMultiSelect == 0 and fLEPane == 0:
- oldSelectedNPs = base.direct.selected.getSelectedAsList()
- for oldNP in oldSelectedNPs:
- obj = self.objectMgr.findObjectByNodePath(oldNP)
- if obj:
- self.ui.sceneGraphUI.deSelect(obj[OG.OBJ_UID])
- self.objectMgr.selectObject(nodePath, fLEPane)
- self.ui.buildContextMenu(nodePath)
-
- if self.mode == self.EDIT_CURVE_MODE:
- taskMgr.add(self.curveEditor.editCurve, "modify")
- self.curveEditor.accept("DIRECT-enter", self.curveEditor.onBaseMode)
-
- 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()
- def reset(self):
- if self.fNeedToSave:
- reply = wx.MessageBox("Do you want to save current scene?", "Save?",
- wx.YES_NO | wx.ICON_QUESTION)
- if reply == wx.YES:
- result = self.ui.onSave()
- if result == False:
- return
- base.direct.deselectAll()
- self.ui.reset()
- self.objectMgr.reset()
- self.actionMgr.reset()
- self.ui.perspView.camera.setPos(-19, -19, 19)
- self.ui.perspView.camera.lookAt(Point3(0, 0, 0))
- self.ui.leftView.camera.setPos(600, 0, 0)
- self.ui.frontView.camera.setPos(0, -600, 0)
- self.ui.topView.camera.setPos(0, 0, 600)
- self.resetOrthoCam(self.ui.topView)
- self.resetOrthoCam(self.ui.frontView)
- self.resetOrthoCam(self.ui.leftView)
- self.fNeedToSave = False
- self.setTitleWithFilename()
-
- def resetOrthoCam(self, view):
- base.direct.drList[base.camList.index(NodePath(view.camNode))].orthoFactor = 0.1
- x = view.ClientSize.GetWidth() * 0.1
- y = view.ClientSize.GetHeight() * 0.1
- view.camLens.setFilmSize(x, y)
-
- def save(self):
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- if self.currentFile:
- self.fileMgr.saveToFile(self.currentFile)
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
- def saveAs(self, fileName):
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- self.fileMgr.saveToFile(fileName)
- self.currentFile = fileName
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
- def load(self, fileName):
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- self.reset()
- self.fileMgr.loadFromFile(fileName)
- self.currentFile = fileName
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
- def saveSettings(self):
- if self.settingsFile is None:
- return
-
- try:
- f = open(self.settingsFile, 'w')
- f.write('gridSize\n%f\n'%self.ui.perspView.grid.gridSize)
- f.write('gridSpacing\n%f\n'%self.ui.perspView.grid.gridSpacing)
- f.write('hotKey\n%s\n'%base.direct.hotKeyMap)
- f.close()
- except:
- pass
- def loadSettings(self):
- if self.settingsFile is None:
- return
-
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- try:
- f = open(self.settingsFile, 'r')
- configLines = f.readlines()
- f.close()
- gridSize = 100.0
- gridSpacing = 5.0
- for i in range(0, len(configLines)):
- line = configLines[i]
- i = i + 1
- if line.startswith('gridSize'):
- gridSize = float(configLines[i])
- elif line.startswith('gridSpacing'):
- gridSpacing = float(configLines[i])
- elif line.startswith('hotKey'):
- customHotKeyMap = eval(configLines[i])
- customHotKeyDict = {}
- for hotKey in customHotKeyMap.keys():
- desc = customHotKeyMap[hotKey]
- customHotKeyDict[desc[1]] = hotKey
- overriddenKeys = []
- for key in base.direct.hotKeyMap.keys():
- desc = base.direct.hotKeyMap[key]
- if desc[1] in customHotKeyDict.keys():
- overriddenKeys.append(key)
- for key in overriddenKeys:
- del base.direct.hotKeyMap[key]
-
- base.direct.hotKeyMap.update(customHotKeyMap)
- self.ui.updateGrids(gridSize, gridSpacing)
- self.ui.updateMenu()
- except:
- pass
- self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
- def convertMaya(self, modelname, callBack, obj=None, isAnim=False):
- if obj and isAnim:
- mayaConverter = MayaConverter(self.ui, self, modelname, callBack, 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, callBack, None, True)
- else:
- mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, False)
- mayaConverter.Show()
- def convertFromMaya(self, modelname, callBack):
- mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, False)
- mayaConverter.Show()
- def exportToMaya(self, mayaFileName):
- exportRootNP = render
- self.exportToMayaCB(mayaFileName, exportRootNP)
- def exportToMayaCB(self, mayaFileName, exportRootNP):
- bamFileName = mayaFileName + ".bam"
- if base.direct.selected.last:
- obj = self.objectMgr.findObjectByNodePath(base.direct.selected.last)
- if obj:
- exportRootNP = obj[OG.OBJ_NP]
- exportRootNP.writeBamFile(bamFileName)
- mayaConverter = MayaConverter(self.ui, self, mayaFileName, None, None, False, FROM_BAM_TO_MAYA)
- mayaConverter.Show()
- def updateStatusReadout(self, status, color=None):
- if status:
- # add new status line, first check to see if it already exists
- alreadyExists = False
- for currLine in self.statusLines:
- if (status == currLine[1]):
- alreadyExists = True
- break
- if (alreadyExists == False):
- time = globalClock.getRealTime() + 15
- self.statusLines.append([time,status,color])
- # update display of new status lines
- self.statusReadout.reparentTo(aspect2d)
- statusText = ""
- lastColor = None
- for currLine in self.statusLines:
- statusText += currLine[1] + '\n'
- lastColor = currLine[2]
- self.statusReadout.setText(statusText)
- if (lastColor):
- self.statusReadout.textNode.setCardColor(
- lastColor[0], lastColor[1], lastColor[2], lastColor[3])
- self.statusReadout.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
- else:
- self.statusReadout.textNode.setCardColor(1,1,1,1)
- self.statusReadout.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
-
- def updateStatusReadoutTimeouts(self,task=None):
- removalList = []
- for currLine in self.statusLines:
- if (globalClock.getRealTime() >= currLine[0]):
- removalList.append(currLine)
- for currRemoval in removalList:
- self.statusLines.remove(currRemoval)
- self.updateStatusReadout(None)
- # perform doMethodLater again after delay
- # This crashes when CTRL-C'ing, so this is a cheap hack.
- #return 2
- from direct.task import Task
- return Task.again
- def propMeetsReq(self, typeName, parentNP):
- if self.ui.parentToSelectedMenuItem.IsChecked():
- if base.direct.selected.last:
- parent = base.le.objectMgr.findObjectByNodePath(base.direct.selected.last)
- if parent:
- parentNP[0] = parent[OG.OBJ_NP]
- else:
- parentNP[0] = None
- return True
|