DirectManipulation.py 73 KB

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