diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index 663c57fe9..d68a981a7 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -959,6 +959,10 @@ class DraftToolBar: self.wipeLine() elif txt.endsWith("s"): self.togglesnap() + elif txt.endsWith("["): + self.toggleradius(1) + elif txt.endsWith("]"): + self.toggleradius(-1) elif txt.endsWith("c"): if self.closeButton.isVisible(): self.closeLine() @@ -1158,6 +1162,11 @@ class DraftToolBar: if hasattr(FreeCADGui,"Snapper"): FreeCADGui.Snapper.toggle() + def toggleradius(self,val): + if hasattr(FreeCADGui,"Snapper"): + par = Draft.getParam("snapRange") + Draft.setParam("snapRange",par+val) + FreeCADGui.Snapper.showradius() #--------------------------------------------------------------------------- # TaskView operations diff --git a/src/Mod/Draft/DraftSnap.py b/src/Mod/Draft/DraftSnap.py index 846cb522c..3e1673460 100644 --- a/src/Mod/Draft/DraftSnap.py +++ b/src/Mod/Draft/DraftSnap.py @@ -70,11 +70,12 @@ class Snapper: self.grid = None self.constrainLine = None self.trackLine = None + self.radiusTracker = None self.snapInfo = None self.lastSnappedObject = None self.active = True self.forceGridOff = False - self.trackers = [[],[],[],[]] # view, grid, snap, extline + self.trackers = [[],[],[],[],[]] # view, grid, snap, extline, radius self.polarAngles = [90,45] @@ -126,10 +127,13 @@ class Snapper: def cstr(point): "constrains if needed" if constrain: - return self.constrain(point,lastpoint) + fpt = self.constrain(point,lastpoint) else: self.unconstrain() - return point + fpt = point + if self.radiusTracker: + self.radiusTracker.update(fpt) + return fpt snaps = [] self.snapInfo = None @@ -150,6 +154,7 @@ class Snapper: self.grid = self.trackers[1][i] self.tracker = self.trackers[2][i] self.extLine = self.trackers[3][i] + self.radiusTracker = self.trackers[4][i] else: if Draft.getParam("grid"): self.grid = DraftTrackers.gridTracker() @@ -157,19 +162,23 @@ class Snapper: self.grid = None self.tracker = DraftTrackers.snapTracker() self.extLine = DraftTrackers.lineTracker(dotted=True) + self.radiusTracker = DraftTrackers.radiusTracker() self.trackers[0].append(v) self.trackers[1].append(self.grid) self.trackers[2].append(self.tracker) self.trackers[3].append(self.extLine) + self.trackers[4].append(self.radiusTracker) # getting current snap Radius - if not self.radius: - self.radius = self.getScreenDist(Draft.getParam("snapRange"),screenpos) + self.radius = self.getScreenDist(Draft.getParam("snapRange"),screenpos) + if self.radiusTracker: + self.radiusTracker.update(self.radius) + self.radiusTracker.off() # set the grid if self.grid and (not self.forceGridOff): self.grid.set() - + # activate snap oldActive = False if Draft.getParam("alwaysSnap"): @@ -195,7 +204,7 @@ class Snapper: eline = None point,eline = self.snapToPolar(point,lastpoint) point,eline = self.snapToExtensions(point,lastpoint,constrain,eline) - + if not self.snapInfo: # nothing has been snapped, check fro grid snap @@ -223,6 +232,9 @@ class Snapper: snaps = [self.snapToVertex(self.snapInfo)] else: + + # first stick to the snapped object + point = self.snapToVertex(self.snapInfo)[0] # active snapping comp = self.snapInfo['Component'] @@ -324,7 +336,12 @@ class Snapper: view = Draft.get3DView() pt = view.getPoint(x,y) if hasattr(FreeCAD,"DraftWorkingPlane"): - dv = view.getViewDirection() + if view.getCameraType() == "Perspective": + camera = view.getCameraNode() + p = camera.getField("position").getValue() + vd = pt.sub(Vector(p[0],p[1],p[2])) + else: + dv = view.getViewDirection() return FreeCAD.DraftWorkingPlane.projectPoint(pt,dv) else: return pt @@ -641,6 +658,8 @@ class Snapper: self.tracker.off() if self.extLine: self.extLine.off() + if self.radiusTracker: + self.radiusTracker.off() if self.grid: if not Draft.getParam("alwaysShowGrid"): self.grid.off() @@ -875,6 +894,13 @@ class Snapper: self.toolbarButtons[i].setEnabled(False) self.saveSnapModes() + def showradius(self): + "shows the snap radius indicator" + self.radius = self.getScreenDist(Draft.getParam("snapRange"),(400,300)) + if self.radiusTracker: + self.radiusTracker.update(self.radius) + self.radiusTracker.on() + def isEnabled(self,but): "returns true if the given button is turned on" for b in self.toolbarButtons: diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 1a55ee3cf..9f31ff1e8 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -143,22 +143,6 @@ def getPoint(target,args,mobile=False,sym=False,workingplane=True): cmod = hasMod(args,MODCONSTRAIN) point = FreeCADGui.Snapper.snap(args["Position"],lastpoint=last,active=amod,constrain=cmod) info = FreeCADGui.Snapper.snapInfo - # project onto working plane if needed - if (not plane.weak) and workingplane: - # working plane was explicitely selected - project onto it - viewDirection = view.getViewDirection() - if view.getCameraType() == "Perspective": - camera = view.getCameraNode() - p = camera.getField("position").getValue() - # view is from camera to point: - viewDirection = point.sub(Vector(p[0],p[1],p[2])) - # if we are not snapping to anything, project along view axis, - # otherwise perpendicularly - if view.getObjectInfo((args["Position"][0],args["Position"][1])): - pass - # point = plane.projectPoint(point) - else: - point = plane.projectPoint(point, viewDirection) ctrlPoint = Vector(point) mask = FreeCADGui.Snapper.affinity if target.node: @@ -169,8 +153,19 @@ def getPoint(target,args,mobile=False,sym=False,workingplane=True): else: ui.displayPoint(point, plane=plane, mask=mask) return point,ctrlPoint,info -def getSupport(args): +def getSupport(args=None): "returns the supporting object and sets the working plane" + if not args: + sel = FreeCADGui.Selection.getSelectionEx() + if len(sel) == 1: + sel = sel[0] + if sel.HasSubObjects: + if len(sel.SubElementNames) == 1: + if "Face" in sel.SubElementNames[0]: + plane.alignToFace(sel.SubObjects[0]) + return sel.Object + return None + snapped = Draft.get3DView().getObjectInfo((args["Position"][0],args["Position"][1])) if not snapped: return None obj = None @@ -204,11 +199,125 @@ def setMod(args,mod,state): elif mod == "alt": args["AltDown"] = state + + + +#--------------------------------------------------------------------------- +# Base Class +#--------------------------------------------------------------------------- + +class DraftTool: + "The base class of all Draft Tools" + + def __init__(self): + self.commitList = [] + + def IsActive(self): + if FreeCADGui.ActiveDocument: + return True + else: + return False + + def Activated(self,name="None"): + if FreeCAD.activeDraftCommand: + FreeCAD.activeDraftCommand.finish() + + global Part, DraftGeomUtils + import Part, DraftGeomUtils + + self.ui = None + self.call = None + self.support = None + self.commitList = [] + self.doc = FreeCAD.ActiveDocument + if not self.doc: + self.finish() + return + + FreeCAD.activeDraftCommand = self + self.view = Draft.get3DView() + self.ui = FreeCADGui.draftToolBar + self.ui.sourceCmd = self + self.ui.setTitle(name) + self.ui.show() + rot = self.view.getCameraNode().getField("orientation").getValue() + upv = Vector(rot.multVec(coin.SbVec3f(0,1,0)).getValue()) + plane.setup(DraftVecUtils.neg(self.view.getViewDirection()), Vector(0,0,0), upv) + self.node = [] + self.pos = [] + self.constrain = None + self.obj = None + self.extendedCopy = False + self.ui.setTitle(name) + self.featureName = name + #self.snap = snapTracker() + #self.extsnap = lineTracker(dotted=True) + self.planetrack = None + if Draft.getParam("showPlaneTracker"): + self.planetrack = PlaneTracker() + + def finish(self): + self.node = [] + #self.snap.finalize() + #self.extsnap.finalize() + FreeCAD.activeDraftCommand = None + if self.ui: + self.ui.offUi() + self.ui.sourceCmd = None + #self.ui.cross(False) + msg("") + if self.planetrack: + self.planetrack.finalize() + if self.support: + plane.restore() + FreeCADGui.Snapper.off() + if self.call: + self.view.removeEventCallback("SoEvent",self.call) + self.call = None + if self.commitList: + todo.delayCommit(self.commitList) + self.commitList = [] + + def commit(self,name,func): + "stores actions to be committed to the FreeCAD document" + # print "committing" + self.commitList.append((name,func)) + + def getStrings(self): + "returns a couple of useful strings fro building python commands" + + # current plane rotation + p = plane.getRotation() + qr = p.Rotation.Q + qr = '('+str(qr[0])+','+str(qr[1])+','+str(qr[2])+','+str(qr[3])+')' + + # support object + if self.support: + sup = 'FreeCAD.ActiveDocument.getObject("' + self.support.Name + '")' + else: + sup = 'None' + + # contents of self.node + points='[' + for n in self.node: + if len(points) > 1: + points += ',' + points += DraftVecUtils.toString(n) + points += ']' + + # fill mode + if self.ui: + fil = str(bool(self.ui.fillmode)) + else: + fil = "True" + + return qr,sup,points,fil + #--------------------------------------------------------------------------- # Helper tools -#--------------------------------------------------------------------------- - -class SelectPlane: +#--------------------------------------------------------------------------- + +class SelectPlane(DraftTool): "The Draft_SelectPlane FreeCAD command definition" def GetResources(self): @@ -216,20 +325,10 @@ class SelectPlane: 'Accel' : "W, P", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"), 'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_SelectPlane", "Select a working plane for geometry creation")} - - def IsActive(self): - if FreeCADGui.ActiveDocument: - return True - else: - return False def Activated(self): - if FreeCAD.activeDraftCommand: - FreeCAD.activeDraftCommand.finish() + DraftTool.Activated(self) self.offset = 0 - self.ui = None - self.call = None - self.doc = FreeCAD.ActiveDocument if self.doc: sel = FreeCADGui.Selection.getSelectionEx() if len(sel) == 1: @@ -242,12 +341,8 @@ class SelectPlane: self.display(plane.axis) self.finish() return - FreeCAD.activeDraftCommand = self - self.view = Draft.get3DView() - self.ui = FreeCADGui.draftToolBar self.ui.selectPlaneUi() msg(translate("draft", "Pick a face to define the drawing plane\n")) - self.ui.sourceCmd = self if plane.alignToSelection(self.offset): FreeCADGui.Selection.clearSelection() self.display(plane.axis) @@ -316,135 +411,25 @@ class SelectPlane: self.ui.wplabel.setText(plv+suffix) FreeCADGui.Snapper.setGrid() - def finish(self): - if self.call: - self.view.removeEventCallback("SoEvent",self.call) - FreeCAD.activeDraftCommand = None - if self.ui: - self.ui.offUi() - #--------------------------------------------------------------------------- # Geometry constructors #--------------------------------------------------------------------------- - -class DraftTool: - "The base class of all Draft Tools" - - def __init__(self): - self.commitList = [] - - def Activated(self,name="None"): - if FreeCAD.activeDraftCommand: - FreeCAD.activeDraftCommand.finish() - - global Part, DraftGeomUtils - import Part, DraftGeomUtils - - self.ui = None - self.call = None - self.support = None - self.commitList = [] - self.doc = FreeCAD.ActiveDocument - if not self.doc: - self.finish() - return - - FreeCAD.activeDraftCommand = self - self.view = Draft.get3DView() - self.ui = FreeCADGui.draftToolBar - self.ui.sourceCmd = self - self.ui.setTitle(name) - self.ui.show() - rot = self.view.getCameraNode().getField("orientation").getValue() - upv = Vector(rot.multVec(coin.SbVec3f(0,1,0)).getValue()) - plane.setup(DraftVecUtils.neg(self.view.getViewDirection()), Vector(0,0,0), upv) - self.node = [] - self.pos = [] - self.constrain = None - self.obj = None - self.extendedCopy = False - self.ui.setTitle(name) - self.featureName = name - #self.snap = snapTracker() - #self.extsnap = lineTracker(dotted=True) - self.planetrack = None - if Draft.getParam("showPlaneTracker"): - self.planetrack = PlaneTracker() - - def finish(self): - self.node = [] - #self.snap.finalize() - #self.extsnap.finalize() - FreeCAD.activeDraftCommand = None - if self.ui: - self.ui.offUi() - self.ui.sourceCmd=None - #self.ui.cross(False) - msg("") - if self.planetrack: - self.planetrack.finalize() - if self.support: - plane.restore() - FreeCADGui.Snapper.off() - if self.call: - self.view.removeEventCallback("SoEvent",self.call) - self.call = None - if self.commitList: - todo.delayCommit(self.commitList) - self.commitList = [] - - def commit(self,name,func): - "stores actions to be committed to the FreeCAD document" - # print "committing" - self.commitList.append((name,func)) - - def getStrings(self): - "returns a couple of useful strings fro building python commands" - - # current plane rotation - p = plane.getRotation() - qr = p.Rotation.Q - qr = '('+str(qr[0])+','+str(qr[1])+','+str(qr[2])+','+str(qr[3])+')' - - # support object - if self.support: - sup = 'FreeCAD.ActiveDocument.getObject("' + self.support.Name + '")' - else: - sup = 'None' - - # contents of self.node - points='[' - for n in self.node: - if len(points) > 1: - points += ',' - points += DraftVecUtils.toString(n) - points += ']' - - # fill mode - if self.ui: - fil = str(bool(self.ui.fillmode)) - else: - fil = "True" - - return qr,sup,points,fil - class Creator(DraftTool): "A generic Draft Creator Tool used by creation tools such as line or arc" def __init__(self): DraftTool.__init__(self) - - def IsActive(self): - if FreeCADGui.ActiveDocument: - return True - else: - return False + def Activated(self,name="None"): + DraftTool.Activated(self) + self.support = getSupport() + class Line(Creator): "The Line FreeCAD command definition" def __init__(self, wiremode=False): + Creator.__init__(self) self.isWire = wiremode def GetResources(self): @@ -508,7 +493,8 @@ class Line(Creator): if (arg["Position"] == self.pos): self.finish(False,cont=True) else: - if not self.node: self.support = getSupport(arg) + if (not self.node) and (not self.support): + self.support = getSupport(arg) point,ctrlPoint,info = getPoint(self,arg) self.pos = arg["Position"] self.node.append(point) @@ -632,7 +618,8 @@ class BSpline(Line): if (arg["Position"] == self.pos): self.finish(False,cont=True) else: - if not self.node: self.support = getSupport(arg) + if (not self.node) and (not self.support): + self.support = getSupport(arg) point,ctrlPoint,info = getPoint(self,arg) self.pos = arg["Position"] self.node.append(point) @@ -822,7 +809,8 @@ class Rectangle(Creator): if (arg["Position"] == self.pos): self.finish() else: - if not self.node: self.support = getSupport(arg) + if (not self.node) and (not self.support): + self.support = getSupport(arg) point,ctrlPoint,info = getPoint(self,arg) self.appendPoint(point) @@ -1019,7 +1007,8 @@ class Arc(Creator): if not DraftVecUtils.isNull(viewdelta): point = point.add(DraftVecUtils.neg(viewdelta)) if (self.step == 0): # choose center - self.support = getSupport(arg) + if not self.support: + self.support = getSupport(arg) if hasMod(arg,MODALT): snapped=self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: @@ -1288,7 +1277,8 @@ class Polygon(Creator): if not DraftVecUtils.isNull(viewdelta): point = point.add(DraftVecUtils.neg(viewdelta)) if (self.step == 0): # choose center - if not self.node: self.support = getSupport(arg) + if (not self.node) and (not self.support): + self.support = getSupport(arg) if hasMod(arg,MODALT): snapped=self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: @@ -1635,7 +1625,8 @@ class Dimension(Creator): elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,ctrlPoint,info = getPoint(self,arg) - if not self.node: self.support = getSupport(arg) + if (not self.node) and (not self.support): + self.support = getSupport(arg) if hasMod(arg,MODALT) and (len(self.node)<3): print "snapped: ",info if info: diff --git a/src/Mod/Draft/DraftTrackers.py b/src/Mod/Draft/DraftTrackers.py index 47e378a34..1d9ba2b6c 100644 --- a/src/Mod/Draft/DraftTrackers.py +++ b/src/Mod/Draft/DraftTrackers.py @@ -640,24 +640,16 @@ class gridTracker(Tracker): def getClosestNode(self,point): "returns the closest node from the given point" + print "in:",point # get the 2D coords. - point = FreeCAD.DraftWorkingPlane.projectPoint(point) - u = DraftVecUtils.project(point,FreeCAD.DraftWorkingPlane.u) - lu = u.Length - if u.getAngle(FreeCAD.DraftWorkingPlane.u) > 1.5: - lu = -lu - v = DraftVecUtils.project(point,FreeCAD.DraftWorkingPlane.v) - lv = v.Length - if v.getAngle(FreeCAD.DraftWorkingPlane.v) > 1.5: - lv = -lv - # print "u = ",u," v = ",v - # find nearest grid node - pu = (round(lu/self.space,0))*self.space - pv = (round(lv/self.space,0))*self.space - rot = FreeCAD.Rotation() - rot.Q = self.trans.rotation.getValue().getValue() - return rot.multVec(Vector(pu,pv,0)) - + # point = FreeCAD.DraftWorkingPlane.projectPoint(point) + pt = FreeCAD.DraftWorkingPlane.getLocalCoords(point) + pu = (round(pt.x/self.space,0))*self.space + pv = (round(pt.y/self.space,0))*self.space + pt = FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(pu,pv,0)) + print "out:",pt + return pt + class boxTracker(Tracker): "A box tracker, can be based on a line object" def __init__(self,line=None,width=0.1,height=1): @@ -719,3 +711,27 @@ class boxTracker(Tracker): self.update() else: return self.cube.depth.getValue() + +class radiusTracker(Tracker): + "A tracker that displays a transparent sphere to inicate a radius" + def __init__(self,position=FreeCAD.Vector(0,0,0),radius=1): + self.trans = coin.SoTransform() + self.trans.translation.setValue([position.x,position.y,position.z]) + m = coin.SoMaterial() + m.transparency.setValue(0.9) + m.diffuseColor.setValue([0,1,0]) + self.sphere = coin.SoSphere() + self.sphere.radius.setValue(radius) + self.baseline = None + Tracker.__init__(self,children=[self.trans,m,self.sphere]) + + def update(self,arg1,arg2=None): + if isinstance(arg1,FreeCAD.Vector): + self.trans.translation.setValue([arg1.x,arg1.y,arg1.z]) + else: + self.sphere.radius.setValue(arg1) + if arg2 != None: + if isinstance(arg2,FreeCAD.Vector): + self.trans.translation.setValue([arg2.x,arg2.y,arg2.z]) + else: + self.sphere.radius.setValue(arg2) diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index e41eaabf7..6c1d6066b 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -86,8 +86,29 @@ class plane: def projectPoint(self, p, direction=None): '''project point onto plane, default direction is orthogonal''' - if not direction: direction = self.axis + if not direction: + direction = self.axis + lp = self.getLocalCoords(p) + gp = self.getGlobalCoords(Vector(lp.x,lp.y,0)) + a = direction.getAngle(gp.sub(p)) + if a > math.pi/2: + direction = DraftVecUtils.neg(direction) + a = math.pi - a + ld = self.getLocalRot(direction) + gd = self.getGlobalRot(Vector(ld.x,ld.y,0)) + hyp = abs(math.tan(a) * lp.z) + return gp.add(DraftVecUtils.scaleTo(gd,hyp)) + + def projectPointOld(self, p, direction=None): + '''project point onto plane, default direction is orthogonal. Obsolete''' + if not direction: + direction = self.axis t = Vector(direction) + #t.normalize() + a = round(t.getAngle(self.axis),DraftVecUtils.precision()) + pp = round((math.pi)/2,DraftVecUtils.precision()) + if a == pp: + return p t.multiply(self.offsetToPoint(p, direction)) return p.add(t) @@ -196,6 +217,31 @@ class plane: def getLocalCoords(self,point): "returns the coordinates of a given point on the working plane" + pt = point.sub(self.position) + xv = DraftVecUtils.project(pt,self.u) + x = xv.Length + if xv.getAngle(self.u) > 1: + x = -x + yv = DraftVecUtils.project(pt,self.v) + y = yv.Length + if yv.getAngle(self.v) > 1: + y = -y + zv = DraftVecUtils.project(pt,self.axis) + z = zv.Length + if zv.getAngle(self.axis) > 1: + z = -z + return Vector(x,y,z) + + def getGlobalCoords(self,point): + "returns the global coordinates of the given point, taken relatively to this working plane" + vx = DraftVecUtils.scale(self.u,point.x) + vy = DraftVecUtils.scale(self.v,point.y) + vz = DraftVecUtils.scale(self.axis,point.z) + pt = (vx.add(vy)).add(vz) + return pt.add(self.position) + + def getLocalRot(self,point): + "Same as getLocalCoords, but discards the WP position" xv = DraftVecUtils.project(point,self.u) x = xv.Length if xv.getAngle(self.u) > 1: @@ -210,13 +256,14 @@ class plane: z = -z return Vector(x,y,z) - def getGlobalCoords(self,point): - "returns the global coordinates of the given point, taken relatively to this working plane" + def getGlobalRot(self,point): + "Same as getGlobalCoords, but discards the WP position" vx = DraftVecUtils.scale(self.u,point.x) vy = DraftVecUtils.scale(self.v,point.y) vz = DraftVecUtils.scale(self.axis,point.z) - return (vx.add(vy)).add(vz) - + pt = (vx.add(vy)).add(vz) + return pt + def getClosestAxis(self,point): "returns which of the workingplane axes is closest from the given vector" ax = point.getAngle(self.u)