import wx import os from wx.lib.agw import fourwaysplitter as FWS from pandac.PandaModules import * from direct.wxwidgets.WxAppShell import * from direct.directtools.DirectSelection import SelectionRay from ViewPort import * from ObjectPaletteUI import * from ObjectPropertyUI import * from SceneGraphUI import * from LayerEditorUI import * from HotKeyUI import * from ProtoPaletteUI import * from ActionMgr import * class PandaTextDropTarget(wx.TextDropTarget): def __init__(self, editor, view): wx.TextDropTarget.__init__(self) self.editor = editor self.view = view def OnDropText(self, x, y, text): # create new object action = ActionAddNewObj(self.editor, text) self.editor.actionMgr.push(action) newobj = action() # change window coordinate to mouse coordinate mx = 2 * (x/float(self.view.ClientSize.GetWidth()) - 0.5) my = -2 * (y/float(self.view.ClientSize.GetHeight()) - 0.5) # create ray from the camera to detect 3d position iRay = SelectionRay(self.view.camera) iRay.collider.setFromLens(self.view.camNode, mx, my) hitPt = None if self.editor.objectMgr.currLiveNP: iRay.collideWithGeom() iRay.ct.traverse(self.editor.objectMgr.currLiveNP) def isEntryBackfacing(iRay, entry): if not entry.hasSurfaceNormal(): # Well, no way to tell. Assume we're not backfacing. return 0 fromNodePath = entry.getFromNodePath() v = Vec3(entry.getSurfacePoint(fromNodePath)) n = entry.getSurfaceNormal(fromNodePath) # Convert to camera space for backfacing test p2cam = iRay.collisionNodePath.getParent().getMat(self.view.camera) v = Vec3(p2cam.xformPoint(v)) n = p2cam.xformVec(n) # Normalize and check angle between to vectors v.normalize() return v.dot(n) >= 0 for entry in iRay.getEntries(): if isEntryBackfacing(iRay, entry): pass else: hitPt = entry.getSurfacePoint(entry.getFromNodePath()) break if hitPt is None: iRay.collideWithBitMask(1) iRay.ct.traverse(self.view.collPlane) if iRay.getNumEntries() > 0: entry = iRay.getEntry(0) hitPt = entry.getSurfacePoint(entry.getFromNodePath()) if hitPt: # create a temp nodePath to get the position np = NodePath('temp') np.setPos(self.view.camera, hitPt) # update temp nodePath's HPR and scale with newobj's np.setHpr(newobj.getHpr()) np.setScale(newobj.getScale()) # transform newobj to cursor position obj = self.editor.objectMgr.findObjectByNodePath(newobj) action = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat())) self.editor.actionMgr.push(action) np.remove() action() del iRay ID_NEW = 101 ID_OPEN = 102 ID_SAVE = 103 ID_SAVE_AS = 104 ID_DUPLICATE = 201 ID_MAKE_LIVE = 202 ID_UNDO = 203 ID_REDO = 204 ID_SHOW_GRID = 301 ID_GRID_SIZE = 302 ID_GRID_SNAP = 303 ID_SHOW_PANDA_OBJECT = 304 ID_HOT_KEYS = 305 class LevelEditorUIBase(WxAppShell): """ Class for Panda3D LevelEditor """ MENU_TEXTS = { ID_NEW : ("&New", "LE-NewScene"), ID_OPEN : ("&Open", "LE-OpenScene"), ID_SAVE : ("&Save", "LE-SaveScene"), ID_SAVE_AS : ("Save &As", None), wx.ID_EXIT : ("&Quit", "LE-Quit"), ID_DUPLICATE : ("&Duplicate", "LE-Duplicate"), ID_MAKE_LIVE : ("Make &Live", "LE-MakeLive"), ID_UNDO : ("&Undo", "LE-Undo"), ID_REDO : ("&Redo", "LE-Redo"), ID_SHOW_GRID : ("&Show Grid", None), ID_GRID_SIZE : ("&Grid Size", None), ID_GRID_SNAP : ("Grid S&nap", None), ID_SHOW_PANDA_OBJECT : ("Show &Panda Objects", None), ID_HOT_KEYS : ("&Hot Keys", None), } def __init__(self, editor, *args, **kw): # Create the Wx app self.wxApp = wx.App(redirect = False) self.wxApp.SetAppName("Panda3D LevelEditor") self.wxApp.SetClassName("P3DLevelEditor") self.editor = editor if not kw.get('size'): kw['size'] = wx.Size(self.frameWidth, self.frameHeight) WxAppShell.__init__(self, *args, **kw) self.wxApp.Bind(wx.EVT_CHAR, self.onKeyEvent) self.initialize() def createMenu(self): menuItem = self.menuFile.Insert(0, ID_NEW, self.MENU_TEXTS[ID_NEW][0]) self.Bind(wx.EVT_MENU, self.onNew, menuItem) menuItem = self.menuFile.Insert(1, ID_OPEN, self.MENU_TEXTS[ID_OPEN][0]) self.Bind(wx.EVT_MENU, self.onOpen, menuItem) menuItem = self.menuFile.Insert(2, ID_SAVE, self.MENU_TEXTS[ID_SAVE][0]) self.Bind(wx.EVT_MENU, self.onSave, menuItem) menuItem = self.menuFile.Insert(3, ID_SAVE_AS, self.MENU_TEXTS[ID_SAVE_AS][0]) self.Bind(wx.EVT_MENU, self.onSaveAs, menuItem) self.menuEdit = wx.Menu() self.menuBar.Insert(1, self.menuEdit, "&Edit") menuItem = self.menuEdit.Append(ID_DUPLICATE, self.MENU_TEXTS[ID_DUPLICATE][0]) self.Bind(wx.EVT_MENU, self.onDuplicate, menuItem) menuItem = self.menuEdit.Append(ID_MAKE_LIVE, self.MENU_TEXTS[ID_MAKE_LIVE][0]) self.Bind(wx.EVT_MENU, self.onMakeLive, menuItem) menuItem = self.menuEdit.Append(ID_UNDO, self.MENU_TEXTS[ID_UNDO][0]) self.Bind(wx.EVT_MENU, self.editor.actionMgr.undo, menuItem) menuItem = self.menuEdit.Append(ID_REDO, self.MENU_TEXTS[ID_REDO][0]) self.Bind(wx.EVT_MENU, self.editor.actionMgr.redo, menuItem) self.menuOptions = wx.Menu() self.menuBar.Insert(2, self.menuOptions, "&Options") self.showGridMenuItem = self.menuOptions.Append(ID_SHOW_GRID, self.MENU_TEXTS[ID_SHOW_GRID][0], kind = wx.ITEM_CHECK) self.Bind(wx.EVT_MENU, self.toggleGrid, self.showGridMenuItem) self.gridSizeMenuItem = self.menuOptions.Append(ID_GRID_SIZE, self.MENU_TEXTS[ID_GRID_SIZE][0]) self.Bind(wx.EVT_MENU, self.onGridSize, self.gridSizeMenuItem) self.gridSnapMenuItem = self.menuOptions.Append(ID_GRID_SNAP, self.MENU_TEXTS[ID_GRID_SNAP][0], kind = wx.ITEM_CHECK) self.Bind(wx.EVT_MENU, self.toggleGridSnap, self.gridSnapMenuItem) self.showPandaObjectsMenuItem = self.menuOptions.Append(ID_SHOW_PANDA_OBJECT, self.MENU_TEXTS[ID_SHOW_PANDA_OBJECT][0], kind = wx.ITEM_CHECK) self.Bind(wx.EVT_MENU, self.onShowPandaObjects, self.showPandaObjectsMenuItem) self.hotKeysMenuItem = self.menuOptions.Append(ID_HOT_KEYS, self.MENU_TEXTS[ID_HOT_KEYS][0]) self.Bind(wx.EVT_MENU, self.onHotKeys, self.hotKeysMenuItem) def updateMenu(self): hotKeyDict = {} for hotKey in base.direct.hotKeyMap.keys(): desc = base.direct.hotKeyMap[hotKey] hotKeyDict[desc[1]] = hotKey for id in self.MENU_TEXTS.keys(): desc = self.MENU_TEXTS[id] if desc[1]: menuItem = self.menuBar.FindItemById(id) hotKey = hotKeyDict.get(desc[1]) if hotKey: menuItem.SetText(desc[0] + "\t%s"%hotKey) def createInterface(self): self.createMenu() self.mainFrame = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_BORDER) self.leftFrame = wx.SplitterWindow(self.mainFrame, style = wx.SP_3D | wx.SP_BORDER) self.baseFrame = wx.SplitterWindow(self.mainFrame, style = wx.SP_3D | wx.SP_BORDER) self.viewFrame = FWS.FourWaySplitter(self.baseFrame, style=wx.SP_LIVE_UPDATE) self.rightFrame = wx.SplitterWindow(self.baseFrame, style = wx.SP_3D | wx.SP_BORDER) self.topView = Viewport.makeTop(self.viewFrame) self.viewFrame.AppendWindow(self.topView) self.frontView = Viewport.makeFront(self.viewFrame) self.viewFrame.AppendWindow(self.frontView) self.leftView = Viewport.makeLeft(self.viewFrame) self.viewFrame.AppendWindow(self.leftView) self.perspView = Viewport.makePerspective(self.viewFrame) self.viewFrame.AppendWindow(self.perspView) self.leftBarUpPane = wx.Panel(self.leftFrame) self.leftBarUpNB = wx.Notebook(self.leftBarUpPane, style=wx.NB_BOTTOM) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.leftBarUpNB, 1, wx.EXPAND) self.leftBarUpPane.SetSizer(sizer) self.leftBarUpPane0 = wx.Panel(self.leftBarUpNB, -1) self.leftBarUpNB.AddPage(self.leftBarUpPane0, 'Object Palette') self.leftBarUpPane1 = wx.Panel(self.leftBarUpNB, -1) self.leftBarUpNB.AddPage(self.leftBarUpPane1, 'Proto Palette') self.leftBarDownPane = wx.Panel(self.leftFrame) self.leftBarDownNB = wx.Notebook(self.leftBarDownPane) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.leftBarDownNB, 1, wx.EXPAND) self.leftBarDownPane.SetSizer(sizer) self.leftBarDownPane0 = wx.Panel(self.leftBarDownNB, -1) self.leftBarDownNB.AddPage(self.leftBarDownPane0, 'Scene Graph') self.rightBarUpPane = wx.Panel(self.rightFrame) self.rightBarDownPane = wx.Panel(self.rightFrame) self.rightBarDownNB = wx.Notebook(self.rightBarDownPane) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.rightBarDownNB, 1, wx.EXPAND) self.rightBarDownPane.SetSizer(sizer) self.rightBarDownPane0 = wx.Panel(self.rightBarDownNB, -1) self.rightBarDownNB.AddPage(self.rightBarDownPane0, 'Layers') self.leftFrame.SplitHorizontally(self.leftBarUpPane, self.leftBarDownPane) self.rightFrame.SplitHorizontally(self.rightBarUpPane, self.rightBarDownPane) self.mainFrame.SplitVertically(self.leftFrame, self.baseFrame, 200) self.baseFrame.SplitVertically(self.viewFrame, self.rightFrame, 600) self.topView.SetDropTarget(PandaTextDropTarget(self.editor, self.topView)) self.frontView.SetDropTarget(PandaTextDropTarget(self.editor, self.frontView)) self.leftView.SetDropTarget(PandaTextDropTarget(self.editor, self.leftView)) self.perspView.SetDropTarget(PandaTextDropTarget(self.editor, self.perspView)) self.leftFrame.SetSashGravity(0.5) self.rightFrame.SetSashGravity(0.5) self.baseFrame.SetSashGravity(1.0) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.mainFrame, 1, wx.EXPAND, 0) self.SetSizer(sizer); self.Layout() self.objectPaletteUI = ObjectPaletteUI(self.leftBarUpPane0, self.editor) self.protoPaletteUI = ProtoPaletteUI(self.leftBarUpPane1, self.editor) self.objectPropertyUI = ObjectPropertyUI(self.rightBarUpPane, self.editor) self.sceneGraphUI = SceneGraphUI(self.leftBarDownPane0, self.editor) self.layerEditorUI = LayerEditorUI(self.rightBarDownPane0, self.editor) self.showGridMenuItem.Check(True) def onKeyEvent(self, evt): if evt.GetKeyCode() in range(97, 123): # for keys from a to z if evt.GetModifiers() == 4: # when shift is pressed while caps lock is on input = 'shift-%s'%chr(evt.GetKeyCode()) else: input = chr(evt.GetKeyCode()) elif evt.GetKeyCode() in range(65, 91): if evt.GetModifiers() == 4: # when shift is pressed input = 'shift-%s'%chr(evt.GetKeyCode() + 32) else: input = chr(evt.GetKeyCode() + 32) elif evt.GetKeyCode() in range(1, 27): # for keys from a to z with control input = 'control-%s'%chr(evt.GetKeyCode()+96) elif evt.GetKeyCode() == wx.WXK_DELETE: input = 'delete' elif evt.GetKeyCode() == wx.WXK_ESCAPE: input = 'escape' else: if evt.GetModifiers() == 4: input = 'shift-%s'%chr(evt.GetKeyCode()) elif evt.GetModifiers() == 2: input = 'control-%s'%chr(evt.GetKeyCode()) else: input = chr(evt.GetKeyCode()) if input in base.direct.hotKeyMap.keys(): keyDesc = base.direct.hotKeyMap[input] messenger.send(keyDesc[1]) def appInit(self): """Overridden from WxAppShell.py.""" # Create a new event loop (to overide default wxEventLoop) self.evtLoop = wx.EventLoop() self.oldLoop = wx.EventLoop.GetActive() wx.EventLoop.SetActive(self.evtLoop) taskMgr.add(self.wxStep, "evtLoopTask") def initialize(self): """Initializes the viewports and editor.""" self.Update() ViewportManager.updateAll() self.wxStep() ViewportManager.initializeAll() # Position the camera if base.trackball != None: base.trackball.node().setPos(0, 30, 0) base.trackball.node().setHpr(0, 15, 0) def wxStep(self, task = None): """A step in the WX event loop. You can either call this yourself or use as task.""" while self.evtLoop.Pending(): self.evtLoop.Dispatch() self.wxApp.ProcessIdle() if task != None: return task.cont def onNew(self, evt=None): self.editor.reset() self.sceneGraphUI.reset() self.layerEditorUI.reset() def onOpen(self, evt=None): dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.OPEN) if dialog.ShowModal() == wx.ID_OK: self.editor.load(dialog.GetPath()) dialog.Destroy() def onSave(self, evt=None): if self.editor.currentFile is None: self.onSaveAs(evt) else: self.editor.save() def onSaveAs(self, evt): dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.SAVE) if dialog.ShowModal() == wx.ID_OK: self.editor.saveAs(dialog.GetPath()) dialog.Destroy() def onDuplicate(self, evt): self.editor.objectMgr.duplicateSelected() def onMakeLive(self, evt): self.editor.objectMgr.makeSelectedLive() def toggleGrid(self, evt): if self.showGridMenuItem.IsChecked(): for grid in [self.perspView.grid, self.topView.grid, self.frontView.grid, self.leftView.grid]: if grid.isHidden(): grid.show() else: for grid in [self.perspView.grid, self.topView.grid, self.frontView.grid, self.leftView.grid]: if not grid.isHidden(): grid.hide() def toggleGridSnap(self, evt): if self.gridSnapMenuItem.IsChecked(): base.direct.manipulationControl.fGridSnap = 1 else: base.direct.manipulationControl.fGridSnap = 0 def onGridSize(self, evt): gridSizeUI = GridSizeUI(self, -1, 'Change Grid Size', self.perspView.grid.gridSize, self.perspView.grid.gridSpacing) gridSizeUI.ShowModal() gridSizeUI.Destroy() def onShowPandaObjects(self, evt): self.sceneGraphUI.showPandaObjectChildren() def onDestroy(self, evt): self.editor.protoPalette.saveToFile() self.editor.saveSettings() self.editor.reset() def updateGrids(self, newSize, newSpacing): self.perspView.grid.gridSize = newSize self.perspView.grid.gridSpacing = newSpacing self.perspView.grid.updateGrid() self.topView.grid.gridSize = newSize self.topView.grid.gridSpacing = newSpacing self.topView.grid.updateGrid() self.frontView.grid.gridSize = newSize self.frontView.grid.gridSpacing = newSpacing self.frontView.grid.updateGrid() self.leftView.grid.gridSize = newSize self.leftView.grid.gridSpacing = newSpacing self.leftView.grid.updateGrid() def onHotKeys(self, evt): hotKeyUI = HotKeyUI(self, -1, 'Hot Key List') hotKeyUI.ShowModal() hotKeyUI.Destroy() class GridSizeUI(wx.Dialog): def __init__(self, parent, id, title, gridSize, gridSpacing): wx.Dialog.__init__(self, parent, id, title, size=(250, 240)) self.parent = parent panel = wx.Panel(self, -1) vbox = wx.BoxSizer(wx.VERTICAL) wx.StaticBox(panel, -1, 'Grid Size', (5, 5), (235, 80)) self.gridSizeSlider = WxSlider(panel, -1, float(gridSize), 10.0, 100000.0, pos = (10, 25), size=(220, -1), style=wx.SL_HORIZONTAL | wx.SL_LABELS, textSize=(80,20)) self.gridSizeSlider.Enable() wx.StaticBox(panel, -1, 'Grid Space', (5, 90), (235, 80)) self.gridSpacingSlider = WxSlider(panel, -1, float(gridSpacing), 0.01, 2000.0, pos = (10, 115), size=(220, -1), style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.gridSpacingSlider.Enable() okButton = wx.Button(self, -1, 'Apply', size=(70, 20)) okButton.Bind(wx.EVT_BUTTON, self.onApply) vbox.Add(panel) vbox.Add(okButton, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 5) self.SetSizer(vbox) def onApply(self, evt): newSize = self.gridSizeSlider.GetValue() newSpacing = self.gridSpacingSlider.GetValue() self.parent.updateGrids(newSize, newSpacing) self.Destroy()