DirectSession.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. from PandaObject import *
  2. from DirectCameraControl import *
  3. from DirectManipulation import *
  4. from DirectSelection import *
  5. from DirectGrid import *
  6. from DirectGeometry import *
  7. from DirectLights import *
  8. import OnscreenText
  9. import types
  10. import __builtin__
  11. DIRECT_FLASH_DURATION = 1.5
  12. class DirectSession(PandaObject):
  13. def __init__(self):
  14. # Establish a global pointer to the direct object early on
  15. # so dependant classes can access it in their code
  16. __builtin__.direct = self
  17. self.drList = DisplayRegionList()
  18. self.dr = self.drList[0]
  19. self.camera = self.dr.camera
  20. self.iRay = self.dr.iRay
  21. self.group = render.attachNewNode('DIRECT')
  22. self.cameraControl = DirectCameraControl()
  23. self.manipulationControl = DirectManipulationControl()
  24. self.useObjectHandles()
  25. self.grid = DirectGrid()
  26. self.grid.disable()
  27. self.lights = DirectLights(direct.group)
  28. # Create some default lights
  29. self.lights.createDefaultLights()
  30. # But turn them off
  31. self.lights.allOff()
  32. # Initialize the collection of selected nodePaths
  33. self.selected = SelectedNodePaths()
  34. # Ancestry of currently selected object
  35. self.ancestry = []
  36. self.ancestryIndex = 0
  37. self.readout = OnscreenText.OnscreenText( '', 0.1, -0.95 )
  38. # Make sure readout is never lit or drawn in wireframe
  39. useDirectRenderStyle(self.readout)
  40. # self.readout.textNode.setCardColor(0.5, 0.5, 0.5, 0.5)
  41. self.readout.reparentTo( hidden )
  42. self.fControl = 0
  43. self.fAlt = 0
  44. self.fShift = 0
  45. self.pos = VBase3()
  46. self.hpr = VBase3()
  47. self.scale = VBase3()
  48. self.hitPt = Point3(0.0)
  49. # Lists for managing undo/redo operations
  50. self.undoList = []
  51. self.redoList = []
  52. # One run through the context task to init everything
  53. self.drList.updateContext()
  54. self.actionEvents = [
  55. ['select', self.select],
  56. ['deselect', self.deselect],
  57. ['deselectAll', self.deselectAll],
  58. ['highlightAll', self.selected.highlightAll],
  59. ['preRemoveNodePath', self.deselect],
  60. # Scene graph explorer functions
  61. ['SGENodePath_Select', self.select],
  62. ['SGENodePath_Deselect', self.deselect],
  63. ['SGENodePath_Flash', self.flash],
  64. ['SGENodePath_Isolate', self.isolate],
  65. ['SGENodePath_Toggle Vis', self.toggleVis],
  66. ['SGENodePath_Show All', self.showAllDescendants],
  67. ['SGENodePath_Delete', self.removeNodePath],
  68. ]
  69. self.keyEvents = ['left', 'right', 'up', 'down',
  70. 'escape', 'delete', 'control', 'control-up',
  71. 'shift', 'shift-up', 'alt', 'alt-up',
  72. 'page_up', 'page_down', 'tab',
  73. '[', '{', ']', '}',
  74. 'b', 'c', 'f', 'l', 's', 't', 'v', 'w']
  75. self.mouseEvents = ['mouse1', 'mouse1-up',
  76. 'mouse2', 'mouse2-up',
  77. 'mouse3', 'mouse3-up']
  78. def enable(self):
  79. # Make sure old tasks are shut down
  80. self.disable()
  81. # Start all display region context tasks
  82. self.drList.spawnContextTask()
  83. # Turn on mouse Flying
  84. self.cameraControl.enableMouseFly()
  85. # Turn on object manipulation
  86. self.manipulationControl.enableManipulation()
  87. # Make sure list of selected items is reset
  88. self.selected.reset()
  89. # Accept appropriate hooks
  90. self.enableKeyEvents()
  91. self.enableMouseEvents()
  92. self.enableActionEvents()
  93. def disable(self):
  94. # Shut down all display region context tasks
  95. self.drList.removeContextTask()
  96. # Turn off camera fly
  97. self.cameraControl.disableMouseFly()
  98. # Turn off object manipulation
  99. self.manipulationControl.disableManipulation()
  100. self.disableKeyEvents()
  101. self.disableMouseEvents()
  102. self.disableActionEvents()
  103. def minimumConfiguration(self):
  104. # Remove context task
  105. self.drList.removeContextTask()
  106. # Turn off camera fly
  107. self.cameraControl.disableMouseFly()
  108. # Ignore keyboard and action events
  109. self.disableKeyEvents()
  110. self.disableActionEvents()
  111. # But let mouse events pass through
  112. self.enableMouseEvents()
  113. def destroy(self):
  114. self.disable()
  115. def reset(self):
  116. self.enable()
  117. # EVENT FUNCTIONS
  118. def enableActionEvents(self):
  119. for event in self.actionEvents:
  120. self.accept(event[0], event[1], extraArgs = event[2:])
  121. def enableKeyEvents(self):
  122. for event in self.keyEvents:
  123. self.accept(event, self.inputHandler, [event])
  124. def enableMouseEvents(self):
  125. for event in self.mouseEvents:
  126. self.accept(event, self.inputHandler, [event])
  127. def disableActionEvents(self):
  128. for event, method in self.actionEvents:
  129. self.ignore(event)
  130. def disableKeyEvents(self):
  131. for event in self.keyEvents:
  132. self.ignore(event)
  133. def disableMouseEvents(self):
  134. for event in self.mouseEvents:
  135. self.ignore(event)
  136. def inputHandler(self, input):
  137. # Deal with keyboard and mouse input
  138. if input == 'mouse1':
  139. messenger.send('handleMouse1')
  140. elif input == 'mouse1-up':
  141. messenger.send('handleMouse1Up')
  142. elif input == 'mouse2':
  143. messenger.send('handleMouse2')
  144. elif input == 'mouse2-up':
  145. messenger.send('handleMouse2Up')
  146. elif input == 'mouse3':
  147. messenger.send('handleMouse3')
  148. elif input == 'mouse3-up':
  149. messenger.send('handleMouse3Up')
  150. elif input == 'shift':
  151. self.fShift = 1
  152. elif input == 'shift-up':
  153. self.fShift = 0
  154. elif input == 'control':
  155. self.fControl = 1
  156. elif input == 'control-up':
  157. self.fControl = 0
  158. elif input == 'alt':
  159. self.fAlt = 1
  160. elif input == 'alt-up':
  161. self.fAlt = 0
  162. elif input == 'page_up':
  163. self.upAncestry()
  164. elif input == 'page_down':
  165. self.downAncestry()
  166. elif input == 'escape':
  167. self.deselectAll()
  168. elif input == 'delete':
  169. self.removeAllSelected()
  170. elif input == 'tab':
  171. self.toggleWidgetVis()
  172. elif input == 'b':
  173. base.toggleBackface()
  174. elif input == 'l':
  175. self.lights.toggle()
  176. elif input == 's':
  177. if self.selected.last:
  178. self.select(self.selected.last)
  179. elif input == 't':
  180. base.toggleTexture()
  181. elif input == 'v':
  182. self.selected.toggleVisAll()
  183. elif input == 'w':
  184. base.toggleWireframe()
  185. elif (input == '[') | (input == '{'):
  186. self.undo()
  187. elif (input == ']') | (input == '}'):
  188. self.redo()
  189. def select(self, nodePath, fMultiSelect = 0, fResetAncestry = 1):
  190. dnp = self.selected.select(nodePath, fMultiSelect)
  191. if dnp:
  192. messenger.send('preSelectNodePath', [dnp])
  193. if fResetAncestry:
  194. # Update ancestry
  195. self.ancestry = dnp.getAncestry()
  196. self.ancestry.reverse()
  197. self.ancestryIndex = 0
  198. # Update the readout
  199. self.readout.reparentTo(render2d)
  200. self.readout.setText(dnp.name)
  201. # Show the manipulation widget
  202. self.widget.showWidgetIfActive()
  203. # Update camera controls coa to this point
  204. # Coa2Camera = Coa2Dnp * Dnp2Camera
  205. mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(self.camera)
  206. row = mCoa2Camera.getRow(3)
  207. coa = Vec3(row[0], row[1], row[2])
  208. self.cameraControl.updateCoa(coa)
  209. # Adjust widgets size
  210. # This uses the additional scaling factor used to grow and
  211. # shrink the widget
  212. self.widget.setScalingFactor(dnp.getRadius())
  213. # Spawn task to have object handles follow the selected object
  214. taskMgr.removeTasksNamed('followSelectedNodePath')
  215. t = Task.Task(self.followSelectedNodePathTask)
  216. t.dnp = dnp
  217. taskMgr.spawnTaskNamed(t, 'followSelectedNodePath')
  218. # Send an message marking the event
  219. messenger.send('selectedNodePath', [dnp])
  220. def followSelectedNodePathTask(self, state):
  221. mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(render)
  222. decomposeMatrix(mCoa2Render,
  223. self.scale,self.hpr,self.pos,
  224. CSDefault)
  225. self.widget.setPosHpr(self.pos,self.hpr)
  226. return Task.cont
  227. def deselect(self, nodePath):
  228. dnp = self.selected.deselect(nodePath)
  229. if dnp:
  230. # Hide the manipulation widget
  231. self.widget.hideWidget()
  232. self.readout.reparentTo(hidden)
  233. self.readout.setText(' ')
  234. taskMgr.removeTasksNamed('followSelectedNodePath')
  235. self.ancestry = []
  236. # Send an message marking the event
  237. messenger.send('deselectedNodePath', [dnp])
  238. def deselectAll(self):
  239. self.selected.deselectAll()
  240. # Hide the manipulation widget
  241. self.widget.hideWidget()
  242. self.readout.reparentTo(hidden)
  243. self.readout.setText(' ')
  244. taskMgr.removeTasksNamed('followSelectedNodePath')
  245. def flash(self, nodePath = 'None Given'):
  246. """ Highlight an object by setting it red for a few seconds """
  247. # Clean up any existing task
  248. taskMgr.removeTasksNamed('flashNodePath')
  249. # Spawn new task if appropriate
  250. if nodePath == 'None Given':
  251. # If nothing specified, try selected node path
  252. nodePath = self.selected.last
  253. if nodePath:
  254. if nodePath.hasColor():
  255. doneColor = nodePath.getColor()
  256. flashColor = VBase4(1) - doneColor
  257. flashColor.setW(1)
  258. else:
  259. doneColor = None
  260. flashColor = VBase4(1,0,0,1)
  261. # Temporarily set node path color
  262. nodePath.setColor(flashColor)
  263. # Clean up color in a few seconds
  264. t = taskMgr.spawnTaskNamed(
  265. Task.doLater(DIRECT_FLASH_DURATION,
  266. # This is just a dummy task
  267. Task.Task(self.flashDummy),
  268. 'flashNodePath'),
  269. 'flashNodePath')
  270. t.nodePath = nodePath
  271. t.doneColor = doneColor
  272. # This really does all the work
  273. t.uponDeath = self.flashDone
  274. def flashDummy(self, state):
  275. # Real work is done in upon death function
  276. return Task.done
  277. def flashDone(self,state):
  278. # Return node Path to original state
  279. if state.doneColor:
  280. state.nodePath.setColor(state.doneColor)
  281. else:
  282. state.nodePath.clearColor()
  283. def isolate(self, nodePath = 'None Given'):
  284. """ Show a node path and hide its siblings """
  285. # First kill the flashing task to avoid complications
  286. taskMgr.removeTasksNamed('flashNodePath')
  287. # Use currently selected node path if node selected
  288. if nodePath == 'None Given':
  289. nodePath = self.selected.last
  290. # Do we have a node path?
  291. if nodePath:
  292. # Yes, show everything in level
  293. self.showAllDescendants(nodePath.getParent())
  294. # Now hide all of this node path's siblings
  295. nodePath.hideSiblings()
  296. def toggleVis(self, nodePath = 'None Given'):
  297. """ Toggle visibility of node path """
  298. # First kill the flashing task to avoid complications
  299. taskMgr.removeTasksNamed('flashNodePath')
  300. if nodePath == 'None Given':
  301. # If nothing specified, try selected node path
  302. nodePath = self.selected.last
  303. if nodePath:
  304. # Now toggle node path's visibility state
  305. nodePath.toggleVis()
  306. def removeNodePath(self, nodePath = 'None Given'):
  307. if nodePath == 'None Given':
  308. # If nothing specified, try selected node path
  309. nodePath = self.selected.last
  310. if nodePath:
  311. nodePath.remove()
  312. def removeAllSelected(self):
  313. self.selected.removeAll()
  314. def showAllDescendants(self, nodePath = render):
  315. """ Show the level and its descendants """
  316. nodePath.showAllDescendants()
  317. nodePath.hideCollisionSolids()
  318. def upAncestry(self):
  319. if self.ancestry:
  320. l = len(self.ancestry)
  321. i = self.ancestryIndex + 1
  322. if i < l:
  323. np = self.ancestry[i]
  324. name = np.getName()
  325. if (name != 'render') & (name != 'renderTop'):
  326. self.ancestryIndex = i
  327. self.select(np, 0, 0)
  328. self.flash(np)
  329. def downAncestry(self):
  330. if self.ancestry:
  331. l = len(self.ancestry)
  332. i = self.ancestryIndex - 1
  333. if i >= 0:
  334. np = self.ancestry[i]
  335. name = np.getName()
  336. if (name != 'render') & (name != 'renderTop'):
  337. self.ancestryIndex = i
  338. self.select(np, 0, 0)
  339. self.flash(np)
  340. # UNDO REDO FUNCTIONS
  341. def pushUndo(self, nodePathList, fResetRedo = 1):
  342. # Assemble group of changes
  343. undoGroup = []
  344. for nodePath in nodePathList:
  345. pos = nodePath.getPos()
  346. hpr = nodePath.getHpr()
  347. scale = nodePath.getScale()
  348. undoGroup.append([nodePath, pos,hpr,scale])
  349. # Now record group
  350. self.undoList.append(undoGroup)
  351. # Truncate list
  352. self.undoList = self.undoList[-25:]
  353. # Alert anyone who cares
  354. messenger.send('pushUndo')
  355. if fResetRedo & (nodePathList != []):
  356. self.redoList = []
  357. messenger.send('redoListEmpty')
  358. def popUndoGroup(self):
  359. # Get last item
  360. undoGroup = self.undoList[-1]
  361. # Strip last item off of undo list
  362. self.undoList = self.undoList[:-1]
  363. # Update state of undo button
  364. if not self.undoList:
  365. messenger.send('undoListEmpty')
  366. # Return last item
  367. return undoGroup
  368. def pushRedo(self, nodePathList):
  369. # Assemble group of changes
  370. redoGroup = []
  371. for nodePath in nodePathList:
  372. pos = nodePath.getPos()
  373. hpr = nodePath.getHpr()
  374. scale = nodePath.getScale()
  375. redoGroup.append([nodePath, pos,hpr,scale])
  376. # Now record redo group
  377. self.redoList.append(redoGroup)
  378. # Truncate list
  379. self.redoList = self.redoList[-25:]
  380. # Alert anyone who cares
  381. messenger.send('pushRedo')
  382. def popRedoGroup(self):
  383. # Get last item
  384. redoGroup = self.redoList[-1]
  385. # Strip last item off of redo list
  386. self.redoList = self.redoList[:-1]
  387. # Update state of redo button
  388. if not self.redoList:
  389. messenger.send('redoListEmpty')
  390. # Return last item
  391. return redoGroup
  392. def undo(self):
  393. if self.undoList:
  394. # Get last item off of redo list
  395. undoGroup = self.popUndoGroup()
  396. # Record redo information
  397. nodePathList = map(lambda x: x[0], undoGroup)
  398. self.pushRedo(nodePathList)
  399. # Now undo xform for group
  400. for pose in undoGroup:
  401. # Undo xform
  402. pose[0].setPosHprScale(pose[1], pose[2], pose[3])
  403. # Alert anyone who cares
  404. messenger.send('undo')
  405. def redo(self):
  406. if self.redoList:
  407. # Get last item off of redo list
  408. redoGroup = self.popRedoGroup()
  409. # Record undo information
  410. nodePathList = map(lambda x: x[0], redoGroup)
  411. self.pushUndo(nodePathList, fResetRedo = 0)
  412. # Redo xform
  413. for pose in redoGroup:
  414. pose[0].setPosHprScale(pose[1], pose[2], pose[3])
  415. # Alert anyone who cares
  416. messenger.send('redo')
  417. # UTILITY FUNCTIONS
  418. def useObjectHandles(self):
  419. self.widget = self.manipulationControl.objectHandles
  420. self.widget.reparentTo(direct.group)
  421. def hideReadout(self):
  422. self.readout.reparentTo(hidden)
  423. def toggleWidgetVis(self):
  424. self.widget.toggleWidget()
  425. class DisplayRegionList:
  426. def __init__(self):
  427. self.displayRegionList = []
  428. for camera in base.cameraList:
  429. self.displayRegionList.append(
  430. DisplayRegionContext(base.win, camera))
  431. def __getitem__(self, index):
  432. return self.displayRegionList[index]
  433. def updateContext(self):
  434. for dr in self.displayRegionList:
  435. dr.contextTask(None)
  436. def spawnContextTask(self):
  437. for dr in self.displayRegionList:
  438. dr.start()
  439. def removeContextTask(self):
  440. for dr in self.displayRegionList:
  441. dr.stop()
  442. def setNearFar(self, near, far):
  443. for dr in self.displayRegionList:
  444. dr.camNode.setNearFar(near, far)
  445. def setNear(self, near):
  446. for dr in self.displayRegionList:
  447. dr.camNode.setNear(near)
  448. def setFar(self, far):
  449. for dr in self.displayRegionList:
  450. dr.camNode.setFar(far)
  451. def setFov(self, hfov, vfov):
  452. for dr in self.displayRegionList:
  453. dr.camNode.setFov(hfov, vfov)
  454. def setHfov(self, fov):
  455. for dr in self.displayRegionList:
  456. dr.camNode.setHfov(fov)
  457. def setVfov(self, fov):
  458. for dr in self.displayRegionList:
  459. dr.camNode.setVfov(fov)
  460. class DisplayRegionContext:
  461. def __init__(self, win, camera):
  462. self.win = win
  463. self.camera = camera
  464. self.cam = self.camera.find('**/+Camera')
  465. self.camNode = self.cam.getNode(0)
  466. self.iRay = SelectionRay(self.camera)
  467. self.nearVec = Vec3(0)
  468. self.mouseX = 0.0
  469. self.mouseY = 0.0
  470. def __getitem__(self,key):
  471. return self.__dict__[key]
  472. def start(self):
  473. # First shutdown any existing task
  474. self.stop()
  475. # Start a new context task
  476. self.spawnContextTask()
  477. def stop(self):
  478. # Kill the existing context task
  479. taskMgr.removeTasksNamed('DIRECTContextTask')
  480. def spawnContextTask(self):
  481. taskMgr.spawnTaskNamed(Task.Task(self.contextTask),
  482. 'DIRECTContextTask')
  483. def removeContextTask(self):
  484. taskMgr.removeTasksNamed('DIRECTContextTask')
  485. def contextTask(self, state):
  486. # Window Data
  487. self.width = self.win.getWidth()
  488. self.height = self.win.getHeight()
  489. self.near = self.camNode.getNear()
  490. self.far = self.camNode.getFar()
  491. self.fovH = self.camNode.getHfov()
  492. self.fovV = self.camNode.getVfov()
  493. self.nearWidth = math.tan(deg2Rad(self.fovH / 2.0)) * self.near * 2.0
  494. self.nearHeight = math.tan(deg2Rad(self.fovV / 2.0)) * self.near * 2.0
  495. self.left = -self.nearWidth/2.0
  496. self.right = self.nearWidth/2.0
  497. self.top = self.nearHeight/2.0
  498. self.bottom = -self.nearHeight/2.0
  499. # Mouse Data
  500. # Last frame
  501. self.mouseLastX = self.mouseX
  502. self.mouseLastY = self.mouseY
  503. # Values for this frame
  504. # This ranges from -1 to 1
  505. if (base.mouseWatcher.node().hasMouse()):
  506. self.mouseX = base.mouseWatcher.node().getMouseX()
  507. self.mouseY = base.mouseWatcher.node().getMouseY()
  508. # Delta percent of window the mouse moved
  509. self.mouseDeltaX = self.mouseX - self.mouseLastX
  510. self.mouseDeltaY = self.mouseY - self.mouseLastY
  511. self.nearVec.set((self.nearWidth/2.0) * self.mouseX,
  512. self.near,
  513. (self.nearHeight/2.0) * self.mouseY)
  514. # Continue the task
  515. return Task.cont