DirectManipulation.py 33 KB

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