DirectSession.py 38 KB

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