seManipulation.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. #################################################################
  2. # seManipulation.py
  3. # Originally from DirectManipulation.py
  4. # Altered by Yi-Hong Lin, [email protected], 2004
  5. #
  6. # We didn't change anything essential.
  7. # Just because we customized the seSession from DirectSession,
  8. # So we need related files can follow the change.
  9. # However, we don't want to change anything inside the original directool
  10. # to let them can work with our scene editor.
  11. # (If we do change original directools, it will force user has to install the latest version of OUR Panda)
  12. #
  13. #################################################################
  14. from direct.showbase.DirectObject import *
  15. from direct.directtools.DirectGlobals import *
  16. from direct.directtools.DirectUtil import *
  17. from seGeometry import *
  18. from direct.task import Task
  19. class DirectManipulationControl(DirectObject):
  20. def __init__(self):
  21. # Create the grid
  22. self.objectHandles = ObjectHandles()
  23. self.hitPt = Point3(0)
  24. self.prevHit = Vec3(0)
  25. self.rotationCenter = Point3(0)
  26. self.initScaleMag = 1
  27. self.manipRef = SEditor.group.attachNewNode('manipRef')
  28. self.hitPtDist = 0
  29. self.constraint = None
  30. self.rotateAxis = 'x'
  31. self.lastCrankAngle = 0
  32. self.fSetCoa = 0
  33. self.fHitInit = 1
  34. self.fScaleInit = 1
  35. self.fWidgetTop = 0
  36. self.fFreeManip = 1
  37. self.fScaling = 0
  38. self.mode = None
  39. self.actionEvents = [
  40. ['DIRECT-mouse1', self.manipulationStart],
  41. ['DIRECT-mouse1Up', self.manipulationStop],
  42. ['tab', self.toggleObjectHandlesMode],
  43. ['.', self.objectHandles.multiplyScalingFactorBy, 2.0],
  44. ['>', self.objectHandles.multiplyScalingFactorBy, 2.0],
  45. [',', self.objectHandles.multiplyScalingFactorBy, 0.5],
  46. ['<', self.objectHandles.multiplyScalingFactorBy, 0.5],
  47. ['shift-f', self.objectHandles.growToFit],
  48. ['i', self.plantSelectedNodePath],
  49. ]
  50. def manipulationStart(self, modifiers):
  51. # Start out in select mode
  52. self.mode = 'select'
  53. # Check for a widget hit point
  54. entry = SEditor.iRay.pickWidget()
  55. # Did we hit a widget?
  56. if entry:
  57. # Yes!
  58. self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
  59. self.hitPtDist = Vec3(self.hitPt).length()
  60. # Constraint determined by nodes name
  61. self.constraint = entry.getIntoNodePath().getName()
  62. else:
  63. # Nope, off the widget, no constraint
  64. self.constraint = None
  65. # Check to see if we are moving the object
  66. # We are moving the object if we either wait long enough
  67. taskMgr.doMethodLater(MANIPULATION_MOVE_DELAY,
  68. self.switchToMoveMode,
  69. 'manip-move-wait')
  70. # Or we move far enough
  71. self.moveDir = None
  72. watchMouseTask = Task.Task(self.watchMouseTask)
  73. watchMouseTask.initX = SEditor.dr.mouseX
  74. watchMouseTask.initY = SEditor.dr.mouseY
  75. taskMgr.add(watchMouseTask, 'manip-watch-mouse')
  76. def switchToMoveMode(self, state):
  77. taskMgr.remove('manip-watch-mouse')
  78. self.mode = 'move'
  79. self.manipulateObject()
  80. return Task.done
  81. def watchMouseTask(self, state):
  82. if (((abs (state.initX - SEditor.dr.mouseX)) > 0.01) or
  83. ((abs (state.initY - SEditor.dr.mouseY)) > 0.01)):
  84. taskMgr.remove('manip-move-wait')
  85. self.mode = 'move'
  86. self.manipulateObject()
  87. return Task.done
  88. else:
  89. return Task.cont
  90. def manipulationStop(self,xy=[]):
  91. taskMgr.remove('manipulateObject')
  92. taskMgr.remove('manip-move-wait')
  93. taskMgr.remove('manip-watch-mouse')
  94. # depending on flag.....
  95. if self.mode == 'select':
  96. # Check for object under mouse
  97. # Don't intersect with hidden or backfacing objects
  98. skipFlags = SKIP_HIDDEN | SKIP_BACKFACE
  99. # Skip camera (and its children), unless control key is pressed
  100. skipFlags |= SKIP_CAMERA * (1 - base.getControl())
  101. entry = SEditor.iRay.pickGeom(skipFlags = skipFlags)
  102. if entry:
  103. # Record hit point information
  104. self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
  105. self.hitPtDist = Vec3(self.hitPt).length()
  106. # Select it
  107. SEditor.select(entry.getIntoNodePath(), SEditor.fShift)
  108. else:
  109. SEditor.deselectAll()
  110. else:
  111. self.manipulateObjectCleanup()
  112. def manipulateObjectCleanup(self):
  113. if self.fScaling:
  114. # We had been scaling, need to reset object handles
  115. self.objectHandles.transferObjectHandlesScale()
  116. self.fScaling = 0
  117. SEditor.selected.highlightAll()
  118. self.objectHandles.showAllHandles()
  119. self.objectHandles.hideGuides()
  120. # Restart followSelectedNodePath task
  121. self.spawnFollowSelectedNodePathTask()
  122. messenger.send('DIRECT_manipulateObjectCleanup')
  123. def spawnFollowSelectedNodePathTask(self):
  124. # If nothing selected, just return
  125. if not SEditor.selected.last:
  126. return
  127. # Clear out old task to make sure
  128. taskMgr.remove('followSelectedNodePath')
  129. # Where are the object handles relative to the selected object
  130. pos = VBase3(0)
  131. hpr = VBase3(0)
  132. decomposeMatrix(SEditor.selected.last.mCoa2Dnp,
  133. VBase3(0), hpr, pos, CSDefault)
  134. # Create the task
  135. t = Task.Task(self.followSelectedNodePathTask)
  136. # Update state variables
  137. t.pos = pos
  138. t.hpr = hpr
  139. t.base = SEditor.selected.last
  140. # Spawn the task
  141. taskMgr.add(t, 'followSelectedNodePath')
  142. def followSelectedNodePathTask(self, state):
  143. SEditor.widget.setPosHpr(state.base, state.pos, state.hpr)
  144. return Task.cont
  145. def enableManipulation(self):
  146. # Accept events
  147. for event in self.actionEvents:
  148. self.accept(event[0], event[1], extraArgs = event[2:])
  149. def disableManipulation(self):
  150. # Ignore events
  151. for event in self.actionEvents:
  152. self.ignore(event[0])
  153. self.removeManipulateObjectTask()
  154. taskMgr.remove('manipulateObject')
  155. taskMgr.remove('manip-move-wait')
  156. taskMgr.remove('manip-watch-mouse')
  157. taskMgr.remove('highlightWidgetTask')
  158. taskMgr.remove('resizeObjectHandles')
  159. def toggleObjectHandlesMode(self):
  160. self.fSetCoa = 1 - self.fSetCoa
  161. if self.fSetCoa:
  162. self.objectHandles.coaModeColor()
  163. else:
  164. self.objectHandles.manipModeColor()
  165. def removeManipulateObjectTask(self):
  166. taskMgr.remove('manipulateObject')
  167. def manipulateObject(self):
  168. # Only do this if something is selected
  169. if SEditor.selected:
  170. # Remove the task to keep the widget attached to the object
  171. taskMgr.remove('followSelectedNodePath')
  172. # and the task to highlight the widget
  173. taskMgr.remove('highlightWidgetTask')
  174. # Set manipulation flag
  175. self.fManip = 1
  176. # Record undo point
  177. SEditor.pushUndo(SEditor.selected)
  178. # Update object handles visibility
  179. self.objectHandles.showGuides()
  180. self.objectHandles.hideAllHandles()
  181. self.objectHandles.showHandle(self.constraint)
  182. # Record relationship between selected nodes and widget
  183. SEditor.selected.getWrtAll()
  184. # hide the bbox of the selected objects during interaction
  185. SEditor.selected.dehighlightAll()
  186. # Send event to signal start of manipulation
  187. messenger.send('DIRECT_manipulateObjectStart')
  188. # Manipulate the real object with the constraint
  189. # The constraint is passed as the name of the node
  190. self.spawnManipulateObjectTask()
  191. def spawnManipulateObjectTask(self):
  192. # reset hit-pt flag
  193. self.fHitInit = 1
  194. self.fScaleInit = 1
  195. # record initial offset between widget and camera
  196. t = Task.Task(self.manipulateObjectTask)
  197. t.fMouseX = abs(SEditor.dr.mouseX) > 0.9
  198. t.fMouseY = abs(SEditor.dr.mouseY) > 0.9
  199. if t.fMouseX:
  200. t.constrainedDir = 'y'
  201. else:
  202. t.constrainedDir = 'x'
  203. # Compute widget's xy coords in screen space
  204. t.coaCenter = getScreenXY(SEditor.widget)
  205. # These are used to rotate about view vector
  206. if t.fMouseX and t.fMouseY:
  207. t.lastAngle = getCrankAngle(t.coaCenter)
  208. taskMgr.add(t, 'manipulateObject')
  209. def manipulateObjectTask(self, state):
  210. # Widget takes precedence
  211. if self.constraint:
  212. type = self.constraint[2:]
  213. if type == 'post':
  214. self.xlate1D(state)
  215. elif type == 'disc':
  216. self.xlate2D(state)
  217. elif type == 'ring':
  218. self.rotate1D(state)
  219. # No widget interaction, determine free manip mode
  220. elif self.fFreeManip:
  221. # If we've been scaling and changed modes, reset object handles
  222. if 0 and self.fScaling and (not SEditor.fAlt):
  223. self.objectHandles.transferObjectHandlesScale()
  224. self.fScaling = 0
  225. # Alt key switches to a scaling mode
  226. if SEditor.fControl:
  227. self.fScaling = 1
  228. self.scale3D(state)
  229. # Otherwise, manip mode depends on where you started
  230. elif state.fMouseX and state.fMouseY:
  231. # In the corner, spin around camera's axis
  232. self.rotateAboutViewVector(state)
  233. elif state.fMouseX or state.fMouseY:
  234. # Mouse started elsewhere in the outer frame, rotate
  235. self.rotate2D(state)
  236. else:
  237. # Mouse started in central region, xlate
  238. # Mode depends on shift key
  239. if SEditor.fShift or SEditor.fControl:
  240. self.xlateCamXY(state)
  241. else:
  242. self.xlateCamXZ(state)
  243. if self.fSetCoa:
  244. # Update coa based on current widget position
  245. SEditor.selected.last.mCoa2Dnp.assign(
  246. SEditor.widget.getMat(SEditor.selected.last))
  247. else:
  248. # Move the objects with the widget
  249. SEditor.selected.moveWrtWidgetAll()
  250. # Continue
  251. return Task.cont
  252. ### WIDGET MANIPULATION METHODS ###
  253. def xlate1D(self, state):
  254. # Constrained 1D Translation along widget axis
  255. # Compute nearest hit point along axis and try to keep
  256. # that point as close to the current mouse position as possible
  257. # what point on the axis is the mouse pointing at?
  258. self.hitPt.assign(self.objectHandles.getAxisIntersectPt(
  259. self.constraint[:1]))
  260. # use it to see how far to move the widget
  261. if self.fHitInit:
  262. # First time through, just record that point
  263. self.fHitInit = 0
  264. self.prevHit.assign(self.hitPt)
  265. else:
  266. # Move widget to keep hit point as close to mouse as possible
  267. offset = self.hitPt - self.prevHit
  268. SEditor.widget.setPos(SEditor.widget, offset)
  269. def xlate2D(self, state):
  270. # Constrained 2D (planar) translation
  271. # Compute point of intersection of ray from eyepoint through cursor
  272. # to one of the three orthogonal planes on the widget.
  273. # This point tracks all subsequent mouse movements
  274. self.hitPt.assign(self.objectHandles.getWidgetIntersectPt(
  275. SEditor.widget, self.constraint[:1]))
  276. # use it to see how far to move the widget
  277. if self.fHitInit:
  278. # First time through just record hit point
  279. self.fHitInit = 0
  280. self.prevHit.assign(self.hitPt)
  281. else:
  282. offset = self.hitPt - self.prevHit
  283. SEditor.widget.setPos(SEditor.widget, offset)
  284. def rotate1D(self, state):
  285. # Constrained 1D rotation about the widget's main axis (X,Y, or Z)
  286. # Rotation depends upon circular motion of the mouse about the
  287. # projection of the widget's origin on the image plane
  288. # A complete circle about the widget results in a change in
  289. # orientation of 360 degrees.
  290. # First initialize hit point/rotation angle
  291. if self.fHitInit:
  292. self.fHitInit = 0
  293. self.rotateAxis = self.constraint[:1]
  294. self.fWidgetTop = self.widgetCheck('top?')
  295. self.rotationCenter = getScreenXY(SEditor.widget)
  296. self.lastCrankAngle = getCrankAngle(self.rotationCenter)
  297. # Rotate widget based on how far cursor has swung around origin
  298. newAngle = getCrankAngle(self.rotationCenter)
  299. deltaAngle = self.lastCrankAngle - newAngle
  300. if self.fWidgetTop:
  301. deltaAngle = -1 * deltaAngle
  302. if self.rotateAxis == 'x':
  303. SEditor.widget.setP(SEditor.widget, deltaAngle)
  304. elif self.rotateAxis == 'y':
  305. if base.config.GetBool('temp-hpr-fix',0):
  306. SEditor.widget.setR(SEditor.widget, deltaAngle)
  307. else:
  308. SEditor.widget.setR(SEditor.widget, -deltaAngle)
  309. elif self.rotateAxis == 'z':
  310. SEditor.widget.setH(SEditor.widget, deltaAngle)
  311. # Record crank angle for next time around
  312. self.lastCrankAngle = newAngle
  313. def widgetCheck(self,type):
  314. # Utility to see if we are looking at the top or bottom of
  315. # a 2D planar widget or if we are looking at a 2D planar widget
  316. # edge on
  317. # Based upon angle between view vector from eye through the
  318. # widget's origin and one of the three principle axes
  319. axis = self.constraint[:1]
  320. # First compute vector from eye through widget origin
  321. mWidget2Cam = SEditor.widget.getMat(SEditor.camera)
  322. # And determine where the viewpoint is relative to widget
  323. pos = VBase3(0)
  324. decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos,
  325. CSDefault)
  326. widgetDir = Vec3(pos)
  327. widgetDir.normalize()
  328. # Convert specified widget axis to view space
  329. if axis == 'x':
  330. widgetAxis = Vec3(mWidget2Cam.xformVec(X_AXIS))
  331. elif axis == 'y':
  332. widgetAxis = Vec3(mWidget2Cam.xformVec(Y_AXIS))
  333. elif axis == 'z':
  334. widgetAxis = Vec3(mWidget2Cam.xformVec(Z_AXIS))
  335. widgetAxis.normalize()
  336. if type == 'top?':
  337. # Check sign of angle between two vectors
  338. return (widgetDir.dot(widgetAxis) < 0.)
  339. elif type == 'edge?':
  340. # Checking to see if we are viewing edge-on
  341. # Check angle between two vectors
  342. return(abs(widgetDir.dot(widgetAxis)) < .2)
  343. ### FREE MANIPULATION METHODS ###
  344. def xlateCamXZ(self, state):
  345. """Constrained 2D motion parallel to the camera's image plane
  346. This moves the object in the camera's XZ plane"""
  347. # reset fHitInit in case we later switch to manip mode
  348. self.fHitInit = 1
  349. # Reset scaling init flag
  350. self.fScaleInit = 1
  351. # Where is the widget relative to current camera view
  352. vWidget2Camera = SEditor.widget.getPos(SEditor.camera)
  353. x = vWidget2Camera[0]
  354. y = vWidget2Camera[1]
  355. z = vWidget2Camera[2]
  356. # Move widget (and objects) based upon mouse motion
  357. # Scaled up accordingly based upon widget distance
  358. dr = SEditor.dr
  359. SEditor.widget.setX(
  360. SEditor.camera,
  361. x + 0.5 * dr.mouseDeltaX * dr.nearWidth * (y/dr.near))
  362. SEditor.widget.setZ(
  363. SEditor.camera,
  364. z + 0.5 * dr.mouseDeltaY * dr.nearHeight * (y/dr.near))
  365. def xlateCamXY(self, state):
  366. """Constrained 2D motion perpendicular to camera's image plane
  367. This moves the object in the camera's XY plane if shift is held
  368. Moves object toward camera if control is held
  369. """
  370. # Reset scaling init flag
  371. self.fScaleInit = 1
  372. # Now, where is the widget relative to current camera view
  373. vWidget2Camera = SEditor.widget.getPos(SEditor.camera)
  374. # If this is first time around, record initial y distance
  375. if self.fHitInit:
  376. self.fHitInit = 0
  377. # Use distance to widget to scale motion along Y
  378. self.xlateSF = Vec3(vWidget2Camera).length()
  379. # Get widget's current xy coords in screen space
  380. coaCenter = getNearProjectionPoint(SEditor.widget)
  381. self.deltaNearX = coaCenter[0] - SEditor.dr.nearVec[0]
  382. # Which way do we move the object?
  383. if SEditor.fControl:
  384. moveDir = Vec3(vWidget2Camera)
  385. # If widget is behind camera invert vector
  386. if moveDir[1] < 0.0:
  387. moveDir.assign(moveDir * -1)
  388. moveDir.normalize()
  389. else:
  390. moveDir = Vec3(Y_AXIS)
  391. # Move selected objects
  392. dr = SEditor.dr
  393. # Scale move dir
  394. moveDir.assign(moveDir * (2.0 * dr.mouseDeltaY * self.xlateSF))
  395. # Add it to current widget offset
  396. vWidget2Camera += moveDir
  397. # The object, however, stays at the same relative point to mouse in X
  398. vWidget2Camera.setX((dr.nearVec[0] + self.deltaNearX) *
  399. (vWidget2Camera[1]/dr.near))
  400. # Move widget
  401. SEditor.widget.setPos(SEditor.camera, vWidget2Camera)
  402. def rotate2D(self, state):
  403. """ Virtual trackball rotation of widget """
  404. # Reset init flag in case we switch to another mode
  405. self.fHitInit = 1
  406. # Reset scaling init flag
  407. self.fScaleInit = 1
  408. tumbleRate = 360
  409. # If moving outside of center, ignore motion perpendicular to edge
  410. if ((state.constrainedDir == 'y') and (abs(SEditor.dr.mouseX) > 0.9)):
  411. deltaX = 0
  412. deltaY = SEditor.dr.mouseDeltaY
  413. elif ((state.constrainedDir == 'x') and (abs(SEditor.dr.mouseY) > 0.9)):
  414. deltaX = SEditor.dr.mouseDeltaX
  415. deltaY = 0
  416. else:
  417. deltaX = SEditor.dr.mouseDeltaX
  418. deltaY = SEditor.dr.mouseDeltaY
  419. # Mouse motion edge to edge of display region results in one full turn
  420. relHpr(SEditor.widget, SEditor.camera, deltaX * tumbleRate,
  421. -deltaY * tumbleRate, 0)
  422. def rotateAboutViewVector(self, state):
  423. # Reset init flag in case we switch to another mode
  424. self.fHitInit = 1
  425. # Reset scaling init flag
  426. self.fScaleInit = 1
  427. # Compute current angle
  428. angle = getCrankAngle(state.coaCenter)
  429. deltaAngle = angle - state.lastAngle
  430. state.lastAngle = angle
  431. # Mouse motion edge to edge of display region results in one full turn
  432. if base.config.GetBool('temp-hpr-fix',0):
  433. relHpr(SEditor.widget, SEditor.camera, 0, 0, -deltaAngle)
  434. else:
  435. relHpr(SEditor.widget, SEditor.camera, 0, 0, deltaAngle)
  436. def scale3D(self, state):
  437. # Scale the selected node based upon up down mouse motion
  438. # Mouse motion from edge to edge results in a factor of 4 scaling
  439. # From midpoint to edge doubles or halves objects scale
  440. if self.fScaleInit:
  441. self.fScaleInit = 0
  442. self.manipRef.setPos(SEditor.widget, 0, 0, 0)
  443. self.manipRef.setHpr(SEditor.camera, 0, 0, 0)
  444. self.initScaleMag = Vec3(
  445. self.objectHandles.getWidgetIntersectPt(
  446. self.manipRef, 'y')).length()
  447. # record initial scale
  448. self.initScale = SEditor.widget.getScale()
  449. # Reset fHitInitFlag
  450. self.fHitInit = 1
  451. # Begin
  452. # Scale factor is ratio current mag with init mag
  453. currScale = (
  454. self.initScale *
  455. (self.objectHandles.getWidgetIntersectPt(
  456. self.manipRef, 'y').length() /
  457. self.initScaleMag)
  458. )
  459. SEditor.widget.setScale(currScale)
  460. ## Utility functions ##
  461. def plantSelectedNodePath(self):
  462. """ Move selected object to intersection point of cursor on scene """
  463. # Check for intersection
  464. entry = SEditor.iRay.pickGeom(
  465. skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA)
  466. # MRM: Need to handle moving COA
  467. if (entry != None) and (SEditor.selected.last != None):
  468. # Record undo point
  469. SEditor.pushUndo(SEditor.selected)
  470. # Record wrt matrix
  471. SEditor.selected.getWrtAll()
  472. # Move selected
  473. SEditor.widget.setPos(
  474. SEditor.camera,entry.getSurfacePoint(entry.getFromNodePath()))
  475. # Move all the selected objects with widget
  476. # Move the objects with the widget
  477. SEditor.selected.moveWrtWidgetAll()
  478. # Let everyone know that something was moved
  479. messenger.send('DIRECT_manipulateObjectCleanup')
  480. class ObjectHandles(NodePath,DirectObject):
  481. def __init__(self):
  482. # Initialize the superclass
  483. NodePath.__init__(self)
  484. # Load up object handles model and assign it to self
  485. self.assign(loader.loadModel('models/misc/objectHandles'))
  486. self.setName('objectHandles')
  487. self.scalingNode = self.getChild(0)
  488. self.scalingNode.setName('ohScalingNode')
  489. self.ohScalingFactor = 1.0
  490. # To avoid recreating a vec every frame
  491. self.hitPt = Vec3(0)
  492. # Get a handle on the components
  493. self.xHandles = self.find('**/X')
  494. self.xPostGroup = self.xHandles.find('**/x-post-group')
  495. self.xPostCollision = self.xHandles.find('**/x-post')
  496. self.xRingGroup = self.xHandles.find('**/x-ring-group')
  497. self.xRingCollision = self.xHandles.find('**/x-ring')
  498. self.xDiscGroup = self.xHandles.find('**/x-disc-group')
  499. self.xDisc = self.xHandles.find('**/x-disc-visible')
  500. self.xDiscCollision = self.xHandles.find('**/x-disc')
  501. self.yHandles = self.find('**/Y')
  502. self.yPostGroup = self.yHandles.find('**/y-post-group')
  503. self.yPostCollision = self.yHandles.find('**/y-post')
  504. self.yRingGroup = self.yHandles.find('**/y-ring-group')
  505. self.yRingCollision = self.yHandles.find('**/y-ring')
  506. self.yDiscGroup = self.yHandles.find('**/y-disc-group')
  507. self.yDisc = self.yHandles.find('**/y-disc-visible')
  508. self.yDiscCollision = self.yHandles.find('**/y-disc')
  509. self.zHandles = self.find('**/Z')
  510. self.zPostGroup = self.zHandles.find('**/z-post-group')
  511. self.zPostCollision = self.zHandles.find('**/z-post')
  512. self.zRingGroup = self.zHandles.find('**/z-ring-group')
  513. self.zRingCollision = self.zHandles.find('**/z-ring')
  514. self.zDiscGroup = self.zHandles.find('**/z-disc-group')
  515. self.zDisc = self.zHandles.find('**/z-disc-visible')
  516. self.zDiscCollision = self.zHandles.find('**/z-disc')
  517. # Adjust visiblity, colors, and transparency
  518. self.xPostCollision.hide()
  519. self.xRingCollision.hide()
  520. self.xDisc.setColor(1,0,0,.2)
  521. self.yPostCollision.hide()
  522. self.yRingCollision.hide()
  523. self.yDisc.setColor(0,1,0,.2)
  524. self.zPostCollision.hide()
  525. self.zRingCollision.hide()
  526. self.zDisc.setColor(0,0,1,.2)
  527. # Augment geometry with lines
  528. self.createObjectHandleLines()
  529. # Create long markers to help line up in world
  530. self.createGuideLines()
  531. self.hideGuides()
  532. # Start with widget handles hidden
  533. self.fActive = 1
  534. self.toggleWidget()
  535. # Make sure object handles are never lit or drawn in wireframe
  536. useDirectRenderStyle(self)
  537. def coaModeColor(self):
  538. self.setColor(.5,.5,.5,1)
  539. def manipModeColor(self):
  540. self.clearColor()
  541. def toggleWidget(self):
  542. if self.fActive:
  543. self.deactivate()
  544. else:
  545. self.activate()
  546. def activate(self):
  547. self.scalingNode.reparentTo(self)
  548. self.fActive = 1
  549. def deactivate(self):
  550. self.scalingNode.reparentTo(hidden)
  551. self.fActive = 0
  552. def showWidgetIfActive(self):
  553. if self.fActive:
  554. self.reparentTo(SEditor.group)
  555. def showWidget(self):
  556. self.reparentTo(SEditor.group)
  557. def hideWidget(self):
  558. self.reparentTo(hidden)
  559. def enableHandles(self, handles):
  560. if type(handles) == types.ListType:
  561. for handle in handles:
  562. self.enableHandle(handle)
  563. elif handles == 'x':
  564. self.enableHandles(['x-post','x-ring','x-disc'])
  565. elif handles == 'y':
  566. self.enableHandles(['y-post','y-ring','y-disc'])
  567. elif handles == 'z':
  568. self.enableHandles(['z-post','z-ring','z-disc'])
  569. elif handles == 'post':
  570. self.enableHandles(['x-post','y-post','z-post'])
  571. elif handles == 'ring':
  572. self.enableHandles(['x-ring','y-ring','z-ring'])
  573. elif handles == 'disc':
  574. self.enableHandles(['x-disc','y-disc','z-disc'])
  575. elif handles == 'all':
  576. self.enableHandles(['x-post','x-ring','x-disc',
  577. 'y-post','y-ring','y-disc',
  578. 'z-post','z-ring','z-disc'])
  579. def enableHandle(self, handle):
  580. if handle == 'x-post':
  581. self.xPostGroup.reparentTo(self.xHandles)
  582. elif handle == 'x-ring':
  583. self.xRingGroup.reparentTo(self.xHandles)
  584. elif handle == 'x-disc':
  585. self.xDiscGroup.reparentTo(self.xHandles)
  586. if handle == 'y-post':
  587. self.yPostGroup.reparentTo(self.yHandles)
  588. elif handle == 'y-ring':
  589. self.yRingGroup.reparentTo(self.yHandles)
  590. elif handle == 'y-disc':
  591. self.yDiscGroup.reparentTo(self.yHandles)
  592. if handle == 'z-post':
  593. self.zPostGroup.reparentTo(self.zHandles)
  594. elif handle == 'z-ring':
  595. self.zRingGroup.reparentTo(self.zHandles)
  596. elif handle == 'z-disc':
  597. self.zDiscGroup.reparentTo(self.zHandles)
  598. def disableHandles(self, handles):
  599. if type(handles) == types.ListType:
  600. for handle in handles:
  601. self.disableHandle(handle)
  602. elif handles == 'x':
  603. self.disableHandles(['x-post','x-ring','x-disc'])
  604. elif handles == 'y':
  605. self.disableHandles(['y-post','y-ring','y-disc'])
  606. elif handles == 'z':
  607. self.disableHandles(['z-post','z-ring','z-disc'])
  608. elif handles == 'post':
  609. self.disableHandles(['x-post','y-post','z-post'])
  610. elif handles == 'ring':
  611. self.disableHandles(['x-ring','y-ring','z-ring'])
  612. elif handles == 'disc':
  613. self.disableHandles(['x-disc','y-disc','z-disc'])
  614. elif handles == 'all':
  615. self.disableHandles(['x-post','x-ring','x-disc',
  616. 'y-post','y-ring','y-disc',
  617. 'z-post','z-ring','z-disc'])
  618. def disableHandle(self, handle):
  619. if handle == 'x-post':
  620. self.xPostGroup.reparentTo(hidden)
  621. elif handle == 'x-ring':
  622. self.xRingGroup.reparentTo(hidden)
  623. elif handle == 'x-disc':
  624. self.xDiscGroup.reparentTo(hidden)
  625. if handle == 'y-post':
  626. self.yPostGroup.reparentTo(hidden)
  627. elif handle == 'y-ring':
  628. self.yRingGroup.reparentTo(hidden)
  629. elif handle == 'y-disc':
  630. self.yDiscGroup.reparentTo(hidden)
  631. if handle == 'z-post':
  632. self.zPostGroup.reparentTo(hidden)
  633. elif handle == 'z-ring':
  634. self.zRingGroup.reparentTo(hidden)
  635. elif handle == 'z-disc':
  636. self.zDiscGroup.reparentTo(hidden)
  637. def showAllHandles(self):
  638. self.xPost.show()
  639. self.xRing.show()
  640. self.xDisc.show()
  641. self.yPost.show()
  642. self.yRing.show()
  643. self.yDisc.show()
  644. self.zPost.show()
  645. self.zRing.show()
  646. self.zDisc.show()
  647. def hideAllHandles(self):
  648. self.xPost.hide()
  649. self.xRing.hide()
  650. self.xDisc.hide()
  651. self.yPost.hide()
  652. self.yRing.hide()
  653. self.yDisc.hide()
  654. self.zPost.hide()
  655. self.zRing.hide()
  656. self.zDisc.hide()
  657. def showHandle(self, handle):
  658. if handle == 'x-post':
  659. self.xPost.show()
  660. elif handle == 'x-ring':
  661. self.xRing.show()
  662. elif handle == 'x-disc':
  663. self.xDisc.show()
  664. elif handle == 'y-post':
  665. self.yPost.show()
  666. elif handle == 'y-ring':
  667. self.yRing.show()
  668. elif handle == 'y-disc':
  669. self.yDisc.show()
  670. elif handle == 'z-post':
  671. self.zPost.show()
  672. elif handle == 'z-ring':
  673. self.zRing.show()
  674. elif handle == 'z-disc':
  675. self.zDisc.show()
  676. def showGuides(self):
  677. self.guideLines.show()
  678. def hideGuides(self):
  679. self.guideLines.hide()
  680. def setScalingFactor(self, scaleFactor):
  681. self.ohScalingFactor = scaleFactor
  682. self.scalingNode.setScale(self.ohScalingFactor)
  683. def getScalingFactor(self):
  684. return self.scalingNode.getScale()
  685. def transferObjectHandlesScale(self):
  686. # see how much object handles have been scaled
  687. ohs = self.getScale()
  688. sns = self.scalingNode.getScale()
  689. # Transfer this to the scaling node
  690. self.scalingNode.setScale(
  691. ohs[0] * sns[0],
  692. ohs[1] * sns[1],
  693. ohs[2] * sns[2])
  694. self.setScale(1)
  695. def multiplyScalingFactorBy(self, factor):
  696. taskMgr.remove('resizeObjectHandles')
  697. sf = self.ohScalingFactor = self.ohScalingFactor * factor
  698. self.scalingNode.lerpScale(sf,sf,sf, 0.5,
  699. blendType = 'easeInOut',
  700. task = 'resizeObjectHandles')
  701. def growToFit(self):
  702. taskMgr.remove('resizeObjectHandles')
  703. # Increase handles scale until they cover 80% of the min dimension
  704. # Originally, here is "cover 30% of the min dimension", we changed.
  705. pos = SEditor.widget.getPos(SEditor.camera)
  706. minDim = min(SEditor.dr.nearWidth, SEditor.dr.nearHeight)
  707. sf = 0.4 * minDim * (pos[1]/SEditor.dr.near)
  708. self.ohScalingFactor = sf
  709. self.scalingNode.lerpScale(sf,sf,sf, 0.5,
  710. blendType = 'easeInOut',
  711. task = 'resizeObjectHandles')
  712. def createObjectHandleLines(self):
  713. # X post
  714. self.xPost = self.xPostGroup.attachNewNode('x-post-visible')
  715. lines = LineNodePath(self.xPost)
  716. lines.setColor(VBase4(1,0,0,1))
  717. lines.setThickness(5)
  718. lines.moveTo(0,0,0)
  719. lines.drawTo(1.5,0,0)
  720. lines.create()
  721. lines = LineNodePath(self.xPost)
  722. lines.setColor(VBase4(1,0,0,1))
  723. lines.setThickness(1.5)
  724. lines.moveTo(0,0,0)
  725. lines.drawTo(-1.5,0,0)
  726. lines.create()
  727. # X ring
  728. self.xRing = self.xRingGroup.attachNewNode('x-ring-visible')
  729. lines = LineNodePath(self.xRing)
  730. lines.setColor(VBase4(1,0,0,1))
  731. lines.setThickness(3)
  732. lines.moveTo(0,1,0)
  733. for ang in range(15, 370, 15):
  734. lines.drawTo(0,
  735. math.cos(deg2Rad(ang)),
  736. math.sin(deg2Rad(ang)))
  737. lines.create()
  738. # Y post
  739. self.yPost = self.yPostGroup.attachNewNode('y-post-visible')
  740. lines = LineNodePath(self.yPost)
  741. lines.setColor(VBase4(0,1,0,1))
  742. lines.setThickness(5)
  743. lines.moveTo(0,0,0)
  744. lines.drawTo(0,1.5,0)
  745. lines.create()
  746. lines = LineNodePath(self.yPost)
  747. lines.setColor(VBase4(0,1,0,1))
  748. lines.setThickness(1.5)
  749. lines.moveTo(0,0,0)
  750. lines.drawTo(0,-1.5,0)
  751. lines.create()
  752. # Y ring
  753. self.yRing = self.yRingGroup.attachNewNode('y-ring-visible')
  754. lines = LineNodePath(self.yRing)
  755. lines.setColor(VBase4(0,1,0,1))
  756. lines.setThickness(3)
  757. lines.moveTo(1,0,0)
  758. for ang in range(15, 370, 15):
  759. lines.drawTo(math.cos(deg2Rad(ang)),
  760. 0,
  761. math.sin(deg2Rad(ang)))
  762. lines.create()
  763. # Z post
  764. self.zPost = self.zPostGroup.attachNewNode('z-post-visible')
  765. lines = LineNodePath(self.zPost)
  766. lines.setColor(VBase4(0,0,1,1))
  767. lines.setThickness(5)
  768. lines.moveTo(0,0,0)
  769. lines.drawTo(0,0,1.5)
  770. lines.create()
  771. lines = LineNodePath(self.zPost)
  772. lines.setColor(VBase4(0,0,1,1))
  773. lines.setThickness(1.5)
  774. lines.moveTo(0,0,0)
  775. lines.drawTo(0,0,-1.5)
  776. lines.create()
  777. # Z ring
  778. self.zRing = self.zRingGroup.attachNewNode('z-ring-visible')
  779. lines = LineNodePath(self.zRing)
  780. lines.setColor(VBase4(0,0,1,1))
  781. lines.setThickness(3)
  782. lines.moveTo(1,0,0)
  783. for ang in range(15, 370, 15):
  784. lines.drawTo(math.cos(deg2Rad(ang)),
  785. math.sin(deg2Rad(ang)),
  786. 0)
  787. lines.create()
  788. def createGuideLines(self):
  789. self.guideLines = self.attachNewNode('guideLines')
  790. # X guide lines
  791. lines = LineNodePath(self.guideLines)
  792. lines.setColor(VBase4(1,0,0,1))
  793. lines.setThickness(0.5)
  794. lines.moveTo(-500,0,0)
  795. lines.drawTo(500,0,0)
  796. lines.create()
  797. lines.setName('x-guide')
  798. # Y guide lines
  799. lines = LineNodePath(self.guideLines)
  800. lines.setColor(VBase4(0,1,0,1))
  801. lines.setThickness(0.5)
  802. lines.moveTo(0,-500,0)
  803. lines.drawTo(0,500,0)
  804. lines.create()
  805. lines.setName('y-guide')
  806. # Z guide lines
  807. lines = LineNodePath(self.guideLines)
  808. lines.setColor(VBase4(0,0,1,1))
  809. lines.setThickness(0.5)
  810. lines.moveTo(0,0,-500)
  811. lines.drawTo(0,0,500)
  812. lines.create()
  813. lines.setName('z-guide')
  814. def getAxisIntersectPt(self, axis):
  815. # Calc the xfrom from camera to widget
  816. mCam2Widget = SEditor.camera.getMat(SEditor.widget)
  817. lineDir = Vec3(mCam2Widget.xformVec(SEditor.dr.nearVec))
  818. lineDir.normalize()
  819. # And determine where the viewpoint is relative to widget
  820. lineOrigin = VBase3(0)
  821. decomposeMatrix(mCam2Widget, VBase3(0), VBase3(0), lineOrigin,
  822. CSDefault)
  823. # Now see where this hits the plane containing the 1D motion axis.
  824. # Pick the intersection plane most normal to the intersection ray
  825. # by comparing lineDir with plane normals. The plane with the
  826. # largest dotProduct is most "normal"
  827. if axis == 'x':
  828. if (abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS))):
  829. self.hitPt.assign(
  830. planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
  831. else:
  832. self.hitPt.assign(
  833. planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
  834. # We really only care about the nearest point on the axis
  835. self.hitPt.setY(0)
  836. self.hitPt.setZ(0)
  837. elif axis == 'y':
  838. if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS))):
  839. self.hitPt.assign(
  840. planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
  841. else:
  842. self.hitPt.assign(
  843. planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
  844. # We really only care about the nearest point on the axis
  845. self.hitPt.setX(0)
  846. self.hitPt.setZ(0)
  847. elif axis == 'z':
  848. if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS))):
  849. self.hitPt.assign(
  850. planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
  851. else:
  852. self.hitPt.assign(
  853. planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
  854. # We really only care about the nearest point on the axis
  855. self.hitPt.setX(0)
  856. self.hitPt.setY(0)
  857. return self.hitPt
  858. def getWidgetIntersectPt(self, nodePath, plane):
  859. # Find out the point of interection of the ray passing though the mouse
  860. # with the plane containing the 2D xlation or 1D rotation widgets
  861. # Calc the xfrom from camera to the nodePath
  862. mCam2NodePath = SEditor.camera.getMat(nodePath)
  863. # And determine where the viewpoint is relative to widget
  864. lineOrigin = VBase3(0)
  865. decomposeMatrix(mCam2NodePath, VBase3(0), VBase3(0), lineOrigin,
  866. CSDefault)
  867. # Next we find the vector from viewpoint to the widget through
  868. # the mouse's position on near plane.
  869. # This defines the intersection ray
  870. lineDir = Vec3(mCam2NodePath.xformVec(SEditor.dr.nearVec))
  871. lineDir.normalize()
  872. # Find the hit point
  873. if plane == 'x':
  874. self.hitPt.assign(planeIntersect(
  875. lineOrigin, lineDir, ORIGIN, X_AXIS))
  876. elif plane == 'y':
  877. self.hitPt.assign(planeIntersect(
  878. lineOrigin, lineDir, ORIGIN, Y_AXIS))
  879. elif plane == 'z':
  880. self.hitPt.assign(planeIntersect(
  881. lineOrigin, lineDir, ORIGIN, Z_AXIS))
  882. return self.hitPt