DirectManipulation.py 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406
  1. from direct.showbase.DirectObject import DirectObject
  2. from DirectGlobals import *
  3. from DirectUtil import *
  4. from DirectGeometry import *
  5. from direct.task import Task
  6. import types
  7. class DirectManipulationControl(DirectObject):
  8. def __init__(self):
  9. # Create the grid
  10. self.objectHandles = ObjectHandles()
  11. self.hitPt = Point3(0)
  12. self.prevHit = Vec3(0)
  13. self.rotationCenter = Point3(0)
  14. self.initScaleMag = 1
  15. self.manipRef = base.direct.group.attachNewNode('manipRef')
  16. self.hitPtDist = 0
  17. self.constraint = None
  18. self.rotateAxis = 'x'
  19. self.lastCrankAngle = 0
  20. self.fSetCoa = 0
  21. self.fHitInit = 1
  22. self.fScaleInit = 1
  23. self.fWidgetTop = 0
  24. self.fFreeManip = 1
  25. self.fScaling = 0
  26. self.fMovable = 1
  27. self.mode = None
  28. self.actionEvents = [
  29. ['DIRECT-mouse1', self.manipulationStart],
  30. ['DIRECT-mouse1Up', self.manipulationStop],
  31. ['tab', self.toggleObjectHandlesMode],
  32. ## ['.', self.objectHandles.multiplyScalingFactorBy, 2.0],
  33. ## ['>', self.objectHandles.multiplyScalingFactorBy, 2.0],
  34. ## [',', self.objectHandles.multiplyScalingFactorBy, 0.5],
  35. ## ['<', self.objectHandles.multiplyScalingFactorBy, 0.5],
  36. ['DIRECT-widgetScaleUp', self.scaleWidget, 2.0],
  37. ['DIRECT-widgetScaleDown', self.scaleWidget, 0.5],
  38. ['shift-f', self.objectHandles.growToFit],
  39. ['i', self.plantSelectedNodePath],
  40. ]
  41. self.defaultSkipFlags = SKIP_HIDDEN | SKIP_BACKFACE
  42. self.optionalSkipFlags = 0
  43. self.unmovableTagList = []
  44. # [gjeon] flag to enable selection while other manipulation is disabled
  45. self.fAllowSelectionOnly = 0
  46. # [gjeon] flag to enable marquee selection feature
  47. self.fAllowMarquee = 0
  48. self.marquee = None
  49. # [gjeon] for new LE's multi-view support
  50. self.fMultiView = 0
  51. # [gjeon] to support grid snapping
  52. self.fGridSnap = 0
  53. def scaleWidget(self, factor):
  54. if hasattr(base.direct, 'widget'):
  55. base.direct.widget.multiplyScalingFactorBy(factor)
  56. else:
  57. self.objectHandles.multiplyScalingFactorBy(factor)
  58. def supportMultiView(self):
  59. if self.fMultiView:
  60. return
  61. self.objectHandles.hide(BitMask32.bit(0))
  62. self.objectHandles.hide(BitMask32.bit(1))
  63. self.objectHandles.hide(BitMask32.bit(2))
  64. self.topViewWidget = ObjectHandles('topViewWidget')
  65. self.frontViewWidget = ObjectHandles('frontViewWidget')
  66. self.leftViewWidget = ObjectHandles('leftViewWidget')
  67. self.widgetList = [self.topViewWidget, self.frontViewWidget, self.leftViewWidget, self.objectHandles]
  68. self.topViewWidget.hide(BitMask32.bit(1))
  69. self.topViewWidget.hide(BitMask32.bit(2))
  70. self.topViewWidget.hide(BitMask32.bit(3))
  71. self.frontViewWidget.hide(BitMask32.bit(0))
  72. self.frontViewWidget.hide(BitMask32.bit(2))
  73. self.frontViewWidget.hide(BitMask32.bit(3))
  74. self.leftViewWidget.hide(BitMask32.bit(0))
  75. self.leftViewWidget.hide(BitMask32.bit(1))
  76. self.leftViewWidget.hide(BitMask32.bit(3))
  77. self.fMultiView = 1
  78. def manipulationStart(self, modifiers):
  79. # Start out in select mode
  80. self.mode = 'select'
  81. if base.direct.cameraControl.useMayaCamControls and modifiers == 4:
  82. self.mode = 'camera'
  83. if self.fAllowSelectionOnly:
  84. return
  85. # Check for a widget hit point
  86. entry = base.direct.iRay.pickWidget(skipFlags = SKIP_WIDGET)
  87. # Did we hit a widget?
  88. if entry:
  89. # Yes!
  90. self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
  91. self.hitPtDist = Vec3(self.hitPt).length()
  92. # Constraint determined by nodes name
  93. self.constraint = entry.getIntoNodePath().getName()
  94. else:
  95. # Nope, off the widget, no constraint
  96. self.constraint = None
  97. # [gjeon] to prohibit unwanted object movement while direct window doesn't have focus
  98. if base.direct.cameraControl.useMayaCamControls and not base.direct.gotControl(modifiers) \
  99. and not self.fAllowMarquee:
  100. return
  101. if not base.direct.gotAlt(modifiers):
  102. if entry:
  103. # Check to see if we are moving the object
  104. # We are moving the object if we either wait long enough
  105. taskMgr.doMethodLater(MANIPULATION_MOVE_DELAY,
  106. self.switchToMoveMode,
  107. 'manip-move-wait')
  108. # Or we move far enough
  109. self.moveDir = None
  110. watchMouseTask = Task.Task(self.watchMouseTask)
  111. watchMouseTask.initX = base.direct.dr.mouseX
  112. watchMouseTask.initY = base.direct.dr.mouseY
  113. taskMgr.add(watchMouseTask, 'manip-watch-mouse')
  114. else:
  115. if base.direct.fControl:
  116. self.mode = 'move'
  117. self.manipulateObject()
  118. elif not base.direct.fAlt and self.fAllowMarquee:
  119. self.moveDir = None
  120. watchMarqueeTask = Task.Task(self.watchMarqueeTask)
  121. watchMarqueeTask.initX = base.direct.dr.mouseX
  122. watchMarqueeTask.initY = base.direct.dr.mouseY
  123. taskMgr.add(watchMarqueeTask, 'manip-marquee-mouse')
  124. def switchToMoveMode(self, state):
  125. taskMgr.remove('manip-watch-mouse')
  126. self.mode = 'move'
  127. self.manipulateObject()
  128. return Task.done
  129. def watchMouseTask(self, state):
  130. if (((abs (state.initX - base.direct.dr.mouseX)) > 0.01) or
  131. ((abs (state.initY - base.direct.dr.mouseY)) > 0.01)):
  132. taskMgr.remove('manip-move-wait')
  133. self.mode = 'move'
  134. self.manipulateObject()
  135. return Task.done
  136. else:
  137. return Task.cont
  138. def watchMarqueeTask(self, state):
  139. taskMgr.remove('manip-watch-mouse')
  140. taskMgr.remove('manip-move-wait')
  141. self.mode = 'select'
  142. self.drawMarquee(state.initX, state.initY)
  143. return Task.cont
  144. def drawMarquee(self, startX, startY):
  145. if self.marquee:
  146. self.marquee.remove()
  147. self.marquee = None
  148. if base.direct.cameraControl.useMayaCamControls and base.direct.fAlt:
  149. return
  150. endX = base.direct.dr.mouseX
  151. endY = base.direct.dr.mouseY
  152. if (((abs (endX - startX)) < 0.01) and
  153. ((abs (endY - startY)) < 0.01)):
  154. return
  155. self.marquee = LineNodePath(render2d, 'marquee', 0.5, VBase4(.8, .6, .6, 1))
  156. self.marqueeInfo = (startX, startY, endX, endY)
  157. self.marquee.drawLines([
  158. [(startX, 0, startY), (startX, 0, endY)],
  159. [(startX, 0, endY), (endX, 0, endY)],
  160. [(endX, 0, endY), (endX, 0, startY)],
  161. [(endX, 0, startY), (startX, 0, startY)]])
  162. self.marquee.create()
  163. if self.fMultiView:
  164. for i in range(4):
  165. if i != base.camList.index(NodePath(base.direct.camNode)):
  166. self.marquee.hide(BitMask32.bit(i))
  167. def manipulationStop(self):
  168. taskMgr.remove('manipulateObject')
  169. taskMgr.remove('manip-move-wait')
  170. taskMgr.remove('manip-watch-mouse')
  171. taskMgr.remove('manip-marquee-mouse')
  172. # depending on flag.....
  173. if self.mode == 'select':
  174. # Check for object under mouse
  175. # Don't intersect with hidden or backfacing objects, as well as any
  176. # optionally specified things
  177. skipFlags = self.defaultSkipFlags | self.optionalSkipFlags
  178. # Skip camera (and its children), unless control key is pressed
  179. skipFlags |= SKIP_CAMERA * (1 - base.getControl())
  180. if self.marquee:
  181. self.marquee.remove()
  182. self.marquee = None
  183. base.direct.deselectAll()
  184. startX = self.marqueeInfo[0]
  185. startY = self.marqueeInfo[1]
  186. endX = self.marqueeInfo[2]
  187. endY = self.marqueeInfo[3]
  188. fll = Point3(0, 0, 0)
  189. flr = Point3(0, 0, 0)
  190. fur = Point3(0, 0, 0)
  191. ful = Point3(0, 0, 0)
  192. nll = Point3(0, 0, 0)
  193. nlr = Point3(0, 0, 0)
  194. nur = Point3(0, 0, 0)
  195. nul = Point3(0, 0, 0)
  196. lens = base.direct.cam.node().getLens()
  197. lens.extrude((startX, startY), nul, ful)
  198. lens.extrude((endX, startY), nur, fur)
  199. lens.extrude((endX, endY), nlr, flr)
  200. lens.extrude((startX, endY), nll, fll)
  201. marqueeFrustum = BoundingHexahedron(fll, flr, fur, ful, nll, nlr, nur, nul);
  202. marqueeFrustum.xform(base.direct.cam.getNetTransform().getMat())
  203. base.marqueeFrustum = marqueeFrustum
  204. def findTaggedNodePath(nodePath):
  205. # Select tagged object if present
  206. for tag in base.direct.selected.tagList:
  207. if nodePath.hasNetTag(tag):
  208. nodePath = nodePath.findNetTag(tag)
  209. return nodePath
  210. return None
  211. selectionList = []
  212. for geom in render.findAllMatches("**/+GeomNode"):
  213. if (skipFlags & SKIP_HIDDEN) and geom.isHidden():
  214. # Skip if hidden node
  215. continue
  216. ## elif (skipFlags & SKIP_BACKFACE) and base.direct.iRay.isEntryBackfacing():
  217. ## # Skip, if backfacing poly
  218. ## pass
  219. elif ((skipFlags & SKIP_CAMERA) and
  220. (camera in geom.getAncestors())):
  221. # Skip if parented to a camera.
  222. continue
  223. # Can pick unpickable, use the first visible node
  224. elif ((skipFlags & SKIP_UNPICKABLE) and
  225. (geom.getName() in base.direct.iRay.unpickable)):
  226. # Skip if in unpickable list
  227. continue
  228. nodePath = findTaggedNodePath(geom)
  229. if nodePath in selectionList:
  230. continue
  231. bb = geom.getBounds()
  232. bbc = bb.makeCopy()
  233. bbc.xform(geom.getParent().getNetTransform().getMat())
  234. boundingSphereTest = marqueeFrustum.contains(bbc)
  235. if boundingSphereTest > 1:
  236. if boundingSphereTest == 7:
  237. if nodePath not in selectionList:
  238. selectionList.append(nodePath)
  239. else:
  240. tMat = Mat4(geom.getMat())
  241. geom.clearMat()
  242. # Get bounds
  243. min = Point3(0)
  244. max = Point3(0)
  245. geom.calcTightBounds(min, max)
  246. # Restore transform
  247. geom.setMat(tMat)
  248. fll = Point3(min[0], max[1], min[2])
  249. flr = Point3(max[0], max[1], min[2])
  250. fur = max
  251. ful = Point3(min[0], max[1], max[2])
  252. nll = min
  253. nlr = Point3(max[0], min[1], min[2])
  254. nur = Point3(max[0], min[1], max[2])
  255. nul = Point3(min[0], min[1], max[2])
  256. tbb = BoundingHexahedron(fll, flr, fur, ful, nll, nlr, nur, nul)
  257. tbb.xform(geom.getNetTransform().getMat())
  258. tightBoundTest = marqueeFrustum.contains(tbb)
  259. if tightBoundTest > 1:
  260. if nodePath not in selectionList:
  261. selectionList.append(nodePath)
  262. for nodePath in selectionList:
  263. base.direct.select(nodePath, 1)
  264. else:
  265. entry = base.direct.iRay.pickGeom(skipFlags = skipFlags)
  266. if entry:
  267. # Record hit point information
  268. self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
  269. self.hitPtDist = Vec3(self.hitPt).length()
  270. # Select it
  271. base.direct.select(entry.getIntoNodePath(), base.direct.fShift)
  272. else:
  273. base.direct.deselectAll()
  274. elif self.mode == 'move':
  275. self.manipulateObjectCleanup()
  276. self.mode = None
  277. def manipulateObjectCleanup(self):
  278. if self.fScaling:
  279. # We had been scaling, need to reset object handles
  280. self.objectHandles.transferObjectHandlesScale()
  281. self.fScaling = 0
  282. base.direct.selected.highlightAll()
  283. self.objectHandles.showAllHandles()
  284. if base.direct.clusterMode == 'client':
  285. cluster(
  286. 'base.direct.manipulationControl.objectHandles.showAllHandles()')
  287. self.objectHandles.hideGuides()
  288. # Restart followSelectedNodePath task
  289. self.spawnFollowSelectedNodePathTask()
  290. messenger.send('DIRECT_manipulateObjectCleanup',
  291. [base.direct.selected.getSelectedAsList()])
  292. def spawnFollowSelectedNodePathTask(self):
  293. # If nothing selected, just return
  294. if not base.direct.selected.last:
  295. return
  296. # Clear out old task to make sure
  297. taskMgr.remove('followSelectedNodePath')
  298. # Where are the object handles relative to the selected object
  299. pos = VBase3(0)
  300. hpr = VBase3(0)
  301. decomposeMatrix(base.direct.selected.last.mCoa2Dnp,
  302. VBase3(0), hpr, pos, CSDefault)
  303. # Create the task
  304. t = Task.Task(self.followSelectedNodePathTask)
  305. # Update state variables
  306. t.pos = pos
  307. t.hpr = hpr
  308. t.base = base.direct.selected.last
  309. # Spawn the task
  310. taskMgr.add(t, 'followSelectedNodePath')
  311. def followSelectedNodePathTask(self, state):
  312. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  313. for widget in base.direct.manipulationControl.widgetList:
  314. widget.setPosHpr(state.base, state.pos, state.hpr)
  315. else:
  316. base.direct.widget.setPosHpr(state.base, state.pos, state.hpr)
  317. return Task.cont
  318. def enableManipulation(self):
  319. # Accept events
  320. for event in self.actionEvents:
  321. self.accept(event[0], event[1], extraArgs = event[2:])
  322. self.fAllowSelectionOnly = 0
  323. def disableManipulation(self, allowSelectionOnly=False):
  324. # Ignore events
  325. for event in self.actionEvents:
  326. self.ignore(event[0])
  327. # [gjeon] to enable selection while other manipulation is disabled
  328. if allowSelectionOnly:
  329. self.fAllowSelectionOnly = allowSelectionOnly
  330. self.accept('DIRECT-mouse1', self.manipulationStart)
  331. self.accept('DIRECT-mouse1Up', self.manipulationStop)
  332. self.removeManipulateObjectTask()
  333. taskMgr.remove('manipulateObject')
  334. taskMgr.remove('manip-move-wait')
  335. taskMgr.remove('manip-watch-mouse')
  336. taskMgr.remove('highlightWidgetTask')
  337. taskMgr.remove('resizeObjectHandles')
  338. def toggleObjectHandlesMode(self):
  339. if self.fMovable:
  340. self.fSetCoa = 1 - self.fSetCoa
  341. if self.fSetCoa:
  342. self.objectHandles.coaModeColor()
  343. else:
  344. self.objectHandles.manipModeColor()
  345. else:
  346. self.objectHandles.disabledModeColor()
  347. def removeManipulateObjectTask(self):
  348. taskMgr.remove('manipulateObject')
  349. def enableWidgetMove(self):
  350. self.fMovable = 1
  351. if self.fSetCoa:
  352. self.objectHandles.coaModeColor()
  353. else:
  354. self.objectHandles.manipModeColor()
  355. def disableWidgetMove(self):
  356. self.fMovable = 0
  357. self.objectHandles.disabledModeColor()
  358. #--------------------------------------------------------------------------
  359. # Function: get edit types list for specified objects which indicate
  360. # how editable the objects are
  361. # Parameters: object, list of object to get edit types for
  362. # Changes: none
  363. # Returns: list of edit types
  364. #--------------------------------------------------------------------------
  365. def getEditTypes(self, objects):
  366. # See if any of the selected in the don't manipulate tag list
  367. editTypes = 0
  368. for tag in self.unmovableTagList:
  369. for selected in objects:
  370. unmovableTag = selected.getTag(tag)
  371. if (unmovableTag):
  372. # check value of unmovableTag to see if it is
  373. # completely uneditable or if it allows only certain
  374. # types of editing
  375. editTypes |= int(unmovableTag)
  376. return editTypes
  377. def manipulateObject(self):
  378. # Only do this if something is selected
  379. selectedList = base.direct.selected.getSelectedAsList()
  380. # See if any of the selected are completely uneditable
  381. editTypes = self.getEditTypes(selectedList)
  382. if (editTypes & EDIT_TYPE_UNEDITABLE == EDIT_TYPE_UNEDITABLE):
  383. return
  384. self.currEditTypes = editTypes
  385. if selectedList:
  386. # Remove the task to keep the widget attached to the object
  387. taskMgr.remove('followSelectedNodePath')
  388. # and the task to highlight the widget
  389. taskMgr.remove('highlightWidgetTask')
  390. # Set manipulation flag
  391. self.fManip = 1
  392. # Record undo point
  393. base.direct.pushUndo(base.direct.selected)
  394. # Update object handles visibility
  395. self.objectHandles.showGuides()
  396. self.objectHandles.hideAllHandles()
  397. self.objectHandles.showHandle(self.constraint)
  398. if base.direct.clusterMode == 'client':
  399. oh = 'base.direct.manipulationControl.objectHandles'
  400. cluster(oh + '.showGuides()', 0)
  401. cluster(oh + '.hideAllHandles()', 0)
  402. cluster(oh + ('.showHandle("%s")'% self.constraint), 0)
  403. # Record relationship between selected nodes and widget
  404. base.direct.selected.getWrtAll()
  405. # hide the bbox of the selected objects during interaction
  406. base.direct.selected.dehighlightAll()
  407. # Send event to signal start of manipulation
  408. messenger.send('DIRECT_manipulateObjectStart')
  409. # Manipulate the real object with the constraint
  410. # The constraint is passed as the name of the node
  411. self.spawnManipulateObjectTask()
  412. def spawnManipulateObjectTask(self):
  413. # reset hit-pt flag
  414. self.fHitInit = 1
  415. self.fScaleInit = 1
  416. # record initial offset between widget and camera
  417. t = Task.Task(self.manipulateObjectTask)
  418. t.fMouseX = abs(base.direct.dr.mouseX) > 0.9
  419. t.fMouseY = abs(base.direct.dr.mouseY) > 0.9
  420. if t.fMouseX:
  421. t.constrainedDir = 'y'
  422. else:
  423. t.constrainedDir = 'x'
  424. # Compute widget's xy coords in screen space
  425. t.coaCenter = getScreenXY(base.direct.widget)
  426. # These are used to rotate about view vector
  427. if t.fMouseX and t.fMouseY:
  428. t.lastAngle = getCrankAngle(t.coaCenter)
  429. taskMgr.add(t, 'manipulateObject')
  430. def manipulateObjectTask(self, state):
  431. # Widget takes precedence
  432. if self.constraint:
  433. type = self.constraint[2:]
  434. if base.direct.fControl and not self.currEditTypes & EDIT_TYPE_UNSCALABLE:
  435. if type == 'post':
  436. # [gjeon] non-uniform scaling
  437. self.fScaling = 1
  438. self.scale1D(state)
  439. else:
  440. # [gjeon] uniform scaling
  441. self.fScaling = 1
  442. self.scale3D(state)
  443. else:
  444. if type == 'post' and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
  445. self.xlate1D(state)
  446. elif type == 'disc' and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
  447. self.xlate2D(state)
  448. elif type == 'ring' and not self.currEditTypes & EDIT_TYPE_UNROTATABLE:
  449. self.rotate1D(state)
  450. # No widget interaction, determine free manip mode
  451. elif self.fFreeManip:
  452. # If we've been scaling and changed modes, reset object handles
  453. if 0 and self.fScaling and (not base.direct.fAlt):
  454. self.objectHandles.transferObjectHandlesScale()
  455. self.fScaling = 0
  456. # Alt key switches to a scaling mode
  457. if base.direct.fControl and not self.currEditTypes & EDIT_TYPE_UNSCALABLE:
  458. self.fScaling = 1
  459. self.scale3D(state)
  460. # Otherwise, manip mode depends on where you started
  461. elif state.fMouseX and state.fMouseY and not self.currEditTypes & EDIT_TYPE_UNROTATABLE:
  462. # In the corner, spin around camera's axis
  463. self.rotateAboutViewVector(state)
  464. elif state.fMouseX or state.fMouseY and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
  465. # Mouse started elsewhere in the outer frame, rotate
  466. self.rotate2D(state)
  467. elif not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
  468. # Mouse started in central region, xlate
  469. # Mode depends on shift key
  470. if base.direct.fShift or base.direct.fControl:
  471. self.xlateCamXY(state)
  472. else:
  473. self.xlateCamXZ(state)
  474. if self.fSetCoa:
  475. # Update coa based on current widget position
  476. base.direct.selected.last.mCoa2Dnp.assign(
  477. base.direct.widget.getMat(base.direct.selected.last))
  478. else:
  479. # Move the objects with the widget
  480. base.direct.selected.moveWrtWidgetAll()
  481. # Continue
  482. return Task.cont
  483. def addTag(self, tag):
  484. if tag not in self.unmovableTagList:
  485. self.unmovableTagList.append(tag)
  486. def removeTag(self, tag):
  487. self.unmovableTagList.remove(tag)
  488. def gridSnapping(self, offset):
  489. offsetX = offset.getX()
  490. offsetY = offset.getY()
  491. offsetZ = offset.getZ()
  492. if math.fabs(offsetX) < base.direct.grid.gridSpacing / 2.0:
  493. offsetX = 0
  494. else:
  495. if offsetX < 0:
  496. offsetX = -1 * base.direct.grid.gridSpacing
  497. else:
  498. offsetX = base.direct.grid.gridSpacing
  499. if math.fabs(offsetY) < base.direct.grid.gridSpacing / 2.0:
  500. offsetY = 0
  501. else:
  502. if offsetY < 0:
  503. offsetY = -1 * base.direct.grid.gridSpacing
  504. else:
  505. offsetY = base.direct.grid.gridSpacing
  506. if math.fabs(offsetZ) < base.direct.grid.gridSpacing / 2.0:
  507. offsetZ = 0
  508. else:
  509. if offsetZ < 0:
  510. offsetZ = -1 * base.direct.grid.gridSpacing
  511. else:
  512. offsetZ = base.direct.grid.gridSpacing
  513. offset.setX(offsetX)
  514. offset.setY(offsetY)
  515. offset.setZ(offsetZ)
  516. return offset
  517. ### WIDGET MANIPULATION METHODS ###
  518. def xlate1D(self, state):
  519. # Constrained 1D Translation along widget axis
  520. # Compute nearest hit point along axis and try to keep
  521. # that point as close to the current mouse position as possible
  522. # what point on the axis is the mouse pointing at?
  523. self.hitPt.assign(self.objectHandles.getAxisIntersectPt(
  524. self.constraint[:1]))
  525. # use it to see how far to move the widget
  526. if self.fHitInit:
  527. # First time through, just record that point
  528. self.fHitInit = 0
  529. self.prevHit.assign(self.hitPt)
  530. else:
  531. # Move widget to keep hit point as close to mouse as possible
  532. offset = self.hitPt - self.prevHit
  533. if self.fGridSnap:
  534. offset = self.gridSnapping(offset)
  535. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  536. for widget in base.direct.manipulationControl.widgetList:
  537. widget.setPos(widget, offset)
  538. else:
  539. base.direct.widget.setPos(base.direct.widget, offset)
  540. def xlate2D(self, state):
  541. # Constrained 2D (planar) translation
  542. # Compute point of intersection of ray from eyepoint through cursor
  543. # to one of the three orthogonal planes on the widget.
  544. # This point tracks all subsequent mouse movements
  545. self.hitPt.assign(self.objectHandles.getWidgetIntersectPt(
  546. base.direct.widget, self.constraint[:1]))
  547. # use it to see how far to move the widget
  548. if self.fHitInit:
  549. # First time through just record hit point
  550. self.fHitInit = 0
  551. self.prevHit.assign(self.hitPt)
  552. else:
  553. offset = self.hitPt - self.prevHit
  554. if self.fGridSnap:
  555. offset = self.gridSnapping(offset)
  556. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  557. for widget in base.direct.manipulationControl.widgetList:
  558. widget.setPos(widget, offset)
  559. else:
  560. base.direct.widget.setPos(base.direct.widget, offset)
  561. def rotate1D(self, state):
  562. # Constrained 1D rotation about the widget's main axis (X, Y, or Z)
  563. # Rotation depends upon circular motion of the mouse about the
  564. # projection of the widget's origin on the image plane
  565. # A complete circle about the widget results in a change in
  566. # orientation of 360 degrees.
  567. # First initialize hit point/rotation angle
  568. if self.fHitInit:
  569. self.fHitInit = 0
  570. self.rotateAxis = self.constraint[:1]
  571. self.fWidgetTop = self.widgetCheck('top?')
  572. self.rotationCenter = getScreenXY(base.direct.widget)
  573. self.lastCrankAngle = getCrankAngle(self.rotationCenter)
  574. # Rotate widget based on how far cursor has swung around origin
  575. newAngle = getCrankAngle(self.rotationCenter)
  576. deltaAngle = self.lastCrankAngle - newAngle
  577. if self.fWidgetTop:
  578. deltaAngle = -1 * deltaAngle
  579. if self.rotateAxis == 'x':
  580. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  581. for widget in base.direct.manipulationControl.widgetList:
  582. widget.setP(widget, deltaAngle)
  583. else:
  584. base.direct.widget.setP(base.direct.widget, deltaAngle)
  585. elif self.rotateAxis == 'y':
  586. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  587. for widget in base.direct.manipulationControl.widgetList:
  588. widget.setR(widget, deltaAngle)
  589. else:
  590. base.direct.widget.setR(base.direct.widget, deltaAngle)
  591. elif self.rotateAxis == 'z':
  592. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  593. for widget in base.direct.manipulationControl.widgetList:
  594. widget.setH(widget, deltaAngle)
  595. else:
  596. base.direct.widget.setH(base.direct.widget, deltaAngle)
  597. # Record crank angle for next time around
  598. self.lastCrankAngle = newAngle
  599. def widgetCheck(self, type):
  600. # Utility to see if we are looking at the top or bottom of
  601. # a 2D planar widget or if we are looking at a 2D planar widget
  602. # edge on
  603. # Based upon angle between view vector from eye through the
  604. # widget's origin and one of the three principle axes
  605. axis = self.constraint[:1]
  606. # First compute vector from eye through widget origin
  607. mWidget2Cam = base.direct.widget.getMat(base.direct.camera)
  608. # And determine where the viewpoint is relative to widget
  609. pos = VBase3(0)
  610. decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos,
  611. CSDefault)
  612. widgetDir = Vec3(pos)
  613. widgetDir.normalize()
  614. # Convert specified widget axis to view space
  615. if axis == 'x':
  616. widgetAxis = Vec3(mWidget2Cam.xformVec(X_AXIS))
  617. elif axis == 'y':
  618. widgetAxis = Vec3(mWidget2Cam.xformVec(Y_AXIS))
  619. elif axis == 'z':
  620. widgetAxis = Vec3(mWidget2Cam.xformVec(Z_AXIS))
  621. widgetAxis.normalize()
  622. if type == 'top?':
  623. # Check sign of angle between two vectors
  624. return (widgetDir.dot(widgetAxis) < 0.)
  625. elif type == 'edge?':
  626. # Checking to see if we are viewing edge-on
  627. # Check angle between two vectors
  628. return(abs(widgetDir.dot(widgetAxis)) < .2)
  629. ### FREE MANIPULATION METHODS ###
  630. def xlateCamXZ(self, state):
  631. """Constrained 2D motion parallel to the camera's image plane
  632. This moves the object in the camera's XZ plane"""
  633. # reset fHitInit in case we later switch to manip mode
  634. self.fHitInit = 1
  635. # Reset scaling init flag
  636. self.fScaleInit = 1
  637. # Where is the widget relative to current camera view
  638. vWidget2Camera = base.direct.widget.getPos(base.direct.camera)
  639. x = vWidget2Camera[0]
  640. y = vWidget2Camera[1]
  641. z = vWidget2Camera[2]
  642. # Move widget (and objects) based upon mouse motion
  643. # Scaled up accordingly based upon widget distance
  644. dr = base.direct.dr
  645. base.direct.widget.setX(
  646. base.direct.camera,
  647. x + 0.5 * dr.mouseDeltaX * dr.nearWidth * (y/dr.near))
  648. base.direct.widget.setZ(
  649. base.direct.camera,
  650. z + 0.5 * dr.mouseDeltaY * dr.nearHeight * (y/dr.near))
  651. def xlateCamXY(self, state):
  652. """Constrained 2D motion perpendicular to camera's image plane
  653. This moves the object in the camera's XY plane if shift is held
  654. Moves object toward camera if control is held
  655. """
  656. # Reset scaling init flag
  657. self.fScaleInit = 1
  658. # Now, where is the widget relative to current camera view
  659. vWidget2Camera = base.direct.widget.getPos(base.direct.camera)
  660. # If this is first time around, record initial y distance
  661. if self.fHitInit:
  662. self.fHitInit = 0
  663. # Use distance to widget to scale motion along Y
  664. self.xlateSF = Vec3(vWidget2Camera).length()
  665. # Get widget's current xy coords in screen space
  666. coaCenter = getNearProjectionPoint(base.direct.widget)
  667. self.deltaNearX = coaCenter[0] - base.direct.dr.nearVec[0]
  668. # Which way do we move the object?
  669. if base.direct.fControl:
  670. moveDir = Vec3(vWidget2Camera)
  671. # If widget is behind camera invert vector
  672. if moveDir[1] < 0.0:
  673. moveDir.assign(moveDir * -1)
  674. moveDir.normalize()
  675. else:
  676. moveDir = Vec3(Y_AXIS)
  677. # Move selected objects
  678. dr = base.direct.dr
  679. # Scale move dir
  680. moveDir.assign(moveDir * (2.0 * dr.mouseDeltaY * self.xlateSF))
  681. # Add it to current widget offset
  682. vWidget2Camera += moveDir
  683. # The object, however, stays at the same relative point to mouse in X
  684. vWidget2Camera.setX((dr.nearVec[0] + self.deltaNearX) *
  685. (vWidget2Camera[1]/dr.near))
  686. # Move widget
  687. base.direct.widget.setPos(base.direct.camera, vWidget2Camera)
  688. def rotate2D(self, state):
  689. """ Virtual trackball rotation of widget """
  690. # Reset init flag in case we switch to another mode
  691. self.fHitInit = 1
  692. # Reset scaling init flag
  693. self.fScaleInit = 1
  694. tumbleRate = 360
  695. # If moving outside of center, ignore motion perpendicular to edge
  696. if ((state.constrainedDir == 'y') and (abs(base.direct.dr.mouseX) > 0.9)):
  697. deltaX = 0
  698. deltaY = base.direct.dr.mouseDeltaY
  699. elif ((state.constrainedDir == 'x') and (abs(base.direct.dr.mouseY) > 0.9)):
  700. deltaX = base.direct.dr.mouseDeltaX
  701. deltaY = 0
  702. else:
  703. deltaX = base.direct.dr.mouseDeltaX
  704. deltaY = base.direct.dr.mouseDeltaY
  705. # Mouse motion edge to edge of display region results in one full turn
  706. relHpr(base.direct.widget, base.direct.camera, deltaX * tumbleRate,
  707. -deltaY * tumbleRate, 0)
  708. def rotateAboutViewVector(self, state):
  709. # Reset init flag in case we switch to another mode
  710. self.fHitInit = 1
  711. # Reset scaling init flag
  712. self.fScaleInit = 1
  713. # Compute current angle
  714. angle = getCrankAngle(state.coaCenter)
  715. deltaAngle = angle - state.lastAngle
  716. state.lastAngle = angle
  717. # Mouse motion edge to edge of display region results in one full turn
  718. relHpr(base.direct.widget, base.direct.camera, 0, 0, -deltaAngle)
  719. def scale1D(self, state):
  720. # [gjeon] Constrained 1D scale of the selected node based upon up down mouse motion
  721. if self.fScaleInit:
  722. self.fScaleInit = 0
  723. self.initScaleMag = Vec3(self.objectHandles.getAxisIntersectPt(self.constraint[:1])).length()
  724. # record initial scale
  725. self.initScale = base.direct.widget.getScale()
  726. # Reset fHitInitFlag
  727. self.fHitInit = 1
  728. # reset the scale of the scaling widget so the calls to
  729. # getAxisIntersectPt calculate the correct distance
  730. base.direct.widget.setScale(1,1,1)
  731. # Scale factor is ratio current mag with init mag
  732. if self.constraint[:1] == 'x':
  733. currScale = Vec3(self.initScale.getX() *
  734. self.objectHandles.getAxisIntersectPt('x').length() / self.initScaleMag,
  735. self.initScale.getY(), self.initScale.getZ())
  736. elif self.constraint[:1] == 'y':
  737. currScale = Vec3(self.initScale.getX(),
  738. self.initScale.getY() * self.objectHandles.getAxisIntersectPt('y').length() / self.initScaleMag,
  739. self.initScale.getZ())
  740. elif self.constraint[:1] == 'z':
  741. currScale = Vec3(self.initScale.getX(), self.initScale.getY(),
  742. self.initScale.getZ() * self.objectHandles.getAxisIntersectPt('z').length() / self.initScaleMag)
  743. base.direct.widget.setScale(currScale)
  744. def scale3D(self, state):
  745. # Scale the selected node based upon up down mouse motion
  746. # Mouse motion from edge to edge results in a factor of 4 scaling
  747. # From midpoint to edge doubles or halves objects scale
  748. if self.fScaleInit:
  749. self.fScaleInit = 0
  750. self.manipRef.setPos(base.direct.widget, 0, 0, 0)
  751. self.manipRef.setHpr(base.direct.camera, 0, 0, 0)
  752. self.initScaleMag = Vec3(
  753. self.objectHandles.getWidgetIntersectPt(
  754. self.manipRef, 'y')).length()
  755. # record initial scale
  756. self.initScale = base.direct.widget.getScale()
  757. # Reset fHitInitFlag
  758. self.fHitInit = 1
  759. # Begin
  760. # Scale factor is ratio current mag with init mag
  761. currScale = (
  762. self.initScale *
  763. (self.objectHandles.getWidgetIntersectPt(
  764. self.manipRef, 'y').length() /
  765. self.initScaleMag)
  766. )
  767. base.direct.widget.setScale(currScale)
  768. ## Utility functions ##
  769. def plantSelectedNodePath(self):
  770. """ Move selected object to intersection point of cursor on scene """
  771. # Check for intersection
  772. entry = base.direct.iRay.pickGeom(
  773. skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA)
  774. # MRM: Need to handle moving COA
  775. if (entry != None) and (base.direct.selected.last != None):
  776. # Record undo point
  777. base.direct.pushUndo(base.direct.selected)
  778. # Record wrt matrix
  779. base.direct.selected.getWrtAll()
  780. # Move selected
  781. base.direct.widget.setPos(
  782. base.direct.camera, entry.getSurfacePoint(entry.getFromNodePath()))
  783. # Move all the selected objects with widget
  784. # Move the objects with the widget
  785. base.direct.selected.moveWrtWidgetAll()
  786. # Let everyone know that something was moved
  787. messenger.send('DIRECT_manipulateObjectCleanup',
  788. [base.direct.selected.getSelectedAsList()])
  789. class ObjectHandles(NodePath, DirectObject):
  790. def __init__(self, name='objectHandles'):
  791. # Initialize the superclass
  792. NodePath.__init__(self)
  793. # Load up object handles model and assign it to self
  794. self.assign(loader.loadModel('models/misc/objectHandles'))
  795. self.setName(name)
  796. self.scalingNode = self.getChild(0)
  797. self.scalingNode.setName('ohScalingNode')
  798. self.ohScalingFactor = 1.0
  799. self.directScalingFactor = 1.0
  800. # To avoid recreating a vec every frame
  801. self.hitPt = Vec3(0)
  802. # Get a handle on the components
  803. self.xHandles = self.find('**/X')
  804. self.xPostGroup = self.xHandles.find('**/x-post-group')
  805. self.xPostCollision = self.xHandles.find('**/x-post')
  806. self.xRingGroup = self.xHandles.find('**/x-ring-group')
  807. self.xRingCollision = self.xHandles.find('**/x-ring')
  808. self.xDiscGroup = self.xHandles.find('**/x-disc-group')
  809. self.xDisc = self.xHandles.find('**/x-disc-visible')
  810. self.xDiscCollision = self.xHandles.find('**/x-disc')
  811. self.yHandles = self.find('**/Y')
  812. self.yPostGroup = self.yHandles.find('**/y-post-group')
  813. self.yPostCollision = self.yHandles.find('**/y-post')
  814. self.yRingGroup = self.yHandles.find('**/y-ring-group')
  815. self.yRingCollision = self.yHandles.find('**/y-ring')
  816. self.yDiscGroup = self.yHandles.find('**/y-disc-group')
  817. self.yDisc = self.yHandles.find('**/y-disc-visible')
  818. self.yDiscCollision = self.yHandles.find('**/y-disc')
  819. self.zHandles = self.find('**/Z')
  820. self.zPostGroup = self.zHandles.find('**/z-post-group')
  821. self.zPostCollision = self.zHandles.find('**/z-post')
  822. self.zRingGroup = self.zHandles.find('**/z-ring-group')
  823. self.zRingCollision = self.zHandles.find('**/z-ring')
  824. self.zDiscGroup = self.zHandles.find('**/z-disc-group')
  825. self.zDisc = self.zHandles.find('**/z-disc-visible')
  826. self.zDiscCollision = self.zHandles.find('**/z-disc')
  827. # Adjust visiblity, colors, and transparency
  828. self.xPostCollision.hide()
  829. self.xRingCollision.hide()
  830. self.xDisc.setColor(1, 0, 0, .2)
  831. self.yPostCollision.hide()
  832. self.yRingCollision.hide()
  833. self.yDisc.setColor(0, 1, 0, .2)
  834. self.zPostCollision.hide()
  835. self.zRingCollision.hide()
  836. self.zDisc.setColor(0, 0, 1, .2)
  837. # Augment geometry with lines
  838. self.createObjectHandleLines()
  839. # Create long markers to help line up in world
  840. self.createGuideLines()
  841. self.hideGuides()
  842. # tag with name so they can skipped during iRay selection
  843. self.xPostCollision.setTag('WidgetName',name)
  844. self.yPostCollision.setTag('WidgetName',name)
  845. self.zPostCollision.setTag('WidgetName',name)
  846. self.xRingCollision.setTag('WidgetName',name)
  847. self.yRingCollision.setTag('WidgetName',name)
  848. self.zRingCollision.setTag('WidgetName',name)
  849. self.xDiscCollision.setTag('WidgetName',name)
  850. self.yDiscCollision.setTag('WidgetName',name)
  851. self.zDiscCollision.setTag('WidgetName',name)
  852. # name disc geoms so they can be added to unpickables
  853. self.xDisc.find("**/+GeomNode").setName('x-disc-geom')
  854. self.yDisc.find("**/+GeomNode").setName('y-disc-geom')
  855. self.zDisc.find("**/+GeomNode").setName('z-disc-geom')
  856. # Start with widget handles hidden
  857. self.fActive = 1
  858. self.toggleWidget()
  859. # Make sure object handles are never lit or drawn in wireframe
  860. useDirectRenderStyle(self)
  861. def coaModeColor(self):
  862. self.setColor(.5, .5, .5, 0.5, 1)
  863. def disabledModeColor(self):
  864. self.setColor(0.1,0.1,0.1,0.1,1)
  865. def manipModeColor(self):
  866. self.clearColor()
  867. def toggleWidget(self):
  868. if self.fActive:
  869. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  870. for widget in base.direct.manipulationControl.widgetList:
  871. widget.deactivate()
  872. else:
  873. self.deactivate()
  874. else:
  875. if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
  876. for widget in base.direct.manipulationControl.widgetList:
  877. widget.activate()
  878. widget.showWidgetIfActive()
  879. else:
  880. self.activate()
  881. def activate(self):
  882. self.scalingNode.reparentTo(self)
  883. self.fActive = 1
  884. def deactivate(self):
  885. self.scalingNode.reparentTo(hidden)
  886. self.fActive = 0
  887. def showWidgetIfActive(self):
  888. if self.fActive:
  889. self.reparentTo(base.direct.group)
  890. def showWidget(self):
  891. self.reparentTo(base.direct.group)
  892. def hideWidget(self):
  893. self.reparentTo(hidden)
  894. def enableHandles(self, handles):
  895. if type(handles) == types.ListType:
  896. for handle in handles:
  897. self.enableHandle(handle)
  898. elif handles == 'x':
  899. self.enableHandles(['x-post','x-ring','x-disc'])
  900. elif handles == 'y':
  901. self.enableHandles(['y-post','y-ring','y-disc'])
  902. elif handles == 'z':
  903. self.enableHandles(['z-post','z-ring','z-disc'])
  904. elif handles == 'post':
  905. self.enableHandles(['x-post','y-post','z-post'])
  906. elif handles == 'ring':
  907. self.enableHandles(['x-ring','y-ring','z-ring'])
  908. elif handles == 'disc':
  909. self.enableHandles(['x-disc','y-disc','z-disc'])
  910. elif handles == 'all':
  911. self.enableHandles(['x-post','x-ring','x-disc',
  912. 'y-post','y-ring','y-disc',
  913. 'z-post','z-ring','z-disc'])
  914. def enableHandle(self, handle):
  915. if handle == 'x-post':
  916. self.xPostGroup.reparentTo(self.xHandles)
  917. elif handle == 'x-ring':
  918. self.xRingGroup.reparentTo(self.xHandles)
  919. elif handle == 'x-disc':
  920. self.xDiscGroup.reparentTo(self.xHandles)
  921. if handle == 'y-post':
  922. self.yPostGroup.reparentTo(self.yHandles)
  923. elif handle == 'y-ring':
  924. self.yRingGroup.reparentTo(self.yHandles)
  925. elif handle == 'y-disc':
  926. self.yDiscGroup.reparentTo(self.yHandles)
  927. if handle == 'z-post':
  928. self.zPostGroup.reparentTo(self.zHandles)
  929. elif handle == 'z-ring':
  930. self.zRingGroup.reparentTo(self.zHandles)
  931. elif handle == 'z-disc':
  932. self.zDiscGroup.reparentTo(self.zHandles)
  933. def disableHandles(self, handles):
  934. if type(handles) == types.ListType:
  935. for handle in handles:
  936. self.disableHandle(handle)
  937. elif handles == 'x':
  938. self.disableHandles(['x-post','x-ring','x-disc'])
  939. elif handles == 'y':
  940. self.disableHandles(['y-post','y-ring','y-disc'])
  941. elif handles == 'z':
  942. self.disableHandles(['z-post','z-ring','z-disc'])
  943. elif handles == 'post':
  944. self.disableHandles(['x-post','y-post','z-post'])
  945. elif handles == 'ring':
  946. self.disableHandles(['x-ring','y-ring','z-ring'])
  947. elif handles == 'disc':
  948. self.disableHandles(['x-disc','y-disc','z-disc'])
  949. elif handles == 'all':
  950. self.disableHandles(['x-post','x-ring','x-disc',
  951. 'y-post','y-ring','y-disc',
  952. 'z-post','z-ring','z-disc'])
  953. def disableHandle(self, handle):
  954. if handle == 'x-post':
  955. self.xPostGroup.reparentTo(hidden)
  956. elif handle == 'x-ring':
  957. self.xRingGroup.reparentTo(hidden)
  958. elif handle == 'x-disc':
  959. self.xDiscGroup.reparentTo(hidden)
  960. if handle == 'y-post':
  961. self.yPostGroup.reparentTo(hidden)
  962. elif handle == 'y-ring':
  963. self.yRingGroup.reparentTo(hidden)
  964. elif handle == 'y-disc':
  965. self.yDiscGroup.reparentTo(hidden)
  966. if handle == 'z-post':
  967. self.zPostGroup.reparentTo(hidden)
  968. elif handle == 'z-ring':
  969. self.zRingGroup.reparentTo(hidden)
  970. elif handle == 'z-disc':
  971. self.zDiscGroup.reparentTo(hidden)
  972. def showAllHandles(self):
  973. self.xPost.show()
  974. self.xRing.show()
  975. self.xDisc.show()
  976. self.yPost.show()
  977. self.yRing.show()
  978. self.yDisc.show()
  979. self.zPost.show()
  980. self.zRing.show()
  981. self.zDisc.show()
  982. def hideAllHandles(self):
  983. self.xPost.hide()
  984. self.xRing.hide()
  985. self.xDisc.hide()
  986. self.yPost.hide()
  987. self.yRing.hide()
  988. self.yDisc.hide()
  989. self.zPost.hide()
  990. self.zRing.hide()
  991. self.zDisc.hide()
  992. def showHandle(self, handle):
  993. if handle == 'x-post':
  994. self.xPost.show()
  995. elif handle == 'x-ring':
  996. self.xRing.show()
  997. elif handle == 'x-disc':
  998. self.xDisc.show()
  999. elif handle == 'y-post':
  1000. self.yPost.show()
  1001. elif handle == 'y-ring':
  1002. self.yRing.show()
  1003. elif handle == 'y-disc':
  1004. self.yDisc.show()
  1005. elif handle == 'z-post':
  1006. self.zPost.show()
  1007. elif handle == 'z-ring':
  1008. self.zRing.show()
  1009. elif handle == 'z-disc':
  1010. self.zDisc.show()
  1011. def showGuides(self):
  1012. self.guideLines.show()
  1013. def hideGuides(self):
  1014. self.guideLines.hide()
  1015. def setDirectScalingFactor(self, factor):
  1016. self.directScalingFactor = factor
  1017. self.setScalingFactor(1)
  1018. def setScalingFactor(self, scaleFactor):
  1019. self.ohScalingFactor = self.ohScalingFactor * scaleFactor
  1020. self.scalingNode.setScale(self.ohScalingFactor * self.directScalingFactor)
  1021. def getScalingFactor(self):
  1022. return self.scalingNode.getScale()
  1023. def transferObjectHandlesScale(self):
  1024. # see how much object handles have been scaled
  1025. ohs = self.getScale()
  1026. sns = self.scalingNode.getScale()
  1027. # Transfer this to the scaling node
  1028. self.scalingNode.setScale(
  1029. ohs[0] * sns[0],
  1030. ohs[1] * sns[1],
  1031. ohs[2] * sns[2])
  1032. self.setScale(1)
  1033. def multiplyScalingFactorBy(self, factor):
  1034. taskMgr.remove('resizeObjectHandles')
  1035. self.ohScalingFactor = self.ohScalingFactor * factor
  1036. sf = self.ohScalingFactor * self.directScalingFactor
  1037. self.scalingNode.lerpScale(sf, sf, sf, 0.5,
  1038. blendType = 'easeInOut',
  1039. task = 'resizeObjectHandles')
  1040. def growToFit(self):
  1041. taskMgr.remove('resizeObjectHandles')
  1042. # Increase handles scale until they cover 30% of the min dimension
  1043. pos = base.direct.widget.getPos(base.direct.camera)
  1044. minDim = min(base.direct.dr.nearWidth, base.direct.dr.nearHeight)
  1045. sf = 0.15 * minDim * (pos[1]/base.direct.dr.near)
  1046. self.ohScalingFactor = sf
  1047. sf = sf * self.directScalingFactor
  1048. self.scalingNode.lerpScale(sf, sf, sf, 0.5,
  1049. blendType = 'easeInOut',
  1050. task = 'resizeObjectHandles')
  1051. def createObjectHandleLines(self):
  1052. # X post
  1053. self.xPost = self.xPostGroup.attachNewNode('x-post-visible')
  1054. lines = LineNodePath(self.xPost)
  1055. lines.setColor(VBase4(1, 0, 0, 1))
  1056. lines.setThickness(5)
  1057. #lines.moveTo(0, 0, 0)
  1058. #lines.drawTo(1.5, 0, 0)
  1059. lines.moveTo(1.5, 0, 0)
  1060. #lines.create()
  1061. #lines = LineNodePath(self.xPost)
  1062. #lines.setColor(VBase4(1, 0, 0, 1))
  1063. #lines.setThickness(1.5)
  1064. #lines.moveTo(0, 0, 0)
  1065. lines.drawTo(-1.5, 0, 0)
  1066. arrowInfo0 = 1.3
  1067. arrowInfo1 = 0.1
  1068. #lines.setThickness(5)
  1069. lines.moveTo(1.5, 0, 0)
  1070. lines.drawTo(arrowInfo0, arrowInfo1, arrowInfo1)
  1071. lines.moveTo(1.5, 0, 0)
  1072. lines.drawTo(arrowInfo0, arrowInfo1, -1 * arrowInfo1)
  1073. lines.moveTo(1.5, 0, 0)
  1074. lines.drawTo(arrowInfo0, -1 * arrowInfo1, arrowInfo1)
  1075. lines.moveTo(1.5, 0, 0)
  1076. lines.drawTo(arrowInfo0, -1 * arrowInfo1, -1 * arrowInfo1)
  1077. lines.create()
  1078. lines.setName('x-post-line')
  1079. # X ring
  1080. self.xRing = self.xRingGroup.attachNewNode('x-ring-visible')
  1081. lines = LineNodePath(self.xRing)
  1082. lines.setColor(VBase4(1, 0, 0, 1))
  1083. lines.setThickness(3)
  1084. lines.moveTo(0, 1, 0)
  1085. for ang in range(15, 370, 15):
  1086. lines.drawTo(0,
  1087. math.cos(deg2Rad(ang)),
  1088. math.sin(deg2Rad(ang)))
  1089. lines.create()
  1090. lines.setName('x-ring-line')
  1091. # Y post
  1092. self.yPost = self.yPostGroup.attachNewNode('y-post-visible')
  1093. lines = LineNodePath(self.yPost)
  1094. lines.setColor(VBase4(0, 1, 0, 1))
  1095. lines.setThickness(5)
  1096. #lines.moveTo(0, 0, 0)
  1097. #lines.drawTo(0, 1.5, 0)
  1098. lines.moveTo(0, 1.5, 0)
  1099. #lines.create()
  1100. #lines = LineNodePath(self.yPost)
  1101. #lines.setColor(VBase4(0, 1, 0, 1))
  1102. #lines.setThickness(1.5)
  1103. #lines.moveTo(0, 0, 0)
  1104. lines.drawTo(0, -1.5, 0)
  1105. #lines.setThickness(5)
  1106. lines.moveTo(0, 1.5, 0)
  1107. lines.drawTo(arrowInfo1, arrowInfo0, arrowInfo1)
  1108. lines.moveTo(0, 1.5, 0)
  1109. lines.drawTo(arrowInfo1, arrowInfo0, -1 * arrowInfo1)
  1110. lines.moveTo(0, 1.5, 0)
  1111. lines.drawTo(-1 * arrowInfo1, arrowInfo0, arrowInfo1)
  1112. lines.moveTo(0, 1.5, 0)
  1113. lines.drawTo(-1 * arrowInfo1, arrowInfo0, -1 * arrowInfo1)
  1114. lines.create()
  1115. lines.setName('y-post-line')
  1116. # Y ring
  1117. self.yRing = self.yRingGroup.attachNewNode('y-ring-visible')
  1118. lines = LineNodePath(self.yRing)
  1119. lines.setColor(VBase4(0, 1, 0, 1))
  1120. lines.setThickness(3)
  1121. lines.moveTo(1, 0, 0)
  1122. for ang in range(15, 370, 15):
  1123. lines.drawTo(math.cos(deg2Rad(ang)),
  1124. 0,
  1125. math.sin(deg2Rad(ang)))
  1126. lines.create()
  1127. lines.setName('y-ring-line')
  1128. # Z post
  1129. self.zPost = self.zPostGroup.attachNewNode('z-post-visible')
  1130. lines = LineNodePath(self.zPost)
  1131. lines.setColor(VBase4(0, 0, 1, 1))
  1132. lines.setThickness(5)
  1133. #lines.moveTo(0, 0, 0)
  1134. #lines.drawTo(0, 0, 1.5)
  1135. lines.moveTo(0, 0, 1.5)
  1136. #lines.create()
  1137. #lines = LineNodePath(self.zPost)
  1138. #lines.setColor(VBase4(0, 0, 1, 1))
  1139. #lines.setThickness(1.5)
  1140. #lines.moveTo(0, 0, 0)
  1141. lines.drawTo(0, 0, -1.5)
  1142. #lines.setThickness(5)
  1143. lines.moveTo(0, 0, 1.5)
  1144. lines.drawTo(arrowInfo1, arrowInfo1, arrowInfo0)
  1145. lines.moveTo(0, 0, 1.5)
  1146. lines.drawTo(arrowInfo1, -1 * arrowInfo1, arrowInfo0)
  1147. lines.moveTo(0, 0, 1.5)
  1148. lines.drawTo(-1 * arrowInfo1, arrowInfo1, arrowInfo0)
  1149. lines.moveTo(0, 0, 1.5)
  1150. lines.drawTo(-1 * arrowInfo1, -1 * arrowInfo1, arrowInfo0)
  1151. lines.create()
  1152. lines.setName('z-post-line')
  1153. # Z ring
  1154. self.zRing = self.zRingGroup.attachNewNode('z-ring-visible')
  1155. lines = LineNodePath(self.zRing)
  1156. lines.setColor(VBase4(0, 0, 1, 1))
  1157. lines.setThickness(3)
  1158. lines.moveTo(1, 0, 0)
  1159. for ang in range(15, 370, 15):
  1160. lines.drawTo(math.cos(deg2Rad(ang)),
  1161. math.sin(deg2Rad(ang)),
  1162. 0)
  1163. lines.create()
  1164. lines.setName('z-ring-line')
  1165. def createGuideLines(self):
  1166. self.guideLines = self.attachNewNode('guideLines')
  1167. # X guide lines
  1168. lines = LineNodePath(self.guideLines)
  1169. lines.setColor(VBase4(1, 0, 0, 1))
  1170. lines.setThickness(0.5)
  1171. lines.moveTo(-500, 0, 0)
  1172. lines.drawTo(500, 0, 0)
  1173. lines.create()
  1174. lines.setName('x-guide')
  1175. # Y guide lines
  1176. lines = LineNodePath(self.guideLines)
  1177. lines.setColor(VBase4(0, 1, 0, 1))
  1178. lines.setThickness(0.5)
  1179. lines.moveTo(0, -500, 0)
  1180. lines.drawTo(0, 500, 0)
  1181. lines.create()
  1182. lines.setName('y-guide')
  1183. # Z guide lines
  1184. lines = LineNodePath(self.guideLines)
  1185. lines.setColor(VBase4(0, 0, 1, 1))
  1186. lines.setThickness(0.5)
  1187. lines.moveTo(0, 0, -500)
  1188. lines.drawTo(0, 0, 500)
  1189. lines.create()
  1190. lines.setName('z-guide')
  1191. def getAxisIntersectPt(self, axis):
  1192. # Calc the xfrom from camera to widget
  1193. mCam2Widget = base.direct.camera.getMat(base.direct.widget)
  1194. lineDir = Vec3(mCam2Widget.xformVec(base.direct.dr.nearVec))
  1195. lineDir.normalize()
  1196. # And determine where the viewpoint is relative to widget
  1197. lineOrigin = VBase3(0)
  1198. decomposeMatrix(mCam2Widget, VBase3(0), VBase3(0), lineOrigin,
  1199. CSDefault)
  1200. # Now see where this hits the plane containing the 1D motion axis.
  1201. # Pick the intersection plane most normal to the intersection ray
  1202. # by comparing lineDir with plane normals. The plane with the
  1203. # largest dotProduct is most "normal"
  1204. if axis == 'x':
  1205. if (abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS))):
  1206. self.hitPt.assign(
  1207. planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
  1208. else:
  1209. self.hitPt.assign(
  1210. planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
  1211. # We really only care about the nearest point on the axis
  1212. self.hitPt.setY(0)
  1213. self.hitPt.setZ(0)
  1214. elif axis == 'y':
  1215. if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS))):
  1216. self.hitPt.assign(
  1217. planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
  1218. else:
  1219. self.hitPt.assign(
  1220. planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
  1221. # We really only care about the nearest point on the axis
  1222. self.hitPt.setX(0)
  1223. self.hitPt.setZ(0)
  1224. elif axis == 'z':
  1225. if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS))):
  1226. self.hitPt.assign(
  1227. planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
  1228. else:
  1229. self.hitPt.assign(
  1230. planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
  1231. # We really only care about the nearest point on the axis
  1232. self.hitPt.setX(0)
  1233. self.hitPt.setY(0)
  1234. return self.hitPt
  1235. def getWidgetIntersectPt(self, nodePath, plane):
  1236. # Find out the point of interection of the ray passing though the mouse
  1237. # with the plane containing the 2D xlation or 1D rotation widgets
  1238. # Calc the xfrom from camera to the nodePath
  1239. mCam2NodePath = base.direct.camera.getMat(nodePath)
  1240. # And determine where the viewpoint is relative to widget
  1241. lineOrigin = VBase3(0)
  1242. decomposeMatrix(mCam2NodePath, VBase3(0), VBase3(0), lineOrigin,
  1243. CSDefault)
  1244. # Next we find the vector from viewpoint to the widget through
  1245. # the mouse's position on near plane.
  1246. # This defines the intersection ray
  1247. lineDir = Vec3(mCam2NodePath.xformVec(base.direct.dr.nearVec))
  1248. lineDir.normalize()
  1249. # Find the hit point
  1250. if plane == 'x':
  1251. self.hitPt.assign(planeIntersect(
  1252. lineOrigin, lineDir, ORIGIN, X_AXIS))
  1253. elif plane == 'y':
  1254. self.hitPt.assign(planeIntersect(
  1255. lineOrigin, lineDir, ORIGIN, Y_AXIS))
  1256. elif plane == 'z':
  1257. self.hitPt.assign(planeIntersect(
  1258. lineOrigin, lineDir, ORIGIN, Z_AXIS))
  1259. return self.hitPt