From b66a795efbe737fd1c72c92a17e4de984a14e0f7 Mon Sep 17 00:00:00 2001 From: Mike Christel Date: Fri, 20 Aug 2010 19:33:15 +0000 Subject: [PATCH] Summer 2010 CMU ETC PandaLE team: (1) fixed bug that made things move in wrong directions when manipulated from an orthographic viewport; (2) added capability to show the manipulation widget in world space rather than the local space of the selected object; (3) added an option to show a separate scale widget rather than using control key to scale --- direct/src/directtools/DirectManipulation.py | 317 ++++++++++++++----- 1 file changed, 244 insertions(+), 73 deletions(-) diff --git a/direct/src/directtools/DirectManipulation.py b/direct/src/directtools/DirectManipulation.py index 0da826321d..6563662fa8 100644 --- a/direct/src/directtools/DirectManipulation.py +++ b/direct/src/directtools/DirectManipulation.py @@ -5,6 +5,7 @@ from DirectGeometry import * from DirectSelection import SelectionRay from direct.task import Task import types +from copy import deepcopy class DirectManipulationControl(DirectObject): def __init__(self): @@ -33,6 +34,9 @@ class DirectManipulationControl(DirectObject): 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], @@ -152,6 +156,12 @@ class DirectManipulationControl(DirectObject): 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' @@ -384,9 +394,17 @@ class DirectManipulationControl(DirectObject): def followSelectedNodePathTask(self, state): if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView: for widget in base.direct.manipulationControl.widgetList: - widget.setPosHpr(state.base, state.pos, state.hpr) + if self.worldSpaceManip: + widget.setPos(state.base, state.pos) + widget.setHpr(render, VBase3(0)) + else: + widget.setPosHpr(state.base, state.pos, state.hpr) else: - base.direct.widget.setPosHpr(state.base, state.pos, state.hpr) + 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): @@ -549,24 +567,39 @@ class DirectManipulationControl(DirectObject): # Widget takes precedence if self.constraint: type = self.constraint[2:] - 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 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: + 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'): @@ -594,6 +627,8 @@ class DirectManipulationControl(DirectObject): 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( @@ -674,8 +709,8 @@ class DirectManipulationControl(DirectObject): widget.setPos(self.gridSnapping(widget, offset)) else: widget.setPos(widget, offset) - if base.direct.camera.getName() != 'persp': - self.prevHit.assign(self.hitPt) + #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)) @@ -881,36 +916,31 @@ class DirectManipulationControl(DirectObject): relHpr(base.direct.widget, base.direct.camera, 0, 0, -deltaAngle) def scale1D(self, state): - print self.constraint if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView: - self.hitPtScale.assign(self.objectHandles.getMouseIntersectPt()) - + 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 - widgetPos).length() - d1 = (self.hitPtScale - widgetPos).length() - offset = d1 - d0 - currScale = base.direct.widget.getScale() - + 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() + offset, currScale.getY(), currScale.getZ()) - if currScale.getX() < 0.0: - currScale.setX(0.01) + currScale = Vec3(currScale.getX() * d1/d0, currScale.getY(), currScale.getZ()) elif self.constraint[:1] == 'y': - currScale = Vec3(currScale.getX(), currScale.getY() + offset, currScale.getZ()) - if currScale.getY() < 0.0: - currScale.setY(0.01) + currScale = Vec3(currScale.getX(), currScale.getY() * d1/d0, currScale.getZ()) elif self.constraint[:1] == 'z': - currScale = Vec3(currScale.getX(), currScale.getY(), currScale.getZ() + offset) - if currScale.getZ() < 0.0: - currScale.setZ(0.01) + currScale = Vec3(currScale.getX(), currScale.getY(), currScale.getZ() * d1/d0) base.direct.widget.setScale(currScale) - self.prevHitScale.assign(self.hitPtScale) return # [gjeon] Constrained 1D scale of the selected node based upon up down mouse motion @@ -943,26 +973,47 @@ class DirectManipulationControl(DirectObject): def scale3D(self, state): if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView: - self.hitPtScale.assign(self.objectHandles.getMouseIntersectPt()) - - if self.fScaleInit1: - # First time through just record hit point - self.fScaleInit1 = 0 - self.prevHitScale.assign(self.hitPtScale) + 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: - widgetPos = base.direct.widget.getPos() - d0 = (self.prevHitScale - widgetPos).length() - d1 = (self.hitPtScale - widgetPos).length() - offset = d1 - d0 - currScale = base.direct.widget.getScale() - currScale += offset - if currScale.getX() < 0.0 and\ - currScale.getY() < 0.0 and\ - currScale.getZ() < 0.0: - currScale = VBase3(0.01, 0.01, 0.01) - base.direct.widget.setScale(currScale) - self.prevHitScale.assign(self.hitPtScale) - return + 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 @@ -1032,6 +1083,10 @@ class ObjectHandles(NodePath, DirectObject): 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') @@ -1041,6 +1096,10 @@ class ObjectHandles(NodePath, DirectObject): 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') @@ -1050,16 +1109,23 @@ class ObjectHandles(NodePath, DirectObject): 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() @@ -1079,12 +1145,19 @@ class ObjectHandles(NodePath, DirectObject): 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() @@ -1139,21 +1212,23 @@ class ObjectHandles(NodePath, DirectObject): for handle in handles: self.enableHandle(handle) elif handles == 'x': - self.enableHandles(['x-post','x-ring','x-disc']) + self.enableHandles(['x-post','x-ring','x-disc', 'x-scale']) elif handles == 'y': - self.enableHandles(['y-post','y-ring','y-disc']) + self.enableHandles(['y-post','y-ring','y-disc', 'y-scale']) elif handles == 'z': - self.enableHandles(['z-post','z-ring','z-disc']) + 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', - 'y-post','y-ring','y-disc', - 'z-post','z-ring','z-disc']) + 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': @@ -1162,39 +1237,47 @@ class ObjectHandles(NodePath, DirectObject): self.xRingGroup.reparentTo(self.xHandles) elif handle == 'x-disc': self.xDiscGroup.reparentTo(self.xHandles) - if handle == 'y-post': + 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) - if handle == 'z-post': + 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']) + self.disableHandles(['x-post','x-ring','x-disc','x-scale']) elif handles == 'y': - self.disableHandles(['y-post','y-ring','y-disc']) + self.disableHandles(['y-post','y-ring','y-disc','y-scale']) elif handles == 'z': - self.disableHandles(['z-post','z-ring','z-disc']) + 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', - 'y-post','y-ring','y-disc', - 'z-post','z-ring','z-disc']) + 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': @@ -1203,40 +1286,52 @@ class ObjectHandles(NodePath, DirectObject): 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': @@ -1245,18 +1340,24 @@ class ObjectHandles(NodePath, DirectObject): 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() @@ -1336,6 +1437,19 @@ class ObjectHandles(NodePath, DirectObject): 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') @@ -1377,6 +1491,19 @@ class ObjectHandles(NodePath, DirectObject): 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) @@ -1418,6 +1545,19 @@ class ObjectHandles(NodePath, DirectObject): 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) @@ -1475,13 +1615,7 @@ class ObjectHandles(NodePath, DirectObject): return self.hitPt 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) - self.hitPt.assign(np.getPos()) - np.remove() + self.hitPt = entry.getSurfacePoint(self) del iRay if axis == 'x': # We really only care about the nearest point on the axis @@ -1598,4 +1732,41 @@ class ObjectHandles(NodePath, DirectObject): 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)