""" Base class for Level Editor You should write your own LevelEditor class inheriting this. Refer LevelEditor.py for example. """ from pandac.PandaModules import * from direct.showbase.ShowBase import * from direct.showbase.DirectObject import * from direct.directtools.DirectGlobals import * from direct.directtools.DirectUtil import * from direct.gui.DirectGui import * base = ShowBase(False) from ObjectMgr 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.fileMgr = FileMgr(self) self.actionMgr = ActionMgr() # define your own config file in inherited class self.settingsFile = None def initialize(self): """ You should call this in your __init__ method of inherited LevelEditor class """ fTk = 0 fWx = 0 base.startDirect(fWantTk = fTk, fWantWx = fWx) base.closeWindow(base.win) base.win = base.winList[3] base.direct.disableMouseEvents() newMouseEvents = map(lambda x: "_le_per_%s"%x, base.direct.mouseEvents) +\ map(lambda x: "_le_fro_%s"%x, base.direct.mouseEvents) +\ map(lambda x: "_le_lef_%s"%x, base.direct.mouseEvents) +\ map(lambda x: "_le_top_%s"%x, base.direct.mouseEvents) base.direct.mouseEvents = newMouseEvents base.direct.enableMouseEvents() base.direct.disableKeyEvents() keyEvents = map(lambda x: "_le_per_%s"%x, base.direct.keyEvents) +\ map(lambda x: "_le_fro_%s"%x, base.direct.keyEvents) +\ map(lambda x: "_le_lef_%s"%x, base.direct.keyEvents) +\ map(lambda x: "_le_top_%s"%x, base.direct.keyEvents) base.direct.keyEvents = keyEvents base.direct.enableKeyEvents() base.direct.disableModifierEvents() modifierEvents = map(lambda x: "_le_per_%s"%x, base.direct.modifierEvents) +\ map(lambda x: "_le_fro_%s"%x, base.direct.modifierEvents) +\ map(lambda x: "_le_lef_%s"%x, base.direct.modifierEvents) +\ map(lambda x: "_le_top_%s"%x, base.direct.modifierEvents) base.direct.modifierEvents = modifierEvents base.direct.enableModifierEvents() base.direct.cameraControl.lockRoll = True base.direct.setFScaleWidgetByCam(1) unpickables = [ "z-guide", "y-guide", "x-guide", "x-disc-geom", "x-ring-line", "x-post-line", "y-disc-geom", "y-ring-line", "y-post-line", "z-disc-geom", "z-ring-line", "z-post-line", "centerLines", "majorLines", "minorLines", "Sphere",] for unpickable in unpickables: base.direct.addUnpickable(unpickable) base.direct.manipulationControl.optionalSkipFlags |= SKIP_UNPICKABLE base.direct.manipulationControl.fAllowMarquee = 1 base.direct.manipulationControl.supportMultiView() base.direct.cameraControl.useMayaCamControls = 1 for widget in base.direct.manipulationControl.widgetList: widget.setBin('gui-popup', 0) widget.setDepthTest(0) # [gjeon] to intercept messages here base.direct.ignore('DIRECT-delete') base.direct.ignore('DIRECT-select') base.direct.ignore('DIRECT-preDeselectAll') base.direct.fIgnoreDirectOnlyKeyMap = 1 # [gjeon] do not use the old way of finding current DR base.direct.drList.tryToGetCurrentDr = False # 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), ]) # 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() 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 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) 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: self.ui.onSave() base.direct.deselectAll() 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 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): if self.currentFile: self.fileMgr.saveToFile(self.currentFile) def saveAs(self, fileName): self.fileMgr.saveToFile(fileName) def load(self, fileName): self.reset() self.fileMgr.loadFromFile(fileName) self.currentFile = fileName 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 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 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() 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