DirectSession.py 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130
  1. import math
  2. import types
  3. import string
  4. from pandac.PandaModules import *
  5. from DirectUtil import *
  6. from direct.showbase.DirectObject import DirectObject
  7. from direct.task import Task
  8. from DirectGlobals import DIRECT_NO_MOD
  9. from DirectCameraControl import DirectCameraControl
  10. from DirectManipulation import DirectManipulationControl
  11. from DirectSelection import SelectionRay, COA_ORIGIN, SelectedNodePaths
  12. from DirectGrid import DirectGrid
  13. #from DirectGeometry import *
  14. from DirectLights import DirectLights
  15. from direct.cluster.ClusterClient import createClusterClient, DummyClusterClient
  16. from direct.cluster.ClusterServer import ClusterServer
  17. ## from direct.tkpanels import Placer
  18. ## from direct.tkwidgets import Slider
  19. ## from direct.tkwidgets import SceneGraphExplorer
  20. from direct.gui import OnscreenText
  21. from direct.showbase import Loader
  22. class DirectSession(DirectObject):
  23. # post this to the bboard to make sure DIRECT doesn't turn on
  24. DIRECTdisablePost = 'disableDIRECT'
  25. def __init__(self):
  26. # Establish a global pointer to the direct object early on
  27. # so dependant classes can access it in their code
  28. __builtins__["direct"] = base.direct = self
  29. # These come early since they are used later on
  30. self.group = render.attachNewNode('DIRECT')
  31. self.font = TextNode.getDefaultFont()
  32. self.fEnabled = 0
  33. self.fEnabledLight = 0
  34. self.fScaleWidgetByCam = 0 # [gjeon] flag for scaling widget by distance from the camera
  35. self.drList = DisplayRegionList()
  36. self.iRayList = map(lambda x: x.iRay, self.drList)
  37. self.dr = self.drList[0]
  38. self.win = base.win
  39. self.camera = base.camera
  40. self.cam = base.cam
  41. self.camNode = base.camNode
  42. self.trueCamera = self.camera
  43. self.iRay = self.dr.iRay
  44. self.coaMode = COA_ORIGIN
  45. self.cameraControl = DirectCameraControl()
  46. self.manipulationControl = DirectManipulationControl()
  47. self.useObjectHandles()
  48. self.grid = DirectGrid()
  49. self.grid.disable()
  50. self.lights = DirectLights(base.direct.group)
  51. # Create some default lights
  52. self.lights.createDefaultLights()
  53. # But turn them off
  54. self.lights.allOff()
  55. # Initialize the collection of selected nodePaths
  56. self.selected = SelectedNodePaths()
  57. # Ancestry of currently selected object
  58. self.ancestry = []
  59. self.ancestryIndex = 0
  60. self.activeParent = None
  61. self.selectedNPReadout = OnscreenText.OnscreenText(
  62. pos = (-1.0, -0.9), bg=Vec4(1, 1, 1, 1),
  63. scale = 0.05, align = TextNode.ALeft,
  64. mayChange = 1, font = self.font)
  65. # Make sure readout is never lit or drawn in wireframe
  66. useDirectRenderStyle(self.selectedNPReadout)
  67. self.selectedNPReadout.reparentTo(hidden)
  68. self.activeParentReadout = OnscreenText.OnscreenText(
  69. pos = (-1.0, -0.975), bg=Vec4(1, 1, 1, 1),
  70. scale = 0.05, align = TextNode.ALeft,
  71. mayChange = 1, font = self.font)
  72. # Make sure readout is never lit or drawn in wireframe
  73. useDirectRenderStyle(self.activeParentReadout)
  74. self.activeParentReadout.reparentTo(hidden)
  75. self.directMessageReadout = OnscreenText.OnscreenText(
  76. pos = (-1.0, 0.9), bg=Vec4(1, 1, 1, 1),
  77. scale = 0.05, align = TextNode.ALeft,
  78. mayChange = 1, font = self.font)
  79. # Make sure readout is never lit or drawn in wireframe
  80. useDirectRenderStyle(self.directMessageReadout)
  81. self.directMessageReadout.reparentTo(hidden)
  82. # Create a vrpn client vrpn-server or default
  83. self.deviceManager = None
  84. self.joybox = None
  85. self.radamec = None
  86. self.fastrak = []
  87. if base.config.GetBool('want-vrpn', 0):
  88. from direct.directdevices import DirectDeviceManager
  89. self.deviceManager = DirectDeviceManager.DirectDeviceManager()
  90. # Automatically create any devices specified in config file
  91. joybox = base.config.GetString('vrpn-joybox-device', '')
  92. radamec = base.config.GetString('vrpn-radamec-device', '')
  93. fastrak = base.config.GetString('vrpn-fastrak-device', '')
  94. if joybox:
  95. from direct.directdevices import DirectJoybox
  96. self.joybox = DirectJoybox.DirectJoybox(joybox)
  97. if radamec:
  98. from direct.directdevices import DirectRadamec
  99. self.radamec = DirectRadamec.DirectRadamec(radamec)
  100. if fastrak:
  101. from direct.directdevices import DirectFastrak
  102. # parse string into format device:N where N is the sensor name
  103. fastrak = string.split(fastrak)
  104. for i in range(len(fastrak))[1:]:
  105. self.fastrak.append(DirectFastrak.DirectFastrak(fastrak[0] + ':' + fastrak[i]))
  106. self.fControl = 0
  107. self.fAlt = 0
  108. self.fShift = 0
  109. self.fMouse1 = 0 # [gjeon] to update alt key information while mouse1 is pressed
  110. self.pos = VBase3()
  111. self.hpr = VBase3()
  112. self.scale = VBase3()
  113. self.hitPt = Point3(0.0)
  114. # Lists for managing undo/redo operations
  115. self.undoList = []
  116. self.redoList = []
  117. # One run through the context task to init everything
  118. self.drList.updateContext()
  119. for dr in self.drList:
  120. dr.camUpdate()
  121. self.actionEvents = [
  122. ['select', self.select],
  123. ['deselect', self.deselect],
  124. ['deselectAll', self.deselectAll],
  125. ['highlightAll', self.selected.highlightAll],
  126. ['preRemoveNodePath', self.deselect],
  127. # Scene graph explorer functions
  128. ['SGE_Select', self.select],
  129. ['SGE_Deselect', self.deselect],
  130. ['SGE_Set Reparent Target', self.setActiveParent],
  131. ['SGE_Reparent', self.reparent],
  132. ['SGE_WRT Reparent', lambda np, s=self: s.reparent(np, fWrt = 1)],
  133. ['SGE_Flash', self.flash],
  134. ['SGE_Isolate', self.isolate],
  135. ['SGE_Toggle Vis', self.toggleVis],
  136. ['SGE_Show All', self.showAllDescendants],
  137. ['SGE_Fit', self.fitOnNodePath],
  138. ['SGE_Delete', self.removeNodePath],
  139. ['SGE_Set Name', self.getAndSetName],
  140. ]
  141. if base.wantTk:
  142. from direct.tkpanels import Placer
  143. from direct.tkwidgets import Slider
  144. from direct.tkwidgets import SceneGraphExplorer
  145. self.actionEvents.extend([
  146. ['SGE_Place', Placer.place],
  147. ['SGE_Set Color', Slider.rgbPanel],
  148. ['SGE_Explore', SceneGraphExplorer.explore],])
  149. self.modifierEvents = ['control', 'control-up',
  150. 'shift', 'shift-up',
  151. 'alt', 'alt-up',
  152. ]
  153. self.keyEvents = ['escape', 'delete', 'page_up', 'page_down',
  154. '[', '{', ']', '}',
  155. 'shift-a', 'b', 'control-f',
  156. 'l', 'shift-l', 'o', 'p', 'r',
  157. 'shift-r', 's', 't', 'v', 'w']
  158. self.mouseEvents = ['mouse1', 'mouse1-up',
  159. 'shift-mouse1', 'shift-mouse1-up',
  160. 'control-mouse1', 'control-mouse1-up',
  161. 'alt-mouse1', 'alt-mouse1-up',
  162. 'mouse2', 'mouse2-up',
  163. 'shift-mouse2', 'shift-mouse2-up',
  164. 'control-mouse2', 'control-mouse2-up',
  165. 'alt-mouse2', 'alt-mouse2-up',
  166. 'mouse3', 'mouse3-up',
  167. 'shift-mouse3', 'shift-mouse3-up',
  168. 'control-mouse3', 'control-mouse3-up',
  169. 'alt-mouse3', 'alt-mouse3-up',
  170. ]
  171. self.passThroughKeys = ['v','b','l','p', 'r', 'shift-r', 's', 't','shift-a', 'w']
  172. if base.wantTk:
  173. from direct.showbase import TkGlobal
  174. from direct.tkpanels import DirectSessionPanel
  175. self.panel = DirectSessionPanel.DirectSessionPanel(parent = tkroot)
  176. try:
  177. # Has the clusterMode been set externally (i.e. via the
  178. # bootstrap application?
  179. self.clusterMode = clusterMode
  180. except NameError:
  181. # Has the clusterMode been set via a config variable?
  182. self.clusterMode = base.config.GetString("cluster-mode", '')
  183. if self.clusterMode == 'client':
  184. self.cluster = createClusterClient()
  185. elif self.clusterMode == 'server':
  186. self.cluster = ClusterServer(base.camera, base.cam)
  187. else:
  188. self.cluster = DummyClusterClient()
  189. __builtins__['cluster'] = self.cluster
  190. def addPassThroughKey(self,key):
  191. self.passThroughKeys.append(key)
  192. def enable(self):
  193. # don't enable DIRECT if someone has posted DIRECTdisablePost
  194. if bboard.has(DirectSession.DIRECTdisablePost):
  195. return
  196. if self.fEnabled:
  197. return
  198. # Make sure old tasks are shut down
  199. self.disable()
  200. # Start all display region context tasks
  201. self.drList.spawnContextTask()
  202. if not self.fEnabledLight:
  203. # Turn on mouse Flying
  204. self.cameraControl.enableMouseFly()
  205. # Turn on object manipulation
  206. self.manipulationControl.enableManipulation()
  207. # Make sure list of selected items is reset
  208. self.selected.reset()
  209. # Accept appropriate hooks
  210. if not self.fEnabledLight:
  211. self.enableKeyEvents()
  212. self.enableMouseEvents()
  213. self.enableActionEvents()
  214. self.enableModifierEvents()
  215. # Set flag
  216. self.fEnabled = 1
  217. def enableLight(self):
  218. self.fEnabledLight = 1
  219. self.enable()
  220. def disable(self):
  221. # Shut down all display region context tasks
  222. self.drList.removeContextTask()
  223. # Turn off camera fly
  224. self.cameraControl.disableMouseFly()
  225. # Turn off object manipulation
  226. self.deselectAll()
  227. self.manipulationControl.disableManipulation()
  228. self.disableKeyEvents()
  229. self.disableModifierEvents()
  230. self.disableMouseEvents()
  231. self.disableActionEvents()
  232. # Kill tasks
  233. taskMgr.remove('flashNodePath')
  234. taskMgr.remove('hideDirectMessage')
  235. taskMgr.remove('hideDirectMessageLater')
  236. # Set flag
  237. self.fEnabled = 0
  238. def toggleDirect(self):
  239. if self.fEnabled:
  240. self.disable()
  241. else:
  242. self.enable()
  243. def minimumConfiguration(self):
  244. # Remove context task
  245. self.drList.removeContextTask()
  246. # Turn off camera fly
  247. self.cameraControl.disableMouseFly()
  248. # Ignore keyboard and action events
  249. self.disableKeyEvents()
  250. self.disableActionEvents()
  251. # But let mouse events pass through
  252. self.enableMouseEvents()
  253. self.enableModifierEvents()
  254. def oobe(self):
  255. # If oobeMode was never set, set it to false and create the
  256. # structures we need to implement OOBE.
  257. try:
  258. self.oobeMode
  259. except:
  260. self.oobeMode = 0
  261. self.oobeCamera = hidden.attachNewNode('oobeCamera')
  262. self.oobeVis = loader.loadModel('models/misc/camera')
  263. if self.oobeVis:
  264. self.oobeVis.node().setFinal(1)
  265. if self.oobeMode:
  266. # Position a target point to lerp the oobe camera to
  267. base.direct.cameraControl.camManipRef.iPosHpr(self.trueCamera)
  268. t = self.oobeCamera.lerpPosHpr(
  269. Point3(0), Vec3(0), 2.0,
  270. other = base.direct.cameraControl.camManipRef,
  271. task = 'manipulateCamera',
  272. blendType = 'easeInOut')
  273. # When move is done, switch to oobe mode
  274. t.setUponDeath(self.endOOBE)
  275. else:
  276. # Place camera marker at true camera location
  277. self.oobeVis.reparentTo(self.trueCamera)
  278. # Remove any transformation on the models arc
  279. self.oobeVis.clearMat()
  280. # Make oobeCamera be a sibling of wherever camera is now.
  281. cameraParent = self.camera.getParent()
  282. # Prepare oobe camera
  283. self.oobeCamera.reparentTo(cameraParent)
  284. self.oobeCamera.iPosHpr(self.trueCamera)
  285. # Put camera under new oobe camera
  286. self.cam.reparentTo(self.oobeCamera)
  287. # Position a target point to lerp the oobe camera to
  288. base.direct.cameraControl.camManipRef.setPos(
  289. self.trueCamera, Vec3(-2, -20, 5))
  290. base.direct.cameraControl.camManipRef.lookAt(self.trueCamera)
  291. t = self.oobeCamera.lerpPosHpr(
  292. Point3(0), Vec3(0), 2.0,
  293. other = base.direct.cameraControl.camManipRef,
  294. task = 'manipulateCamera',
  295. blendType = 'easeInOut')
  296. # When move is done, switch to oobe mode
  297. t.setUponDeath(self.beginOOBE)
  298. def beginOOBE(self, state):
  299. # Make sure we've reached our final destination
  300. self.oobeCamera.iPosHpr(base.direct.cameraControl.camManipRef)
  301. base.direct.camera = self.oobeCamera
  302. self.oobeMode = 1
  303. def endOOBE(self, state):
  304. # Make sure we've reached our final destination
  305. self.oobeCamera.iPosHpr(self.trueCamera)
  306. # Disable OOBE mode.
  307. self.cam.reparentTo(self.trueCamera)
  308. base.direct.camera = self.trueCamera
  309. # Get rid of ancillary node paths
  310. self.oobeVis.reparentTo(hidden)
  311. self.oobeCamera.reparentTo(hidden)
  312. self.oobeMode = 0
  313. def destroy(self):
  314. self.disable()
  315. def reset(self):
  316. self.enable()
  317. # EVENT FUNCTIONS
  318. def enableActionEvents(self):
  319. for event in self.actionEvents:
  320. self.accept(event[0], event[1], extraArgs = event[2:])
  321. def enableModifierEvents(self):
  322. for event in self.modifierEvents:
  323. self.accept(event, self.inputHandler, [event])
  324. def enableKeyEvents(self):
  325. for event in self.keyEvents:
  326. self.accept(event, self.inputHandler, [event])
  327. def enableMouseEvents(self):
  328. for event in self.mouseEvents:
  329. self.accept(event, self.inputHandler, [event])
  330. def disableActionEvents(self):
  331. for event, method in self.actionEvents:
  332. self.ignore(event)
  333. def disableModifierEvents(self):
  334. for event in self.modifierEvents:
  335. self.ignore(event)
  336. def disableKeyEvents(self):
  337. for event in self.keyEvents:
  338. self.ignore(event)
  339. def disableMouseEvents(self):
  340. for event in self.mouseEvents:
  341. self.ignore(event)
  342. def inputHandler(self, input):
  343. # [gjeon] change current camera dr, iRay, mouseWatcher accordingly to support multiple windows
  344. if not hasattr(self, 'oobeMode') or self.oobeMode == 0:
  345. for winCtrl in base.winControls:
  346. if winCtrl.mouseWatcher.node().hasMouse():
  347. self.win = winCtrl.win
  348. self.camera = winCtrl.camera
  349. self.trueCamera = self.camera
  350. self.cam = NodePath(winCtrl.camNode)
  351. self.camNode = winCtrl.camNode
  352. base.direct.dr = base.direct.drList[base.camList.index(NodePath(winCtrl.camNode))]
  353. base.direct.iRay = base.direct.dr.iRay
  354. base.mouseWatcher = winCtrl.mouseWatcher
  355. base.mouseWatcherNode = winCtrl.mouseWatcher.node()
  356. break
  357. # Deal with keyboard and mouse input
  358. if input == 'mouse1-up':
  359. self.fMouse1 = 0 # [gjeon] to update alt key information while mouse1 is pressed
  360. messenger.send('DIRECT-mouse1Up')
  361. elif input.find('mouse1') != -1:
  362. self.fMouse1 = 1 # [gjeon] to update alt key information while mouse1 is pressed
  363. modifiers = self.getModifiers(input, 'mouse1')
  364. messenger.send('DIRECT-mouse1', sentArgs = [modifiers])
  365. elif input == 'mouse2-up':
  366. messenger.send('DIRECT-mouse2Up')
  367. elif input.find('mouse2') != -1:
  368. modifiers = self.getModifiers(input, 'mouse2')
  369. messenger.send('DIRECT-mouse2', sentArgs = [modifiers])
  370. elif input == 'mouse3-up':
  371. messenger.send('DIRECT-mouse3Up')
  372. elif input.find('mouse3') != -1:
  373. modifiers = self.getModifiers(input, 'mouse3')
  374. messenger.send('DIRECT-mouse3', sentArgs = [modifiers])
  375. elif input == 'shift':
  376. self.fShift = 1
  377. elif input == 'shift-up':
  378. self.fShift = 0
  379. elif input == 'control':
  380. self.fControl = 1
  381. # [gjeon] to update control key information while mouse1 is pressed
  382. if self.fMouse1:
  383. modifiers = DIRECT_NO_MOD
  384. modifiers |= DIRECT_CONTROL_MOD
  385. messenger.send('DIRECT-mouse1', sentArgs = [modifiers])
  386. elif input == 'control-up':
  387. self.fControl = 0
  388. elif input == 'alt':
  389. self.fAlt = 1
  390. # [gjeon] to update alt key information while mouse1 is pressed
  391. if self.fMouse1:
  392. modifiers = DIRECT_NO_MOD
  393. modifiers |= DIRECT_ALT_MOD
  394. messenger.send('DIRECT-mouse1', sentArgs = [modifiers])
  395. elif input == 'alt-up':
  396. self.fAlt = 0
  397. elif input == 'page_up':
  398. self.upAncestry()
  399. elif input == 'page_down':
  400. self.downAncestry()
  401. elif input == 'escape':
  402. self.deselectAll()
  403. elif input == 'delete':
  404. self.removeAllSelected()
  405. elif input == 'v':
  406. self.toggleWidgetVis()
  407. elif input == 'b':
  408. base.toggleBackface()
  409. elif input == 'control-f':
  410. self.flash(last)
  411. elif input == 'l':
  412. self.lights.toggle()
  413. elif input == 'shift-l':
  414. self.cameraControl.toggleCOALock()
  415. elif input == 'o':
  416. self.oobe()
  417. elif input == 'p':
  418. if self.selected.last:
  419. self.setActiveParent(self.selected.last)
  420. elif input == 'r':
  421. # Do wrt reparent
  422. if self.selected.last:
  423. self.reparent(self.selected.last, fWrt = 1)
  424. elif input == 'shift-r':
  425. # Do regular reparent
  426. if self.selected.last:
  427. self.reparent(self.selected.last)
  428. elif input == 's':
  429. if self.selected.last:
  430. self.select(self.selected.last)
  431. elif input == 't':
  432. base.toggleTexture()
  433. elif input == 'shift-a':
  434. self.selected.toggleVisAll()
  435. elif input == 'w':
  436. base.toggleWireframe()
  437. elif (input == '[') or (input == '{'):
  438. self.undo()
  439. elif (input == ']') or (input == '}'):
  440. self.redo()
  441. #Pass along certain events if this display is a cluster client
  442. if self.clusterMode == 'client':
  443. if input in self.passThroughKeys:
  444. self.cluster('messenger.send("%s")' % input, 0)
  445. def getModifiers(self, input, base):
  446. modifiers = DIRECT_NO_MOD
  447. modifierString = input[: input.find(base)]
  448. if modifierString.find('shift') != -1:
  449. modifiers |= DIRECT_SHIFT_MOD
  450. if modifierString.find('control') != -1:
  451. modifiers |= DIRECT_CONTROL_MOD
  452. if modifierString.find('alt') != -1:
  453. modifiers |= DIRECT_ALT_MOD
  454. return modifiers
  455. def gotShift(self, modifiers):
  456. return modifiers & DIRECT_SHIFT_MOD
  457. def gotControl(self, modifiers):
  458. return modifiers & DIRECT_CONTROL_MOD
  459. def gotAlt(self, modifiers):
  460. return modifiers & DIRECT_ALT_MOD
  461. def setFScaleWidgetByCam(self, flag):
  462. self.fScaleWidgetByCam = flag
  463. if flag:
  464. taskMgr.add(self.widgetResizeTask, 'DIRECTWidgetResize')
  465. else:
  466. taskMgr.remove('DIRECTWidgetResize')
  467. def widgetResizeTask(self, state):
  468. if not taskMgr.hasTaskNamed('resizeObjectHandles'):
  469. dnp = self.selected.last
  470. if dnp:
  471. nodeCamDist = Vec3(dnp.getPos(direct.camera)).length()
  472. sf = 0.075 * nodeCamDist * math.tan(deg2Rad(direct.drList.getCurrentDr().fovV))
  473. self.widget.setDirectScalingFactor(sf)
  474. return Task.cont
  475. def select(self, nodePath, fMultiSelect = 0,
  476. fSelectTag = 1, fResetAncestry = 1):
  477. dnp = self.selected.select(nodePath, fMultiSelect, fSelectTag)
  478. if dnp:
  479. messenger.send('DIRECT_preSelectNodePath', [dnp])
  480. if fResetAncestry:
  481. # Update ancestry
  482. self.ancestry = dnp.getAncestors()
  483. self.ancestryIndex = 0
  484. # Update the selectedNPReadout
  485. self.selectedNPReadout.reparentTo(aspect2d)
  486. self.selectedNPReadout.setText(
  487. 'Selected:' + dnp.getName())
  488. # Show the manipulation widget
  489. self.widget.showWidget()
  490. editTypes = self.manipulationControl.getEditTypes([dnp])
  491. if (editTypes & EDIT_TYPE_UNEDITABLE == EDIT_TYPE_UNEDITABLE):
  492. self.manipulationControl.disableWidgetMove()
  493. else:
  494. self.manipulationControl.enableWidgetMove()
  495. # Update camera controls coa to this point
  496. # Coa2Camera = Coa2Dnp * Dnp2Camera
  497. mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(self.camera)
  498. row = mCoa2Camera.getRow(3)
  499. coa = Vec3(row[0], row[1], row[2])
  500. self.cameraControl.updateCoa(coa)
  501. # Adjust widgets size
  502. # This uses the additional scaling factor used to grow and
  503. # shrink the widget
  504. if not self.fScaleWidgetByCam: # [gjeon] for not scaling widget by distance from camera
  505. self.widget.setScalingFactor(dnp.getRadius())
  506. # Spawn task to have object handles follow the selected object
  507. taskMgr.remove('followSelectedNodePath')
  508. t = Task.Task(self.followSelectedNodePathTask)
  509. t.dnp = dnp
  510. taskMgr.add(t, 'followSelectedNodePath')
  511. # Send an message marking the event
  512. messenger.send('DIRECT_selectedNodePath', [dnp])
  513. messenger.send('DIRECT_selectedNodePath_fMulti_fTag', [dnp, fMultiSelect, fSelectTag])
  514. def followSelectedNodePathTask(self, state):
  515. mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(render)
  516. decomposeMatrix(mCoa2Render,
  517. self.scale, self.hpr, self.pos,
  518. CSDefault)
  519. self.widget.setPosHpr(self.pos, self.hpr)
  520. return Task.cont
  521. def deselect(self, nodePath):
  522. dnp = self.selected.deselect(nodePath)
  523. if dnp:
  524. # Hide the manipulation widget
  525. self.widget.hideWidget()
  526. self.selectedNPReadout.reparentTo(hidden)
  527. self.selectedNPReadout.setText(' ')
  528. taskMgr.remove('followSelectedNodePath')
  529. self.ancestry = []
  530. # Send an message marking the event
  531. messenger.send('DIRECT_deselectedNodePath', [dnp])
  532. def deselectAll(self):
  533. self.selected.deselectAll()
  534. # Hide the manipulation widget
  535. self.widget.hideWidget()
  536. self.selectedNPReadout.reparentTo(hidden)
  537. self.selectedNPReadout.setText(' ')
  538. taskMgr.remove('followSelectedNodePath')
  539. def setActiveParent(self, nodePath = None):
  540. # Record new parent
  541. self.activeParent = nodePath
  542. # Update the activeParentReadout
  543. self.activeParentReadout.reparentTo(aspect2d)
  544. self.activeParentReadout.setText(
  545. 'Active Reparent Target:' + nodePath.getName())
  546. # Alert everyone else
  547. messenger.send('DIRECT_activeParent', [self.activeParent])
  548. def reparent(self, nodePath = None, fWrt = 0):
  549. if (nodePath and self.activeParent and
  550. self.isNotCycle(nodePath, self.activeParent)):
  551. oldParent = nodePath.getParent()
  552. if fWrt:
  553. nodePath.wrtReparentTo(self.activeParent)
  554. else:
  555. nodePath.reparentTo(self.activeParent)
  556. # Alert everyone else
  557. messenger.send('DIRECT_reparent',
  558. [nodePath, oldParent, self.activeParent])
  559. # [gjeon] for others who needs fWrt information
  560. messenger.send('DIRECT_reparent_fWrt',
  561. [nodePath, oldParent, self.activeParent, fWrt])
  562. def isNotCycle(self, nodePath, parent):
  563. if nodePath.id() == parent.id():
  564. print 'DIRECT.reparent: Invalid parent'
  565. return 0
  566. elif parent.hasParent():
  567. return self.isNotCycle(nodePath, parent.getParent())
  568. else:
  569. return 1
  570. def flash(self, nodePath = 'None Given'):
  571. """ Highlight an object by setting it red for a few seconds """
  572. # Clean up any existing task
  573. taskMgr.remove('flashNodePath')
  574. # Spawn new task if appropriate
  575. if nodePath == 'None Given':
  576. # If nothing specified, try selected node path
  577. nodePath = self.selected.last
  578. if nodePath:
  579. if nodePath.hasColor():
  580. doneColor = nodePath.getColor()
  581. flashColor = VBase4(1) - doneColor
  582. flashColor.setW(1)
  583. else:
  584. doneColor = None
  585. flashColor = VBase4(1, 0, 0, 1)
  586. # Temporarily set node path color
  587. nodePath.setColor(flashColor)
  588. # Clean up color in a few seconds
  589. t = taskMgr.doMethodLater(DIRECT_FLASH_DURATION,
  590. # This is just a dummy task
  591. self.flashDummy,
  592. 'flashNodePath',)
  593. t.nodePath = nodePath
  594. t.doneColor = doneColor
  595. # This really does all the work
  596. t.setUponDeath(self.flashDone)
  597. def flashDummy(self, state):
  598. # Real work is done in upon death function
  599. return Task.done
  600. def flashDone(self, state):
  601. # Return node Path to original state
  602. if state.nodePath.isEmpty():
  603. # Node path doesn't exist anymore, bail
  604. return
  605. if state.doneColor:
  606. state.nodePath.setColor(state.doneColor)
  607. else:
  608. state.nodePath.clearColor()
  609. def fitOnNodePath(self, nodePath = 'None Given'):
  610. if nodePath == 'None Given':
  611. # If nothing specified, try selected node path
  612. nodePath = self.selected.last
  613. base.direct.select(nodePath)
  614. def fitTask(state, self = self):
  615. self.cameraControl.fitOnWidget()
  616. return Task.done
  617. taskMgr.doMethodLater(0.1, fitTask, 'manipulateCamera')
  618. def isolate(self, nodePath = 'None Given'):
  619. """ Show a node path and hide its siblings """
  620. # First kill the flashing task to avoid complications
  621. taskMgr.remove('flashNodePath')
  622. # Use currently selected node path if node selected
  623. if nodePath == 'None Given':
  624. nodePath = self.selected.last
  625. # Do we have a node path?
  626. if nodePath:
  627. # Yes, show everything in level
  628. self.showAllDescendants(nodePath.getParent())
  629. # Now hide all of this node path's siblings
  630. nodePath.hideSiblings()
  631. def toggleVis(self, nodePath = 'None Given'):
  632. """ Toggle visibility of node path """
  633. # First kill the flashing task to avoid complications
  634. taskMgr.remove('flashNodePath')
  635. if nodePath == 'None Given':
  636. # If nothing specified, try selected node path
  637. nodePath = self.selected.last
  638. if nodePath:
  639. # Now toggle node path's visibility state
  640. nodePath.toggleVis()
  641. def removeNodePath(self, nodePath = 'None Given'):
  642. if nodePath == 'None Given':
  643. # If nothing specified, try selected node path
  644. nodePath = self.selected.last
  645. if nodePath:
  646. nodePath.remove()
  647. def removeAllSelected(self):
  648. self.selected.removeAll()
  649. def showAllDescendants(self, nodePath = render):
  650. """ Show the level and its descendants """
  651. nodePath.showAllDescendants()
  652. nodePath.hideCS()
  653. def upAncestry(self):
  654. if self.ancestry:
  655. l = len(self.ancestry)
  656. i = self.ancestryIndex + 1
  657. if i < l:
  658. np = self.ancestry[i]
  659. name = np.getName()
  660. if (name != 'render') and (name != 'renderTop'):
  661. self.ancestryIndex = i
  662. self.select(np, 0, 0)
  663. self.flash(np)
  664. def downAncestry(self):
  665. if self.ancestry:
  666. l = len(self.ancestry)
  667. i = self.ancestryIndex - 1
  668. if i >= 0:
  669. np = self.ancestry[i]
  670. name = np.getName()
  671. if (name != 'render') and (name != 'renderTop'):
  672. self.ancestryIndex = i
  673. self.select(np, 0, 0)
  674. self.flash(np)
  675. def getAndSetName(self, nodePath):
  676. """ Prompt user for new node path name """
  677. from tkSimpleDialog import askstring
  678. newName = askstring('Node Path: ' + nodePath.getName(),
  679. 'Enter new name:')
  680. if newName:
  681. nodePath.setName(newName)
  682. messenger.send('DIRECT_nodePathSetName', [nodePath, newName])
  683. # UNDO REDO FUNCTIONS
  684. def pushUndo(self, nodePathList, fResetRedo = 1):
  685. # Assemble group of changes
  686. undoGroup = []
  687. for nodePath in nodePathList:
  688. t = nodePath.getTransform()
  689. undoGroup.append([nodePath, t])
  690. # Now record group
  691. self.undoList.append(undoGroup)
  692. # Truncate list
  693. self.undoList = self.undoList[-25:]
  694. # Alert anyone who cares
  695. messenger.send('DIRECT_pushUndo')
  696. if fResetRedo and (nodePathList != []):
  697. self.redoList = []
  698. messenger.send('DIRECT_redoListEmpty')
  699. def popUndoGroup(self):
  700. # Get last item
  701. undoGroup = self.undoList[-1]
  702. # Strip last item off of undo list
  703. self.undoList = self.undoList[:-1]
  704. # Update state of undo button
  705. if not self.undoList:
  706. messenger.send('DIRECT_undoListEmpty')
  707. # Return last item
  708. return undoGroup
  709. def pushRedo(self, nodePathList):
  710. # Assemble group of changes
  711. redoGroup = []
  712. for nodePath in nodePathList:
  713. t = nodePath.getTransform()
  714. redoGroup.append([nodePath, t])
  715. # Now record redo group
  716. self.redoList.append(redoGroup)
  717. # Truncate list
  718. self.redoList = self.redoList[-25:]
  719. # Alert anyone who cares
  720. messenger.send('DIRECT_pushRedo')
  721. def popRedoGroup(self):
  722. # Get last item
  723. redoGroup = self.redoList[-1]
  724. # Strip last item off of redo list
  725. self.redoList = self.redoList[:-1]
  726. # Update state of redo button
  727. if not self.redoList:
  728. messenger.send('DIRECT_redoListEmpty')
  729. # Return last item
  730. return redoGroup
  731. def undo(self):
  732. if self.undoList:
  733. # Get last item off of redo list
  734. undoGroup = self.popUndoGroup()
  735. # Record redo information
  736. nodePathList = map(lambda x: x[0], undoGroup)
  737. self.pushRedo(nodePathList)
  738. # Now undo xform for group
  739. for pose in undoGroup:
  740. # Undo xform
  741. pose[0].setTransform(pose[1])
  742. # Alert anyone who cares
  743. messenger.send('DIRECT_undo', [nodePathList])
  744. def redo(self):
  745. if self.redoList:
  746. # Get last item off of redo list
  747. redoGroup = self.popRedoGroup()
  748. # Record undo information
  749. nodePathList = map(lambda x: x[0], redoGroup)
  750. self.pushUndo(nodePathList, fResetRedo = 0)
  751. # Redo xform
  752. for pose in redoGroup:
  753. pose[0].setTransform(pose[1])
  754. # Alert anyone who cares
  755. messenger.send('DIRECT_redo', [nodePathList])
  756. # UTILITY FUNCTIONS
  757. def message(self, text):
  758. taskMgr.remove('hideDirectMessage')
  759. taskMgr.remove('hideDirectMessageLater')
  760. self.directMessageReadout.reparentTo(aspect2d)
  761. self.directMessageReadout.setText(text)
  762. self.hideDirectMessageLater()
  763. def hideDirectMessageLater(self):
  764. taskMgr.doMethodLater(3.0, self.hideDirectMessage, 'hideDirectMessage')
  765. def hideDirectMessage(self, state):
  766. self.directMessageReadout.reparentTo(hidden)
  767. return Task.done
  768. def useObjectHandles(self):
  769. self.widget = self.manipulationControl.objectHandles
  770. self.widget.reparentTo(base.direct.group)
  771. def hideSelectedNPReadout(self):
  772. self.selectedNPReadout.reparentTo(hidden)
  773. def hideActiveParentReadout(self):
  774. self.activeParentReadout.reparentTo(hidden)
  775. def toggleWidgetVis(self):
  776. self.widget.toggleWidget()
  777. def setCOAMode(self, mode):
  778. self.coaMode = mode
  779. def isEnabled(self):
  780. return self.fEnabled
  781. def addUnpickable(self, item):
  782. for iRay in self.iRayList:
  783. iRay.addUnpickable(item)
  784. def removeUnpickable(self, item):
  785. for iRay in self.iRayList:
  786. iRay.removeUnpickable(item)
  787. class DisplayRegionContext(DirectObject):
  788. regionCount = 0
  789. def __init__(self, cam):
  790. self.cam = cam
  791. self.camNode = self.cam.node()
  792. self.camLens = self.camNode.getLens()
  793. # set lens change callback
  794. changeEvent = 'dr%d-change-event' % DisplayRegionContext.regionCount
  795. DisplayRegionContext.regionCount += 1
  796. self.camLens.setChangeEvent(changeEvent)
  797. self.accept(changeEvent, self.camUpdate)
  798. self.iRay = SelectionRay(self.cam)
  799. self.nearVec = Vec3(0)
  800. self.mouseX = 0.0
  801. self.mouseY = 0.0
  802. self.orthoFactor = 0.1
  803. # A Camera node can have more than one display region
  804. # associated with it. Here I assume that there is only
  805. # one display region per camera, since we are defining a
  806. # display region on a per-camera basis. See note in
  807. # DisplayRegionList.__init__()
  808. try:
  809. self.dr = self.camNode.getDr(0)
  810. except:
  811. self.dr = self.camNode.getDisplayRegion(0)
  812. left = self.dr.getLeft()
  813. right = self.dr.getRight()
  814. bottom = self.dr.getBottom()
  815. top = self.dr.getTop()
  816. self.originX = left+right-1
  817. self.originY = top+bottom-1
  818. self.scaleX = 1.0/(right-left)
  819. self.scaleY = 1.0/(top-bottom)
  820. self.setOrientation()
  821. self.camUpdate()
  822. def __getitem__(self, key):
  823. return self.__dict__[key]
  824. def setOrientation(self):
  825. # MRM This assumes orientation is set on transform above cam
  826. hpr = self.cam.getHpr()
  827. if hpr[2] < 135 and hpr[2]>45 or hpr[2]>225 and hpr[2]<315:
  828. self.isSideways = 1
  829. elif hpr[2] > -135 and hpr[2] < -45 or hpr[2] < -225 and hpr[2] > -315:
  830. self.isSideways = 1
  831. else:
  832. self.isSideways = 0
  833. # The following take into consideration sideways displays
  834. def getHfov(self):
  835. if self.isSideways:
  836. return self.camLens.getVfov()
  837. else:
  838. return self.camLens.getHfov()
  839. def getVfov(self):
  840. if self.isSideways:
  841. return self.camLens.getHfov()
  842. else:
  843. return self.camLens.getVfov()
  844. def setHfov(self, hfov):
  845. if self.isSideways:
  846. self.camLens.setFov(self.camLens.getHfov(), hfov)
  847. else:
  848. self.camLens.setFov(hfov, self.camLens.getVfov())
  849. def setVfov(self, vfov):
  850. if self.isSideways:
  851. self.camLens.setFov(vfov, self.camLens.getVfov())
  852. else:
  853. self.camLens.setFov(self.camLens.getHfov(), vfov)
  854. def setFov(self, hfov, vfov):
  855. if self.isSideways:
  856. self.camLens.setFov(vfov, hfov)
  857. else:
  858. self.camLens.setFov(hfov, vfov)
  859. def getWidth(self):
  860. prop = self.win.getProperties()
  861. if prop.hasSize():
  862. return prop.getXSize()
  863. else:
  864. return 640
  865. def getHeight(self):
  866. prop = self.win.getProperties()
  867. if prop.hasSize():
  868. return prop.getYSize()
  869. else:
  870. return 480
  871. def updateFilmSize(self, width, height):
  872. if self.camLens.__class__.__name__ == "OrthographicLens":
  873. width *= self.orthoFactor
  874. height *= self.orthoFactor
  875. self.camLens.setFilmSize(width, height)
  876. def camUpdate(self, lens = None):
  877. # Window Data
  878. self.near = self.camLens.getNear()
  879. self.far = self.camLens.getFar()
  880. self.fovH = self.camLens.getHfov()
  881. self.fovV = self.camLens.getVfov()
  882. self.nearWidth = math.tan(deg2Rad(self.fovH * 0.5)) * self.near * 2.0
  883. self.nearHeight = math.tan(deg2Rad(self.fovV * 0.5)) * self.near * 2.0
  884. self.left = -self.nearWidth * 0.5
  885. self.right = self.nearWidth * 0.5
  886. self.top = self.nearHeight * 0.5
  887. self.bottom = -self.nearHeight * 0.5
  888. def mouseUpdate(self):
  889. # Mouse Data
  890. # Last frame
  891. self.mouseLastX = self.mouseX
  892. self.mouseLastY = self.mouseY
  893. # Values for this frame
  894. # This ranges from -1 to 1
  895. if (base.mouseWatcherNode.hasMouse()):
  896. self.mouseX = base.mouseWatcherNode.getMouseX()
  897. self.mouseY = base.mouseWatcherNode.getMouseY()
  898. self.mouseX = (self.mouseX-self.originX)*self.scaleX
  899. self.mouseY = (self.mouseY-self.originY)*self.scaleY
  900. # Delta percent of window the mouse moved
  901. self.mouseDeltaX = self.mouseX - self.mouseLastX
  902. self.mouseDeltaY = self.mouseY - self.mouseLastY
  903. self.nearVec.set((self.nearWidth*0.5) * self.mouseX,
  904. self.near,
  905. (self.nearHeight*0.5) * self.mouseY)
  906. class DisplayRegionList(DirectObject):
  907. def __init__(self):
  908. self.displayRegionList = []
  909. i = 0
  910. # Things are funky if we are oobe
  911. if (hasattr(base, 'oobeMode') and base.oobeMode):
  912. # assume we only have one cam at this point
  913. drc = DisplayRegionContext(base.cam)
  914. self.displayRegionList.append(drc)
  915. else:
  916. # MRM: Doesn't properly handle multiple camera groups anymore
  917. # Assumes everything is under main camera
  918. # This is following the old way of setting up
  919. # display regions. A display region is set up for
  920. # each camera node in the scene graph. This was done
  921. # so that only display regions in the scene graph are
  922. # considered. The right way to do this is to set up
  923. # a display region for each real display region, and then
  924. # keep track of which are currently active (e.g. use a flag)
  925. # processing only them.
  926. for camIndex in range(len(base.camList)):
  927. cam = base.camList[camIndex]
  928. if cam.getName()=='<noname>':
  929. cam.setName('Camera%d' % camIndex)
  930. drc = DisplayRegionContext(cam)
  931. self.displayRegionList.append(drc)
  932. self.accept("DIRECT-mouse1", self.mouseUpdate)
  933. self.accept("DIRECT-mouse2", self.mouseUpdate)
  934. self.accept("DIRECT-mouse3", self.mouseUpdate)
  935. self.accept("DIRECT-mouse1Up", self.mouseUpdate)
  936. self.accept("DIRECT-mouse2Up", self.mouseUpdate)
  937. self.accept("DIRECT-mouse3Up", self.mouseUpdate)
  938. def __getitem__(self, index):
  939. return self.displayRegionList[index]
  940. def __len__(self):
  941. return len(self.displayRegionList)
  942. def updateContext(self):
  943. self.contextTask(None)
  944. def setNearFar(self, near, far):
  945. for dr in self.displayRegionList:
  946. dr.camLens.setNearFar(near, far)
  947. def setNear(self, near):
  948. for dr in self.displayRegionList:
  949. dr.camLens.setNear(near)
  950. def setFar(self, far):
  951. for dr in self.displayRegionList:
  952. dr.camLens.setFar(far)
  953. def setFov(self, hfov, vfov):
  954. for dr in self.displayRegionList:
  955. dr.setFov(hfov, vfov)
  956. def setHfov(self, fov):
  957. for dr in self.displayRegionList:
  958. dr.setHfov(fov)
  959. def setVfov(self, fov):
  960. for dr in self.displayRegionList:
  961. dr.setVfov(fov)
  962. def mouseUpdate(self, modifiers = DIRECT_NO_MOD):
  963. for dr in self.displayRegionList:
  964. dr.mouseUpdate()
  965. #base.direct.dr = self.getCurrentDr()
  966. def getCurrentDr(self):
  967. for dr in self.displayRegionList:
  968. if (dr.mouseX >= -1.0 and dr.mouseX <= 1.0 and
  969. dr.mouseY >= -1.0 and dr.mouseY <= 1.0):
  970. return dr
  971. return self.displayRegionList[0]
  972. def start(self):
  973. # First shutdown any existing task
  974. self.stop()
  975. # Start a new context task
  976. self.spawnContextTask()
  977. def stop(self):
  978. # Kill the existing context task
  979. taskMgr.remove('DIRECTContextTask')
  980. def spawnContextTask(self):
  981. taskMgr.add(self.contextTask, 'DIRECTContextTask')
  982. def removeContextTask(self):
  983. taskMgr.remove('DIRECTContextTask')
  984. def contextTask(self, state):
  985. # Window Data
  986. self.mouseUpdate()
  987. # hack to test movement
  988. return Task.cont
  989. # Create one
  990. __builtins__['direct'] = base.direct = DirectSession()