DirectSession.py 22 KB

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