LevelEditorBase.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. """
  2. Base class for Level Editor
  3. You should write your own LevelEditor class inheriting this.
  4. Refer LevelEditor.py for example.
  5. """
  6. from direct.showbase.DirectObject import *
  7. from direct.directtools.DirectUtil import *
  8. from direct.gui.DirectGui import *
  9. from CurveEditor import *
  10. from FileMgr import *
  11. from ActionMgr import *
  12. from MayaConverter import *
  13. class LevelEditorBase(DirectObject):
  14. """ Base Class for Panda3D LevelEditor """
  15. def __init__(self):
  16. #loadPrcFileData('startup', 'window-type none')
  17. self.currentFile = None
  18. self.fNeedToSave = False
  19. self.actionEvents = []
  20. #self.objectMgr = ObjectMgr(self)
  21. self.curveEditor = CurveEditor(self)
  22. self.fileMgr = FileMgr(self)
  23. self.actionMgr = ActionMgr()
  24. self.fMoveCamera = False
  25. self.NPParent = render
  26. # define your own config file in inherited class
  27. self.settingsFile = None
  28. # you can show/hide specific properties by using propertiesMask and this mode
  29. self.BASE_MODE = BitMask32.bit(0)
  30. self.CREATE_CURVE_MODE = BitMask32.bit(2)
  31. self.EDIT_CURVE_MODE = BitMask32.bit(3)
  32. self.mode = self.BASE_MODE
  33. self.preMode = None
  34. def initialize(self):
  35. """ You should call this in your __init__ method of inherited LevelEditor class """
  36. # specifiy what obj can be 'selected' as objects
  37. base.direct.selected.addTag('OBJRoot')
  38. self.actionEvents.extend([
  39. # Node path events
  40. ('DIRECT-select', self.select),
  41. ('DIRECT-delete', self.handleDelete),
  42. ('DIRECT-preDeselectAll', self.deselectAll),
  43. ('DIRECT_deselectAll', self.deselectAllCB),
  44. ('preRemoveNodePath', self.removeNodePathHook),
  45. ('DIRECT_deselectedNodePath', self.deselectAllCB),
  46. ('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', self.selectedNodePathHook),
  47. ('DIRECT_deselectAll', self.deselectAll),
  48. ('LE-Undo', self.actionMgr.undo),
  49. ('LE-Redo', self.actionMgr.redo),
  50. ('LE-Duplicate', self.objectMgr.duplicateSelected),
  51. ('DIRECT_manipulateObjectCleanup', self.cleanUpManipulating),
  52. ('LE-MakeLive', self.objectMgr.makeSelectedLive),
  53. ('LE-NewScene', self.ui.onNew),
  54. ('LE-SaveScene', self.ui.onSave),
  55. ('LE-OpenScene', self.ui.onOpen),
  56. ('LE-Quit', self.ui.quit),
  57. ('DIRECT-mouse1', self.handleMouse1),
  58. ('DIRECT-mouse1Up', self.handleMouse1Up),
  59. ('DIRECT-mouse2', self.handleMouse2),
  60. ('DIRECT-mouse2Up', self.handleMouse2Up),
  61. ('DIRECT-mouse3', self.handleMouse3),
  62. ('DIRECT-mouse3Up', self.handleMouse3Up),
  63. ('DIRECT-toggleWidgetVis', self.toggleWidget),
  64. ])
  65. # Add all the action events
  66. for event in self.actionEvents:
  67. if len(event) == 3:
  68. self.accept(event[0], event[1], event[2])
  69. else:
  70. self.accept(event[0], event[1])
  71. # editor state text display such as edit mode
  72. self.statusReadout = OnscreenText(
  73. pos = (-1.2, 0.9), bg=Vec4(1,1,1,1),
  74. scale = 0.05, align = TextNode.ALeft,
  75. mayChange = 1, font = TextNode.getDefaultFont())
  76. self.statusReadout.setText("")
  77. # Make sure readout is never lit or drawn in wireframe
  78. useDirectRenderStyle(self.statusReadout)
  79. self.statusReadout.reparentTo(hidden)
  80. self.statusLines = []
  81. taskMgr.doMethodLater(5, self.updateStatusReadoutTimeouts, 'updateStatus')
  82. self.loadSettings()
  83. self.reset()
  84. def setTitleWithFilename(self, filename=""):
  85. title = self.ui.appname
  86. if filename != "":
  87. filenameshort = os.path.basename(filename)
  88. title = title + " (%s)"%filenameshort
  89. self.ui.SetLabel(title)
  90. def removeNodePathHook(self, nodePath):
  91. if nodePath is None:
  92. return
  93. base.direct.deselect(nodePath)
  94. self.objectMgr.removeObjectByNodePath(nodePath)
  95. if (base.direct.selected.last != None and nodePath.compareTo(base.direct.selected.last)==0):
  96. # if base.direct.selected.last is refering to this
  97. # removed obj, clear the reference
  98. if (hasattr(__builtins__,'last')):
  99. __builtins__.last = None
  100. else:
  101. __builtins__['last'] = None
  102. base.direct.selected.last = None
  103. def toggleWidget(self):
  104. if self.objectMgr.currNodePath:
  105. obj = self.objectMgr.findObjectByNodePath(self.objectMgr.currNodePath)
  106. if obj and not obj[OG.OBJ_DEF].movable:
  107. return
  108. base.direct.toggleWidgetVis()
  109. def handleMouse1(self, modifiers):
  110. if base.direct.fAlt or modifiers == 4:
  111. self.fMoveCamera = True
  112. return
  113. if self.mode == self.CREATE_CURVE_MODE :
  114. self.curveEditor.createCurve()
  115. def handleMouse1Up(self):
  116. self.fMoveCamera = False
  117. def handleMouse2(self, modifiers):
  118. if base.direct.fAlt or modifiers == 4:
  119. self.fMoveCamera = True
  120. return
  121. def handleMouse2Up(self):
  122. self.fMoveCamera = False
  123. def handleMouse3(self, modifiers):
  124. if base.direct.fAlt or modifiers == 4:
  125. self.fMoveCamera = True
  126. return
  127. self.ui.onRightDown()
  128. def handleMouse3Up(self):
  129. self.fMoveCamera = False
  130. def handleDelete(self):
  131. oldSelectedNPs = base.direct.selected.getSelectedAsList()
  132. oldUIDs = []
  133. for oldNP in oldSelectedNPs:
  134. obj = self.objectMgr.findObjectByNodePath(oldNP)
  135. if obj:
  136. oldUIDs.append(obj[OG.OBJ_UID])
  137. action = ActionDeleteObj(self)
  138. self.actionMgr.push(action)
  139. action()
  140. for uid in oldUIDs:
  141. self.ui.sceneGraphUI.delete(uid)
  142. ## reply = wx.MessageBox("Do you want to delete selected?", "Delete?",
  143. ## wx.YES_NO | wx.ICON_QUESTION)
  144. ## if reply == wx.YES:
  145. ## base.direct.removeAllSelected()
  146. ## else:
  147. ## # need to reset COA
  148. ## dnp = base.direct.selected.last
  149. ## # Update camera controls coa to this point
  150. ## # Coa2Camera = Coa2Dnp * Dnp2Camera
  151. ## mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(base.direct.camera)
  152. ## row = mCoa2Camera.getRow(3)
  153. ## coa = Vec3(row[0], row[1], row[2])
  154. ## base.direct.cameraControl.updateCoa(coa)
  155. def cleanUpManipulating(self, selectedNPs):
  156. for np in selectedNPs:
  157. obj = self.objectMgr.findObjectByNodePath(np)
  158. if obj:
  159. action = ActionTransformObj(self, obj[OG.OBJ_UID], Mat4(np.getMat()))
  160. self.actionMgr.push(action)
  161. action()
  162. def select(self, nodePath, fMultiSelect=0, fSelectTag=1, fResetAncestry=1, fLEPane=0, fUndo=1):
  163. if fUndo:
  164. # Select tagged object if present
  165. if fSelectTag:
  166. for tag in base.direct.selected.tagList:
  167. if nodePath.hasNetTag(tag):
  168. nodePath = nodePath.findNetTag(tag)
  169. break
  170. action = ActionSelectObj(self, nodePath, fMultiSelect)
  171. self.actionMgr.push(action)
  172. action()
  173. else:
  174. base.direct.selectCB(nodePath, fMultiSelect, fSelectTag, fResetAncestry, fLEPane, fUndo)
  175. def selectedNodePathHook(self, nodePath, fMultiSelect = 0, fSelectTag = 1, fLEPane = 0):
  176. # handle unpickable nodepath
  177. if nodePath.getName() in base.direct.iRay.unpickable:
  178. base.direct.deselect(nodePath)
  179. return
  180. if fMultiSelect == 0 and fLEPane == 0:
  181. oldSelectedNPs = base.direct.selected.getSelectedAsList()
  182. for oldNP in oldSelectedNPs:
  183. obj = self.objectMgr.findObjectByNodePath(oldNP)
  184. if obj:
  185. self.ui.sceneGraphUI.deSelect(obj[OG.OBJ_UID])
  186. self.objectMgr.selectObject(nodePath, fLEPane)
  187. self.ui.buildContextMenu(nodePath)
  188. if self.mode == self.EDIT_CURVE_MODE:
  189. taskMgr.add(self.curveEditor.editCurve, "modify")
  190. self.curveEditor.accept("DIRECT-enter", self.curveEditor.onBaseMode)
  191. def deselectAll(self, np=None):
  192. if len(base.direct.selected.getSelectedAsList()) ==0:
  193. return
  194. action = ActionDeselectAll(self)
  195. self.actionMgr.push(action)
  196. action()
  197. def deselectAllCB(self, dnp=None):
  198. self.objectMgr.deselectAll()
  199. def reset(self):
  200. if self.fNeedToSave:
  201. reply = wx.MessageBox("Do you want to save current scene?", "Save?",
  202. wx.YES_NO | wx.ICON_QUESTION)
  203. if reply == wx.YES:
  204. result = self.ui.onSave()
  205. if result == False:
  206. return
  207. base.direct.deselectAll()
  208. self.ui.reset()
  209. self.objectMgr.reset()
  210. self.actionMgr.reset()
  211. self.ui.perspView.camera.setPos(-19, -19, 19)
  212. self.ui.perspView.camera.lookAt(Point3(0, 0, 0))
  213. self.ui.leftView.camera.setPos(600, 0, 0)
  214. self.ui.frontView.camera.setPos(0, -600, 0)
  215. self.ui.topView.camera.setPos(0, 0, 600)
  216. self.resetOrthoCam(self.ui.topView)
  217. self.resetOrthoCam(self.ui.frontView)
  218. self.resetOrthoCam(self.ui.leftView)
  219. self.fNeedToSave = False
  220. self.setTitleWithFilename()
  221. def resetOrthoCam(self, view):
  222. base.direct.drList[base.camList.index(NodePath(view.camNode))].orthoFactor = 0.1
  223. x = view.ClientSize.GetWidth() * 0.1
  224. y = view.ClientSize.GetHeight() * 0.1
  225. view.camLens.setFilmSize(x, y)
  226. def save(self):
  227. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
  228. if self.currentFile:
  229. self.fileMgr.saveToFile(self.currentFile)
  230. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
  231. def saveAs(self, fileName):
  232. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
  233. self.fileMgr.saveToFile(fileName)
  234. self.currentFile = fileName
  235. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
  236. def load(self, fileName):
  237. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
  238. self.reset()
  239. self.fileMgr.loadFromFile(fileName)
  240. self.currentFile = fileName
  241. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
  242. def saveSettings(self):
  243. if self.settingsFile is None:
  244. return
  245. try:
  246. f = open(self.settingsFile, 'w')
  247. f.write('gridSize\n%f\n'%self.ui.perspView.grid.gridSize)
  248. f.write('gridSpacing\n%f\n'%self.ui.perspView.grid.gridSpacing)
  249. f.write('hotKey\n%s\n'%base.direct.hotKeyMap)
  250. f.close()
  251. except:
  252. pass
  253. def loadSettings(self):
  254. if self.settingsFile is None:
  255. return
  256. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
  257. try:
  258. f = open(self.settingsFile, 'r')
  259. configLines = f.readlines()
  260. f.close()
  261. gridSize = 100.0
  262. gridSpacing = 5.0
  263. for i in range(0, len(configLines)):
  264. line = configLines[i]
  265. i = i + 1
  266. if line.startswith('gridSize'):
  267. gridSize = float(configLines[i])
  268. elif line.startswith('gridSpacing'):
  269. gridSpacing = float(configLines[i])
  270. elif line.startswith('hotKey'):
  271. customHotKeyMap = eval(configLines[i])
  272. customHotKeyDict = {}
  273. for hotKey in customHotKeyMap.keys():
  274. desc = customHotKeyMap[hotKey]
  275. customHotKeyDict[desc[1]] = hotKey
  276. overriddenKeys = []
  277. for key in base.direct.hotKeyMap.keys():
  278. desc = base.direct.hotKeyMap[key]
  279. if desc[1] in customHotKeyDict.keys():
  280. overriddenKeys.append(key)
  281. for key in overriddenKeys:
  282. del base.direct.hotKeyMap[key]
  283. base.direct.hotKeyMap.update(customHotKeyMap)
  284. self.ui.updateGrids(gridSize, gridSpacing)
  285. self.ui.updateMenu()
  286. except:
  287. pass
  288. self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
  289. def convertMaya(self, modelname, callBack, obj=None, isAnim=False):
  290. if obj and isAnim:
  291. mayaConverter = MayaConverter(self.ui, self, modelname, callBack, obj, isAnim)
  292. else:
  293. reply = wx.MessageBox("Is it an animation file?", "Animation?",
  294. wx.YES_NO | wx.ICON_QUESTION)
  295. if reply == wx.YES:
  296. mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, True)
  297. else:
  298. mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, False)
  299. mayaConverter.Show()
  300. def convertFromMaya(self, modelname, callBack):
  301. mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, False)
  302. mayaConverter.Show()
  303. def exportToMaya(self, mayaFileName):
  304. exportRootNP = render
  305. self.exportToMayaCB(mayaFileName, exportRootNP)
  306. def exportToMayaCB(self, mayaFileName, exportRootNP):
  307. bamFileName = mayaFileName + ".bam"
  308. if base.direct.selected.last:
  309. obj = self.objectMgr.findObjectByNodePath(base.direct.selected.last)
  310. if obj:
  311. exportRootNP = obj[OG.OBJ_NP]
  312. exportRootNP.writeBamFile(bamFileName)
  313. mayaConverter = MayaConverter(self.ui, self, mayaFileName, None, None, False, FROM_BAM_TO_MAYA)
  314. mayaConverter.Show()
  315. def updateStatusReadout(self, status, color=None):
  316. if status:
  317. # add new status line, first check to see if it already exists
  318. alreadyExists = False
  319. for currLine in self.statusLines:
  320. if (status == currLine[1]):
  321. alreadyExists = True
  322. break
  323. if (alreadyExists == False):
  324. time = globalClock.getRealTime() + 15
  325. self.statusLines.append([time,status,color])
  326. # update display of new status lines
  327. self.statusReadout.reparentTo(aspect2d)
  328. statusText = ""
  329. lastColor = None
  330. for currLine in self.statusLines:
  331. statusText += currLine[1] + '\n'
  332. lastColor = currLine[2]
  333. self.statusReadout.setText(statusText)
  334. if (lastColor):
  335. self.statusReadout.textNode.setCardColor(
  336. lastColor[0], lastColor[1], lastColor[2], lastColor[3])
  337. self.statusReadout.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
  338. else:
  339. self.statusReadout.textNode.setCardColor(1,1,1,1)
  340. self.statusReadout.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
  341. def updateStatusReadoutTimeouts(self,task=None):
  342. removalList = []
  343. for currLine in self.statusLines:
  344. if (globalClock.getRealTime() >= currLine[0]):
  345. removalList.append(currLine)
  346. for currRemoval in removalList:
  347. self.statusLines.remove(currRemoval)
  348. self.updateStatusReadout(None)
  349. # perform doMethodLater again after delay
  350. # This crashes when CTRL-C'ing, so this is a cheap hack.
  351. #return 2
  352. from direct.task import Task
  353. return Task.again
  354. def propMeetsReq(self, typeName, parentNP):
  355. if self.ui.parentToSelectedMenuItem.IsChecked():
  356. if base.direct.selected.last:
  357. parent = base.le.objectMgr.findObjectByNodePath(base.direct.selected.last)
  358. if parent:
  359. parentNP[0] = parent[OG.OBJ_NP]
  360. else:
  361. parentNP[0] = None
  362. return True