""" 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.ANIM_MODE = BitMask32.bit(4) self.GRAPH_EDITOR = False 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() base.direct.selected.last = None self.ui.reset() self.objectMgr.reset() self.animMgr.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