mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-25 20:52:26 -06:00
1772 lines
73 KiB
Python
1772 lines
73 KiB
Python
|
from direct.showbase.DirectObject import DirectObject
|
||
|
from DirectGlobals import *
|
||
|
from DirectUtil import *
|
||
|
from DirectGeometry import *
|
||
|
from DirectSelection import SelectionRay
|
||
|
from direct.task import Task
|
||
|
import types
|
||
|
from copy import deepcopy
|
||
|
|
||
|
class DirectManipulationControl(DirectObject):
|
||
|
def __init__(self):
|
||
|
# Create the grid
|
||
|
self.objectHandles = ObjectHandles()
|
||
|
self.hitPt = Point3(0)
|
||
|
self.prevHit = Vec3(0)
|
||
|
|
||
|
self.hitPtScale = Point3(0) # [gjeon] to be used in new LE's camera control
|
||
|
self.prevHitScale = Vec3(0) # [gjeon] to be used in new LE's camera control
|
||
|
|
||
|
self.rotationCenter = Point3(0)
|
||
|
self.initScaleMag = 1
|
||
|
self.manipRef = base.direct.group.attachNewNode('manipRef')
|
||
|
self.hitPtDist = 0
|
||
|
self.constraint = None
|
||
|
self.rotateAxis = 'x'
|
||
|
self.lastCrankAngle = 0
|
||
|
self.fSetCoa = 0
|
||
|
self.fHitInit = 1
|
||
|
self.fScaleInit = 1
|
||
|
self.fScaleInit1 = 1 # [gjeon] to be used in new LE's camera control
|
||
|
self.fWidgetTop = 0
|
||
|
self.fFreeManip = 1
|
||
|
self.fScaling3D = 0
|
||
|
self.fScaling1D = 0
|
||
|
self.fMovable = 1
|
||
|
self.mode = None
|
||
|
self.worldSpaceManip = False
|
||
|
#turn this on to enable separate handles for scaling
|
||
|
self.useSeparateScaleHandles = False
|
||
|
self.actionEvents = [
|
||
|
['DIRECT-mouse1', self.manipulationStart],
|
||
|
['DIRECT-mouse1Up', self.manipulationStop],
|
||
|
['tab', self.toggleObjectHandlesMode],
|
||
|
## ['.', self.objectHandles.multiplyScalingFactorBy, 2.0],
|
||
|
## ['>', self.objectHandles.multiplyScalingFactorBy, 2.0],
|
||
|
## [',', self.objectHandles.multiplyScalingFactorBy, 0.5],
|
||
|
## ['<', self.objectHandles.multiplyScalingFactorBy, 0.5],
|
||
|
['DIRECT-widgetScaleUp', self.scaleWidget, 2.0],
|
||
|
['DIRECT-widgetScaleDown', self.scaleWidget, 0.5],
|
||
|
['shift-f', self.objectHandles.growToFit],
|
||
|
['i', self.plantSelectedNodePath],
|
||
|
]
|
||
|
self.defaultSkipFlags = SKIP_HIDDEN | SKIP_BACKFACE
|
||
|
self.optionalSkipFlags = 0
|
||
|
self.unmovableTagList = []
|
||
|
|
||
|
# [gjeon] flag to enable selection while other manipulation is disabled
|
||
|
self.fAllowSelectionOnly = 0
|
||
|
|
||
|
# [gjeon] flag to enable marquee selection feature
|
||
|
self.fAllowMarquee = 0
|
||
|
self.marquee = None
|
||
|
|
||
|
# [gjeon] for new LE's multi-view support
|
||
|
self.fMultiView = 0
|
||
|
|
||
|
# [gjeon] to support grid snapping
|
||
|
self.fGridSnap = 0
|
||
|
|
||
|
def scaleWidget(self, factor):
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.multiplyScalingFactorBy(factor)
|
||
|
else:
|
||
|
self.objectHandles.multiplyScalingFactorBy(factor)
|
||
|
|
||
|
def supportMultiView(self):
|
||
|
if self.fMultiView:
|
||
|
return
|
||
|
|
||
|
self.objectHandles.hide(BitMask32.bit(0))
|
||
|
self.objectHandles.hide(BitMask32.bit(1))
|
||
|
self.objectHandles.hide(BitMask32.bit(2))
|
||
|
|
||
|
self.topViewWidget = ObjectHandles('topViewWidget')
|
||
|
self.frontViewWidget = ObjectHandles('frontViewWidget')
|
||
|
self.leftViewWidget = ObjectHandles('leftViewWidget')
|
||
|
self.widgetList = [self.topViewWidget, self.frontViewWidget, self.leftViewWidget, self.objectHandles]
|
||
|
|
||
|
self.topViewWidget.hide(BitMask32.bit(1))
|
||
|
self.topViewWidget.hide(BitMask32.bit(2))
|
||
|
self.topViewWidget.hide(BitMask32.bit(3))
|
||
|
|
||
|
self.frontViewWidget.hide(BitMask32.bit(0))
|
||
|
self.frontViewWidget.hide(BitMask32.bit(2))
|
||
|
self.frontViewWidget.hide(BitMask32.bit(3))
|
||
|
|
||
|
self.leftViewWidget.hide(BitMask32.bit(0))
|
||
|
self.leftViewWidget.hide(BitMask32.bit(1))
|
||
|
self.leftViewWidget.hide(BitMask32.bit(3))
|
||
|
|
||
|
self.fMultiView = 1
|
||
|
|
||
|
def manipulationStart(self, modifiers):
|
||
|
# Start out in select mode
|
||
|
self.mode = 'select'
|
||
|
|
||
|
if base.direct.cameraControl.useMayaCamControls and modifiers == 4:
|
||
|
self.mode = 'camera'
|
||
|
|
||
|
if self.fAllowSelectionOnly:
|
||
|
return
|
||
|
|
||
|
if self.fScaling1D == 0 and\
|
||
|
self.fScaling3D == 0:
|
||
|
|
||
|
# Check for a widget hit point
|
||
|
entry = base.direct.iRay.pickWidget(skipFlags = SKIP_WIDGET)
|
||
|
# Did we hit a widget?
|
||
|
if entry:
|
||
|
# Yes!
|
||
|
self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
|
||
|
self.hitPtDist = Vec3(self.hitPt).length()
|
||
|
# Constraint determined by nodes name
|
||
|
self.constraint = entry.getIntoNodePath().getName()
|
||
|
else:
|
||
|
# Nope, off the widget, no constraint
|
||
|
self.constraint = None
|
||
|
# [gjeon] to prohibit unwanted object movement while direct window doesn't have focus
|
||
|
if base.direct.cameraControl.useMayaCamControls and not base.direct.gotControl(modifiers) \
|
||
|
and not self.fAllowMarquee:
|
||
|
return
|
||
|
else:
|
||
|
entry = None
|
||
|
|
||
|
if not base.direct.gotAlt(modifiers):
|
||
|
if entry:
|
||
|
# Check to see if we are moving the object
|
||
|
# We are moving the object if we either wait long enough
|
||
|
taskMgr.doMethodLater(MANIPULATION_MOVE_DELAY,
|
||
|
self.switchToMoveMode,
|
||
|
'manip-move-wait')
|
||
|
# Or we move far enough
|
||
|
self.moveDir = None
|
||
|
watchMouseTask = Task.Task(self.watchMouseTask)
|
||
|
watchMouseTask.initX = base.direct.dr.mouseX
|
||
|
watchMouseTask.initY = base.direct.dr.mouseY
|
||
|
taskMgr.add(watchMouseTask, 'manip-watch-mouse')
|
||
|
else:
|
||
|
if base.direct.fControl:
|
||
|
self.mode = 'move'
|
||
|
self.manipulateObject()
|
||
|
elif not base.direct.fAlt and self.fAllowMarquee:
|
||
|
self.moveDir = None
|
||
|
watchMarqueeTask = Task.Task(self.watchMarqueeTask)
|
||
|
watchMarqueeTask.initX = base.direct.dr.mouseX
|
||
|
watchMarqueeTask.initY = base.direct.dr.mouseY
|
||
|
taskMgr.add(watchMarqueeTask, 'manip-marquee-mouse')
|
||
|
|
||
|
def switchToWorldSpaceMode(self):
|
||
|
self.worldSpaceManip = True
|
||
|
|
||
|
def switchToLocalSpaceMode(self):
|
||
|
self.worldSpaceManip = False
|
||
|
|
||
|
def switchToMoveMode(self, state):
|
||
|
taskMgr.remove('manip-watch-mouse')
|
||
|
self.mode = 'move'
|
||
|
self.manipulateObject()
|
||
|
return Task.done
|
||
|
|
||
|
def watchMouseTask(self, state):
|
||
|
if (((abs (state.initX - base.direct.dr.mouseX)) > 0.01) or
|
||
|
((abs (state.initY - base.direct.dr.mouseY)) > 0.01)):
|
||
|
taskMgr.remove('manip-move-wait')
|
||
|
self.mode = 'move'
|
||
|
self.manipulateObject()
|
||
|
return Task.done
|
||
|
else:
|
||
|
return Task.cont
|
||
|
|
||
|
def watchMarqueeTask(self, state):
|
||
|
taskMgr.remove('manip-watch-mouse')
|
||
|
taskMgr.remove('manip-move-wait')
|
||
|
self.mode = 'select'
|
||
|
self.drawMarquee(state.initX, state.initY)
|
||
|
return Task.cont
|
||
|
|
||
|
def drawMarquee(self, startX, startY):
|
||
|
if self.marquee:
|
||
|
self.marquee.remove()
|
||
|
self.marquee = None
|
||
|
|
||
|
if base.direct.cameraControl.useMayaCamControls and base.direct.fAlt:
|
||
|
return
|
||
|
if base.direct.fControl:
|
||
|
return
|
||
|
|
||
|
endX = base.direct.dr.mouseX
|
||
|
endY = base.direct.dr.mouseY
|
||
|
|
||
|
if (((abs (endX - startX)) < 0.01) and
|
||
|
((abs (endY - startY)) < 0.01)):
|
||
|
return
|
||
|
|
||
|
self.marquee = LineNodePath(render2d, 'marquee', 0.5, VBase4(.8, .6, .6, 1))
|
||
|
self.marqueeInfo = (startX, startY, endX, endY)
|
||
|
self.marquee.drawLines([
|
||
|
[(startX, 0, startY), (startX, 0, endY)],
|
||
|
[(startX, 0, endY), (endX, 0, endY)],
|
||
|
[(endX, 0, endY), (endX, 0, startY)],
|
||
|
[(endX, 0, startY), (startX, 0, startY)]])
|
||
|
self.marquee.create()
|
||
|
|
||
|
if self.fMultiView:
|
||
|
LE_showInOneCam(self.marquee, base.direct.camera.getName())
|
||
|
|
||
|
def manipulationStop(self):
|
||
|
taskMgr.remove('manipulateObject')
|
||
|
taskMgr.remove('manip-move-wait')
|
||
|
taskMgr.remove('manip-watch-mouse')
|
||
|
taskMgr.remove('manip-marquee-mouse')
|
||
|
# depending on flag.....
|
||
|
if self.mode == 'select':
|
||
|
# Check for object under mouse
|
||
|
# Don't intersect with hidden or backfacing objects, as well as any
|
||
|
# optionally specified things
|
||
|
skipFlags = self.defaultSkipFlags | self.optionalSkipFlags
|
||
|
# Skip camera (and its children), unless control key is pressed
|
||
|
skipFlags |= SKIP_CAMERA * (1 - base.getControl())
|
||
|
|
||
|
if self.marquee:
|
||
|
self.marquee.remove()
|
||
|
self.marquee = None
|
||
|
base.direct.deselectAll()
|
||
|
|
||
|
startX = self.marqueeInfo[0]
|
||
|
startY = self.marqueeInfo[1]
|
||
|
endX = self.marqueeInfo[2]
|
||
|
endY = self.marqueeInfo[3]
|
||
|
|
||
|
fll = Point3(0, 0, 0)
|
||
|
flr = Point3(0, 0, 0)
|
||
|
fur = Point3(0, 0, 0)
|
||
|
ful = Point3(0, 0, 0)
|
||
|
nll = Point3(0, 0, 0)
|
||
|
nlr = Point3(0, 0, 0)
|
||
|
nur = Point3(0, 0, 0)
|
||
|
nul = Point3(0, 0, 0)
|
||
|
|
||
|
lens = base.direct.cam.node().getLens()
|
||
|
lens.extrude((startX, startY), nul, ful)
|
||
|
lens.extrude((endX, startY), nur, fur)
|
||
|
lens.extrude((endX, endY), nlr, flr)
|
||
|
lens.extrude((startX, endY), nll, fll)
|
||
|
|
||
|
marqueeFrustum = BoundingHexahedron(fll, flr, fur, ful, nll, nlr, nur, nul);
|
||
|
marqueeFrustum.xform(base.direct.cam.getNetTransform().getMat())
|
||
|
|
||
|
base.marqueeFrustum = marqueeFrustum
|
||
|
|
||
|
def findTaggedNodePath(nodePath):
|
||
|
# Select tagged object if present
|
||
|
for tag in base.direct.selected.tagList:
|
||
|
if nodePath.hasNetTag(tag):
|
||
|
nodePath = nodePath.findNetTag(tag)
|
||
|
return nodePath
|
||
|
return None
|
||
|
|
||
|
selectionList = []
|
||
|
for geom in render.findAllMatches("**/+GeomNode"):
|
||
|
if (skipFlags & SKIP_HIDDEN) and geom.isHidden():
|
||
|
# Skip if hidden node
|
||
|
continue
|
||
|
## elif (skipFlags & SKIP_BACKFACE) and base.direct.iRay.isEntryBackfacing():
|
||
|
## # Skip, if backfacing poly
|
||
|
## pass
|
||
|
elif ((skipFlags & SKIP_CAMERA) and
|
||
|
(camera in geom.getAncestors())):
|
||
|
# Skip if parented to a camera.
|
||
|
continue
|
||
|
# Can pick unpickable, use the first visible node
|
||
|
elif ((skipFlags & SKIP_UNPICKABLE) and
|
||
|
(geom.getName() in base.direct.iRay.unpickable)):
|
||
|
# Skip if in unpickable list
|
||
|
continue
|
||
|
|
||
|
nodePath = findTaggedNodePath(geom)
|
||
|
if nodePath in selectionList:
|
||
|
continue
|
||
|
|
||
|
bb = geom.getBounds()
|
||
|
bbc = bb.makeCopy()
|
||
|
bbc.xform(geom.getParent().getNetTransform().getMat())
|
||
|
|
||
|
boundingSphereTest = marqueeFrustum.contains(bbc)
|
||
|
if boundingSphereTest > 1:
|
||
|
if boundingSphereTest == 7:
|
||
|
if nodePath not in selectionList:
|
||
|
selectionList.append(nodePath)
|
||
|
else:
|
||
|
tMat = Mat4(geom.getMat())
|
||
|
geom.clearMat()
|
||
|
# Get bounds
|
||
|
min = Point3(0)
|
||
|
max = Point3(0)
|
||
|
geom.calcTightBounds(min, max)
|
||
|
# Restore transform
|
||
|
geom.setMat(tMat)
|
||
|
|
||
|
fll = Point3(min[0], max[1], min[2])
|
||
|
flr = Point3(max[0], max[1], min[2])
|
||
|
fur = max
|
||
|
ful = Point3(min[0], max[1], max[2])
|
||
|
nll = min
|
||
|
nlr = Point3(max[0], min[1], min[2])
|
||
|
nur = Point3(max[0], min[1], max[2])
|
||
|
nul = Point3(min[0], min[1], max[2])
|
||
|
|
||
|
tbb = BoundingHexahedron(fll, flr, fur, ful, nll, nlr, nur, nul)
|
||
|
|
||
|
tbb.xform(geom.getNetTransform().getMat())
|
||
|
|
||
|
tightBoundTest = marqueeFrustum.contains(tbb)
|
||
|
|
||
|
if tightBoundTest > 1:
|
||
|
if nodePath not in selectionList:
|
||
|
selectionList.append(nodePath)
|
||
|
|
||
|
for nodePath in selectionList:
|
||
|
base.direct.select(nodePath, 1)
|
||
|
|
||
|
else:
|
||
|
entry = base.direct.iRay.pickGeom(skipFlags = skipFlags)
|
||
|
if entry:
|
||
|
# Record hit point information
|
||
|
self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
|
||
|
self.hitPtDist = Vec3(self.hitPt).length()
|
||
|
# Select it
|
||
|
base.direct.select(entry.getIntoNodePath(), base.direct.fShift)
|
||
|
else:
|
||
|
base.direct.deselectAll()
|
||
|
#elif self.mode == 'move':
|
||
|
self.manipulateObjectCleanup()
|
||
|
|
||
|
self.mode = None
|
||
|
|
||
|
def manipulateObjectCleanup(self):
|
||
|
if self.fScaling3D or self.fScaling1D:
|
||
|
# We had been scaling, need to reset object handles
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.transferObjectHandlesScale()
|
||
|
else:
|
||
|
self.objectHandles.transferObjectHandlesScale()
|
||
|
self.fScaling3D = 0
|
||
|
self.fScaling1D = 0
|
||
|
base.direct.selected.highlightAll()
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.showAllHandles()
|
||
|
else:
|
||
|
self.objectHandles.showAllHandles()
|
||
|
if base.direct.clusterMode == 'client':
|
||
|
cluster(
|
||
|
'base.direct.manipulationControl.objectHandles.showAllHandles()')
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.hideGuides()
|
||
|
else:
|
||
|
self.objectHandles.hideGuides()
|
||
|
|
||
|
# Restart followSelectedNodePath task
|
||
|
self.spawnFollowSelectedNodePathTask()
|
||
|
messenger.send('DIRECT_manipulateObjectCleanup',
|
||
|
[base.direct.selected.getSelectedAsList()])
|
||
|
|
||
|
def spawnFollowSelectedNodePathTask(self):
|
||
|
# If nothing selected, just return
|
||
|
if not base.direct.selected.last:
|
||
|
return
|
||
|
# Clear out old task to make sure
|
||
|
taskMgr.remove('followSelectedNodePath')
|
||
|
# Where are the object handles relative to the selected object
|
||
|
pos = VBase3(0)
|
||
|
hpr = VBase3(0)
|
||
|
decomposeMatrix(base.direct.selected.last.mCoa2Dnp,
|
||
|
VBase3(0), hpr, pos, CSDefault)
|
||
|
# Create the task
|
||
|
t = Task.Task(self.followSelectedNodePathTask)
|
||
|
# Update state variables
|
||
|
t.pos = pos
|
||
|
t.hpr = hpr
|
||
|
t.base = base.direct.selected.last
|
||
|
# Spawn the task
|
||
|
taskMgr.add(t, 'followSelectedNodePath')
|
||
|
|
||
|
def followSelectedNodePathTask(self, state):
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
if self.worldSpaceManip:
|
||
|
widget.setPos(state.base, state.pos)
|
||
|
widget.setHpr(render, VBase3(0))
|
||
|
else:
|
||
|
widget.setPosHpr(state.base, state.pos, state.hpr)
|
||
|
else:
|
||
|
if self.worldSpaceManip:
|
||
|
widget.setPos(state.base, state.pos)
|
||
|
widget.setHpr(render, VBase3(0))
|
||
|
else:
|
||
|
base.direct.widget.setPosHpr(state.base, state.pos, state.hpr)
|
||
|
return Task.cont
|
||
|
|
||
|
def enableManipulation(self):
|
||
|
# Accept events
|
||
|
for event in self.actionEvents:
|
||
|
self.accept(event[0], event[1], extraArgs = event[2:])
|
||
|
|
||
|
self.fAllowSelectionOnly = 0
|
||
|
|
||
|
def disableManipulation(self, allowSelectionOnly=False):
|
||
|
# Ignore events
|
||
|
for event in self.actionEvents:
|
||
|
self.ignore(event[0])
|
||
|
|
||
|
# [gjeon] to enable selection while other manipulation is disabled
|
||
|
if allowSelectionOnly:
|
||
|
self.fAllowSelectionOnly = allowSelectionOnly
|
||
|
self.accept('DIRECT-mouse1', self.manipulationStart)
|
||
|
self.accept('DIRECT-mouse1Up', self.manipulationStop)
|
||
|
|
||
|
self.removeManipulateObjectTask()
|
||
|
taskMgr.remove('manipulateObject')
|
||
|
taskMgr.remove('manip-move-wait')
|
||
|
taskMgr.remove('manip-watch-mouse')
|
||
|
taskMgr.remove('highlightWidgetTask')
|
||
|
|
||
|
def toggleObjectHandlesMode(self):
|
||
|
if self.fMovable:
|
||
|
self.fSetCoa = 1 - self.fSetCoa
|
||
|
|
||
|
if self.fSetCoa:
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.coaModeColor()
|
||
|
else:
|
||
|
self.objectHandles.coaModeColor()
|
||
|
else:
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.manipModeColor()
|
||
|
else:
|
||
|
self.objectHandles.manipModeColor()
|
||
|
else:
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.disabledModeColor()
|
||
|
else:
|
||
|
self.objectHandles.disabledModeColor()
|
||
|
|
||
|
def removeManipulateObjectTask(self):
|
||
|
taskMgr.remove('manipulateObject')
|
||
|
|
||
|
def enableWidgetMove(self):
|
||
|
self.fMovable = 1
|
||
|
if self.fSetCoa:
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.coaModeColor()
|
||
|
else:
|
||
|
self.objectHandles.coaModeColor()
|
||
|
else:
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.manipModeColor()
|
||
|
else:
|
||
|
self.objectHandles.manipModeColor()
|
||
|
|
||
|
def disableWidgetMove(self):
|
||
|
self.fMovable = 0
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.disabledModeColor()
|
||
|
else:
|
||
|
self.objectHandles.disabledModeColor()
|
||
|
|
||
|
#--------------------------------------------------------------------------
|
||
|
# Function: get edit types list for specified objects which indicate
|
||
|
# how editable the objects are
|
||
|
# Parameters: object, list of object to get edit types for
|
||
|
# Changes: none
|
||
|
# Returns: list of edit types
|
||
|
#--------------------------------------------------------------------------
|
||
|
def getEditTypes(self, objects):
|
||
|
# See if any of the selected in the don't manipulate tag list
|
||
|
editTypes = 0
|
||
|
for tag in self.unmovableTagList:
|
||
|
for selected in objects:
|
||
|
unmovableTag = selected.getTag(tag)
|
||
|
if (unmovableTag):
|
||
|
# check value of unmovableTag to see if it is
|
||
|
# completely uneditable or if it allows only certain
|
||
|
# types of editing
|
||
|
editTypes |= int(unmovableTag)
|
||
|
return editTypes
|
||
|
|
||
|
def manipulateObject(self):
|
||
|
# Only do this if something is selected
|
||
|
selectedList = base.direct.selected.getSelectedAsList()
|
||
|
# See if any of the selected are completely uneditable
|
||
|
editTypes = self.getEditTypes(selectedList)
|
||
|
if (editTypes & EDIT_TYPE_UNEDITABLE == EDIT_TYPE_UNEDITABLE):
|
||
|
return
|
||
|
self.currEditTypes = editTypes
|
||
|
if selectedList:
|
||
|
# Remove the task to keep the widget attached to the object
|
||
|
taskMgr.remove('followSelectedNodePath')
|
||
|
# and the task to highlight the widget
|
||
|
taskMgr.remove('highlightWidgetTask')
|
||
|
# Set manipulation flag
|
||
|
self.fManip = 1
|
||
|
# Record undo point
|
||
|
base.direct.pushUndo(base.direct.selected)
|
||
|
# Update object handles visibility
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.showGuides()
|
||
|
base.direct.widget.hideAllHandles()
|
||
|
base.direct.widget.showHandle(self.constraint)
|
||
|
else:
|
||
|
self.objectHandles.showGuides()
|
||
|
self.objectHandles.hideAllHandles()
|
||
|
self.objectHandles.showHandle(self.constraint)
|
||
|
if base.direct.clusterMode == 'client':
|
||
|
oh = 'base.direct.manipulationControl.objectHandles'
|
||
|
cluster(oh + '.showGuides()', 0)
|
||
|
cluster(oh + '.hideAllHandles()', 0)
|
||
|
cluster(oh + ('.showHandle("%s")'% self.constraint), 0)
|
||
|
# Record relationship between selected nodes and widget
|
||
|
base.direct.selected.getWrtAll()
|
||
|
# hide the bbox of the selected objects during interaction
|
||
|
base.direct.selected.dehighlightAll()
|
||
|
# Send event to signal start of manipulation
|
||
|
messenger.send('DIRECT_manipulateObjectStart')
|
||
|
# Manipulate the real object with the constraint
|
||
|
# The constraint is passed as the name of the node
|
||
|
self.spawnManipulateObjectTask()
|
||
|
|
||
|
def spawnManipulateObjectTask(self):
|
||
|
# reset hit-pt flag
|
||
|
self.fHitInit = 1
|
||
|
self.fScaleInit = 1
|
||
|
if not self.fScaling1D and\
|
||
|
not self.fScaling3D:
|
||
|
self.fScaleInit1 = 1
|
||
|
# record initial offset between widget and camera
|
||
|
t = Task.Task(self.manipulateObjectTask)
|
||
|
t.fMouseX = abs(base.direct.dr.mouseX) > 0.9
|
||
|
t.fMouseY = abs(base.direct.dr.mouseY) > 0.9
|
||
|
if t.fMouseX:
|
||
|
t.constrainedDir = 'y'
|
||
|
else:
|
||
|
t.constrainedDir = 'x'
|
||
|
# Compute widget's xy coords in screen space
|
||
|
t.coaCenter = getScreenXY(base.direct.widget)
|
||
|
# These are used to rotate about view vector
|
||
|
if t.fMouseX and t.fMouseY:
|
||
|
t.lastAngle = getCrankAngle(t.coaCenter)
|
||
|
taskMgr.add(t, 'manipulateObject')
|
||
|
|
||
|
def manipulateObjectTask(self, state):
|
||
|
if self.fScaling1D:
|
||
|
self.scale1D(state)
|
||
|
elif self.fScaling3D:
|
||
|
self.scale3D(state)
|
||
|
else:
|
||
|
# Widget takes precedence
|
||
|
if self.constraint:
|
||
|
type = self.constraint[2:]
|
||
|
if self.useSeparateScaleHandles:
|
||
|
if type == 'post' and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
|
||
|
self.xlate1D(state)
|
||
|
elif type == 'disc' and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
|
||
|
self.xlate2D(state)
|
||
|
elif type == 'ring' and not self.currEditTypes & EDIT_TYPE_UNROTATABLE:
|
||
|
self.rotate1D(state)
|
||
|
elif type == 'scale' and not self.currEditTypes & EDIT_TYPE_UNSCALABLE:
|
||
|
if base.direct.fShift:
|
||
|
self.fScaling3D = 1
|
||
|
self.scale3D(state)
|
||
|
else:
|
||
|
self.fScaling1D = 1
|
||
|
self.scale1D(state)
|
||
|
else:
|
||
|
if base.direct.fControl and not self.currEditTypes & EDIT_TYPE_UNSCALABLE:
|
||
|
if type == 'post':
|
||
|
# [gjeon] non-uniform scaling
|
||
|
self.fScaling1D = 1
|
||
|
self.scale1D(state)
|
||
|
else:
|
||
|
# [gjeon] uniform scaling
|
||
|
self.fScaling3D = 1
|
||
|
self.scale3D(state)
|
||
|
else:
|
||
|
if type == 'post' and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
|
||
|
self.xlate1D(state)
|
||
|
elif type == 'disc' and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
|
||
|
self.xlate2D(state)
|
||
|
elif type == 'ring' and not self.currEditTypes & EDIT_TYPE_UNROTATABLE:
|
||
|
self.rotate1D(state)
|
||
|
# No widget interaction, determine free manip mode
|
||
|
elif self.fFreeManip and not self.useSeparateScaleHandles:
|
||
|
# If we've been scaling and changed modes, reset object handles
|
||
|
if 0 and (self.fScaling1D or self.fScaling3D) and (not base.direct.fAlt):
|
||
|
if hasattr(base.direct, 'widget'):
|
||
|
base.direct.widget.transferObjectHandleScale()
|
||
|
else:
|
||
|
self.objectHandles.transferObjectHandlesScale()
|
||
|
|
||
|
self.fScaling1D = 0
|
||
|
self.fScaling3D = 0
|
||
|
# Alt key switches to a scaling mode
|
||
|
if base.direct.fControl and not self.currEditTypes & EDIT_TYPE_UNSCALABLE:
|
||
|
self.fScaling3D = 1
|
||
|
self.scale3D(state)
|
||
|
# Otherwise, manip mode depends on where you started
|
||
|
elif state.fMouseX and state.fMouseY and not self.currEditTypes & EDIT_TYPE_UNROTATABLE:
|
||
|
# In the corner, spin around camera's axis
|
||
|
self.rotateAboutViewVector(state)
|
||
|
elif state.fMouseX or state.fMouseY and not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
|
||
|
# Mouse started elsewhere in the outer frame, rotate
|
||
|
self.rotate2D(state)
|
||
|
elif not self.currEditTypes & EDIT_TYPE_UNMOVABLE:
|
||
|
# Mouse started in central region, xlate
|
||
|
# Mode depends on shift key
|
||
|
if base.direct.fShift or base.direct.fControl:
|
||
|
self.xlateCamXY(state)
|
||
|
else:
|
||
|
self.xlateCamXZ(state)
|
||
|
else:
|
||
|
return Task.done
|
||
|
if self.fSetCoa:
|
||
|
# Update coa based on current widget position
|
||
|
base.direct.selected.last.mCoa2Dnp.assign(
|
||
|
base.direct.widget.getMat(base.direct.selected.last))
|
||
|
else:
|
||
|
# Move the objects with the widget
|
||
|
base.direct.selected.moveWrtWidgetAll()
|
||
|
# Continue
|
||
|
return Task.cont
|
||
|
|
||
|
def addTag(self, tag):
|
||
|
if tag not in self.unmovableTagList:
|
||
|
self.unmovableTagList.append(tag)
|
||
|
|
||
|
def removeTag(self, tag):
|
||
|
self.unmovableTagList.remove(tag)
|
||
|
|
||
|
def gridSnapping(self, nodePath, offset):
|
||
|
offsetX = nodePath.getX() + offset.getX()
|
||
|
offsetY = nodePath.getY() + offset.getY()
|
||
|
offsetZ = nodePath.getZ() + offset.getZ()
|
||
|
|
||
|
if offsetX < 0.0:
|
||
|
signX = -1.0
|
||
|
else:
|
||
|
signX = 1.0
|
||
|
modX = math.fabs(offsetX) % base.direct.grid.gridSpacing
|
||
|
floorX = math.floor(math.fabs(offsetX) / base.direct.grid.gridSpacing)
|
||
|
if modX < base.direct.grid.gridSpacing / 2.0:
|
||
|
offsetX = signX * floorX * base.direct.grid.gridSpacing
|
||
|
else:
|
||
|
offsetX = signX * (floorX + 1) * base.direct.grid.gridSpacing
|
||
|
|
||
|
if offsetY < 0.0:
|
||
|
signY = -1.0
|
||
|
else:
|
||
|
signY = 1.0
|
||
|
modY = math.fabs(offsetY) % base.direct.grid.gridSpacing
|
||
|
floorY = math.floor(math.fabs(offsetY) / base.direct.grid.gridSpacing)
|
||
|
if modY < base.direct.grid.gridSpacing / 2.0:
|
||
|
offsetY = signY * floorY * base.direct.grid.gridSpacing
|
||
|
else:
|
||
|
offsetY = signY * (floorY + 1) * base.direct.grid.gridSpacing
|
||
|
|
||
|
if offsetZ < 0.0:
|
||
|
signZ = -1.0
|
||
|
else:
|
||
|
signZ = 1.0
|
||
|
modZ = math.fabs(offsetZ) % base.direct.grid.gridSpacing
|
||
|
floorZ = math.floor(math.fabs(offsetZ) / base.direct.grid.gridSpacing)
|
||
|
if modZ < base.direct.grid.gridSpacing / 2.0:
|
||
|
offsetZ = signZ * floorZ * base.direct.grid.gridSpacing
|
||
|
else:
|
||
|
offsetZ = signZ * (floorZ + 1) * base.direct.grid.gridSpacing
|
||
|
|
||
|
return Point3(offsetX, offsetY, offsetZ)
|
||
|
|
||
|
### WIDGET MANIPULATION METHODS ###
|
||
|
def xlate1D(self, state):
|
||
|
# Constrained 1D Translation along widget axis
|
||
|
# Compute nearest hit point along axis and try to keep
|
||
|
# that point as close to the current mouse position as possible
|
||
|
# what point on the axis is the mouse pointing at?
|
||
|
self.hitPt.assign(self.objectHandles.getAxisIntersectPt(
|
||
|
self.constraint[:1]))
|
||
|
# use it to see how far to move the widget
|
||
|
if self.fHitInit:
|
||
|
# First time through, just record that point
|
||
|
self.fHitInit = 0
|
||
|
self.prevHit.assign(self.hitPt)
|
||
|
else:
|
||
|
# Move widget to keep hit point as close to mouse as possible
|
||
|
offset = self.hitPt - self.prevHit
|
||
|
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
if self.fGridSnap:
|
||
|
widget.setPos(self.gridSnapping(widget, offset))
|
||
|
else:
|
||
|
widget.setPos(widget, offset)
|
||
|
#if base.direct.camera.getName() != 'persp':
|
||
|
#self.prevHit.assign(self.hitPt)
|
||
|
else:
|
||
|
if self.fGridSnap:
|
||
|
base.direct.widget.setPos(self.gridSnapping(base.direct.widget, offset))
|
||
|
else:
|
||
|
base.direct.widget.setPos(base.direct.widget, offset)
|
||
|
|
||
|
def xlate2D(self, state):
|
||
|
# Constrained 2D (planar) translation
|
||
|
# Compute point of intersection of ray from eyepoint through cursor
|
||
|
# to one of the three orthogonal planes on the widget.
|
||
|
# This point tracks all subsequent mouse movements
|
||
|
self.hitPt.assign(self.objectHandles.getWidgetIntersectPt(
|
||
|
base.direct.widget, self.constraint[:1]))
|
||
|
|
||
|
# use it to see how far to move the widget
|
||
|
if self.fHitInit:
|
||
|
# First time through just record hit point
|
||
|
self.fHitInit = 0
|
||
|
self.prevHit.assign(self.hitPt)
|
||
|
else:
|
||
|
offset = self.hitPt - self.prevHit
|
||
|
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
if self.fGridSnap:
|
||
|
widget.setPos(self.gridSnapping(widget, offset))
|
||
|
else:
|
||
|
widget.setPos(widget, offset)
|
||
|
if base.direct.camera.getName() != 'persp':
|
||
|
self.prevHit.assign(self.hitPt)
|
||
|
else:
|
||
|
if self.fGridSnap:
|
||
|
base.direct.widget.setPos(self.gridSnapping(base.direct.widget, offset))
|
||
|
else:
|
||
|
base.direct.widget.setPos(base.direct.widget, offset)
|
||
|
|
||
|
def rotate1D(self, state):
|
||
|
# Constrained 1D rotation about the widget's main axis (X, Y, or Z)
|
||
|
# Rotation depends upon circular motion of the mouse about the
|
||
|
# projection of the widget's origin on the image plane
|
||
|
# A complete circle about the widget results in a change in
|
||
|
# orientation of 360 degrees.
|
||
|
|
||
|
# First initialize hit point/rotation angle
|
||
|
if self.fHitInit:
|
||
|
self.fHitInit = 0
|
||
|
self.rotateAxis = self.constraint[:1]
|
||
|
self.fWidgetTop = self.widgetCheck('top?')
|
||
|
self.rotationCenter = getScreenXY(base.direct.widget)
|
||
|
self.lastCrankAngle = getCrankAngle(self.rotationCenter)
|
||
|
|
||
|
# Rotate widget based on how far cursor has swung around origin
|
||
|
newAngle = getCrankAngle(self.rotationCenter)
|
||
|
deltaAngle = self.lastCrankAngle - newAngle
|
||
|
if self.fWidgetTop:
|
||
|
deltaAngle = -1 * deltaAngle
|
||
|
if self.rotateAxis == 'x':
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
widget.setP(widget, deltaAngle)
|
||
|
else:
|
||
|
base.direct.widget.setP(base.direct.widget, deltaAngle)
|
||
|
elif self.rotateAxis == 'y':
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
widget.setR(widget, deltaAngle)
|
||
|
else:
|
||
|
base.direct.widget.setR(base.direct.widget, deltaAngle)
|
||
|
elif self.rotateAxis == 'z':
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
widget.setH(widget, deltaAngle)
|
||
|
else:
|
||
|
base.direct.widget.setH(base.direct.widget, deltaAngle)
|
||
|
# Record crank angle for next time around
|
||
|
self.lastCrankAngle = newAngle
|
||
|
|
||
|
def widgetCheck(self, type):
|
||
|
# Utility to see if we are looking at the top or bottom of
|
||
|
# a 2D planar widget or if we are looking at a 2D planar widget
|
||
|
# edge on
|
||
|
# Based upon angle between view vector from eye through the
|
||
|
# widget's origin and one of the three principle axes
|
||
|
axis = self.constraint[:1]
|
||
|
# First compute vector from eye through widget origin
|
||
|
mWidget2Cam = base.direct.widget.getMat(base.direct.camera)
|
||
|
# And determine where the viewpoint is relative to widget
|
||
|
pos = VBase3(0)
|
||
|
decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos,
|
||
|
CSDefault)
|
||
|
widgetDir = Vec3(pos)
|
||
|
widgetDir.normalize()
|
||
|
# Convert specified widget axis to view space
|
||
|
if axis == 'x':
|
||
|
widgetAxis = Vec3(mWidget2Cam.xformVec(X_AXIS))
|
||
|
elif axis == 'y':
|
||
|
widgetAxis = Vec3(mWidget2Cam.xformVec(Y_AXIS))
|
||
|
elif axis == 'z':
|
||
|
widgetAxis = Vec3(mWidget2Cam.xformVec(Z_AXIS))
|
||
|
widgetAxis.normalize()
|
||
|
if type == 'top?':
|
||
|
# Check sign of angle between two vectors
|
||
|
return (widgetDir.dot(widgetAxis) < 0.)
|
||
|
elif type == 'edge?':
|
||
|
# Checking to see if we are viewing edge-on
|
||
|
# Check angle between two vectors
|
||
|
return(abs(widgetDir.dot(widgetAxis)) < .2)
|
||
|
|
||
|
### FREE MANIPULATION METHODS ###
|
||
|
def xlateCamXZ(self, state):
|
||
|
"""Constrained 2D motion parallel to the camera's image plane
|
||
|
This moves the object in the camera's XZ plane"""
|
||
|
# reset fHitInit in case we later switch to manip mode
|
||
|
self.fHitInit = 1
|
||
|
# Reset scaling init flag
|
||
|
self.fScaleInit = 1
|
||
|
# Where is the widget relative to current camera view
|
||
|
vWidget2Camera = base.direct.widget.getPos(base.direct.camera)
|
||
|
x = vWidget2Camera[0]
|
||
|
y = vWidget2Camera[1]
|
||
|
z = vWidget2Camera[2]
|
||
|
# Move widget (and objects) based upon mouse motion
|
||
|
# Scaled up accordingly based upon widget distance
|
||
|
dr = base.direct.dr
|
||
|
|
||
|
base.direct.widget.setX(
|
||
|
base.direct.camera,
|
||
|
x + 0.5 * dr.mouseDeltaX * dr.nearWidth * (y/dr.near))
|
||
|
base.direct.widget.setZ(
|
||
|
base.direct.camera,
|
||
|
z + 0.5 * dr.mouseDeltaY * dr.nearHeight * (y/dr.near))
|
||
|
|
||
|
def xlateCamXY(self, state):
|
||
|
"""Constrained 2D motion perpendicular to camera's image plane
|
||
|
This moves the object in the camera's XY plane if shift is held
|
||
|
Moves object toward camera if control is held
|
||
|
"""
|
||
|
# Reset scaling init flag
|
||
|
self.fScaleInit = 1
|
||
|
# Now, where is the widget relative to current camera view
|
||
|
vWidget2Camera = base.direct.widget.getPos(base.direct.camera)
|
||
|
# If this is first time around, record initial y distance
|
||
|
if self.fHitInit:
|
||
|
self.fHitInit = 0
|
||
|
# Use distance to widget to scale motion along Y
|
||
|
self.xlateSF = Vec3(vWidget2Camera).length()
|
||
|
# Get widget's current xy coords in screen space
|
||
|
coaCenter = getNearProjectionPoint(base.direct.widget)
|
||
|
self.deltaNearX = coaCenter[0] - base.direct.dr.nearVec[0]
|
||
|
# Which way do we move the object?
|
||
|
if base.direct.fControl:
|
||
|
moveDir = Vec3(vWidget2Camera)
|
||
|
# If widget is behind camera invert vector
|
||
|
if moveDir[1] < 0.0:
|
||
|
moveDir.assign(moveDir * -1)
|
||
|
moveDir.normalize()
|
||
|
else:
|
||
|
moveDir = Vec3(Y_AXIS)
|
||
|
# Move selected objects
|
||
|
dr = base.direct.dr
|
||
|
# Scale move dir
|
||
|
moveDir.assign(moveDir * (2.0 * dr.mouseDeltaY * self.xlateSF))
|
||
|
# Add it to current widget offset
|
||
|
vWidget2Camera += moveDir
|
||
|
# The object, however, stays at the same relative point to mouse in X
|
||
|
vWidget2Camera.setX((dr.nearVec[0] + self.deltaNearX) *
|
||
|
(vWidget2Camera[1]/dr.near))
|
||
|
|
||
|
# Move widget
|
||
|
base.direct.widget.setPos(base.direct.camera, vWidget2Camera)
|
||
|
|
||
|
def rotate2D(self, state):
|
||
|
""" Virtual trackball rotation of widget """
|
||
|
# Reset init flag in case we switch to another mode
|
||
|
self.fHitInit = 1
|
||
|
# Reset scaling init flag
|
||
|
self.fScaleInit = 1
|
||
|
tumbleRate = 360
|
||
|
# If moving outside of center, ignore motion perpendicular to edge
|
||
|
if ((state.constrainedDir == 'y') and (abs(base.direct.dr.mouseX) > 0.9)):
|
||
|
deltaX = 0
|
||
|
deltaY = base.direct.dr.mouseDeltaY
|
||
|
elif ((state.constrainedDir == 'x') and (abs(base.direct.dr.mouseY) > 0.9)):
|
||
|
deltaX = base.direct.dr.mouseDeltaX
|
||
|
deltaY = 0
|
||
|
else:
|
||
|
deltaX = base.direct.dr.mouseDeltaX
|
||
|
deltaY = base.direct.dr.mouseDeltaY
|
||
|
# Mouse motion edge to edge of display region results in one full turn
|
||
|
relHpr(base.direct.widget, base.direct.camera, deltaX * tumbleRate,
|
||
|
-deltaY * tumbleRate, 0)
|
||
|
|
||
|
def rotateAboutViewVector(self, state):
|
||
|
# Reset init flag in case we switch to another mode
|
||
|
self.fHitInit = 1
|
||
|
# Reset scaling init flag
|
||
|
self.fScaleInit = 1
|
||
|
# Compute current angle
|
||
|
angle = getCrankAngle(state.coaCenter)
|
||
|
deltaAngle = angle - state.lastAngle
|
||
|
state.lastAngle = angle
|
||
|
# Mouse motion edge to edge of display region results in one full turn
|
||
|
relHpr(base.direct.widget, base.direct.camera, 0, 0, -deltaAngle)
|
||
|
|
||
|
def scale1D(self, state):
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
self.hitPtScale.assign(self.objectHandles.getAxisIntersectPt(self.constraint[:1]))
|
||
|
self.hitPtScale = self.objectHandles.getMat().xformVec(self.hitPtScale)
|
||
|
if self.fScaleInit1:
|
||
|
# First time through just record hit point
|
||
|
self.fScaleInit1 = 0
|
||
|
self.prevHitScale.assign(self.hitPtScale)
|
||
|
self.origScale = base.direct.widget.getScale()
|
||
|
else:
|
||
|
widgetPos = base.direct.widget.getPos()
|
||
|
d0 = (self.prevHitScale).length()
|
||
|
if d0 == 0: #make sure we don't divide by zero
|
||
|
d0 = 0.001
|
||
|
d1 = (self.hitPtScale).length()
|
||
|
if d1 == 0: #make sure we don't set scale to zero
|
||
|
d1 = 0.001
|
||
|
currScale = self.origScale
|
||
|
# Scale factor is ratio current mag with init mag
|
||
|
if self.constraint[:1] == 'x':
|
||
|
currScale = Vec3(currScale.getX() * d1/d0, currScale.getY(), currScale.getZ())
|
||
|
elif self.constraint[:1] == 'y':
|
||
|
currScale = Vec3(currScale.getX(), currScale.getY() * d1/d0, currScale.getZ())
|
||
|
elif self.constraint[:1] == 'z':
|
||
|
currScale = Vec3(currScale.getX(), currScale.getY(), currScale.getZ() * d1/d0)
|
||
|
base.direct.widget.setScale(currScale)
|
||
|
return
|
||
|
|
||
|
# [gjeon] Constrained 1D scale of the selected node based upon up down mouse motion
|
||
|
if self.fScaleInit:
|
||
|
self.fScaleInit = 0
|
||
|
self.initScaleMag = Vec3(self.objectHandles.getAxisIntersectPt(self.constraint[:1])).length()
|
||
|
# record initial scale
|
||
|
self.initScale = base.direct.widget.getScale()
|
||
|
# Reset fHitInitFlag
|
||
|
self.fHitInit = 1
|
||
|
|
||
|
# reset the scale of the scaling widget so the calls to
|
||
|
# getAxisIntersectPt calculate the correct distance
|
||
|
base.direct.widget.setScale(1,1,1)
|
||
|
|
||
|
# Scale factor is ratio current mag with init mag
|
||
|
if self.constraint[:1] == 'x':
|
||
|
currScale = Vec3(self.initScale.getX() *
|
||
|
self.objectHandles.getAxisIntersectPt('x').length() / self.initScaleMag,
|
||
|
self.initScale.getY(), self.initScale.getZ())
|
||
|
elif self.constraint[:1] == 'y':
|
||
|
currScale = Vec3(self.initScale.getX(),
|
||
|
self.initScale.getY() * self.objectHandles.getAxisIntersectPt('y').length() / self.initScaleMag,
|
||
|
self.initScale.getZ())
|
||
|
elif self.constraint[:1] == 'z':
|
||
|
currScale = Vec3(self.initScale.getX(), self.initScale.getY(),
|
||
|
self.initScale.getZ() * self.objectHandles.getAxisIntersectPt('z').length() / self.initScaleMag)
|
||
|
|
||
|
base.direct.widget.setScale(currScale)
|
||
|
|
||
|
def scale3D(self, state):
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
if self.useSeparateScaleHandles:
|
||
|
self.hitPtScale.assign(self.objectHandles.getAxisIntersectPt(self.constraint[:1]))
|
||
|
self.hitPtScale = self.objectHandles.getMat().xformVec(self.hitPtScale)
|
||
|
if self.fScaleInit1:
|
||
|
# First time through just record hit point
|
||
|
self.fScaleInit1 = 0
|
||
|
self.prevHitScale.assign(self.hitPtScale)
|
||
|
self.origScale = base.direct.widget.getScale()
|
||
|
else:
|
||
|
widgetPos = base.direct.widget.getPos()
|
||
|
d0 = (self.prevHitScale).length()
|
||
|
if d0 == 0: #make sure we don't divide by zero
|
||
|
d0 = 0.001
|
||
|
d1 = (self.hitPtScale).length()
|
||
|
if d1 == 0: #make sure we don't set scale to zero
|
||
|
d1 = 0.001
|
||
|
currScale = self.origScale
|
||
|
# Scale factor is ratio current mag with init mag
|
||
|
currScale = Vec3(currScale.getX() * d1/d0, currScale.getY() * d1/d0, currScale.getZ() * d1/d0)
|
||
|
base.direct.widget.setScale(currScale)
|
||
|
return
|
||
|
else:
|
||
|
self.hitPtScale.assign(self.objectHandles.getMouseIntersectPt())
|
||
|
|
||
|
if self.fScaleInit1:
|
||
|
# First time through just record hit point
|
||
|
self.fScaleInit1 = 0
|
||
|
self.prevHitScale.assign(self.hitPtScale)
|
||
|
self.origScale = base.direct.widget.getScale()
|
||
|
else:
|
||
|
widgetPos = base.direct.widget.getPos()
|
||
|
d0 = (self.prevHitScale - widgetPos).length()
|
||
|
if d0 == 0: #make sure we don't divide by zero
|
||
|
d0 = 0.001
|
||
|
d1 = (self.hitPtScale - widgetPos).length()
|
||
|
if d1 == 0:
|
||
|
d1 = 0.001 #make sure we don't set scale to zero
|
||
|
currScale = self.origScale
|
||
|
currScale = currScale * d1/d0
|
||
|
base.direct.widget.setScale(currScale)
|
||
|
return
|
||
|
# Scale the selected node based upon up down mouse motion
|
||
|
# Mouse motion from edge to edge results in a factor of 4 scaling
|
||
|
# From midpoint to edge doubles or halves objects scale
|
||
|
if self.fScaleInit:
|
||
|
self.fScaleInit = 0
|
||
|
self.manipRef.setPos(base.direct.widget, 0, 0, 0)
|
||
|
self.manipRef.setHpr(base.direct.camera, 0, 0, 0)
|
||
|
self.initScaleMag = Vec3(
|
||
|
self.objectHandles.getWidgetIntersectPt(
|
||
|
self.manipRef, 'y')).length()
|
||
|
# record initial scale
|
||
|
self.initScale = base.direct.widget.getScale()
|
||
|
# Reset fHitInitFlag
|
||
|
self.fHitInit = 1
|
||
|
# Begin
|
||
|
# Scale factor is ratio current mag with init mag
|
||
|
currScale = (
|
||
|
self.initScale *
|
||
|
(self.objectHandles.getWidgetIntersectPt(
|
||
|
self.manipRef, 'y').length() /
|
||
|
self.initScaleMag)
|
||
|
)
|
||
|
base.direct.widget.setScale(currScale)
|
||
|
|
||
|
## Utility functions ##
|
||
|
def plantSelectedNodePath(self):
|
||
|
""" Move selected object to intersection point of cursor on scene """
|
||
|
# Check for intersection
|
||
|
entry = base.direct.iRay.pickGeom(
|
||
|
skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA)
|
||
|
# MRM: Need to handle moving COA
|
||
|
if (entry != None) and (base.direct.selected.last != None):
|
||
|
# Record undo point
|
||
|
base.direct.pushUndo(base.direct.selected)
|
||
|
# Record wrt matrix
|
||
|
base.direct.selected.getWrtAll()
|
||
|
# Move selected
|
||
|
base.direct.widget.setPos(
|
||
|
base.direct.camera, entry.getSurfacePoint(entry.getFromNodePath()))
|
||
|
# Move all the selected objects with widget
|
||
|
# Move the objects with the widget
|
||
|
base.direct.selected.moveWrtWidgetAll()
|
||
|
# Let everyone know that something was moved
|
||
|
messenger.send('DIRECT_manipulateObjectCleanup',
|
||
|
[base.direct.selected.getSelectedAsList()])
|
||
|
|
||
|
class ObjectHandles(NodePath, DirectObject):
|
||
|
def __init__(self, name='objectHandles'):
|
||
|
# Initialize the superclass
|
||
|
NodePath.__init__(self)
|
||
|
|
||
|
# Load up object handles model and assign it to self
|
||
|
self.assign(loader.loadModel('models/misc/objectHandles'))
|
||
|
self.setName(name)
|
||
|
self.scalingNode = NodePath(self)
|
||
|
self.scalingNode.setName('ohScalingNode')
|
||
|
self.ohScalingFactor = 1.0
|
||
|
self.directScalingFactor = 1.0
|
||
|
# To avoid recreating a vec every frame
|
||
|
self.hitPt = Vec3(0)
|
||
|
# Get a handle on the components
|
||
|
self.xHandles = self.find('**/X')
|
||
|
self.xPostGroup = self.xHandles.find('**/x-post-group')
|
||
|
self.xPostCollision = self.xHandles.find('**/x-post')
|
||
|
self.xRingGroup = self.xHandles.find('**/x-ring-group')
|
||
|
self.xRingCollision = self.xHandles.find('**/x-ring')
|
||
|
self.xDiscGroup = self.xHandles.find('**/x-disc-group')
|
||
|
self.xDisc = self.xHandles.find('**/x-disc-visible')
|
||
|
self.xDiscCollision = self.xHandles.find('**/x-disc')
|
||
|
self.xScaleGroup = deepcopy(self.xPostGroup)
|
||
|
self.xScaleGroup.setName('x-scale-group')
|
||
|
self.xScaleCollision = self.xScaleGroup.find('**/x-post')
|
||
|
self.xScaleCollision.setName('x-scale')
|
||
|
|
||
|
self.yHandles = self.find('**/Y')
|
||
|
self.yPostGroup = self.yHandles.find('**/y-post-group')
|
||
|
self.yPostCollision = self.yHandles.find('**/y-post')
|
||
|
self.yRingGroup = self.yHandles.find('**/y-ring-group')
|
||
|
self.yRingCollision = self.yHandles.find('**/y-ring')
|
||
|
self.yDiscGroup = self.yHandles.find('**/y-disc-group')
|
||
|
self.yDisc = self.yHandles.find('**/y-disc-visible')
|
||
|
self.yDiscCollision = self.yHandles.find('**/y-disc')
|
||
|
self.yScaleGroup = deepcopy(self.yPostGroup)
|
||
|
self.yScaleGroup.setName('y-scale-group')
|
||
|
self.yScaleCollision = self.yScaleGroup.find('**/y-post')
|
||
|
self.yScaleCollision.setName('y-scale')
|
||
|
|
||
|
self.zHandles = self.find('**/Z')
|
||
|
self.zPostGroup = self.zHandles.find('**/z-post-group')
|
||
|
self.zPostCollision = self.zHandles.find('**/z-post')
|
||
|
self.zRingGroup = self.zHandles.find('**/z-ring-group')
|
||
|
self.zRingCollision = self.zHandles.find('**/z-ring')
|
||
|
self.zDiscGroup = self.zHandles.find('**/z-disc-group')
|
||
|
self.zDisc = self.zHandles.find('**/z-disc-visible')
|
||
|
self.zDiscCollision = self.zHandles.find('**/z-disc')
|
||
|
self.zScaleGroup = deepcopy(self.zPostGroup)
|
||
|
self.zScaleGroup.setName('z-scale-group')
|
||
|
self.zScaleCollision = self.zScaleGroup.find('**/z-post')
|
||
|
self.zScaleCollision.setName('z-scale')
|
||
|
|
||
|
# Adjust visiblity, colors, and transparency
|
||
|
self.xPostCollision.hide()
|
||
|
self.xRingCollision.hide()
|
||
|
self.xScaleCollision.hide()
|
||
|
self.xDisc.setColor(1, 0, 0, .2)
|
||
|
self.yPostCollision.hide()
|
||
|
self.yRingCollision.hide()
|
||
|
self.yScaleCollision.hide()
|
||
|
self.yDisc.setColor(0, 1, 0, .2)
|
||
|
self.zPostCollision.hide()
|
||
|
self.zRingCollision.hide()
|
||
|
self.zScaleCollision.hide()
|
||
|
self.zDisc.setColor(0, 0, 1, .2)
|
||
|
# Augment geometry with lines
|
||
|
self.createObjectHandleLines()
|
||
|
# Create long markers to help line up in world
|
||
|
self.createGuideLines()
|
||
|
self.hideGuides()
|
||
|
|
||
|
# tag with name so they can skipped during iRay selection
|
||
|
self.xPostCollision.setTag('WidgetName',name)
|
||
|
self.yPostCollision.setTag('WidgetName',name)
|
||
|
self.zPostCollision.setTag('WidgetName',name)
|
||
|
|
||
|
self.xRingCollision.setTag('WidgetName',name)
|
||
|
self.yRingCollision.setTag('WidgetName',name)
|
||
|
self.zRingCollision.setTag('WidgetName',name)
|
||
|
|
||
|
self.xDiscCollision.setTag('WidgetName',name)
|
||
|
self.yDiscCollision.setTag('WidgetName',name)
|
||
|
self.zDiscCollision.setTag('WidgetName',name)
|
||
|
|
||
|
self.xScaleCollision.setTag('WidgetName',name)
|
||
|
self.yScaleCollision.setTag('WidgetName',name)
|
||
|
self.zScaleCollision.setTag('WidgetName',name)
|
||
|
|
||
|
# name disc geoms so they can be added to unpickables
|
||
|
self.xDisc.find("**/+GeomNode").setName('x-disc-geom')
|
||
|
self.yDisc.find("**/+GeomNode").setName('y-disc-geom')
|
||
|
self.zDisc.find("**/+GeomNode").setName('z-disc-geom')
|
||
|
|
||
|
#turn scale off by default
|
||
|
self.disableHandles('scale')
|
||
|
|
||
|
# Start with widget handles hidden
|
||
|
self.fActive = 1
|
||
|
self.toggleWidget()
|
||
|
|
||
|
# Make sure object handles are never lit or drawn in wireframe
|
||
|
useDirectRenderStyle(self)
|
||
|
|
||
|
def coaModeColor(self):
|
||
|
self.setColor(.5, .5, .5, 0.5, 1)
|
||
|
|
||
|
def disabledModeColor(self):
|
||
|
self.setColor(0.1,0.1,0.1,0.1,1)
|
||
|
|
||
|
def manipModeColor(self):
|
||
|
self.clearColor()
|
||
|
|
||
|
def toggleWidget(self):
|
||
|
if self.fActive:
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
widget.deactivate()
|
||
|
else:
|
||
|
self.deactivate()
|
||
|
else:
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView:
|
||
|
for widget in base.direct.manipulationControl.widgetList:
|
||
|
widget.activate()
|
||
|
widget.showWidgetIfActive()
|
||
|
else:
|
||
|
self.activate()
|
||
|
|
||
|
def activate(self):
|
||
|
self.scalingNode.reparentTo(self)
|
||
|
self.fActive = 1
|
||
|
|
||
|
def deactivate(self):
|
||
|
self.scalingNode.reparentTo(hidden)
|
||
|
self.fActive = 0
|
||
|
|
||
|
def showWidgetIfActive(self):
|
||
|
if self.fActive:
|
||
|
self.reparentTo(base.direct.group)
|
||
|
|
||
|
def showWidget(self):
|
||
|
self.reparentTo(base.direct.group)
|
||
|
|
||
|
def hideWidget(self):
|
||
|
self.reparentTo(hidden)
|
||
|
|
||
|
def enableHandles(self, handles):
|
||
|
if type(handles) == types.ListType:
|
||
|
for handle in handles:
|
||
|
self.enableHandle(handle)
|
||
|
elif handles == 'x':
|
||
|
self.enableHandles(['x-post','x-ring','x-disc', 'x-scale'])
|
||
|
elif handles == 'y':
|
||
|
self.enableHandles(['y-post','y-ring','y-disc', 'y-scale'])
|
||
|
elif handles == 'z':
|
||
|
self.enableHandles(['z-post','z-ring','z-disc', 'z-scale'])
|
||
|
elif handles == 'post':
|
||
|
self.enableHandles(['x-post','y-post','z-post'])
|
||
|
elif handles == 'ring':
|
||
|
self.enableHandles(['x-ring','y-ring','z-ring'])
|
||
|
elif handles == 'disc':
|
||
|
self.enableHandles(['x-disc','y-disc','z-disc'])
|
||
|
elif handles == 'scale':
|
||
|
self.enableHandles(['x-scale','y-scale','z-scale'])
|
||
|
elif handles == 'all':
|
||
|
self.enableHandles(['x-post','x-ring','x-disc','x-scale',
|
||
|
'y-post','y-ring','y-disc','y-scale',
|
||
|
'z-post','z-ring','z-disc','z-scale'])
|
||
|
|
||
|
def enableHandle(self, handle):
|
||
|
if handle == 'x-post':
|
||
|
self.xPostGroup.reparentTo(self.xHandles)
|
||
|
elif handle == 'x-ring':
|
||
|
self.xRingGroup.reparentTo(self.xHandles)
|
||
|
elif handle == 'x-disc':
|
||
|
self.xDiscGroup.reparentTo(self.xHandles)
|
||
|
elif handle == 'x-scale' and base.direct.manipulationControl.useSeparateScaleHandles:
|
||
|
self.xScaleGroup.reparentTo(self.xHandles)
|
||
|
elif handle == 'y-post':
|
||
|
self.yPostGroup.reparentTo(self.yHandles)
|
||
|
elif handle == 'y-ring':
|
||
|
self.yRingGroup.reparentTo(self.yHandles)
|
||
|
elif handle == 'y-disc':
|
||
|
self.yDiscGroup.reparentTo(self.yHandles)
|
||
|
elif handle == 'y-scale' and base.direct.manipulationControl.useSeparateScaleHandles:
|
||
|
self.yScaleGroup.reparentTo(self.yHandles)
|
||
|
elif handle == 'z-post':
|
||
|
self.zPostGroup.reparentTo(self.zHandles)
|
||
|
elif handle == 'z-ring':
|
||
|
self.zRingGroup.reparentTo(self.zHandles)
|
||
|
elif handle == 'z-disc':
|
||
|
self.zDiscGroup.reparentTo(self.zHandles)
|
||
|
elif handle == 'z-scale' and base.direct.manipulationControl.useSeparateScaleHandles:
|
||
|
self.zScaleGroup.reparentTo(self.zHandles)
|
||
|
|
||
|
def disableHandles(self, handles):
|
||
|
if type(handles) == types.ListType:
|
||
|
for handle in handles:
|
||
|
self.disableHandle(handle)
|
||
|
elif handles == 'x':
|
||
|
self.disableHandles(['x-post','x-ring','x-disc','x-scale'])
|
||
|
elif handles == 'y':
|
||
|
self.disableHandles(['y-post','y-ring','y-disc','y-scale'])
|
||
|
elif handles == 'z':
|
||
|
self.disableHandles(['z-post','z-ring','z-disc','z-scale'])
|
||
|
elif handles == 'post':
|
||
|
self.disableHandles(['x-post','y-post','z-post'])
|
||
|
elif handles == 'ring':
|
||
|
self.disableHandles(['x-ring','y-ring','z-ring'])
|
||
|
elif handles == 'disc':
|
||
|
self.disableHandles(['x-disc','y-disc','z-disc'])
|
||
|
elif handles == 'scale':
|
||
|
self.disableHandles(['x-scale','y-scale','z-scale'])
|
||
|
elif handles == 'all':
|
||
|
self.disableHandles(['x-post','x-ring','x-disc','x-scale',
|
||
|
'y-post','y-ring','y-disc','y-scale',
|
||
|
'z-post','z-ring','z-disc','z-scale'])
|
||
|
|
||
|
def disableHandle(self, handle):
|
||
|
if handle == 'x-post':
|
||
|
self.xPostGroup.reparentTo(hidden)
|
||
|
elif handle == 'x-ring':
|
||
|
self.xRingGroup.reparentTo(hidden)
|
||
|
elif handle == 'x-disc':
|
||
|
self.xDiscGroup.reparentTo(hidden)
|
||
|
elif handle == 'x-scale':
|
||
|
self.xScaleGroup.reparentTo(hidden)
|
||
|
if handle == 'y-post':
|
||
|
self.yPostGroup.reparentTo(hidden)
|
||
|
elif handle == 'y-ring':
|
||
|
self.yRingGroup.reparentTo(hidden)
|
||
|
elif handle == 'y-disc':
|
||
|
self.yDiscGroup.reparentTo(hidden)
|
||
|
elif handle == 'y-scale':
|
||
|
self.yScaleGroup.reparentTo(hidden)
|
||
|
if handle == 'z-post':
|
||
|
self.zPostGroup.reparentTo(hidden)
|
||
|
elif handle == 'z-ring':
|
||
|
self.zRingGroup.reparentTo(hidden)
|
||
|
elif handle == 'z-disc':
|
||
|
self.zDiscGroup.reparentTo(hidden)
|
||
|
elif handle == 'z-scale':
|
||
|
self.zScaleGroup.reparentTo(hidden)
|
||
|
|
||
|
def showAllHandles(self):
|
||
|
self.xPost.show()
|
||
|
self.xRing.show()
|
||
|
self.xDisc.show()
|
||
|
self.xScale.show()
|
||
|
self.yPost.show()
|
||
|
self.yRing.show()
|
||
|
self.yDisc.show()
|
||
|
self.yScale.show()
|
||
|
self.zPost.show()
|
||
|
self.zRing.show()
|
||
|
self.zDisc.show()
|
||
|
self.zScale.show()
|
||
|
|
||
|
def hideAllHandles(self):
|
||
|
self.xPost.hide()
|
||
|
self.xRing.hide()
|
||
|
self.xDisc.hide()
|
||
|
self.xScale.hide()
|
||
|
self.yPost.hide()
|
||
|
self.yRing.hide()
|
||
|
self.yDisc.hide()
|
||
|
self.yScale.hide()
|
||
|
self.zPost.hide()
|
||
|
self.zRing.hide()
|
||
|
self.zDisc.hide()
|
||
|
self.zScale.hide()
|
||
|
|
||
|
def showHandle(self, handle):
|
||
|
if handle == 'x-post':
|
||
|
self.xPost.show()
|
||
|
elif handle == 'x-ring':
|
||
|
self.xRing.show()
|
||
|
elif handle == 'x-disc':
|
||
|
self.xDisc.show()
|
||
|
elif handle == 'x-scale':
|
||
|
self.xScale.show()
|
||
|
elif handle == 'y-post':
|
||
|
self.yPost.show()
|
||
|
elif handle == 'y-ring':
|
||
|
self.yRing.show()
|
||
|
elif handle == 'y-disc':
|
||
|
self.yDisc.show()
|
||
|
elif handle == 'y-scale':
|
||
|
self.yScale.show()
|
||
|
elif handle == 'z-post':
|
||
|
self.zPost.show()
|
||
|
elif handle == 'z-ring':
|
||
|
self.zRing.show()
|
||
|
elif handle == 'z-disc':
|
||
|
self.zDisc.show()
|
||
|
elif handle == 'z-scale':
|
||
|
self.zScale.show()
|
||
|
|
||
|
def showGuides(self):
|
||
|
self.guideLines.show()
|
||
|
|
||
|
def hideGuides(self):
|
||
|
self.guideLines.hide()
|
||
|
|
||
|
def setDirectScalingFactor(self, factor):
|
||
|
self.directScalingFactor = factor
|
||
|
self.setScalingFactor(1)
|
||
|
|
||
|
def setScalingFactor(self, scaleFactor):
|
||
|
self.ohScalingFactor = self.ohScalingFactor * scaleFactor
|
||
|
self.scalingNode.setScale(self.ohScalingFactor * self.directScalingFactor)
|
||
|
|
||
|
def getScalingFactor(self):
|
||
|
return self.scalingNode.getScale()
|
||
|
|
||
|
def transferObjectHandlesScale(self):
|
||
|
# see how much object handles have been scaled
|
||
|
ohs = self.getScale()
|
||
|
sns = self.scalingNode.getScale()
|
||
|
# Transfer this to the scaling node
|
||
|
self.scalingNode.setScale(
|
||
|
ohs[0] * sns[0],
|
||
|
ohs[1] * sns[1],
|
||
|
ohs[2] * sns[2])
|
||
|
self.setScale(1)
|
||
|
|
||
|
def multiplyScalingFactorBy(self, factor):
|
||
|
self.ohScalingFactor = self.ohScalingFactor * factor
|
||
|
sf = self.ohScalingFactor * self.directScalingFactor
|
||
|
ival = self.scalingNode.scaleInterval(0.5, (sf, sf, sf),
|
||
|
blendType = 'easeInOut',
|
||
|
name = 'resizeObjectHandles')
|
||
|
ival.start()
|
||
|
|
||
|
def growToFit(self):
|
||
|
# Increase handles scale until they cover 30% of the min dimension
|
||
|
pos = base.direct.widget.getPos(base.direct.camera)
|
||
|
minDim = min(base.direct.dr.nearWidth, base.direct.dr.nearHeight)
|
||
|
sf = 0.15 * minDim * (pos[1]/base.direct.dr.near)
|
||
|
self.ohScalingFactor = sf
|
||
|
sf = sf * self.directScalingFactor
|
||
|
ival = self.scalingNode.scaleInterval(0.5, (sf, sf, sf),
|
||
|
blendType = 'easeInOut',
|
||
|
name = 'resizeObjectHandles')
|
||
|
ival.start()
|
||
|
|
||
|
def createObjectHandleLines(self):
|
||
|
# X post
|
||
|
self.xPost = self.xPostGroup.attachNewNode('x-post-visible')
|
||
|
lines = LineNodePath(self.xPost)
|
||
|
lines.setColor(VBase4(1, 0, 0, 1))
|
||
|
lines.setThickness(5)
|
||
|
#lines.moveTo(0, 0, 0)
|
||
|
#lines.drawTo(1.5, 0, 0)
|
||
|
lines.moveTo(1.5, 0, 0)
|
||
|
#lines.create()
|
||
|
#lines = LineNodePath(self.xPost)
|
||
|
#lines.setColor(VBase4(1, 0, 0, 1))
|
||
|
#lines.setThickness(1.5)
|
||
|
#lines.moveTo(0, 0, 0)
|
||
|
lines.drawTo(-1.5, 0, 0)
|
||
|
|
||
|
arrowInfo0 = 1.3
|
||
|
arrowInfo1 = 0.1
|
||
|
#lines.setThickness(5)
|
||
|
lines.moveTo(1.5, 0, 0)
|
||
|
lines.drawTo(arrowInfo0, arrowInfo1, arrowInfo1)
|
||
|
lines.moveTo(1.5, 0, 0)
|
||
|
lines.drawTo(arrowInfo0, arrowInfo1, -1 * arrowInfo1)
|
||
|
lines.moveTo(1.5, 0, 0)
|
||
|
lines.drawTo(arrowInfo0, -1 * arrowInfo1, arrowInfo1)
|
||
|
lines.moveTo(1.5, 0, 0)
|
||
|
lines.drawTo(arrowInfo0, -1 * arrowInfo1, -1 * arrowInfo1)
|
||
|
|
||
|
lines.create()
|
||
|
lines.setName('x-post-line')
|
||
|
|
||
|
#X scale
|
||
|
self.xScale = self.xScaleGroup.attachNewNode('x-scale-visible')
|
||
|
lines = LineNodePath(self.xScale)
|
||
|
lines.setColor(VBase4(1, 0, 0, 1))
|
||
|
lines.setThickness(5)
|
||
|
lines.moveTo(1.3, 0, 0)
|
||
|
lines.drawTo(-1.5, 0, 0)
|
||
|
|
||
|
drawBox(lines, (1.3, 0, 0), 0.2)
|
||
|
|
||
|
lines.create()
|
||
|
lines.setName('x-scale-line')
|
||
|
|
||
|
# X ring
|
||
|
self.xRing = self.xRingGroup.attachNewNode('x-ring-visible')
|
||
|
lines = LineNodePath(self.xRing)
|
||
|
lines.setColor(VBase4(1, 0, 0, 1))
|
||
|
lines.setThickness(3)
|
||
|
lines.moveTo(0, 1, 0)
|
||
|
for ang in range(15, 370, 15):
|
||
|
lines.drawTo(0,
|
||
|
math.cos(deg2Rad(ang)),
|
||
|
math.sin(deg2Rad(ang)))
|
||
|
lines.create()
|
||
|
lines.setName('x-ring-line')
|
||
|
|
||
|
# Y post
|
||
|
self.yPost = self.yPostGroup.attachNewNode('y-post-visible')
|
||
|
lines = LineNodePath(self.yPost)
|
||
|
lines.setColor(VBase4(0, 1, 0, 1))
|
||
|
lines.setThickness(5)
|
||
|
#lines.moveTo(0, 0, 0)
|
||
|
#lines.drawTo(0, 1.5, 0)
|
||
|
lines.moveTo(0, 1.5, 0)
|
||
|
#lines.create()
|
||
|
#lines = LineNodePath(self.yPost)
|
||
|
#lines.setColor(VBase4(0, 1, 0, 1))
|
||
|
#lines.setThickness(1.5)
|
||
|
#lines.moveTo(0, 0, 0)
|
||
|
lines.drawTo(0, -1.5, 0)
|
||
|
|
||
|
#lines.setThickness(5)
|
||
|
lines.moveTo(0, 1.5, 0)
|
||
|
lines.drawTo(arrowInfo1, arrowInfo0, arrowInfo1)
|
||
|
lines.moveTo(0, 1.5, 0)
|
||
|
lines.drawTo(arrowInfo1, arrowInfo0, -1 * arrowInfo1)
|
||
|
lines.moveTo(0, 1.5, 0)
|
||
|
lines.drawTo(-1 * arrowInfo1, arrowInfo0, arrowInfo1)
|
||
|
lines.moveTo(0, 1.5, 0)
|
||
|
lines.drawTo(-1 * arrowInfo1, arrowInfo0, -1 * arrowInfo1)
|
||
|
lines.create()
|
||
|
lines.setName('y-post-line')
|
||
|
|
||
|
#Y scale
|
||
|
self.yScale = self.yScaleGroup.attachNewNode('y-scale-visible')
|
||
|
lines = LineNodePath(self.yScale)
|
||
|
lines.setColor(VBase4(0, 1, 0, 1))
|
||
|
lines.setThickness(5)
|
||
|
lines.moveTo(0, 1.3, 0)
|
||
|
lines.drawTo(0, -1.5, 0)
|
||
|
|
||
|
drawBox(lines, (0, 1.4, 0), 0.2)
|
||
|
|
||
|
lines.create()
|
||
|
lines.setName('y-scale-line')
|
||
|
|
||
|
# Y ring
|
||
|
self.yRing = self.yRingGroup.attachNewNode('y-ring-visible')
|
||
|
lines = LineNodePath(self.yRing)
|
||
|
lines.setColor(VBase4(0, 1, 0, 1))
|
||
|
lines.setThickness(3)
|
||
|
lines.moveTo(1, 0, 0)
|
||
|
for ang in range(15, 370, 15):
|
||
|
lines.drawTo(math.cos(deg2Rad(ang)),
|
||
|
0,
|
||
|
math.sin(deg2Rad(ang)))
|
||
|
lines.create()
|
||
|
lines.setName('y-ring-line')
|
||
|
|
||
|
# Z post
|
||
|
self.zPost = self.zPostGroup.attachNewNode('z-post-visible')
|
||
|
lines = LineNodePath(self.zPost)
|
||
|
lines.setColor(VBase4(0, 0, 1, 1))
|
||
|
lines.setThickness(5)
|
||
|
#lines.moveTo(0, 0, 0)
|
||
|
#lines.drawTo(0, 0, 1.5)
|
||
|
lines.moveTo(0, 0, 1.5)
|
||
|
#lines.create()
|
||
|
#lines = LineNodePath(self.zPost)
|
||
|
#lines.setColor(VBase4(0, 0, 1, 1))
|
||
|
#lines.setThickness(1.5)
|
||
|
#lines.moveTo(0, 0, 0)
|
||
|
lines.drawTo(0, 0, -1.5)
|
||
|
|
||
|
#lines.setThickness(5)
|
||
|
lines.moveTo(0, 0, 1.5)
|
||
|
lines.drawTo(arrowInfo1, arrowInfo1, arrowInfo0)
|
||
|
lines.moveTo(0, 0, 1.5)
|
||
|
lines.drawTo(arrowInfo1, -1 * arrowInfo1, arrowInfo0)
|
||
|
lines.moveTo(0, 0, 1.5)
|
||
|
lines.drawTo(-1 * arrowInfo1, arrowInfo1, arrowInfo0)
|
||
|
lines.moveTo(0, 0, 1.5)
|
||
|
lines.drawTo(-1 * arrowInfo1, -1 * arrowInfo1, arrowInfo0)
|
||
|
|
||
|
lines.create()
|
||
|
lines.setName('z-post-line')
|
||
|
|
||
|
#Z scale
|
||
|
self.zScale = self.zScaleGroup.attachNewNode('z-scale-visible')
|
||
|
lines = LineNodePath(self.zScale)
|
||
|
lines.setColor(VBase4(0, 0, 1, 1))
|
||
|
lines.setThickness(5)
|
||
|
lines.moveTo(0, 0, 1.3)
|
||
|
lines.drawTo(0, 0, -1.5)
|
||
|
|
||
|
drawBox(lines, (0, 0, 1.4), 0.2)
|
||
|
|
||
|
lines.create()
|
||
|
lines.setName('y-scale-line')
|
||
|
|
||
|
# Z ring
|
||
|
self.zRing = self.zRingGroup.attachNewNode('z-ring-visible')
|
||
|
lines = LineNodePath(self.zRing)
|
||
|
lines.setColor(VBase4(0, 0, 1, 1))
|
||
|
lines.setThickness(3)
|
||
|
lines.moveTo(1, 0, 0)
|
||
|
for ang in range(15, 370, 15):
|
||
|
lines.drawTo(math.cos(deg2Rad(ang)),
|
||
|
math.sin(deg2Rad(ang)),
|
||
|
0)
|
||
|
lines.create()
|
||
|
lines.setName('z-ring-line')
|
||
|
|
||
|
def createGuideLines(self):
|
||
|
self.guideLines = self.attachNewNode('guideLines')
|
||
|
# X guide lines
|
||
|
lines = LineNodePath(self.guideLines)
|
||
|
lines.setColor(VBase4(1, 0, 0, 1))
|
||
|
lines.setThickness(0.5)
|
||
|
lines.moveTo(-500, 0, 0)
|
||
|
lines.drawTo(500, 0, 0)
|
||
|
lines.create()
|
||
|
lines.setName('x-guide')
|
||
|
|
||
|
# Y guide lines
|
||
|
lines = LineNodePath(self.guideLines)
|
||
|
lines.setColor(VBase4(0, 1, 0, 1))
|
||
|
lines.setThickness(0.5)
|
||
|
lines.moveTo(0, -500, 0)
|
||
|
lines.drawTo(0, 500, 0)
|
||
|
lines.create()
|
||
|
lines.setName('y-guide')
|
||
|
|
||
|
# Z guide lines
|
||
|
lines = LineNodePath(self.guideLines)
|
||
|
lines.setColor(VBase4(0, 0, 1, 1))
|
||
|
lines.setThickness(0.5)
|
||
|
lines.moveTo(0, 0, -500)
|
||
|
lines.drawTo(0, 0, 500)
|
||
|
lines.create()
|
||
|
lines.setName('z-guide')
|
||
|
|
||
|
def getAxisIntersectPt(self, axis):
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView and\
|
||
|
base.direct.camera.getName() != 'persp':
|
||
|
# create ray from the camera to detect 3d position
|
||
|
iRay = SelectionRay(base.direct.camera)
|
||
|
iRay.collider.setFromLens(base.direct.camNode, base.direct.dr.mouseX, base.direct.dr.mouseY)
|
||
|
#iRay.collideWithBitMask(1)
|
||
|
iRay.collideWithBitMask(BitMask32.bit(21))
|
||
|
iRay.ct.traverse(base.direct.grid)
|
||
|
|
||
|
if iRay.getNumEntries() == 0:
|
||
|
del iRay
|
||
|
return self.hitPt
|
||
|
|
||
|
entry = iRay.getEntry(0)
|
||
|
self.hitPt = entry.getSurfacePoint(self)
|
||
|
del iRay
|
||
|
if axis == 'x':
|
||
|
# We really only care about the nearest point on the axis
|
||
|
self.hitPt.setY(0)
|
||
|
self.hitPt.setZ(0)
|
||
|
elif axis == 'y':
|
||
|
self.hitPt.setX(0)
|
||
|
self.hitPt.setZ(0)
|
||
|
elif axis == 'z':
|
||
|
self.hitPt.setX(0)
|
||
|
self.hitPt.setY(0)
|
||
|
return self.hitPt
|
||
|
|
||
|
# Calc the xfrom from camera to widget
|
||
|
mCam2Widget = base.direct.camera.getMat(base.direct.widget)
|
||
|
lineDir = Vec3(mCam2Widget.xformVec(base.direct.dr.nearVec))
|
||
|
lineDir.normalize()
|
||
|
# And determine where the viewpoint is relative to widget
|
||
|
lineOrigin = VBase3(0)
|
||
|
decomposeMatrix(mCam2Widget, VBase3(0), VBase3(0), lineOrigin,
|
||
|
CSDefault)
|
||
|
# Now see where this hits the plane containing the 1D motion axis.
|
||
|
# Pick the intersection plane most normal to the intersection ray
|
||
|
# by comparing lineDir with plane normals. The plane with the
|
||
|
# largest dotProduct is most "normal"
|
||
|
if axis == 'x':
|
||
|
if (abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS))):
|
||
|
self.hitPt.assign(
|
||
|
planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
|
||
|
else:
|
||
|
self.hitPt.assign(
|
||
|
planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
|
||
|
# We really only care about the nearest point on the axis
|
||
|
self.hitPt.setY(0)
|
||
|
self.hitPt.setZ(0)
|
||
|
elif axis == 'y':
|
||
|
if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS))):
|
||
|
self.hitPt.assign(
|
||
|
planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
|
||
|
else:
|
||
|
self.hitPt.assign(
|
||
|
planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
|
||
|
# We really only care about the nearest point on the axis
|
||
|
self.hitPt.setX(0)
|
||
|
self.hitPt.setZ(0)
|
||
|
elif axis == 'z':
|
||
|
if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS))):
|
||
|
self.hitPt.assign(
|
||
|
planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
|
||
|
else:
|
||
|
self.hitPt.assign(
|
||
|
planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
|
||
|
# We really only care about the nearest point on the axis
|
||
|
self.hitPt.setX(0)
|
||
|
self.hitPt.setY(0)
|
||
|
return self.hitPt
|
||
|
|
||
|
def getMouseIntersectPt(self):
|
||
|
# create ray from the camera to detect 3d position
|
||
|
iRay = SelectionRay(base.direct.camera)
|
||
|
iRay.collider.setFromLens(base.direct.camNode, base.direct.dr.mouseX, base.direct.dr.mouseY)
|
||
|
#iRay.collideWithBitMask(1)
|
||
|
iRay.collideWithBitMask(BitMask32.bit(21))
|
||
|
iRay.ct.traverse(base.direct.grid)
|
||
|
|
||
|
if iRay.getNumEntries() == 0:
|
||
|
del iRay
|
||
|
return Point3(0)
|
||
|
|
||
|
entry = iRay.getEntry(0)
|
||
|
hitPt = entry.getSurfacePoint(entry.getFromNodePath())
|
||
|
|
||
|
# create a temp nodePath to get the position
|
||
|
np = NodePath('temp')
|
||
|
np.setPos(base.direct.camera, hitPt)
|
||
|
resultPt = Point3(0)
|
||
|
resultPt.assign(np.getPos())
|
||
|
np.remove()
|
||
|
del iRay
|
||
|
return resultPt
|
||
|
|
||
|
def getWidgetIntersectPt(self, nodePath, plane):
|
||
|
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView and\
|
||
|
base.direct.camera.getName() != 'persp':
|
||
|
self.hitPt.assign(self.getMouseIntersectPt())
|
||
|
return self.hitPt
|
||
|
|
||
|
# Find out the point of interection of the ray passing though the mouse
|
||
|
# with the plane containing the 2D xlation or 1D rotation widgets
|
||
|
|
||
|
# Calc the xfrom from camera to the nodePath
|
||
|
mCam2NodePath = base.direct.camera.getMat(nodePath)
|
||
|
|
||
|
# And determine where the viewpoint is relative to widget
|
||
|
lineOrigin = VBase3(0)
|
||
|
decomposeMatrix(mCam2NodePath, VBase3(0), VBase3(0), lineOrigin,
|
||
|
CSDefault)
|
||
|
|
||
|
# Next we find the vector from viewpoint to the widget through
|
||
|
# the mouse's position on near plane.
|
||
|
# This defines the intersection ray
|
||
|
lineDir = Vec3(mCam2NodePath.xformVec(base.direct.dr.nearVec))
|
||
|
lineDir.normalize()
|
||
|
# Find the hit point
|
||
|
if plane == 'x':
|
||
|
self.hitPt.assign(planeIntersect(
|
||
|
lineOrigin, lineDir, ORIGIN, X_AXIS))
|
||
|
elif plane == 'y':
|
||
|
self.hitPt.assign(planeIntersect(
|
||
|
lineOrigin, lineDir, ORIGIN, Y_AXIS))
|
||
|
elif plane == 'z':
|
||
|
self.hitPt.assign(planeIntersect(
|
||
|
lineOrigin, lineDir, ORIGIN, Z_AXIS))
|
||
|
|
||
|
return self.hitPt
|
||
|
|
||
|
def drawBox(lines, center, sideLength):
|
||
|
|
||
|
l = sideLength * 0.5
|
||
|
lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
|
||
|
lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
|
||
|
lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
|
||
|
|
||
|
lines.moveTo(center[0] - l, center[1] + l, center[2] + l)
|
||
|
lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
|
||
|
lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
|
||
|
|
||
|
lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
|
||
|
|
||
|
lines.moveTo(center[0] + l, center[1] - l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
|
||
|
|
||
|
lines.moveTo(center[0] + l, center[1] + l, center[2] + l)
|
||
|
lines.drawTo(center[0] - l, center[1] + l, center[2] + l)
|
||
|
lines.drawTo(center[0] - l, center[1] - l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] - l, center[2] + l)
|
||
|
lines.drawTo(center[0] + l, center[1] + l, center[2] + l)
|
||
|
|
||
|
lines.moveTo(center[0] + l, center[1] + l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] + l, center[2] - l)
|
||
|
lines.drawTo(center[0] - l, center[1] - l, center[2] - l)
|
||
|
lines.drawTo(center[0] + l, center[1] - l, center[2] - l)
|
||
|
lines.drawTo(center[0] + l, center[1] + l, center[2] - l)
|