#*************************************************************************** #* * #* Copyright (c) 2009, 2010 * #* Yorik van Havre , Ken Cline * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU General Public License (GPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** __title__="FreeCAD Draft Workbench GUI Tools" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin" __url__ = "http://free-cad.sourceforge.net" #--------------------------------------------------------------------------- # Generic stuff #--------------------------------------------------------------------------- import os, FreeCAD, FreeCADGui, Part, WorkingPlane, math, re, importSVG, Draft, Draft_rc from functools import partial from draftlibs import fcvec,fcgeo from FreeCAD import Vector from draftGui import todo,QtCore,QtGui from pivy import coin # loads a translation engine #locale = QtCore.QLocale(eval("QtCore.QLocale."+FreeCADGui.getLocale())).name() #translator = QtCore.QTranslator() #translator.load('Draft_'+locale+'.qm',':/translations/') #QtGui.QApplication.installTranslator(translator) FreeCADGui.updateLocale() def translate(context,text): "convenience function for Qt translator" return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8).toUtf8() def msg(text=None,mode=None): "prints the given message on the FreeCAD status bar" if not text: FreeCAD.Console.PrintMessage("") else: if mode == 'warning': FreeCAD.Console.PrintWarning(text) elif mode == 'error': FreeCAD.Console.PrintError(text) else: FreeCAD.Console.PrintMessage(text) # loads the fill patterns FreeCAD.svgpatterns = importSVG.getContents(Draft_rc.qt_resource_data,'pattern',True) altpat = Draft.getParam("patternFile") if os.path.isdir(altpat): for f in os.listdir(altpat): if '.svg' in f: p = importSVG.getContents(altpat+os.sep+f,'pattern') if p: FreeCAD.svgpatterns[p[0]]=p[1] # sets the default working plane plane = WorkingPlane.plane() FreeCAD.DraftWorkingPlane = plane defaultWP = Draft.getParam("defaultWP") if defaultWP == 1: plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,0,1), 0) elif defaultWP == 2: plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,1,0), 0) elif defaultWP == 3: plane.alignToPointAndAxis(Vector(0,0,0), Vector(1,0,0), 0) # last snapped objects, for quick intersection calculation lastObj = [0,0] # set modifier keys MODS = ["shift","ctrl","alt"] MODCONSTRAIN = MODS[Draft.getParam("modconstrain")] MODSNAP = MODS[Draft.getParam("modsnap")] MODALT = MODS[Draft.getParam("modalt")] #--------------------------------------------------------------------------- # Snapping stuff #--------------------------------------------------------------------------- def snapPoint(target,point,cursor,ctrl=False): ''' Snap function used by the Draft tools Currently has two modes: passive and active. Pressing CTRL while clicking puts you in active mode: - In passive mode (an open circle appears), your point is snapped to the nearest point on any underlying geometry. - In active mode (ctrl pressed, a filled circle appears), your point can currently be snapped to the following points: - Nodes and midpoints of all Part shapes - Nodes and midpoints of lines/wires - Centers and quadrant points of circles - Endpoints of arcs - Intersection between line, wires segments, arcs and circles - When constrained (SHIFT pressed), Intersections between constraining axis and lines/wires ''' def getConstrainedPoint(edge,last,constrain): "check for constrained snappoint" p1 = edge.Vertexes[0].Point p2 = edge.Vertexes[-1].Point ar = [] if (constrain == 0): if ((last.y > p1.y) and (last.y < p2.y) or (last.y > p2.y) and (last.y < p1.y)): pc = (last.y-p1.y)/(p2.y-p1.y) cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z))) ar.append([cp,1,cp]) # constrainpoint if (constrain == 1): if ((last.x > p1.x) and (last.x < p2.x) or (last.x > p2.x) and (last.x < p1.x)): pc = (last.x-p1.x)/(p2.x-p1.x) cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z))) ar.append([cp,1,cp]) # constrainpoint return ar def getPassivePoint(info): "returns a passive snap point" cur = Vector(info['x'],info['y'],info['z']) return [cur,2,cur] def getScreenDist(dist,cursor): "returns a 3D distance from a screen pixels distance" p1 = FreeCADGui.ActiveDocument.ActiveView.getPoint(cursor) p2 = FreeCADGui.ActiveDocument.ActiveView.getPoint((cursor[0]+dist,cursor[1])) return (p2.sub(p1)).Length def getGridSnap(target,point): "returns a grid snap point if available" if target.grid: return target.grid.getClosestNode(point) return None def getPerpendicular(edge,last): "returns a point on an edge, perpendicular to the given point" dv = last.sub(edge.Vertexes[0].Point) nv = fcvec.project(dv,fcgeo.vec(edge)) np = (edge.Vertexes[0].Point).add(nv) return np # checking if alwaySnap setting is on extractrl = False if Draft.getParam("alwaysSnap"): extractrl = ctrl ctrl = True # setting Radius radius = getScreenDist(Draft.getParam("snapRange"),cursor) # checking if parallel to one of the edges of the last objects target.snap.off() target.extsnap.off() if (len(target.node) > 0): for o in [lastObj[1],lastObj[0]]: if o: ob = target.doc.getObject(o) if ob: edges = ob.Shape.Edges if len(edges)<10: for e in edges: if isinstance(e.Curve,Part.Line): last = target.node[len(target.node)-1] de = Part.Line(last,last.add(fcgeo.vec(e))).toShape() np = getPerpendicular(e,point) if (np.sub(point)).Length < radius: target.snap.coords.point.setValue((np.x,np.y,np.z)) target.snap.setMarker("circle") target.snap.on() target.extsnap.p1(e.Vertexes[0].Point) target.extsnap.p2(np) target.extsnap.on() point = np else: last = target.node[len(target.node)-1] de = Part.Line(last,last.add(fcgeo.vec(e))).toShape() np = getPerpendicular(de,point) if (np.sub(point)).Length < radius: target.snap.coords.point.setValue((np.x,np.y,np.z)) target.snap.setMarker("circle") target.snap.on() point = np # check if we snapped to something snapped=target.view.getObjectInfo((cursor[0],cursor[1])) if (snapped == None): # nothing has been snapped, check fro grid snap gpt = getGridSnap(target,point) if gpt: if radius != 0: dv = point.sub(gpt) if dv.Length <= radius: target.snap.coords.point.setValue((gpt.x,gpt.y,gpt.z)) target.snap.setMarker("point") target.snap.on() return gpt return point else: # we have something to snap obj = target.doc.getObject(snapped['Object']) if hasattr(obj.ViewObject,"Selectable"): if not obj.ViewObject.Selectable: return point if not ctrl: # are we in passive snap? snapArray = [getPassivePoint(snapped)] else: snapArray = [] comp = snapped['Component'] if obj.isDerivedFrom("Part::Feature"): if "Edge" in comp: # get the stored objects to calculate intersections intedges = [] if lastObj[0]: lo = target.doc.getObject(lastObj[0]) if lo: if lo.isDerivedFrom("Part::Feature"): intedges = lo.Shape.Edges nr = int(comp[4:])-1 edge = obj.Shape.Edges[nr] for v in edge.Vertexes: snapArray.append([v.Point,0,v.Point]) if isinstance(edge.Curve,Part.Line): # the edge is a line midpoint = fcgeo.findMidpoint(edge) snapArray.append([midpoint,1,midpoint]) if (len(target.node) > 0): last = target.node[len(target.node)-1] snapArray.extend(getConstrainedPoint(edge,last,target.constrain)) np = getPerpendicular(edge,last) snapArray.append([np,1,np]) elif isinstance (edge.Curve,Part.Circle): # the edge is an arc rad = edge.Curve.Radius pos = edge.Curve.Center for i in [0,30,45,60,90,120,135,150,180,210,225,240,270,300,315,330]: ang = math.radians(i) cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) snapArray.append([cur,1,cur]) for i in [15,37.5,52.5,75,105,127.5,142.5,165,195,217.5,232.5,255,285,307.5,322.5,345]: ang = math.radians(i) cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) snapArray.append([cur,0,pos]) for e in intedges: # get the intersection points pt = fcgeo.findIntersection(e,edge) if pt: for p in pt: snapArray.append([p,3,p]) elif "Vertex" in comp: # directly snapped to a vertex p = Vector(snapped['x'],snapped['y'],snapped['z']) snapArray.append([p,0,p]) elif comp == '': # workaround for the new view provider p = Vector(snapped['x'],snapped['y'],snapped['z']) snapArray.append([p,2,p]) else: snapArray = [getPassivePoint(snapped)] elif Draft.getType(obj) == "Dimension": for pt in [obj.Start,obj.End,obj.Dimline]: snapArray.append([pt,0,pt]) elif Draft.getType(obj) == "Mesh": for v in obj.Mesh.Points: snapArray.append([v.Vector,0,v.Vector]) if not lastObj[0]: lastObj[0] = obj.Name lastObj[1] = obj.Name if (lastObj[1] != obj.Name): lastObj[0] = lastObj[1] lastObj[1] = obj.Name # calculating shortest distance shortest = 1000000000000000000 spt = Vector(snapped['x'],snapped['y'],snapped['z']) newpoint = [Vector(0,0,0),0,Vector(0,0,0)] for pt in snapArray: if pt[0] == None: print "snapPoint: debug 'i[0]' is 'None'" di = pt[0].sub(spt) if di.Length < shortest: shortest = di.Length newpoint = pt if radius != 0: dv = point.sub(newpoint[2]) if (not extractrl) and (dv.Length > radius): newpoint = getPassivePoint(snapped) target.snap.coords.point.setValue((newpoint[2].x,newpoint[2].y,newpoint[2].z)) if (newpoint[1] == 1): target.snap.setMarker("square") elif (newpoint[1] == 0): target.snap.setMarker("point") elif (newpoint[1] == 3): target.snap.setMarker("square") else: target.snap.setMarker("circle") target.snap.on() return newpoint[2] def constrainPoint (target,pt,mobile=False,sym=False): ''' Constrain function used by the Draft tools On commands that need to enter several points (currently only line/wire), you can constrain the next point to be picked to the last drawn point by pressing SHIFT. The vertical or horizontal constraining depends on the position of your mouse in relation to last point at the moment you press SHIFT. if mobile=True, mobile behaviour applies. If sym=True, x alway = y ''' point = Vector(pt) if len(target.node) > 0: last = target.node[-1] dvec = point.sub(last) affinity = plane.getClosestAxis(dvec) if ((target.constrain == None) or mobile): if affinity == "x": dv = fcvec.project(dvec,plane.u) point = last.add(dv) if sym: l = dv.Length if dv.getAngle(plane.u) > 1: l = -l point = last.add(plane.getGlobalCoords(Vector(l,l,l))) target.constrain = 0 #x direction target.ui.xValue.setEnabled(True) target.ui.yValue.setEnabled(False) target.ui.zValue.setEnabled(False) target.ui.xValue.setFocus() elif affinity == "y": dv = fcvec.project(dvec,plane.v) point = last.add(dv) if sym: l = dv.Length if dv.getAngle(plane.v) > 1: l = -l point = last.add(plane.getGlobalCoords(Vector(l,l,l))) target.constrain = 1 #y direction target.ui.xValue.setEnabled(False) target.ui.yValue.setEnabled(True) target.ui.zValue.setEnabled(False) target.ui.yValue.setFocus() elif affinity == "z": dv = fcvec.project(dvec,plane.axis) point = last.add(dv) if sym: l = dv.Length if dv.getAngle(plane.axis) > 1: l = -l point = last.add(plane.getGlobalCoords(Vector(l,l,l))) target.constrain = 2 #z direction target.ui.xValue.setEnabled(False) target.ui.yValue.setEnabled(False) target.ui.zValue.setEnabled(True) target.ui.zValue.setFocus() else: target.constrain = 3 elif (target.constrain == 0): dv = fcvec.project(dvec,plane.u) point = last.add(dv) if sym: l = dv.Length if dv.getAngle(plane.u) > 1: l = -l point = last.add(plane.getGlobalCoords(Vector(l,l,l))) elif (target.constrain == 1): dv = fcvec.project(dvec,plane.v) point = last.add(dv) if sym: l = dv.Length if dv.getAngle(plane.u) > 1: l = -l point = last.add(plane.getGlobalCoords(Vector(l,l,l))) elif (target.constrain == 2): dv = fcvec.project(dvec,plane.axis) point = last.add(dv) if sym: l = dv.Length if dv.getAngle(plane.u) > 1: l = -l point = last.add(plane.getGlobalCoords(Vector(l,l,l))) return point def selectObject(arg): '''this is a scene even handler, to be called from the Draft tools when they need to select an object''' if (arg["Type"] == "SoKeyboardEvent"): if (arg["Key"] == "ESCAPE"): FreeCAD.activeDraftCommand.finish() # TODO : this part raises a coin3D warning about scene traversal, to be fixed. if (arg["Type"] == "SoMouseButtonEvent"): if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): cursor = arg["Position"] snapped = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo((cursor[0],cursor[1])) if snapped: obj = FreeCAD.ActiveDocument.getObject(snapped['Object']) FreeCADGui.Selection.addSelection(obj) FreeCAD.activeDraftCommand.component=snapped['Component'] FreeCAD.activeDraftCommand.proceed() def getPoint(target,args,mobile=False,sym=False,workingplane=True): ''' Function used by the Draft Tools. returns a constrained 3d point and its original point. if mobile=True, the constraining occurs from the location of mouse cursor when Shift is pressed, otherwise from last entered point. If sym=True, x and y values stay always equal. If workingplane=False, the point wont be projected on the Working Plane. ''' ui = FreeCADGui.draftToolBar view = FreeCADGui.ActiveDocument.ActiveView point = view.getPoint(args["Position"][0],args["Position"][1]) point = snapPoint(target,point,args["Position"],hasMod(args,MODSNAP)) if (not plane.weak) and workingplane: # working plane was explicitely selected - project onto it viewDirection = view.getViewDirection() if FreeCADGui.ActiveDocument.ActiveView.getCameraType() == "Perspective": camera = FreeCADGui.ActiveDocument.ActiveView.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.x,point.y,point.z) if (hasMod(args,MODCONSTRAIN)): # constraining if mobile and (target.constrain == None): target.node.append(point) point = constrainPoint(target,point,mobile=mobile,sym=sym) else: target.constrain = None ui.xValue.setEnabled(True) ui.yValue.setEnabled(True) ui.zValue.setEnabled(True) if target.node: if target.featureName == "Rectangle": ui.displayPoint(point, target.node[0], plane=plane) else: ui.displayPoint(point, target.node[-1], plane=plane) else: ui.displayPoint(point, plane=plane) return point,ctrlPoint def getSupport(args): "returns the supporting object and sets the working plane" snapped = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo((args["Position"][0],args["Position"][1])) if not snapped: return None obj = None plane.save() try: obj = FreeCAD.ActiveDocument.getObject(snapped['Object']) shape = obj.Shape component = getattr(shape,snapped["Component"]) if plane.alignToFace(component, 0) \ or plane.alignToCurve(component, 0): self.display(plane.axis) except: pass return obj def hasMod(args,mod): "checks if args has a specific modifier" if mod == "shift": return args["ShiftDown"] elif mod == "ctrl": return args["CtrlDown"] elif mod == "alt": return args["AltDown"] def setMod(args,mod,state): "sets a specific modifier state in args" if mod == "shift": args["ShiftDown"] = state elif mod == "ctrl": args["CtrlDown"] = state elif mod == "alt": args["AltDown"] = state #--------------------------------------------------------------------------- # Trackers #--------------------------------------------------------------------------- class Tracker: "A generic Draft Tracker, to be used by other specific trackers" def __init__(self,dotted=False,scolor=None,swidth=None,children=[],ontop=False): self.ontop = ontop color = coin.SoBaseColor() color.rgb = scolor or FreeCADGui.draftToolBar.getDefaultColor("ui") drawstyle = coin.SoDrawStyle() if swidth: drawstyle.lineWidth = swidth if dotted: drawstyle.style = coin.SoDrawStyle.LINES drawstyle.lineWeight = 3 drawstyle.linePattern = 0x0f0f #0xaa node = coin.SoSeparator() for c in [drawstyle, color] + children: node.addChild(c) self.switch = coin.SoSwitch() # this is the on/off switch self.switch.addChild(node) self.switch.whichChild = -1 self.Visible = False todo.delay(self._insertSwitch, self.switch) def finalize(self): todo.delay(self._removeSwitch, self.switch) self.switch = None def _insertSwitch(self, switch): '''insert self.switch into the scene graph. Must not be called from an event handler (or other scene graph traversal).''' sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() if self.ontop: sg.insertChild(switch,0) else: sg.addChild(switch) def _removeSwitch(self, switch): '''remove self.switch from the scene graph. As with _insertSwitch, must not be called during scene graph traversal).''' sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() sg.removeChild(switch) def on(self): self.switch.whichChild = 0 self.Visible = True def off(self): self.switch.whichChild = -1 self.Visible = False class snapTracker(Tracker): "A Snap Mark tracker, used by tools that support snapping" def __init__(self): color = coin.SoBaseColor() color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 self.coords = coin.SoCoordinate3() # this is the coordinate self.coords.point.setValue((0,0,0)) node = coin.SoAnnotation() node.addChild(self.coords) node.addChild(color) node.addChild(self.marker) Tracker.__init__(self,children=[node]) def setMarker(self,style): if (style == "point"): self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 elif (style == "square"): self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9 elif (style == "circle"): self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_9_9 class lineTracker(Tracker): "A Line tracker, used by the tools that need to draw temporary lines" def __init__(self,dotted=False,scolor=None,swidth=None): line = coin.SoLineSet() line.numVertices.setValue(2) self.coords = coin.SoCoordinate3() # this is the coordinate self.coords.point.setValues(0,2,[[0,0,0],[1,0,0]]) Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) def p1(self,point=None): "sets or gets the first point of the line" if point: self.coords.point.set1Value(0,point.x,point.y,point.z) else: return Vector(self.coords.point.getValues()[0].getValue()) def p2(self,point=None): "sets or gets the second point of the line" if point: self.coords.point.set1Value(1,point.x,point.y,point.z) else: return Vector(self.coords.point.getValues()[-1].getValue()) def getLength(self): "returns the length of the line" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[-1].getValue()) return (p2.sub(p1)).Length class rectangleTracker(Tracker): "A Rectangle tracker, used by the rectangle tool" def __init__(self,dotted=False,scolor=None,swidth=None): self.origin = Vector(0,0,0) line = coin.SoLineSet() line.numVertices.setValue(5) self.coords = coin.SoCoordinate3() # this is the coordinate self.coords.point.setValues(0,50,[[0,0,0],[2,0,0],[2,2,0],[0,2,0],[0,0,0]]) Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) self.u = plane.u self.v = plane.v def setorigin(self,point): "sets the base point of the rectangle" self.coords.point.set1Value(0,point.x,point.y,point.z) self.coords.point.set1Value(4,point.x,point.y,point.z) self.origin = point def update(self,point): "sets the opposite (diagonal) point of the rectangle" diagonal = point.sub(self.origin) inpoint1 = self.origin.add(fcvec.project(diagonal,self.v)) inpoint2 = self.origin.add(fcvec.project(diagonal,self.u)) self.coords.point.set1Value(1,inpoint1.x,inpoint1.y,inpoint1.z) self.coords.point.set1Value(2,point.x,point.y,point.z) self.coords.point.set1Value(3,inpoint2.x,inpoint2.y,inpoint2.z) def setPlane(self,u,v=None): '''sets given (u,v) vectors as working plane. You can give only u and v will be deduced automatically given current workplane''' self.u = u if v: self.v = v else: norm = plane.u.cross(plane.v) self.v = self.u.cross(norm) def p1(self,point=None): "sets or gets the base point of the rectangle" if point: self.setorigin(point) else: return Vector(self.coords.point.getValues()[0].getValue()) def p2(self): "gets the second point (on u axis) of the rectangle" return Vector(self.coords.point.getValues()[3].getValue()) def p3(self,point=None): "sets or gets the opposite (diagonal) point of the rectangle" if point: self.update(point) else: return Vector(self.coords.point.getValues()[2].getValue()) def p4(self): "gets the fourth point (on v axis) of the rectangle" return Vector(self.coords.point.getValues()[1].getValue()) def getSize(self): "returns (length,width) of the rectangle" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[2].getValue()) diag = p2.sub(p1) return ((fcvec.project(diag,self.u)).Length,(fcvec.project(diag,self.v)).Length) def getNormal(self): "returns the normal of the rectangle" return (self.u.cross(self.v)).normalize() class dimTracker(Tracker): "A Dimension tracker, used by the dimension tool" def __init__(self,dotted=False,scolor=None,swidth=None): line = coin.SoLineSet() line.numVertices.setValue(4) self.coords = coin.SoCoordinate3() # this is the coordinate self.coords.point.setValues(0,4,[[0,0,0],[0,0,0],[0,0,0],[0,0,0]]) Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) self.p1 = self.p2 = self.p3 = None def update(self,pts): if len(pts) == 1: self.p3 = pts[0] else: self.p1 = pts[0] self.p2 = pts[1] if len(pts) > 2: self.p3 = pts[2] self.calc() def calc(self): if (self.p1 != None) and (self.p2 != None): points = [fcvec.tup(self.p1,True),fcvec.tup(self.p2,True),\ fcvec.tup(self.p1,True),fcvec.tup(self.p2,True)] if self.p3 != None: p1 = self.p1 p4 = self.p2 if fcvec.equals(p1,p4): proj = None else: base = Part.Line(p1,p4).toShape() proj = fcgeo.findDistance(self.p3,base) if not proj: p2 = p1 p3 = p4 else: p2 = p1.add(fcvec.neg(proj)) p3 = p4.add(fcvec.neg(proj)) points = [fcvec.tup(p1),fcvec.tup(p2),fcvec.tup(p3),fcvec.tup(p4)] self.coords.point.setValues(0,4,points) class bsplineTracker(Tracker): "A bspline tracker" def __init__(self,dotted=False,scolor=None,swidth=None,points = []): self.bspline = None self.points = points self.trans = coin.SoTransform() self.sep = coin.SoSeparator() self.recompute() Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep]) def update(self, points): self.points = points self.recompute() def recompute(self): if (len(self.points) >= 2): if self.bspline: self.sep.removeChild(self.bspline) self.bspline = None c = Part.BSplineCurve() # DNC: allows to close the curve by placing ends close to each other if ( len(self.points) >= 3 ) and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ): # YVH: Added a try to bypass some hazardous situations try: c.interpolate(self.points[:-1], True) except: pass elif self.points: try: c.interpolate(self.points, False) except: pass c = c.toShape() buf=c.writeInventor(2,0.01) #fp=open("spline.iv","w") #fp.write(buf) #fp.close() ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) # In case reading from buffer failed if ivob and ivob.getNumChildren() > 1: self.bspline = ivob.getChild(1).getChild(0) self.bspline.removeChild(self.bspline.getChild(0)) self.bspline.removeChild(self.bspline.getChild(0)) self.sep.addChild(self.bspline) else: FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n") class arcTracker(Tracker): "An arc tracker" def __init__(self,dotted=False,scolor=None,swidth=None,start=0,end=math.pi*2): self.circle = None self.startangle = math.degrees(start) self.endangle = math.degrees(end) self.trans = coin.SoTransform() self.trans.translation.setValue([0,0,0]) self.sep = coin.SoSeparator() self.recompute() Tracker.__init__(self,dotted,scolor,swidth,[self.trans, self.sep]) def setCenter(self,cen): "sets the center point" self.trans.translation.setValue([cen.x,cen.y,cen.z]) def setRadius(self,rad): "sets the radius" self.trans.scaleFactor.setValue([rad,rad,rad]) def getRadius(self): "returns the current radius" return self.trans.scaleFactor.getValue()[0] def setStartAngle(self,ang): "sets the start angle" self.startangle = math.degrees(ang) self.recompute() def setEndAngle(self,ang): "sets the end angle" self.endangle = math.degrees(ang) self.recompute() def getAngle(self,pt): "returns the angle of a given vector" c = self.trans.translation.getValue() center = Vector(c[0],c[1],c[2]) base = plane.u rad = pt.sub(center) return(fcvec.angle(rad,base,plane.axis)) def getAngles(self): "returns the start and end angles" return(self.startangle,self.endangle) def setStartPoint(self,pt): "sets the start angle from a point" self.setStartAngle(-self.getAngle(pt)) def setEndPoint(self,pt): "sets the end angle from a point" self.setEndAngle(self.getAngle(pt)) def setApertureAngle(self,ang): "sets the end angle by giving the aperture angle" ap = math.degrees(ang) self.endangle = self.startangle + ap self.recompute() def recompute(self): if self.circle: self.sep.removeChild(self.circle) self.circle = None if self.endangle < self.startangle: c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.endangle,self.startangle) else: c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.startangle,self.endangle) buf=c.writeInventor(2,0.01) ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) # In case reading from buffer failed if ivob and ivob.getNumChildren() > 1: self.circle = ivob.getChild(1).getChild(0) self.circle.removeChild(self.circle.getChild(0)) self.circle.removeChild(self.circle.getChild(0)) self.sep.addChild(self.circle) else: FreeCAD.Console.PrintWarning("arcTracker.recompute() failed to read-in Inventor string\n") class ghostTracker(Tracker): '''A Ghost tracker, that allows to copy whole object representations. You can pass it an object or a list of objects, or a shape.''' def __init__(self,sel): self.trans = coin.SoTransform() self.trans.translation.setValue([0,0,0]) self.children = [self.trans] self.ivsep = coin.SoSeparator() try: if isinstance(sel,Part.Shape): ivin = coin.SoInput() ivin.setBuffer(sel.writeInventor()) ivob = coin.SoDB.readAll(ivin) self.ivsep.addChild(ivob.getChildren()[1]) else: if not isinstance(sel,list): sel = [sel] for obj in sel: self.ivsep.addChild(obj.ViewObject.RootNode.copy()) except: print "draft: Couldn't create ghost" self.children.append(self.ivsep) Tracker.__init__(self,children=self.children) def update(self,obj): obj.ViewObject.show() self.finalize() self.ivsep = coin.SoSeparator() self.ivsep.addChild(obj.ViewObject.RootNode.copy()) Tracker.__init__(self,children=[self.ivsep]) self.on() obj.ViewObject.hide() class editTracker(Tracker): "A node edit tracker" def __init__(self,pos=Vector(0,0,0),name="None",idx=0,objcol=None): color = coin.SoBaseColor() if objcol: color.rgb = objcol[:3] else: color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = coin.SoMarkerSet.SQUARE_FILLED_9_9 self.coords = coin.SoCoordinate3() # this is the coordinate self.coords.point.setValue((pos.x,pos.y,pos.z)) selnode = coin.SoType.fromName("SoFCSelection").createInstance() selnode.documentName.setValue(FreeCAD.ActiveDocument.Name) selnode.objectName.setValue(name) selnode.subElementName.setValue("EditNode"+str(idx)) node = coin.SoAnnotation() selnode.addChild(self.coords) selnode.addChild(color) selnode.addChild(self.marker) node.addChild(selnode) Tracker.__init__(self,children=[node],ontop=True) self.on() def set(self,pos): self.coords.point.setValue((pos.x,pos.y,pos.z)) def get(self): p = self.coords.point.getValues()[0] return Vector(p[0],p[1],p[2]) def move(self,delta): self.set(self.get().add(delta)) class PlaneTracker(Tracker): "A working plane tracker" def __init__(self): # getting screen distance p1 = FreeCADGui.ActiveDocument.ActiveView.getPoint((100,100)) p2 = FreeCADGui.ActiveDocument.ActiveView.getPoint((110,100)) bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange")/2) self.trans = coin.SoTransform() self.trans.translation.setValue([0,0,0]) m1 = coin.SoMaterial() m1.transparency.setValue(0.8) m1.diffuseColor.setValue([0.4,0.4,0.6]) c1 = coin.SoCoordinate3() c1.point.setValues([[-bl,-bl,0],[bl,-bl,0],[bl,bl,0],[-bl,bl,0]]) f = coin.SoIndexedFaceSet() f.coordIndex.setValues([0,1,2,3]) m2 = coin.SoMaterial() m2.transparency.setValue(0.7) m2.diffuseColor.setValue([0.2,0.2,0.3]) c2 = coin.SoCoordinate3() c2.point.setValues([[0,bl,0],[0,0,0],[bl,0,0],[-.05*bl,.95*bl,0],[0,bl,0], [.05*bl,.95*bl,0],[.95*bl,.05*bl,0],[bl,0,0],[.95*bl,-.05*bl,0]]) l = coin.SoLineSet() l.numVertices.setValues([3,3,3]) s = coin.SoSeparator() s.addChild(self.trans) s.addChild(m1) s.addChild(c1) s.addChild(f) s.addChild(m2) s.addChild(c2) s.addChild(l) Tracker.__init__(self,children=[s]) def set(self,pos=None): if pos: Q = plane.getRotation().Rotation.Q else: plm = plane.getPlacement() Q = plm.Rotation.Q pos = plm.Base self.trans.translation.setValue([pos.x,pos.y,pos.z]) self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) self.on() class wireTracker(Tracker): "A wire tracker" def __init__(self,wire): self.line = coin.SoLineSet() self.closed = fcgeo.isReallyClosed(wire) if self.closed: self.line.numVertices.setValue(len(wire.Vertexes)+1) else: self.line.numVertices.setValue(len(wire.Vertexes)) self.coords = coin.SoCoordinate3() self.update(wire) Tracker.__init__(self,children=[self.coords,self.line]) def update(self,wire): if wire: self.line.numVertices.setValue(len(wire.Vertexes)) for i in range(len(wire.Vertexes)): p=wire.Vertexes[i].Point self.coords.point.set1Value(i,[p.x,p.y,p.z]) if self.closed: t = len(wire.Vertexes) p = wire.Vertexes[0].Point self.coords.point.set1Value(t,[p.x,p.y,p.z]) class gridTracker(Tracker): "A grid tracker" def __init__(self): # self.space = 1 self.space = Draft.getParam("gridSpacing") # self.mainlines = 10 self.mainlines = Draft.getParam("gridEvery") self.numlines = 100 col = [0.2,0.2,0.3] self.trans = coin.SoTransform() self.trans.translation.setValue([0,0,0]) bound = (self.numlines/2)*self.space pts = [] mpts = [] for i in range(self.numlines+1): curr = -bound + i*self.space z = 0 if i/float(self.mainlines) == i/self.mainlines: mpts.extend([[-bound,curr,z],[bound,curr,z]]) mpts.extend([[curr,-bound,z],[curr,bound,z]]) else: pts.extend([[-bound,curr,z],[bound,curr,z]]) pts.extend([[curr,-bound,z],[curr,bound,z]]) idx = [] midx = [] for p in range(0,len(pts),2): idx.append(2) for mp in range(0,len(mpts),2): midx.append(2) mat1 = coin.SoMaterial() mat1.transparency.setValue(0.7) mat1.diffuseColor.setValue(col) self.coords1 = coin.SoCoordinate3() self.coords1.point.setValues(pts) lines1 = coin.SoLineSet() lines1.numVertices.setValues(idx) mat2 = coin.SoMaterial() mat2.transparency.setValue(0.3) mat2.diffuseColor.setValue(col) self.coords2 = coin.SoCoordinate3() self.coords2.point.setValues(mpts) lines2 = coin.SoLineSet() lines2.numVertices.setValues(midx) s = coin.SoSeparator() s.addChild(self.trans) s.addChild(mat1) s.addChild(self.coords1) s.addChild(lines1) s.addChild(mat2) s.addChild(self.coords2) s.addChild(lines2) Tracker.__init__(self,children=[s]) self.update() def update(self): bound = (self.numlines/2)*self.space pts = [] mpts = [] for i in range(self.numlines+1): curr = -bound + i*self.space if i/float(self.mainlines) == i/self.mainlines: mpts.extend([[-bound,curr,0],[bound,curr,0]]) mpts.extend([[curr,-bound,0],[curr,bound,0]]) else: pts.extend([[-bound,curr,0],[bound,curr,0]]) pts.extend([[curr,-bound,0],[curr,bound,0]]) self.coords1.point.setValues(pts) self.coords2.point.setValues(mpts) def setSpacing(self,space): self.space = space self.update() def setMainlines(self,ml): self.mainlines = ml self.update() def set(self): Q = plane.getRotation().Rotation.Q self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) self.on() def getClosestNode(self,point): "returns the closest node from the given point" # get the 2D coords. point = plane.projectPoint(point) u = fcvec.project(point,plane.u) lu = u.Length if u.getAngle(plane.u) > 1.5: lu = -lu v = fcvec.project(point,plane.v) lv = v.Length if v.getAngle(plane.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)) #--------------------------------------------------------------------------- # Helper tools #--------------------------------------------------------------------------- class SelectPlane: "The Draft_SelectPlane FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_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() self.offset = 0 self.ui = None self.call = None self.doc = FreeCAD.ActiveDocument if self.doc: FreeCAD.activeDraftCommand = self self.view = FreeCADGui.ActiveDocument.ActiveView 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) self.finish() else: self.call = self.view.addEventCallback("SoEvent", self.action) def action(self, arg): if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": self.finish() if arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): cursor = arg["Position"] doc = FreeCADGui.ActiveDocument info = doc.ActiveView.getObjectInfo((cursor[0],cursor[1])) if info: try: shape = doc.getObject(info["Object"]).Object.Shape component = getattr(shape, info["Component"]) if plane.alignToFace(component, self.offset) \ or plane.alignToCurve(component, self.offset): self.display(plane.axis) self.finish() except: pass def selectHandler(self, arg): try: self.offset = float(self.ui.offsetValue.text()) except: self.offset = 0 if arg == "XY": plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,0,1), self.offset) self.display('top') self.finish() elif arg == "XZ": plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,-1,0), self.offset) self.display('front') self.finish() elif arg == "YZ": plane.alignToPointAndAxis(Vector(0,0,0), Vector(1,0,0), self.offset) self.display('side') self.finish() elif arg == "currentView": viewDirection = fcvec.neg(self.view.getViewDirection()) plane.alignToPointAndAxis(Vector(0,0,0), viewDirection, self.offset) self.display(viewDirection) self.finish() elif arg == "reset": plane.reset() self.display('None') self.finish() def offsetHandler(self, arg): self.offset = arg def display(self,arg): if self.offset: if self.offset > 0: suffix = ' + '+str(self.offset) else: suffix = ' - '+str(self.offset) else: suffix = '' if type(arg).__name__ == 'str': self.ui.wplabel.setText(arg+suffix) elif type(arg).__name__ == 'Vector': plv = 'd('+str(arg.x)+','+str(arg.y)+','+str(arg.z)+')' self.ui.wplabel.setText(plv+suffix) def finish(self): if self.call: self.view.removeEventCallback("SoEvent",self.call) FreeCAD.activeDraftCommand = None if self.ui: self.ui.offUi() #--------------------------------------------------------------------------- # Geometry constructors #--------------------------------------------------------------------------- class Creator: "A generic Draft Creator Tool used by creation tools such as line or arc" def __init__(self): self.commitList = [] def Activated(self,name="None"): if FreeCAD.activeDraftCommand: FreeCAD.activeDraftCommand.finish() self.ui = None self.call = None self.doc = None self.support = None self.commitList = [] self.doc = FreeCAD.ActiveDocument self.view = FreeCADGui.ActiveDocument.ActiveView self.featureName = name if not self.doc: self.finish() else: FreeCAD.activeDraftCommand = self self.ui = FreeCADGui.draftToolBar self.ui.cross(True) 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(fcvec.neg(self.view.getViewDirection()), Vector(0,0,0), upv) self.node = [] self.pos = [] self.constrain = None self.obj = None self.snap = snapTracker() self.extsnap = lineTracker(dotted=True) self.planetrack = PlaneTracker() if Draft.getParam("grid"): self.grid = gridTracker() self.grid.set() else: self.grid = None def IsActive(self): if FreeCADGui.ActiveDocument: return True else: return False def finish(self): self.snap.finalize() self.extsnap.finalize() self.node=[] self.planetrack.finalize() if self.grid: self.grid.finalize() if self.support: plane.restore() FreeCAD.activeDraftCommand = None if self.ui: self.ui.offUi() self.ui.cross(False) self.ui.sourceCmd = None msg("") 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 partial actions to be committed to the FreeCAD document" self.commitList.append((name,func)) class Line(Creator): "The Line FreeCAD command definition" def __init__(self, wiremode=False): self.isWire = wiremode def GetResources(self): return {'Pixmap' : 'Draft_Line', 'Accel' : "L,I", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Line", "Line"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Line", "Creates a 2-point line. CTRL to snap, SHIFT to constrain")} def Activated(self,name="Line"): Creator.Activated(self,name) if self.doc: self.obj = None self.ui.lineUi() self.linetrack = lineTracker() self.constraintrack = lineTracker(dotted=True) self.obj=self.doc.addObject("Part::Feature",self.featureName) # self.obj.ViewObject.Selectable = False Draft.formatObject(self.obj) if not Draft.getParam("UiMode"): self.makeDumbTask() self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick first point:\n")) def makeDumbTask(self): "create a dumb taskdialog to prevent deleting the temp object" class TaskPanel: def __init__(self): pass def getStandardButtons(self): return 0 panel = TaskPanel() FreeCADGui.Control.showDialog(panel) def finish(self,closed=False,cont=False): "terminates the operation and closes the poly if asked" if not Draft.getParam("UiMode"): FreeCADGui.Control.closeDialog() if self.obj: old = self.obj.Name todo.delay(self.doc.removeObject,old) self.obj = None if (len(self.node) > 1): self.commit(translate("draft","Create Wire"), partial(Draft.makeWire,self.node,closed, face=self.ui.hasFill.isChecked(),support=self.support)) if self.ui: self.linetrack.finalize() self.constraintrack.finalize() Creator.finish(self) if cont and self.ui: if self.ui.continueMode: self.Activated() def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection point,ctrlPoint = getPoint(self,arg) self.ui.cross(True) self.linetrack.p2(point) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): if (arg["Position"] == self.pos): self.finish(False,cont=True) else: if not self.node: self.support = getSupport(arg) point,ctrlPoint = getPoint(self,arg) self.pos = arg["Position"] self.node.append(point) self.linetrack.p1(point) self.drawSegment(point) if (not self.isWire and len(self.node) == 2): self.finish(False,cont=True) if (len(self.node) > 2): # DNC: allows to close the curve # by placing ends close to each other # with tol = Draft tolerance # old code has been to insensitive # if fcvec.equals(point,self.node[0]): if ((point-self.node[0]).Length < Draft.tolerance()): self.undolast() self.finish(True,cont=True) msg(translate("draft", "Wire has been closed\n")) def undolast(self): "undoes last line segment" if (len(self.node) > 1): self.node.pop() last = self.node[len(self.node)-1] self.linetrack.p1(last) if self.obj.Shape.Edges: edges = self.obj.Shape.Edges if len(edges) > 1: edges.pop() newshape = Part.Wire(edges) else: newshape = Part.Shape() self.obj.Shape = newshape # DNC: report on removal msg(translate("draft", "Last point has been removed\n")) def drawSegment(self,point): "draws a new segment" if (len(self.node) == 1): self.linetrack.on() msg(translate("draft", "Pick next point:\n")) self.planetrack.set(self.node[0]) elif (len(self.node) == 2): last = self.node[len(self.node)-2] newseg = Part.Line(last,point).toShape() self.obj.Shape = newseg self.obj.ViewObject.Visibility = True if self.isWire: msg(translate("draft", "Pick next point, or (F)inish or (C)lose:\n")) else: currentshape = self.obj.Shape last = self.node[len(self.node)-2] newseg = Part.Line(last,point).toShape() newshape=currentshape.fuse(newseg) self.obj.Shape = newshape msg(translate("draft", "Pick next point, or (F)inish or (C)lose:\n")) def wipe(self): "removes all previous segments and starts from last point" if len(self.node) > 1: print "nullifying" # self.obj.Shape.nullify() - for some reason this fails self.obj.ViewObject.Visibility = False self.node = [self.node[-1]] print "setting trackers" self.linetrack.p1(self.node[0]) self.planetrack.set(self.node[0]) msg(translate("draft", "Pick next point:\n")) print "done" def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" point = Vector(numx,numy,numz) self.node.append(point) self.linetrack.p1(point) self.drawSegment(point) if (not self.isWire and len(self.node) == 2): self.finish(False,cont=True) if self.ui.xValue.isEnabled(): self.ui.xValue.setFocus() self.ui.xValue.selectAll() elif self.ui.yValue.isEnabled(): self.ui.yValue.setFocus() self.ui.yValue.selectAll() else: self.ui.zValue.setFocus() self.ui.zValue.selectAll() class Wire(Line): "a FreeCAD command for creating a wire" def __init__(self): Line.__init__(self,wiremode=True) def GetResources(self): return {'Pixmap' : 'Draft_Wire', 'Accel' : "W, I", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Wire", "Wire"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Wire", "Creates a multiple-point wire. CTRL to snap, SHIFT to constrain")} class BSpline(Line): "a FreeCAD command for creating a b-spline" def __init__(self): Line.__init__(self,wiremode=True) def GetResources(self): return {'Pixmap' : 'Draft_BSpline', 'Accel' : "B, S", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_BSpline", "B-Spline"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_BSpline", "Creates a multiple-point b-spline. CTRL to snap, SHIFT to constrain")} def Activated(self): Line.Activated(self,"BSpline") if self.doc: self.bsplinetrack = bsplineTracker() def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection point,ctrlPoint = getPoint(self,arg) self.ui.cross(True) self.bsplinetrack.update(self.node + [point]) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): if (arg["Position"] == self.pos): self.finish(False,cont=True) else: if not self.node: self.support = getSupport(arg) point,ctrlPoint = getPoint(self,arg) self.pos = arg["Position"] self.node.append(point) self.drawUpdate(point) if (not self.isWire and len(self.node) == 2): self.finish(False,cont=True) if (len(self.node) > 2): # DNC: allows to close the curve # by placing ends close to each other # with tol = Draft tolerance # old code has been to insensitive if ((point-self.node[0]).Length < Draft.tolerance()): self.undolast() self.finish(True,cont=True) msg(translate("draft", "Spline has been closed\n")) def undolast(self): "undoes last line segment" if (len(self.node) > 1): self.node.pop() self.bsplinetrack.update(self.node) spline = Part.BSplineCurve() spline.interpolate(self.node, False) self.obj.Shape = spline.toShape() msg(translate("draft", "Last point has been removed\n")) def drawUpdate(self,point): if (len(self.node) == 1): self.bsplinetrack.on() self.planetrack.set(self.node[0]) msg(translate("draft", "Pick next point:\n")) else: spline = Part.BSplineCurve() spline.interpolate(self.node, False) self.obj.Shape = spline.toShape() msg(translate("draft", "Pick next point, or (F)inish or (C)lose:\n")) def finish(self,closed=False,cont=False): "terminates the operation and closes the poly if asked" if not Draft.getParam("UiMode"): FreeCADGui.Control.closeDialog() if (len(self.node) > 1): old = self.obj.Name self.doc.removeObject(old) self.commit(translate("draft","Create BSpline"), partial(Draft.makeBSpline,self.node,closed, face=self.ui.hasFill.isChecked(),support=self.support)) if self.ui: self.bsplinetrack.finalize() self.constraintrack.finalize() Creator.finish(self) if cont and self.ui: if self.ui.continueMode: self.Activated() class FinishLine: "a FreeCAD command to finish any running Line drawing operation" def Activated(self): if (FreeCAD.activeDraftCommand != None): if (FreeCAD.activeDraftCommand.featureName == "Line"): FreeCAD.activeDraftCommand.finish(False) def GetResources(self): return {'Pixmap' : 'Draft_Finish', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_FinishLine", "Finish line"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_FinishLine", "Finishes a line without closing it")} def IsActive(self): if FreeCAD.activeDraftCommand: if FreeCAD.activeDraftCommand.featureName == "Line": return True return False class CloseLine: "a FreeCAD command to close any running Line drawing operation" def Activated(self): if (FreeCAD.activeDraftCommand != None): if (FreeCAD.activeDraftCommand.featureName == "Line"): FreeCAD.activeDraftCommand.finish(True) def GetResources(self): return {'Pixmap' : 'Draft_Lock', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_CloseLine", "Close Line"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_CloseLine", "Closes the line being drawn")} def IsActive(self): if FreeCAD.activeDraftCommand: if FreeCAD.activeDraftCommand.featureName == "Line": return True return False class UndoLine: "a FreeCAD command to undo last drawn segment of a line" def Activated(self): if (FreeCAD.activeDraftCommand != None): if (FreeCAD.activeDraftCommand.featureName == "Line"): FreeCAD.activeDraftCommand.undolast() def GetResources(self): return {'Pixmap' : 'Draft_Rotate', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_UndoLine", "Undo last segment"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_UndoLine", "Undoes the last drawn segment of the line being drawn")} def IsActive(self): if FreeCAD.activeDraftCommand: if FreeCAD.activeDraftCommand.featureName == "Line": return True return False class Rectangle(Creator): "the Draft_Rectangle FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_Rectangle', 'Accel' : "R, E", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Rectangle", "Rectangle"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Rectangle", "Creates a 2-point rectangle. CTRL to snap")} def Activated(self): Creator.Activated(self,"Rectangle") if self.ui: self.refpoint = None self.ui.pointUi() self.ui.extUi() self.call = self.view.addEventCallback("SoEvent",self.action) self.rect = rectangleTracker() msg(translate("draft", "Pick first point:\n")) def finish(self,closed=False,cont=False): "terminates the operation and closes the poly if asked" Creator.finish(self) if self.ui: self.rect.off() self.rect.finalize() if cont and self.ui: if self.ui.continueMode: self.Activated() def createObject(self): "creates the final object in the current doc" p1 = self.node[0] p3 = self.node[-1] diagonal = p3.sub(p1) p2 = p1.add(fcvec.project(diagonal, plane.v)) p4 = p1.add(fcvec.project(diagonal, plane.u)) length = p4.sub(p1).Length if abs(fcvec.angle(p4.sub(p1),plane.u,plane.axis)) > 1: length = -length height = p2.sub(p1).Length if abs(fcvec.angle(p2.sub(p1),plane.v,plane.axis)) > 1: height = -height p = plane.getRotation() p.move(p1) self.commit(translate("draft","Create Rectangle"), partial(Draft.makeRectangle,length,height, p,self.ui.hasFill.isChecked(),support=self.support)) self.finish(cont=True) def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection point,ctrlPoint = getPoint(self,arg,mobile=True) self.rect.update(point) self.ui.cross(True) elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): if (arg["Position"] == self.pos): self.finish() else: if not self.node: self.support = getSupport(arg) point,ctrlPoint = getPoint(self,arg) self.appendPoint(point) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" point = Vector(numx,numy,numz) self.appendPoint(point) def appendPoint(self,point): self.node.append(point) if (len(self.node) > 1): self.rect.update(point) self.createObject() else: msg(translate("draft", "Pick opposite point:\n")) self.ui.isRelative.show() self.rect.setorigin(point) self.rect.on() self.planetrack.set(point) class Arc(Creator): "the Draft_Arc FreeCAD command definition" def __init__(self): self.closedCircle=False self.featureName = "Arc" def GetResources(self): return {'Pixmap' : 'Draft_Arc', 'Accel' : "A, R", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Arc", "Arc"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Arc", "Creates an arc. CTRL to snap, SHIFT to constrain")} def Activated(self): Creator.Activated(self,self.featureName) if self.ui: self.step = 0 self.center = None self.rad = None self.angle = 0 # angle inscribed by arc self.tangents = [] self.tanpoints = [] if self.featureName == "Arc": self.ui.arcUi() else: self.ui.circleUi() self.altdown = False self.ui.sourceCmd = self self.linetrack = lineTracker(dotted=True) self.constraintrack = lineTracker(dotted=True) self.arctrack = arcTracker() self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick center point:\n")) def finish(self,closed=False,cont=False): "finishes the arc" Creator.finish(self) if self.ui: self.linetrack.finalize() self.constraintrack.finalize() self.arctrack.finalize() self.doc.recompute() if cont and self.ui: if self.ui.continueMode: self.Activated() def updateAngle(self, angle): # previous absolute angle lastangle = self.firstangle + self.angle if lastangle <= -2*math.pi: lastangle += 2*math.pi if lastangle >= 2*math.pi: lastangle -= 2*math.pi # compute delta = change in angle: d0 = angle-lastangle d1 = d0 + 2*math.pi d2 = d0 - 2*math.pi if abs(d0) < min(abs(d1), abs(d2)): delta = d0 elif abs(d1) < abs(d2): delta = d1 else: delta = d2 newangle = self.angle + delta # normalize angle, preserving direction if newangle >= 2*math.pi: newangle -= 2*math.pi if newangle <= -2*math.pi: newangle += 2*math.pi self.angle = newangle def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": point,ctrlPoint = getPoint(self,arg) # this is to make sure radius is what you see on screen self.ui.cross(True) if self.center and fcvec.dist(point,self.center) > 0: viewdelta = fcvec.project(point.sub(self.center), plane.axis) if not fcvec.isNull(viewdelta): point = point.add(fcvec.neg(viewdelta)) if (self.step == 0): # choose center if hasMod(arg,MODALT): if not self.altdown: self.ui.cross(False) self.altdown = True self.ui.switchUi(True) else: if self.altdown: self.ui.cross(True) self.altdown = False self.ui.switchUi(False) elif (self.step == 1): # choose radius if len(self.tangents) == 2: cir = fcgeo.circleFrom2tan1pt(self.tangents[0], self.tangents[1], point) self.center = fcgeo.findClosestCircle(point,cir).Center self.arctrack.setCenter(self.center) elif self.tangents and self.tanpoints: cir = fcgeo.circleFrom1tan2pt(self.tangents[0], self.tanpoints[0], point) self.center = fcgeo.findClosestCircle(point,cir).Center self.arctrack.setCenter(self.center) if hasMod(arg,MODALT): if not self.altdown: self.ui.cross(False) self.altdown = True snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: ob = self.doc.getObject(snapped['Object']) num = int(snapped['Component'].lstrip('Edge'))-1 ed = ob.Shape.Edges[num] if len(self.tangents) == 2: cir = fcgeo.circleFrom3tan(self.tangents[0], self.tangents[1], ed) cl = fcgeo.findClosestCircle(point,cir) self.center = cl.Center self.rad = cl.Radius self.arctrack.setCenter(self.center) else: self.rad = self.center.add(fcgeo.findDistance(self.center,ed).sub(self.center)).Length else: self.rad = fcvec.dist(point,self.center) else: if self.altdown: self.ui.cross(True) self.altdown = False self.rad = fcvec.dist(point,self.center) self.ui.setRadiusValue(self.rad) self.arctrack.setRadius(self.rad) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() self.linetrack.p1(self.center) self.linetrack.p2(point) self.linetrack.on() elif (self.step == 2): # choose first angle currentrad = fcvec.dist(point,self.center) if currentrad != 0: angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis) else: angle = 0 self.linetrack.p2(fcvec.scaleTo(point.sub(self.center),self.rad).add(self.center)) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() self.ui.setRadiusValue(math.degrees(angle)) self.firstangle = angle else: # choose second angle currentrad = fcvec.dist(point,self.center) if currentrad != 0: angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis) else: angle = 0 self.linetrack.p2(fcvec.scaleTo(point.sub(self.center),self.rad).add(self.center)) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() self.ui.setRadiusValue(math.degrees(angle)) self.updateAngle(angle) self.arctrack.setApertureAngle(self.angle) elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,ctrlPoint = getPoint(self,arg) # this is to make sure radius is what you see on screen if self.center and fcvec.dist(point,self.center) > 0: viewdelta = fcvec.project(point.sub(self.center), plane.axis) if not fcvec.isNull(viewdelta): point = point.add(fcvec.neg(viewdelta)) if (self.step == 0): # choose center self.support = getSupport(arg) if hasMod(arg,MODALT): snapped=self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: ob = self.doc.getObject(snapped['Object']) num = int(snapped['Component'].lstrip('Edge'))-1 ed = ob.Shape.Edges[num] self.tangents.append(ed) if len(self.tangents) == 2: self.arctrack.on() self.ui.radiusUi() self.step = 1 self.linetrack.on() msg(translate("draft", "Pick radius:\n")) else: if len(self.tangents) == 1: self.tanpoints.append(point) else: self.center = point self.node = [point] self.arctrack.setCenter(self.center) self.linetrack.p1(self.center) self.linetrack.p2(self.view.getPoint(arg["Position"][0],arg["Position"][1])) self.arctrack.on() self.ui.radiusUi() self.step = 1 self.linetrack.on() msg(translate("draft", "Pick radius:\n")) self.planetrack.set(point) elif (self.step == 1): # choose radius if self.closedCircle: self.ui.cross(False) self.drawArc() else: self.ui.labelRadius.setText("Start angle") self.linetrack.p1(self.center) self.linetrack.on() self.step = 2 msg(translate("draft", "Pick start angle:\n")) elif (self.step == 2): # choose first angle self.ui.labelRadius.setText("Aperture") self.step = 3 # scale center->point vector for proper display u = fcvec.scaleTo(point.sub(self.center), self.rad) self.arctrack.setStartAngle(self.firstangle) msg(translate("draft", "Pick aperture:\n")) else: # choose second angle self.step = 4 self.drawArc() def drawArc(self): "actually draws the FreeCAD object" p = plane.getRotation() p.move(self.center) if self.closedCircle: self.commit(translate("draft","Create Circle"), partial(Draft.makeCircle,self.rad,p, self.ui.hasFill.isChecked(),support=self.support)) else: sta = math.degrees(self.firstangle) end = math.degrees(self.firstangle+self.angle) print "debug:",sta, end if end < sta: sta,end = end,sta self.commit(translate("draft","Create Arc"), partial(Draft.makeCircle,self.rad,p,self.ui.hasFill.isChecked(), sta,end,support=self.support)) self.finish(cont=True) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" self.center = Vector(numx,numy,numz) self.node = [self.center] self.arctrack.setCenter(self.center) self.arctrack.on() self.ui.radiusUi() self.step = 1 self.ui.radiusValue.setFocus() msg(translate("draft", "Pick radius:\n")) def numericRadius(self,rad): "this function gets called by the toolbar when valid radius have been entered there" if (self.step == 1): self.rad = rad if len(self.tangents) == 2: cir = fcgeo.circleFrom2tan1rad(self.tangents[0], self.tangents[1], rad) if self.center: self.center = fcgeo.findClosestCircle(self.center,cir).Center else: self.center = cir[-1].Center elif self.tangents and self.tanpoints: cir = fcgeo.circleFrom1tan1pt1rad(self.tangents[0],self.tanpoints[0],rad) if self.center: self.center = fcgeo.findClosestCircle(self.center,cir).Center else: self.center = cir[-1].Center if self.closedCircle: self.drawArc() else: self.step = 2 self.arctrack.setCenter(self.center) self.ui.labelRadius.setText("Start angle") self.linetrack.p1(self.center) self.linetrack.on() self.ui.radiusValue.setText("") self.ui.radiusValue.setFocus() msg(translate("draft", "Pick start angle:\n")) elif (self.step == 2): self.ui.labelRadius.setText("Aperture") self.firstangle = math.radians(rad) if fcvec.equals(plane.axis, Vector(1,0,0)): u = Vector(0,self.rad,0) else: u = fcvec.scaleTo(Vector(1,0,0).cross(plane.axis), self.rad) urotated = fcvec.rotate(u, math.radians(rad), plane.axis) self.arctrack.setStartAngle(self.firstangle) self.step = 3 self.ui.radiusValue.setText("") self.ui.radiusValue.setFocus() msg(translate("draft", "Aperture angle:\n")) else: self.updateAngle(rad) self.angle = math.radians(rad) self.step = 4 self.drawArc() class Circle(Arc): "The Draft_Circle FreeCAD command definition" def __init__(self): self.closedCircle=True self.featureName = "Circle" def GetResources(self): return {'Pixmap' : 'Draft_Circle', 'Accel' : "C, I", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Circle", "Circle"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Circle", "Creates a circle. CTRL to snap, ALT to select tangent objects")} class Polygon(Creator): "the Draft_Polygon FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_Polygon', 'Accel' : "P, G", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Polygon", "Polygon"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Polygon", "Creates a regular polygon. CTRL to snap, SHIFT to constrain")} def Activated(self): Creator.Activated(self,"Polygon") if self.ui: self.step = 0 self.center = None self.rad = None self.tangents = [] self.tanpoints = [] self.ui.pointUi() self.ui.extUi() self.ui.numFaces.show() self.altdown = False self.ui.sourceCmd = self self.linetrack = lineTracker(dotted=True) self.constraintrack = lineTracker(dotted=True) self.arctrack = arcTracker() self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick center point:\n")) def finish(self,closed=False,cont=False): "finishes the arc" Creator.finish(self) if self.ui: self.linetrack.finalize() self.constraintrack.finalize() self.arctrack.finalize() self.doc.recompute() if cont and self.ui: if self.ui.continueMode: self.Activated() def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": point,ctrlPoint = getPoint(self,arg) # this is to make sure radius is what you see on screen self.ui.cross(True) if self.center and fcvec.dist(point,self.center) > 0: viewdelta = fcvec.project(point.sub(self.center), plane.axis) if not fcvec.isNull(viewdelta): point = point.add(fcvec.neg(viewdelta)) if (self.step == 0): # choose center if hasMod(arg,MODALT): if not self.altdown: self.ui.cross(False) self.altdown = True self.ui.switchUi(True) else: if self.altdown: self.ui.cross(True) self.altdown = False self.ui.switchUi(False) else: # choose radius if len(self.tangents) == 2: cir = fcgeo.circleFrom2tan1pt(self.tangents[0], self.tangents[1], point) self.center = fcgeo.findClosestCircle(point,cir).Center self.arctrack.setCenter(self.center) elif self.tangents and self.tanpoints: cir = fcgeo.circleFrom1tan2pt(self.tangents[0], self.tanpoints[0], point) self.center = fcgeo.findClosestCircle(point,cir).Center self.arctrack.setCenter(self.center) if hasMod(arg,MODALT): if not self.altdown: self.ui.cross(False) self.altdown = True snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: ob = self.doc.getObject(snapped['Object']) num = int(snapped['Component'].lstrip('Edge'))-1 ed = ob.Shape.Edges[num] if len(self.tangents) == 2: cir = fcgeo.circleFrom3tan(self.tangents[0], self.tangents[1], ed) cl = fcgeo.findClosestCircle(point,cir) self.center = cl.Center self.rad = cl.Radius self.arctrack.setCenter(self.center) else: self.rad = self.center.add(fcgeo.findDistance(self.center,ed).sub(self.center)).Length else: self.rad = fcvec.dist(point,self.center) else: if self.altdown: self.ui.cross(True) self.altdown = False self.rad = fcvec.dist(point,self.center) self.ui.setRadiusValue(self.rad) self.arctrack.setRadius(self.rad) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() self.linetrack.p1(self.center) self.linetrack.p2(point) self.linetrack.on() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,ctrlPoint = getPoint(self,arg) # this is to make sure radius is what you see on screen if self.center and fcvec.dist(point,self.center) > 0: viewdelta = fcvec.project(point.sub(self.center), plane.axis) if not fcvec.isNull(viewdelta): point = point.add(fcvec.neg(viewdelta)) if (self.step == 0): # choose center if not self.node: self.support = getSupport(arg) if hasMod(arg,MODALT): snapped=self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: ob = self.doc.getObject(snapped['Object']) num = int(snapped['Component'].lstrip('Edge'))-1 ed = ob.Shape.Edges[num] self.tangents.append(ed) if len(self.tangents) == 2: self.arctrack.on() self.ui.radiusUi() self.step = 1 self.linetrack.on() msg(translate("draft", "Pick radius:\n")) else: if len(self.tangents) == 1: self.tanpoints.append(point) else: self.center = point self.node = [point] self.arctrack.setCenter(self.center) self.linetrack.p1(self.center) self.linetrack.p2(self.view.getPoint(arg["Position"][0],arg["Position"][1])) self.arctrack.on() self.ui.radiusUi() self.step = 1 self.linetrack.on() msg(translate("draft", "Pick radius:\n")) self.planetrack.set(point) elif (self.step == 1): # choose radius self.ui.cross(False) self.drawPolygon() def drawPolygon(self): "actually draws the FreeCAD object" p = plane.getRotation() p.move(self.center) self.commit(translate("draft","Create Polygon"), partial(Draft.makePolygon,self.ui.numFaces.value(),self.rad, True,p,face=self.ui.hasFill.isChecked(),support=self.support)) self.finish(cont=True) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" self.center = Vector(numx,numy,numz) self.node = [self.center] self.arctrack.setCenter(self.center) self.arctrack.on() self.ui.radiusUi() self.step = 1 self.ui.radiusValue.setFocus() msg(translate("draft", "Pick radius:\n")) def numericRadius(self,rad): "this function gets called by the toolbar when valid radius have been entered there" self.rad = rad if len(self.tangents) == 2: cir = fcgeo.circleFrom2tan1rad(self.tangents[0], self.tangents[1], rad) if self.center: self.center = fcgeo.findClosestCircle(self.center,cir).Center else: self.center = cir[-1].Center elif self.tangents and self.tanpoints: cir = fcgeo.circleFrom1tan1pt1rad(self.tangents[0],self.tanpoints[0],rad) if self.center: self.center = fcgeo.findClosestCircle(self.center,cir).Center else: self.center = cir[-1].Center self.drawPolygon() class Text(Creator): "This class creates an annotation feature." def GetResources(self): return {'Pixmap' : 'Draft_Text', 'Accel' : "T, E", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Text", "Text"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Text", "Creates an annotation. CTRL to snap")} def Activated(self): Creator.Activated(self,"Text") if self.ui: self.dialog = None self.text = '' self.ui.sourceCmd = self self.ui.pointUi() self.call = self.view.addEventCallback("SoEvent",self.action) self.ui.xValue.setFocus() self.ui.xValue.selectAll() msg(translate("draft", "Pick location point:\n")) FreeCADGui.draftToolBar.show() def finish(self,closed=False,cont=False): "terminates the operation" Creator.finish(self) if self.ui: del self.dialog if cont and self.ui: if self.ui.continueMode: self.Activated() def createObject(self): "creates an object in the current doc" self.commit(translate("draft","Create Text"), partial(Draft.makeText,self.text,self.node[0])) self.finish(cont=True) def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection point,ctrlPoint = getPoint(self,arg) elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,dtrlPoint = getPoint(self,arg) self.node.append(point) self.ui.textUi() self.ui.textValue.setFocus() self.ui.cross(False) def numericInput(self,numx,numy,numz): '''this function gets called by the toolbar when valid x, y, and z have been entered there''' point = Vector(numx,numy,numz) self.node.append(point) self.ui.textUi() self.ui.textValue.setFocus() self.ui.cross(False) class Dimension(Creator): "The Draft_Dimension FreeCAD command definition" def __init__(self): self.max=2 self.cont = None self.dir = None def GetResources(self): return {'Pixmap' : 'Draft_Dimension', 'Accel' : "D, I", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Dimension", "Dimension"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Dimension", "Creates a dimension. CTRL to snap, SHIFT to constrain, ALT to select a segment")} def Activated(self): if self.cont: self.finish() elif self.hasMeasures(): Creator.Activated(self,"Dimension") self.dimtrack = dimTracker() self.arctrack = arcTracker() self.constraintrack = lineTracker(dotted=True) self.createOnMeasures() self.finish() else: Creator.Activated(self,"Dimension") if self.ui: self.ui.pointUi() self.ui.continueCmd.show() self.altdown = False self.call = self.view.addEventCallback("SoEvent",self.action) self.dimtrack = dimTracker() self.arctrack = arcTracker() self.link = None self.edges = [] self.pts = [] self.angledata = None self.indices = [] self.center = None self.arcmode = False self.point2 = None self.constraintrack = lineTracker(dotted=True) msg(translate("draft", "Pick first point:\n")) FreeCADGui.draftToolBar.show() def hasMeasures(self): "checks if only measurements objects are selected" sel = FreeCADGui.Selection.getSelection() if not sel: return False for o in sel: if not o.isDerivedFrom("App::MeasureDistance"): return False return True def finish(self,closed=False): "terminates the operation" self.cont = None self.dir = None Creator.finish(self) if self.ui: self.dimtrack.finalize() self.arctrack.finalize() self.constraintrack.finalize() def createOnMeasures(self): for o in FreeCADGui.Selection.getSelection(): p1 = o.P1 p2 = o.P2 pt = o.ViewObject.RootNode.getChildren()[1].getChildren()[0].getChildren()[0].getChildren()[3] p3 = Vector(pt.point.getValues()[2].getValue()) self.commit(translate("draft","Create Dimension"), partial(Draft.makeDimension,p1,p2,p3)) self.commit(translate("draft","Delete Measurement"), partial(FreeCAD.ActiveDocument.removeObject,o.Name)) def createObject(self): "creates an object in the current doc" if self.angledata: self.commit(translate("draft","Create Dimension"), partial(Draft.makeAngularDimension,self.center, self.angledata,self.node[-1])) elif self.link and (not self.arcmode): self.commit(translate("draft","Create Dimension"), partial(Draft.makeDimension,self.link[0],self.link[1], self.link[2],self.node[2])) elif self.arcmode: self.commit(translate("draft","Create Dimension"), partial(Draft.makeDimension,self.link[0],self.link[1], self.arcmode,self.node[2])) else: self.commit(translate("draft","Create Dimension"), partial(Draft.makeDimension,self.node[0],self.node[1], self.node[2])) if self.ui.continueMode: self.cont = self.node[2] if not self.dir: if self.link: v1 = self.link[0].Shape.Vertexes[self.link[1]].Point v2 = self.link[0].Shape.Vertexes[self.link[2]].Point self.dir = v2.sub(v1) else: self.dir = self.node[1].sub(self.node[0]) self.node = [self.node[1]] self.link = None def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection shift = hasMod(arg,MODCONSTRAIN) if self.arcmode or self.point2: setMod(arg,MODCONSTRAIN,False) point,ctrlPoint = getPoint(self,arg) self.ui.cross(True) if hasMod(arg,MODALT) and (len(self.node)<3): self.ui.cross(False) self.dimtrack.off() if not self.altdown: self.altdown = True self.ui.switchUi(True) snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: ob = self.doc.getObject(snapped['Object']) if "Edge" in snapped['Component']: num = int(snapped['Component'].lstrip('Edge'))-1 ed = ob.Shape.Edges[num] v1 = ed.Vertexes[0].Point v2 = ed.Vertexes[-1].Point self.dimtrack.update([v1,v2,self.cont]) else: self.ui.cross(True) if self.node and (len(self.edges) < 2): self.dimtrack.on() if len(self.edges) == 2: # angular dimension self.dimtrack.off() r = point.sub(self.center) self.arctrack.setRadius(r.Length) a = self.arctrack.getAngle(point) pair = fcgeo.getBoundaryAngles(a,self.pts) if not (pair[0] < a < pair[1]): self.angledata = [4*math.pi-pair[0],2*math.pi-pair[1]] else: self.angledata = [2*math.pi-pair[0],2*math.pi-pair[1]] self.arctrack.setStartAngle(self.angledata[0]) self.arctrack.setEndAngle(self.angledata[1]) if self.altdown: self.altdown = False self.ui.switchUi(False) if self.dir: point = self.node[0].add(fcvec.project(point.sub(self.node[0]),self.dir)) if len(self.node) == 2: if self.arcmode and self.edges: cen = self.edges[0].Curve.Center rad = self.edges[0].Curve.Radius baseray = point.sub(cen) v2 = fcvec.scaleTo(baseray,rad) v1 = fcvec.neg(v2) if shift: self.node = [cen,cen.add(v2)] self.arcmode = "radius" else: self.node = [cen.add(v1),cen.add(v2)] self.arcmode = "diameter" self.dimtrack.update(self.node) # Draw constraint tracker line. if shift and (not self.arcmode): if len(self.node) == 2: if not self.point2: self.point2 = self.node[1] else: self.node[1] = self.point2 a=abs(point.sub(self.node[0]).getAngle(plane.u)) if (a > math.pi/4) and (a <= 0.75*math.pi): self.node[1] = Vector(self.node[0].x,self.node[1].y,self.node[0].z) else: self.node[1] = Vector(self.node[1].x,self.node[0].y,self.node[0].z) self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: if self.point2: self.node[1] = self.point2 self.point2 = None self.constraintrack.off() # update the dimline if self.node and (not self.arcmode): self.dimtrack.update(self.node+[point]+[self.cont]) elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,ctrlPoint = getPoint(self,arg) if not self.node: self.support = getSupport(arg) if hasMod(arg,MODALT) and (len(self.node)<3): snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: ob = self.doc.getObject(snapped['Object']) if 'Edge' in snapped['Component']: num = int(snapped['Component'].lstrip('Edge'))-1 ed = ob.Shape.Edges[num] v1 = ed.Vertexes[0].Point v2 = ed.Vertexes[-1].Point i1 = i2 = None for i in range(len(ob.Shape.Vertexes)): if v1 == ob.Shape.Vertexes[i].Point: i1 = i if v2 == ob.Shape.Vertexes[i].Point: i2 = i if (i1 != None) and (i2 != None): self.indices.append(num) if not self.edges: # nothing snapped yet, we treat it as normal edge-snapped dimension self.node = [v1,v2] self.link = [ob,i1,i2] self.edges.append(ed) if isinstance(ed.Curve,Part.Circle): # snapped edge is an arc self.arcmode = "diameter" self.link = [ob,num] else: # there is already a snapped edge, so we start angular dimension self.edges.append(ed) self.node.extend([v1,v2]) # self.node now has the 4 endpoints c = fcgeo.findIntersection(self.node[0], self.node[1], self.node[2], self.node[3], True,True) if c: self.center = c[0] self.arctrack.setCenter(self.center) self.arctrack.on() for e in self.edges: for v in e.Vertexes: self.pts.append(self.arctrack.getAngle(v.Point)) self.link = [self.link[0],ob] else: msg(translate("draft", "Edges don't intersect!\n")) self.finish() self.dimtrack.on() else: if self.dir: point = self.node[0].add(fcvec.project(point.sub(self.node[0]),self.dir)) self.node.append(point) self.dimtrack.update(self.node) if (len(self.node) == 2): self.point2 = self.node[1] if (len(self.node) == 1): self.dimtrack.on() self.planetrack.set(self.node[0]) elif (len(self.node) == 2) and self.cont: self.node.append(self.cont) self.createObject() if not self.cont: self.finish() elif (len(self.node) == 3): # for unlinked arc mode: # if self.arcmode: # v = self.node[1].sub(self.node[0]) # v = fcvec.scale(v,0.5) # cen = self.node[0].add(v) # self.node = [self.node[0],self.node[1],cen] self.createObject() if not self.cont: self.finish() elif self.angledata: self.node.append(point) self.createObject() self.finish() def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" point = Vector(numx,numy,numz) self.node.append(point) self.dimtrack.update(self.node) if (len(self.node) == 1): self.dimtrack.on() elif (len(self.node) == 3): self.createObject() if not self.cont: self.finish() #--------------------------------------------------------------------------- # Modifier functions #--------------------------------------------------------------------------- class Modifier: "A generic Modifier Tool, used by modification tools such as move" def __init__(self): self.commitList = [] def Activated(self,name="None"): if FreeCAD.activeDraftCommand: FreeCAD.activeDraftCommand.finish() self.ui = None self.call = None self.commitList = [] self.doc = FreeCAD.ActiveDocument if not self.doc: self.finish() else: FreeCAD.activeDraftCommand = self self.view = FreeCADGui.ActiveDocument.ActiveView self.ui = FreeCADGui.draftToolBar FreeCADGui.draftToolBar.show() rot = self.view.getCameraNode().getField("orientation").getValue() upv = Vector(rot.multVec(coin.SbVec3f(0,1,0)).getValue()) plane.setup(fcvec.neg(self.view.getViewDirection()), Vector(0,0,0), upv) self.node = [] self.ui.sourceCmd = self 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 = PlaneTracker() if Draft.getParam("grid"): self.grid = gridTracker() self.grid.set() else: self.grid = None def IsActive(self): if FreeCADGui.ActiveDocument: return True else: return False 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("") self.planetrack.finalize() if self.grid: self.grid.finalize() 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 partial actions to be committed to the FreeCAD document" # print "committing" self.commitList.append((name,func)) class Move(Modifier): "The Draft_Move FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_Move', 'Accel' : "M, V", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Move", "Move"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Move", "Moves the selected objects between 2 points. CTRL to snap, SHIFT to constrain, ALT to copy")} def Activated(self): Modifier.Activated(self,"Move") if self.ui: if not Draft.getSelection(): self.ghost = None self.linetrack = None self.constraintrack = None self.ui.selectUi() msg(translate("draft", "Select an object to move\n")) self.call = self.view.addEventCallback("SoEvent",selectObject) else: self.proceed() def proceed(self): if self.call: self.view.removeEventCallback("SoEvent",self.call) self.sel = Draft.getSelection() self.sel = Draft.getGroupContents(self.sel) self.ui.pointUi() self.ui.modUi() self.ui.xValue.setFocus() self.ui.xValue.selectAll() self.linetrack = lineTracker() self.constraintrack = lineTracker(dotted=True) self.ghost = ghostTracker(self.sel) self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick start point:\n")) self.ui.cross(True) def finish(self,closed=False,cont=False): if self.ui: self.ghost.finalize() self.linetrack.finalize() self.constraintrack.finalize() Modifier.finish(self) if cont and self.ui: if self.ui.continueMode: FreeCADGui.Selection.clearSelection() self.Activated() def move(self,delta,copy=False): "moving the real shapes" if copy: self.commit(translate("draft","Copy"),partial(Draft.move,self.sel,delta,copy)) else: self.commit(translate("draft","Move"),partial(Draft.move,self.sel,delta,copy)) self.doc.recompute() def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection point,ctrlPoint = getPoint(self,arg) self.linetrack.p2(point) self.ui.cross(True) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() if (len(self.node) > 0): last = self.node[len(self.node)-1] delta = point.sub(last) self.ghost.trans.translation.setValue([delta.x,delta.y,delta.z]) if self.extendedCopy: if not hasMod(arg,MODALT): self.finish() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,ctrlPoint = getPoint(self,arg) if (self.node == []): self.node.append(point) self.ui.isRelative.show() self.linetrack.on() self.ghost.on() self.linetrack.p1(point) msg(translate("draft", "Pick end point:\n")) self.planetrack.set(point) else: last = self.node[0] if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): self.move(point.sub(last),True) else: self.move(point.sub(last)) if hasMod(arg,MODALT): self.extendedCopy = True else: self.finish(cont=True) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" point = Vector(numx,numy,numz) if not self.node: self.node.append(point) self.ui.isRelative.show() self.ui.isCopy.show() self.linetrack.p1(point) self.linetrack.on() self.ghost.on() msg(translate("draft", "Pick end point:\n")) else: last = self.node[-1] if self.ui.isCopy.isChecked(): self.move(point.sub(last),True) else: self.move(point.sub(last)) self.finish() class ApplyStyle(Modifier): "The Draft_ApplyStyle FreeCA command definition" def GetResources(self): return {'Pixmap' : 'Draft_Apply', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ApplyStyle", "Apply Current Style"), 'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_ApplyStyle", "Applies current line width and color to selected objects")} def IsActive(self): if Draft.getSelection(): return True else: return False def Activated(self): Modifier.Activated(self) if self.ui: self.sel = Draft.getSelection() if (len(self.sel)>0): for ob in self.sel: if (ob.Type == "App::DocumentObjectGroup"): self.formatGroup(ob) else: self.commit(translate("draft","Change Style"),partial(Draft.formatObject,ob)) def formatGroup(self,grpob): for ob in grpob.Group: if (ob.Type == "App::DocumentObjectGroup"): self.formatGroup(ob) else: self.commit(translate("draft","Change Style"),partial(Draft.formatObject,ob)) class Rotate(Modifier): "The Draft_Rotate FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_Rotate', 'Accel' : "R, O", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Rotate", "Rotate"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Rotate", "Rotates the selected objects. CTRL to snap, SHIFT to constrain, ALT creates a copy")} def Activated(self): Modifier.Activated(self,"Rotate") if self.ui: if not Draft.getSelection(): self.ghost = None self.linetrack = None self.arctrack = None self.constraintrack = None self.ui.selectUi() msg(translate("draft", "Select an object to rotate\n")) self.call = self.view.addEventCallback("SoEvent",selectObject) else: self.proceed() def proceed(self): if self.call: self.view.removeEventCallback("SoEvent",self.call) self.sel = Draft.getSelection() self.sel = Draft.getGroupContents(self.sel) self.step = 0 self.center = None self.ui.arcUi() self.ui.isCopy.show() self.ui.setTitle("Rotate") self.linetrack = lineTracker() self.constraintrack = lineTracker(dotted=True) self.arctrack = arcTracker() self.ghost = ghostTracker(self.sel) self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick rotation center:\n")) self.ui.cross(True) def finish(self,closed=False,cont=False): "finishes the arc" Modifier.finish(self) if self.ui: self.linetrack.finalize() self.constraintrack.finalize() self.arctrack.finalize() self.ghost.finalize() self.doc.recompute() if cont and self.ui: if self.ui.continueMode: FreeCADGui.Selection.clearSelection() self.Activated() def rot (self,angle,copy=False): "rotating the real shapes" if copy: self.commit(translate("draft","Copy"), partial(Draft.rotate,self.sel, math.degrees(angle),self.center,plane.axis,copy)) else: self.commit(translate("draft","Rotate"), partial(Draft.rotate,self.sel, math.degrees(angle),self.center,plane.axis,copy)) def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": point,ctrlPoint = getPoint(self,arg) self.ui.cross(True) # this is to make sure radius is what you see on screen if self.center and fcvec.dist(point,self.center): viewdelta = fcvec.project(point.sub(self.center), plane.axis) if not fcvec.isNull(viewdelta): point = point.add(fcvec.neg(viewdelta)) if self.extendedCopy: if not hasMod(arg,MODALT): self.step = 3 self.finish() if (self.step == 0): pass elif (self.step == 1): currentrad = fcvec.dist(point,self.center) if (currentrad != 0): angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis) else: angle = 0 self.linetrack.p2(point) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() self.ui.radiusValue.setText("%.2f" % math.degrees(angle)) self.firstangle = angle self.ui.radiusValue.setFocus() self.ui.radiusValue.selectAll() elif (self.step == 2): currentrad = fcvec.dist(point,self.center) if (currentrad != 0): angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis) else: angle = 0 if (angle < self.firstangle): sweep = (2*math.pi-self.firstangle)+angle else: sweep = angle - self.firstangle self.arctrack.setApertureAngle(sweep) self.ghost.trans.rotation.setValue(coin.SbVec3f(fcvec.tup(plane.axis)),sweep) self.linetrack.p2(point) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() self.ui.radiusValue.setText("%.2f" % math.degrees(sweep)) self.ui.radiusValue.setFocus() self.ui.radiusValue.selectAll() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,ctrlPoint = getPoint(self,arg) if self.center and fcvec.dist(point,self.center): viewdelta = fcvec.project(point.sub(self.center), plane.axis) if not fcvec.isNull(viewdelta): point = point.add(fcvec.neg(viewdelta)) if (self.step == 0): self.center = point self.node = [point] self.ui.radiusUi() self.ui.hasFill.hide() self.ui.labelRadius.setText("Base angle") self.linetrack.p1(self.center) self.arctrack.setCenter(self.center) self.ghost.trans.center.setValue(self.center.x,self.center.y,self.center.z) self.linetrack.on() self.step = 1 msg(translate("draft", "Pick base angle:\n")) self.planetrack.set(point) elif (self.step == 1): self.ui.labelRadius.setText("Rotation") self.rad = fcvec.dist(point,self.center) self.arctrack.on() self.arctrack.setStartPoint(point) self.ghost.on() self.step = 2 msg(translate("draft", "Pick rotation angle:\n")) else: currentrad = fcvec.dist(point,self.center) angle = point.sub(self.center).getAngle(plane.u) if fcvec.project(point.sub(self.center), plane.v).getAngle(plane.v) > 1: angle = -angle if (angle < self.firstangle): sweep = (2*math.pi-self.firstangle)+angle else: sweep = angle - self.firstangle if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): self.rot(sweep,True) else: self.rot(sweep) if hasMod(arg,MODALT): self.extendedCopy = True else: self.finish(cont=True) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" self.center = Vector(numx,numy,numz) self.node = [self.center] self.arctrack.setCenter(self.center) self.ghost.trans.center.setValue(self.center.x,self.center.y,self.center.z) self.linetrack.p1(self.center) # self.arctrack.on() self.linetrack.on() self.ui.radiusUi() self.ui.hasFill.hide() self.ui.labelRadius.setText("Base angle") self.step = 1 msg(translate("draft", "Pick base angle:\n")) def numericRadius(self,rad): "this function gets called by the toolbar when valid radius have been entered there" if (self.step == 1): self.ui.labelRadius.setText("Rotation") self.firstangle = math.radians(rad) self.arctrack.setStartAngle(self.firstangle) self.arctrack.on() self.ghost.on() self.step = 2 msg(translate("draft", "Pick rotation angle:\n")) else: self.rot(math.radians(rad),self.ui.isCopy.isChecked()) self.finish(cont=True) class Offset(Modifier): "The Draft_Offset FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_Offset', 'Accel' : "O, S", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Offset", "Offset"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Offset", "Offsets the active object. CTRL to snap, SHIFT to constrain, ALT to copy")} def Activated(self): self.running = False Modifier.Activated(self,"Offset") if self.ui: if not Draft.getSelection(): self.ghost = None self.linetrack = None self.arctrack = None self.constraintrack = None self.ui.selectUi() msg(translate("draft", "Select an object to offset\n")) self.call = self.view.addEventCallback("SoEvent",selectObject) elif len(Draft.getSelection()) > 1: msg(translate("draft", "Offset only works on one object at a time\n"),"warning") else: self.proceed() def proceed(self): if self.call: self.view.removeEventCallback("SoEvent",self.call) self.sel = Draft.getSelection()[0] if not self.sel.isDerivedFrom("Part::Feature"): msg(translate("draft", "Cannot offset this object type\n"),"warning") self.finish() else: self.step = 0 self.dvec = None self.constrainSeg = None self.ui.offsetUi() self.linetrack = lineTracker() self.constraintrack = lineTracker(dotted=True) self.faces = False self.shape = self.sel.Shape self.mode = None if Draft.getType(self.sel) in ["Circle","Arc"]: self.ghost = arcTracker() self.mode = "Circle" self.center = self.shape.Edges[0].Curve.Center self.ghost.setCenter(self.center) self.ghost.setStartAngle(math.radians(self.sel.FirstAngle)) self.ghost.setEndAngle(math.radians(self.sel.LastAngle)) else: self.ghost = wireTracker(self.shape) self.mode = "Wire" self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick distance:\n")) self.ui.cross(True) self.planetrack.set(self.shape.Vertexes[0].Point) self.running = True def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": self.ui.cross(True) point,ctrlPoint = getPoint(self,arg) if hasMod(arg,MODCONSTRAIN) and self.constrainSeg: dist = fcgeo.findPerpendicular(point,self.shape,self.constrainSeg[1]) e = self.shape.Edges[self.constrainSeg[1]] self.constraintrack.p1(e.Vertexes[0].Point) self.constraintrack.p2(point.add(dist[0])) self.constraintrack.on() else: dist = fcgeo.findPerpendicular(point,self.shape.Edges) self.constraintrack.off() if dist: self.ghost.on() if self.mode == "Wire": d = fcvec.neg(dist[0]) v1 = fcgeo.getTangent(self.shape.Edges[0],point) v2 = fcgeo.getTangent(self.shape.Edges[dist[1]],point) a = -fcvec.angle(v1,v2) self.dvec = fcvec.rotate(d,a,plane.axis) self.ghost.update(fcgeo.offsetWire(self.shape,self.dvec,occ=self.ui.occOffset.isChecked())) elif self.mode == "Circle": self.dvec = point.sub(self.center).Length self.ghost.setRadius(self.dvec) self.constrainSeg = dist self.linetrack.on() self.linetrack.p1(point) self.linetrack.p2(point.add(dist[0])) self.ui.radiusValue.setText("%.2f" % dist[0].Length) else: self.dvec = None self.ghost.off() self.constrainSeg = None self.linetrack.off() self.ui.radiusValue.setText("off") self.ui.radiusValue.setFocus() self.ui.radiusValue.selectAll() if self.extendedCopy: if not hasMod(arg,MODALT): self.finish() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): copymode = False occmode = self.ui.occOffset.isChecked() if hasMod(arg,MODALT) or self.ui.isCopy.isChecked(): copymode = True if self.dvec: self.commit(translate("draft","Offset"), partial(Draft.offset,self.sel, self.dvec,copymode,occ=occmode)) if hasMod(arg,MODALT): self.extendedCopy = True else: self.finish() def finish(self,closed=False): Modifier.finish(self) if self.ui and self.running: self.linetrack.finalize() self.constraintrack.finalize() self.ghost.finalize() def numericRadius(self,rad): '''this function gets called by the toolbar when valid radius have been entered there''' if self.dvec: self.dvec.normalize() self.dvec.multiply(rad) copymode = False occmode = self.ui.occOffset.isChecked() if self.ui.isCopy.isChecked(): copymode = True self.commit(translate("draft","Offset"), partial(Draft.offset,self.sel, self.dvec,copymode,occ=occmode)) self.finish() class Upgrade(Modifier): '''The Draft_Upgrade FreeCAD command definition. This class upgrades selected objects in different ways, following this list (in order): - if there are more than one faces, the faces are merged (union) - if there is only one face, nothing is done - if there are closed wires, they are transformed in a face - otherwise join all edges into a wire (closed if applicable) - if nothing of the above is possible, a Compound is created ''' def GetResources(self): return {'Pixmap' : 'Draft_Upgrade', 'Accel' : "U, P", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Upgrade", "Upgrade"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Upgrade", "Joins the selected objects into one, or converts closed wires to filled faces, or unite faces")} def Activated(self): Modifier.Activated(self,"Upgrade") if self.ui: if not Draft.getSelection(): self.ui.selectUi() msg(translate("draft", "Select an object to upgrade\n")) self.call = self.view.addEventCallback("SoEvent",selectObject) else: self.proceed() def compound(self): # shapeslist = [] # for ob in self.sel: shapeslist.append(ob.Shape) # newob = self.doc.addObject("Part::Feature","Compound") # newob.Shape = Part.makeCompound(shapeslist) newob = Draft.makeBlock(self.sel) self.nodelete = True return newob def proceed(self): if self.call: self.view.removeEventCallback("SoEvent",self.call) self.sel = Draft.getSelection() newob = None self.nodelete = False edges = [] wires = [] openwires = [] faces = [] groups = [] curves = [] facewires = [] # determining what we have in our selection for ob in self.sel: if ob.Type == "App::DocumentObjectGroup": groups.append(ob) else: if ob.Shape.ShapeType == 'Edge': openwires.append(ob.Shape) for f in ob.Shape.Faces: faces.append(f) facewires.extend(f.Wires) for w in ob.Shape.Wires: if w.isClosed(): wires.append(w) else: openwires.append(w) for e in ob.Shape.Edges: if not isinstance(e.Curve,Part.Line): curves.append(e) lastob = ob # print "objects:",self.sel," edges:",edges," wires:",wires," openwires:",openwires," faces:",faces # print "groups:",groups," curves:",curves," facewires:",facewires # applying transformation self.doc.openTransaction("Upgrade") if groups: # if we have a group: turn each closed wire inside into a face msg(translate("draft", "Found groups: closing each open object inside\n")) for grp in groups: for ob in grp.Group: if not ob.Shape.Faces: for w in ob.Shape.Wires: newob = Draft.makeWire(w,closed=w.isClosed()) self.sel.append(ob) grp.addObject(newob) elif faces and (len(wires)+len(openwires)==len(facewires)): # we have only faces here, no lone edges if (len(self.sel) == 1) and (len(faces) > 1): # we have a shell: we try to make a solid sol = Part.makeSolid(self.sel[0].Shape) if sol.isClosed(): msg(translate("draft", "Found 1 solidificable object: solidifying it\n")) newob = self.doc.addObject("Part::Feature","Solid") newob.Shape = sol Draft.formatObject(newob,lastob) elif (len(self.sel) == 2) and (not curves): # we have exactly 2 objects: we fuse them msg(translate("draft", "Found 2 objects: fusing them\n")) newob = Draft.fuse(self.sel[0],self.sel[1]) self.nodelete = True elif (len(self.sel) > 2) and (len(faces) > 10): # we have many separate faces: we try to make a shell sh = Part.makeShell(faces) newob = self.doc.addObject("Part::Feature","Shell") newob.Shape = sh Draft.formatObject(newob,lastob) elif (len(self.sel) > 2) or (len(faces) > 1): # more than 2 objects or faces: we try the draft way: make one face out of them u = faces.pop(0) for f in faces: u = u.fuse(f) if fcgeo.isCoplanar(faces): if self.sel[0].ViewObject.DisplayMode == "Wireframe": f = False else: f = True u = fcgeo.concatenate(u) if not curves: msg(translate("draft", "Found several objects or faces: making a parametric face\n")) newob = Draft.makeWire(u.Wires[0],closed=True,face=f) Draft.formatObject(newob,lastob) else: # if not possible, we do a non-parametric union msg(translate("draft", "Found objects containing curves: fusing them\n")) newob = self.doc.addObject("Part::Feature","Union") newob.Shape = u Draft.formatObject(newob,lastob) else: # if not possible, we do a non-parametric union msg(translate("draft", "Found several objects: fusing them\n")) # if we have a solid, make sure we really return a solid if (len(u.Faces) > 1) and u.isClosed(): u = Part.makeSolid(u) newob = self.doc.addObject("Part::Feature","Union") newob.Shape = u Draft.formatObject(newob,lastob) elif len(self.sel) == 1: # only one object: if not parametric, we "draftify" it self.nodelete = True if (not curves) and (Draft.getType(self.sel[0]) == "Part"): msg(translate("draft", "Found 1 non-parametric objects: draftifying it\n")) Draft.draftify(self.sel[0]) elif wires and (not faces) and (not openwires): # we have only wires, no faces if (len(self.sel) == 1) and self.sel[0].isDerivedFrom("Sketcher::SketchObject") and (not curves): # we have a sketch msg(translate("draft", "Found 1 closed sketch object: making a face from it\n")) newob = Draft.makeWire(self.sel[0].Shape,closed=True) newob.Base = self.sel[0] self.sel[0].ViewObject.Visibility = False self.nodelete = True else: # only closed wires for w in wires: f = Part.Face(w) faces.append(f) for f in faces: if not curves: newob = Draft.makeWire(f.Wire,closed=True) else: # if there are curved segments, we do a non-parametric face msg(translate("draft", "Found closed wires: making faces\n")) newob = self.doc.addObject("Part::Feature","Face") newob.Shape = f Draft.formatObject(newob,lastob) elif (len(openwires) == 1) and (not faces) and (not wires): # special case, we have only one open wire. We close it!" p0 = openwires[0].Vertexes[0].Point p1 = openwires[0].Vertexes[-1].Point edges = openwires[0].Edges edges.append(Part.Line(p1,p0).toShape()) w = Part.Wire(fcgeo.sortEdges(edges)) msg(translate("draft", "Found 1 open wire: closing it\n")) if not curves: newob = Draft.makeWire(w,closed=True) else: # if not possible, we do a non-parametric union newob = self.doc.addObject("Part::Feature","Wire") newob.Shape = w Draft.formatObject(newob,lastob) elif openwires and (not wires) and (not faces): # only open wires and edges: we try to join their edges for ob in self.sel: for e in ob.Shape.Edges: edges.append(e) newob = None nedges = fcgeo.sortEdges(edges[:]) # for e in nedges: print "debug: ",e.Curve,e.Vertexes[0].Point,e.Vertexes[-1].Point w = Part.Wire(nedges) if len(w.Edges) == len(edges): msg(translate("draft", "Found several edges: wiring them\n")) if not curves: newob = Draft.makeWire(w) else: newob = self.doc.addObject("Part::Feature","Wire") newob.Shape = w Draft.formatObject(newob,lastob) if not newob: print "no new object found" msg(translate("draft", "Found several non-connected edges: making compound\n")) newob = self.compound() Draft.formatObject(newob,lastob) else: # all other cases msg(translate("draft", "Found several non-treatable objects: making compound\n")) newob = self.compound() Draft.formatObject(newob,lastob) if not self.nodelete: # deleting original objects, if needed for ob in self.sel: if not ob.Type == "App::DocumentObjectGroup": self.doc.removeObject(ob.Name) self.doc.commitTransaction() if newob: Draft.select(newob) Modifier.finish(self) class Downgrade(Modifier): ''' The Draft_Downgrade FreeCAD command definition. This class downgrades selected objects in different ways, following this list (in order): - if there are more than one faces, the subsequent faces are subtracted from the first one - if there is only one face, it gets converted to a wire - otherwise wires are exploded into single edges ''' def GetResources(self): return {'Pixmap' : 'Draft_Downgrade', 'Accel' : "D, N", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Downgrade", "Downgrade"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Downgrade", "Explodes the selected objects into simpler objects, or subtract faces")} def Activated(self): Modifier.Activated(self,"Downgrade") if self.ui: if not Draft.getSelection(): self.ui.selectUi() msg(translate("draft", "Select an object to upgrade\n")) self.call = self.view.addEventCallback("SoEvent",selectObject) else: self.proceed() def proceed(self): self.sel = Draft.getSelection() edges = [] faces = [] # scanning objects for ob in self.sel: for f in ob.Shape.Faces: faces.append(f) for ob in self.sel: for e in ob.Shape.Edges: edges.append(e) lastob = ob # applying transformation self.doc.openTransaction("Downgrade") if (len(self.sel) == 1) and (Draft.getType(self.sel[0]) == "Block"): # a block, we explode it pl = self.sel[0].Placement newob = [] for ob in self.sel[0].Components: ob.ViewObject.Visibility = True ob.Placement = ob.Placement.multiply(pl) newob.append(ob) self.doc.removeObject(self.sel[0].Name) elif (len(self.sel) == 1) and (self.sel[0].isDerivedFrom("Part::Feature")) and ("Base" in self.sel[0].PropertiesList): # special case, we have one parametric object: we "de-parametrize" it msg(translate("draft", "Found 1 parametric object: breaking its dependencies\n")) newob = Draft.shapify(self.sel[0]) elif len(self.sel) == 2: # we have only 2 objects: cut 2nd from 1st msg(translate("draft", "Found 2 objects: subtracting them\n")) newob = Draft.cut(self.sel[0],self.sel[1]) elif (len(faces) > 1): if len(self.sel) == 1: # one object with several faces: split it for f in faces: msg(translate("draft", "Found several faces: splitting them\n")) newob = self.doc.addObject("Part::Feature","Face") newob.Shape = f Draft.formatObject(newob,self.sel[0]) self.doc.removeObject(ob.Name) else: # several objects: remove all the faces from the first one msg(translate("draft", "Found several objects: subtracting them from the first one\n")) u = faces.pop(0) for f in faces: u = u.cut(f) newob = self.doc.addObject("Part::Feature","Subtraction") newob.Shape = u for ob in self.sel: Draft.formatObject(newob,ob) self.doc.removeObject(ob.Name) elif (len(faces) > 0): # only one face: we extract its wires msg(translate("draft", "Found 1 face: extracting its wires\n")) for w in faces[0].Wires: newob = self.doc.addObject("Part::Feature","Wire") newob.Shape = w Draft.formatObject(newob,lastob) for ob in self.sel: self.doc.removeObject(ob.Name) else: # no faces: split wire into single edges msg(translate("draft", "Found only wires: extracting their edges\n")) for ob in self.sel: for e in edges: newob = self.doc.addObject("Part::Feature","Edge") newob.Shape = e Draft.formatObject(newob,ob) self.doc.removeObject(ob.Name) self.doc.commitTransaction() Draft.select(newob) Modifier.finish(self) class Trimex(Modifier): ''' The Draft_Trimex FreeCAD command definition. This tool trims or extends lines, wires and arcs, or extrudes single faces. SHIFT constrains to the last point or extrudes in direction to the face normal.''' def GetResources(self): return {'Pixmap' : 'Draft_Trimex', 'Accel' : "T, R", 'MenuText' : QtCore.QT_TRANSLATE_NOOP("Draft_Trimex", "Trimex"), 'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_Trimex", "Trims or extends the selected object, or extrudes single faces. CTRL snaps, SHIFT constrains to current segment or to normal, ALT inverts")} def Activated(self): Modifier.Activated(self,"Trimex") self.edges = [] self.placement = None self.ghost = None self.linetrack = None self.constraintrack = None if self.ui: if not Draft.getSelection(): self.ui.selectUi() msg(translate("draft", "Select an object to trim/extend\n")) self.call = self.view.addEventCallback("SoEvent",selectObject) else: self.proceed() def proceed(self): if self.call: self.view.removeEventCallback("SoEvent",self.call) self.obj = Draft.getSelection()[0] self.ui.trimUi() self.linetrack = lineTracker() self.constraintrack = lineTracker(dotted=True) if not "Shape" in self.obj.PropertiesList: return if "Placement" in self.obj.PropertiesList: self.placement = self.obj.Placement if len(self.obj.Shape.Faces) == 1: # simple extrude mode, the object itself is extruded self.extrudeMode = True self.ghost = [ghostTracker([self.obj])] self.normal = self.obj.Shape.Faces[0].normalAt(.5,.5) for v in self.obj.Shape.Vertexes: self.ghost.append(lineTracker()) elif len(self.obj.Shape.Faces) > 1: # face extrude mode, a new object is created ss = FreeCADGui.Selection.getSelectionEx()[0] if len(ss.SubObjects) == 1: if ss.SubObjects[0].ShapeType == "Face": self.obj = self.doc.addObject("Part::Feature","Face") self.obj.Shape = ss.SubObjects[0] self.extrudeMode = True self.ghost = [ghostTracker([self.obj])] self.normal = self.obj.Shape.Faces[0].normalAt(.5,.5) for v in self.obj.Shape.Vertexes: self.ghost.append(lineTracker()) else: # normal wire trimex mode self.obj.ViewObject.Visibility = False self.extrudeMode = False if self.obj.Shape.Wires: self.edges = self.obj.Shape.Wires[0].Edges self.edges = fcgeo.sortEdges(self.edges) else: self.edges = self.obj.Shape.Edges self.ghost = [] lc = self.obj.ViewObject.LineColor sc = (lc[0],lc[1],lc[2]) sw = self.obj.ViewObject.LineWidth for e in self.edges: if isinstance(e.Curve,Part.Line): self.ghost.append(lineTracker(scolor=sc,swidth=sw)) else: self.ghost.append(arcTracker(scolor=sc,swidth=sw)) if not self.ghost: self.finish() for g in self.ghost: g.on() self.activePoint = 0 self.nodes = [] self.shift = False self.alt = False self.force = None self.cv = None self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick distance:\n")) self.ui.cross(True) def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection self.ui.cross(True) self.shift = hasMod(arg,MODCONSTRAIN) self.alt = hasMod(arg,MODALT) wp = not(self.extrudeMode and self.shift) self.point = getPoint(self,arg,workingplane=wp)[0] if hasMod(arg,MODSNAP): self.snapped = None else: self.snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if self.extrudeMode: dist = self.extrude(self.shift) else: dist = self.redraw(self.point,self.snapped,self.shift,self.alt) self.constraintrack.p1(self.point) self.constraintrack.p2(self.newpoint) self.constraintrack.on() self.ui.radiusValue.setText("%.2f" % dist) self.ui.radiusValue.setFocus() self.ui.radiusValue.selectAll() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): cursor = arg["Position"] self.shift = hasMod(arg,MODCONSTRAIN) self.alt = hasMod(arg,MODALT) if hasMod(arg,MODSNAP): self.snapped = None else: self.snapped = self.view.getObjectInfo((cursor[0],cursor[1])) self.trimObject() self.finish() def extrude(self,shift=False,real=False): "redraws the ghost in extrude mode" self.newpoint = self.obj.Shape.Faces[0].CenterOfMass dvec = self.point.sub(self.newpoint) if shift: delta = fcvec.project(dvec,self.normal) else: delta = dvec if self.force: ratio = self.force/delta.Length delta.multiply(ratio) if real: return delta self.ghost[0].trans.translation.setValue([delta.x,delta.y,delta.z]) for i in range(1,len(self.ghost)): base = self.obj.Shape.Vertexes[i-1].Point self.ghost[i].p1(base) self.ghost[i].p2(base.add(delta)) return delta.Length def redraw(self,point,snapped=None,shift=False,alt=False,real=None): "redraws the ghost" # initializing reverse = False for g in self.ghost: g.off() if real: newedges = [] # finding the active point vlist = [] for e in self.edges: vlist.append(e.Vertexes[0].Point) vlist.append(self.edges[-1].Vertexes[-1].Point) if shift: npoint = self.activePoint else: npoint = fcgeo.findClosest(point,vlist) if npoint > len(self.edges)/2: reverse = True if alt: reverse = not reverse self.activePoint = npoint # sorting out directions if reverse and (npoint > 0): npoint = npoint-1 if (npoint > len(self.edges)-1): edge = self.edges[-1] ghost = self.ghost[-1] else: edge = self.edges[npoint] ghost = self.ghost[npoint] if reverse: v1 = edge.Vertexes[-1].Point v2 = edge.Vertexes[0].Point else: v1 = edge.Vertexes[0].Point v2 = edge.Vertexes[-1].Point # snapping if snapped: snapped = self.doc.getObject(snapped['Object']) pts = [] for e in snapped.Shape.Edges: int = fcgeo.findIntersection(edge,e,True,True) if int: pts.extend(int) if pts: point = pts[fcgeo.findClosest(point,pts)] # modifying active edge if isinstance(edge.Curve,Part.Line): perp = fcgeo.vec(edge).cross(Vector(0,0,1)) chord = v1.sub(point) proj = fcvec.project(chord,perp) self.newpoint = Vector.add(point,proj) dist = v1.sub(self.newpoint).Length ghost.p1(self.newpoint) ghost.p2(v2) self.ui.labelRadius.setText("Distance") if real: if self.force: ray = self.newpoint.sub(v1) ray = fcvec.scale(ray,self.force/ray.Length) self.newpoint = Vector.add(v1,ray) newedges.append(Part.Line(self.newpoint,v2).toShape()) else: center = edge.Curve.Center rad = edge.Curve.Radius ang1 = fcvec.angle(v2.sub(center)) ang2 = fcvec.angle(point.sub(center)) self.newpoint=Vector.add(center,fcvec.rotate(Vector(rad,0,0),-ang2)) self.ui.labelRadius.setText("Angle") dist = math.degrees(-ang2) # if ang1 > ang2: ang1,ang2 = ang2,ang1 print "last calculated:",math.degrees(-ang1),math.degrees(-ang2) ghost.setEndAngle(-ang2) ghost.setStartAngle(-ang1) ghost.setCenter(center) ghost.setRadius(rad) if real: if self.force: angle = math.radians(self.force) newray = fcvec.rotate(Vector(rad,0,0),-angle) self.newpoint = Vector.add(center,newray) chord = self.newpoint.sub(v2) perp = chord.cross(Vector(0,0,1)) scaledperp = fcvec.scaleTo(perp,rad) midpoint = Vector.add(center,scaledperp) newedges.append(Part.Arc(self.newpoint,midpoint,v2).toShape()) ghost.on() # resetting the visible edges if not reverse: list = range(npoint+1,len(self.edges)) else: list = range(npoint-1,-1,-1) for i in list: edge = self.edges[i] ghost = self.ghost[i] if isinstance(edge.Curve,Part.Line): ghost.p1(edge.Vertexes[0].Point) ghost.p2(edge.Vertexes[-1].Point) else: ang1 = fcvec.angle(edge.Vertexes[0].Point.sub(center)) ang2 = fcvec.angle(edge.Vertexes[-1].Point.sub(center)) # if ang1 > ang2: ang1,ang2 = ang2,ang1 ghost.setEndAngle(-ang2) ghost.setStartAngle(-ang1) ghost.setCenter(edge.Curve.Center) ghost.setRadius(edge.Curve.Radius) if real: newedges.append(edge) ghost.on() # finishing if real: return newedges else: return dist def trimObject(self): "trims the actual object" if self.extrudeMode: delta = self.extrude(self.shift,real=True) print "delta",delta self.doc.openTransaction("Extrude") obj = Draft.extrude(self.obj,delta) self.doc.commitTransaction() self.obj = obj else: edges = self.redraw(self.point,self.snapped,self.shift,self.alt,real=True) newshape = Part.Wire(edges) self.doc.openTransaction("Trim/extend") if Draft.getType(self.obj) in ["Wire","BSpline"]: p = [] if self.placement: invpl = self.placement.inverse() for v in newshape.Vertexes: np = v.Point if self.placement: np = invpl.multVec(np) p.append(np) self.obj.Points = p elif Draft.getType(self.obj) == "Circle": angles = self.ghost[0].getAngles() print "original",self.obj.FirstAngle," ",self.obj.LastAngle print "new",angles if angles[0] > angles[1]: angles = (angles[1],angles[0]) self.obj.FirstAngle = angles[0] self.obj.LastAngle = angles[1] else: self.obj.Shape = newshape self.doc.commitTransaction() for g in self.ghost: g.off() def finish(self,closed=False): Modifier.finish(self) self.force = None if self.ui: self.linetrack.finalize() self.constraintrack.finalize() if self.ghost: for g in self.ghost: g.finalize() self.obj.ViewObject.Visibility = True Draft.select(self.obj) def numericRadius(self,dist): "this function gets called by the toolbar when valid distance have been entered there" self.force = dist self.trimObject() self.finish() class Scale(Modifier): '''The Draft_Scale FreeCAD command definition. This tool scales the selected objects from a base point.''' def GetResources(self): return {'Pixmap' : 'Draft_Scale', 'Accel' : "S, C", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Scale", "Scale"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Scale", "Scales the selected objects from a base point. CTRL to snap, SHIFT to constrain, ALT to copy")} def Activated(self): Modifier.Activated(self,"Scale") if self.ui: if not Draft.getSelection(): self.ghost = None self.linetrack = None self.constraintrack = None self.ui.selectUi() msg(translate("draft", "Select an object to scale\n")) self.call = self.view.addEventCallback("SoEvent",selectObject) else: self.proceed() def proceed(self): if self.call: self.view.removeEventCallback("SoEvent",self.call) self.sel = Draft.getSelection() self.sel = Draft.getGroupContents(self.sel) self.ui.pointUi() self.ui.modUi() self.ui.xValue.setFocus() self.ui.xValue.selectAll() self.linetrack = lineTracker() self.constraintrack = lineTracker(dotted=True) self.ghost = ghostTracker(self.sel) self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick base point:\n")) self.ui.cross(True) def finish(self,closed=False,cont=False): Modifier.finish(self) if self.ui: self.ghost.finalize() self.linetrack.finalize() self.constraintrack.finalize() if cont and self.ui: if self.ui.continueMode: FreeCADGui.Selection.clearSelection() self.Activated() def scale(self,delta,copy=False): "moving the real shapes" if copy: self.commit(translate("draft","Copy"), partial(Draft.scale,self.sel,delta,self.node[0],copy)) else: self.commit(translate("draft","Scale"), partial(Draft.scale,self.sel,delta,self.node[0],copy)) def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection point,ctrlPoint = getPoint(self,arg,sym=True) self.linetrack.p2(point) self.ui.cross(True) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() if (len(self.node) > 0): last = self.node[len(self.node)-1] delta = point.sub(last) self.ghost.trans.scaleFactor.setValue([delta.x,delta.y,delta.z]) corr = Vector(self.node[0].x,self.node[0].y,self.node[0].z) corr.scale(delta.x,delta.y,delta.z) corr = fcvec.neg(corr.sub(self.node[0])) self.ghost.trans.translation.setValue([corr.x,corr.y,corr.z]) if self.extendedCopy: if not hasMod(arg,MODALT): self.finish() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): point,ctrlPoint = getPoint(self,arg,sym=True) if (self.node == []): self.node.append(point) self.ui.isRelative.show() self.ui.isCopy.show() self.linetrack.on() self.ghost.on() self.linetrack.p1(point) msg(translate("draft", "Pick scale factor:\n")) else: last = self.node[0] if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): self.scale(point.sub(last),True) else: self.scale(point.sub(last)) if hasMod(arg,MODALT): self.extendedCopy = True else: self.finish(cont=True) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" point = Vector(numx,numy,numz) if not self.node: self.node.append(point) self.ui.isRelative.show() self.ui.isCopy.show() self.linetrack.p1(point) self.linetrack.on() self.ghost.on() msg(translate("draft", "Pick scale factor:\n")) else: last = self.node[-1] if self.ui.isCopy.isChecked(): self.scale(point.sub(last),True) else: self.scale(point.sub(last)) self.finish(cont=True) class ToggleConstructionMode(): "The Draft_ToggleConstructionMode FreeCAD command definition" def GetResources(self): return {'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode", "Toggle construcion Mode"), 'Accel' : "C, M", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode", "Toggles the Construction Mode for next objects.")} def Activated(self): FreeCADGui.draftToolBar.constrButton.toggle() class ToggleContinueMode(): "The Draft_ToggleContinueMode FreeCAD command definition" def GetResources(self): return {'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleContinueMode", "Toggle continue Mode"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleContinueMode", "Toggles the Continue Mode for next commands.")} def Activated(self): FreeCADGui.draftToolBar.continueCmd.toggle() class Drawing(Modifier): "The Draft Drawing command definition" def GetResources(self): return {'Pixmap' : 'Draft_Drawing', 'Accel' : "D, D", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Drawing", "Drawing"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Drawing", "Puts the selected objects on a Drawing sheet.")} def IsActive(self): if Draft.getSelection(): return True else: return False def Activated(self): Modifier.Activated(self,"Drawing") sel = Draft.getSelection() if not sel: self.page = self.createDefaultPage() else: self.page = None for obj in sel: if obj.isDerivedFrom("Drawing::FeaturePage"): self.page = obj sel.pop(sel.index(obj)) if not self.page: for obj in self.doc.Objects: if obj.isDerivedFrom("Drawing::FeaturePage"): self.page = obj if not self.page: self.page = self.createDefaultPage() sel.reverse() for obj in sel: self.insertPattern(obj) if obj.ViewObject.isVisible(): name = 'View'+obj.Name oldobj = self.page.getObject(name) if oldobj: self.doc.removeObject(oldobj.Name) Draft.makeDrawingView(obj,self.page) self.doc.recompute() def insertPattern(self,obj): "inserts a pattern object on the page" if 'FillStyle' in obj.ViewObject.PropertiesList: if obj.ViewObject.FillStyle != 'shape color': hatch = obj.ViewObject.FillStyle vobj = self.page.getObject('Pattern'+hatch) if not vobj: if hatch in FreeCAD.svgpatterns: view = self.doc.addObject('Drawing::FeatureView','Pattern'+hatch) svg = FreeCAD.svgpatterns[hatch] view.ViewResult = svg view.X = 0 view.Y = 0 view.Scale = 1 self.page.addObject(view) def createDefaultPage(self): "created a default page" template = Draft.getParam("template") if not template: template = FreeCAD.getResourceDir()+'Mod/Drawing/Templates/A3_Landscape.svg' page = self.doc.addObject('Drawing::FeaturePage','Page') page.ViewObject.HintOffsetX = 200 page.ViewObject.HintOffsetY = 100 page.ViewObject.HintScale = 20 page.Template = template self.doc.recompute() return page class ToggleDisplayMode(): "The ToggleDisplayMode FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_SwitchMode', 'Accel' : "Shift+Space", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode", "Toggle display mode"), 'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode", "Swaps display mode of selected objects between wireframe and flatlines")} def IsActive(self): if Draft.getSelection(): return True else: return False def Activated(self): for obj in Draft.getSelection(): if obj.ViewObject.DisplayMode == "Flat Lines": if "Wireframe" in obj.ViewObject.listDisplayModes(): obj.ViewObject.DisplayMode = "Wireframe" elif obj.ViewObject.DisplayMode == "Wireframe": if "Flat Lines" in obj.ViewObject.listDisplayModes(): obj.ViewObject.DisplayMode = "Flat Lines" class Edit(Modifier): "The Draft_Edit FreeCAD command definition" def __init__(self): self.running = False def GetResources(self): return {'Pixmap' : 'Draft_Edit', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edit"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edits the active object")} def IsActive(self): if Draft.getSelection(): self.selection = Draft.getSelection() if "Proxy" in self.selection[0].PropertiesList: if hasattr(self.selection[0].Proxy,"Type"): return True return False def Activated(self): if self.running: self.finish() else: Modifier.Activated(self,"Edit") self.ui.editUi() if self.doc: self.obj = Draft.getSelection() if self.obj: self.obj = self.obj[0] # store selectable state of the object self.selectstate = self.obj.ViewObject.Selectable self.obj.ViewObject.Selectable = False if not Draft.getType(self.obj) in ["Wire","BSpline"]: self.ui.setEditButtons(False) else: self.ui.setEditButtons(True) self.editing = None self.editpoints = [] self.pl = None if "Placement" in self.obj.PropertiesList: self.pl = self.obj.Placement self.invpl = self.pl.inverse() if Draft.getType(self.obj) in ["Wire","BSpline"]: for p in self.obj.Points: if self.pl: p = self.pl.multVec(p) self.editpoints.append(p) elif Draft.getType(self.obj) == "Circle": self.editpoints.append(self.obj.Placement.Base) if self.obj.FirstAngle == self.obj.LastAngle: self.editpoints.append(self.obj.Shape.Vertexes[0].Point) elif Draft.getType(self.obj) == "Rectangle": self.editpoints.append(self.obj.Placement.Base) self.editpoints.append(self.obj.Shape.Vertexes[2].Point) v = self.obj.Shape.Vertexes self.bx = v[1].Point.sub(v[0].Point) if self.obj.Length < 0: self.bx = fcvec.neg(self.bx) self.by = v[2].Point.sub(v[1].Point) if self.obj.Height < 0: self.by = fcvec.neg(self.by) elif Draft.getType(self.obj) == "Polygon": self.editpoints.append(self.obj.Placement.Base) self.editpoints.append(self.obj.Shape.Vertexes[0].Point) elif Draft.getType(self.obj) == "Dimension": p = self.obj.ViewObject.Proxy.textpos.translation.getValue() self.editpoints.append(self.obj.Start) self.editpoints.append(self.obj.End) self.editpoints.append(self.obj.Dimline) self.editpoints.append(Vector(p[0],p[1],p[2])) self.trackers = [] self.constraintrack = None if self.editpoints: for ep in range(len(self.editpoints)): self.trackers.append(editTracker(self.editpoints[ep],self.obj.Name, ep,self.obj.ViewObject.LineColor)) self.constraintrack = lineTracker(dotted=True) self.call = self.view.addEventCallback("SoEvent",self.action) self.running = True plane.save() if "Shape" in self.obj.PropertiesList: plane.alignToFace(self.obj.Shape) self.planetrack.set(self.editpoints[0]) else: msg(translate("draft", "This object type is not editable\n"),'warning') self.finish() else: self.finish() def finish(self,closed=False): "terminates the operation" if closed: if "Closed" in self.obj.PropertiesList: if not self.obj.Closed: self.obj.Closed = True if self.ui: if self.trackers: for t in self.trackers: t.finalize() if self.constraintrack: self.constraintrack.finalize() self.obj.ViewObject.Selectable = self.selectstate Modifier.finish(self) plane.restore() self.running = False def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection if self.editing != None: point,ctrlPoint = getPoint(self,arg) # Draw constraint tracker line. if hasMod(arg,MODCONSTRAIN): self.constraintrack.p1(point) self.constraintrack.p2(ctrlPoint) self.constraintrack.on() else: self.constraintrack.off() self.trackers[self.editing].set(point) self.update(self.trackers[self.editing].get()) elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): if self.editing == None: snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1])) if snapped: if snapped['Object'] == self.obj.Name: if self.ui.addButton.isChecked(): point,ctrlPoint = getPoint(self,arg) self.pos = arg["Position"] self.addPoint(point) elif self.ui.delButton.isChecked(): if 'EditNode' in snapped['Component']: self.delPoint(int(snapped['Component'][8:])) elif 'EditNode' in snapped['Component']: self.ui.pointUi() self.ui.isRelative.show() self.editing = int(snapped['Component'][8:]) self.trackers[self.editing].off() self.obj.ViewObject.Selectable = False if "Points" in self.obj.PropertiesList: self.node.append(self.obj.Points[self.editing]) else: print "finishing edit" self.trackers[self.editing].on() self.obj.ViewObject.Selectable = True self.numericInput(self.trackers[self.editing].get()) def update(self,v): if Draft.getType(self.obj) in ["Wire","BSpline"]: pts = self.obj.Points editPnt = self.invpl.multVec(v) # DNC: allows to close the curve by placing ends close to each other tol = 0.001 if ( ( self.editing == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( self.editing == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol): self.obj.Closed = True # DNC: fix error message if edited point coinsides with one of the existing points if ( editPnt in pts ) == False: pts[self.editing] = editPnt self.obj.Points = pts self.trackers[self.editing].set(v) elif Draft.getType(self.obj) == "Circle": delta = v.sub(self.obj.Placement.Base) if self.editing == 0: p = self.obj.Placement p.move(delta) self.obj.Placement = p self.trackers[0].set(self.obj.Placement.Base) elif self.editing == 1: self.obj.Radius = delta.Length self.trackers[1].set(self.obj.Shape.Vertexes[0].Point) elif Draft.getType(self.obj) == "Rectangle": delta = v.sub(self.obj.Placement.Base) if self.editing == 0: p = self.obj.Placement p.move(delta) self.obj.Placement = p elif self.editing == 1: diag = v.sub(self.obj.Placement.Base) nx = fcvec.project(diag,self.bx) ny = fcvec.project(diag,self.by) ax = nx.Length ay = ny.Length if ax and ay: if abs(nx.getAngle(self.bx)) > 0.1: ax = -ax if abs(ny.getAngle(self.by)) > 0.1: ay = -ay self.obj.Length = ax self.obj.Height = ay self.trackers[0].set(self.obj.Placement.Base) self.trackers[1].set(self.obj.Shape.Vertexes[2].Point) elif Draft.getType(self.obj) == "Polygon": delta = v.sub(self.obj.Placement.Base) if self.editing == 0: p = self.obj.Placement p.move(delta) self.obj.Placement = p self.trackers[0].set(self.obj.Placement.Base) elif self.editing == 1: if self.obj.DrawMode == 'inscribed': self.obj.Radius = delta.Length else: halfangle = ((math.pi*2)/self.obj.FacesNumber)/2 rad = math.cos(halfangle)*delta.Length self.obj.Radius = rad self.trackers[1].set(self.obj.Shape.Vertexes[0].Point) elif Draft.getType(self.obj) == "Dimension": if self.editing == 0: self.obj.Start = v elif self.editing == 1: self.obj.End = v elif self.editing == 2: self.obj.Dimline = v elif self.editing == 3: self.obj.ViewObject.TextPosition = v def numericInput(self,v,numy=None,numz=None): '''this function gets called by the toolbar when valid x, y, and z have been entered there''' if (numy != None): v = Vector(v,numy,numz) self.doc.openTransaction("Edit "+self.obj.Name) self.update(v) self.doc.commitTransaction() self.editing = None self.ui.editUi() self.node = [] def addPoint(self,point): if not (Draft.getType(self.obj) in ["Wire","BSpline"]): return pts = self.obj.Points if ( Draft.getType(self.obj) == "Wire" ): if (self.obj.Closed == True): # DNC: work around.... seems there is a # bug in approximate method for closed wires... edges = self.obj.Shape.Wires[0].Edges e1 = edges[-1] # last edge v1 = e1.Vertexes[0].Point v2 = e1.Vertexes[1].Point v2.multiply(0.9999) edges[-1] = Part.makeLine(v1,v2) edges.reverse() wire = Part.Wire(edges) curve = wire.approximate(0.0001,0.0001,100,25) else: # DNC: this version is much more reliable near sharp edges! curve = self.obj.Shape.Wires[0].approximate(0.0001,0.0001,100,25) elif ( Draft.getType(self.obj) == "BSpline" ): if (self.obj.Closed == True): curve = self.obj.Shape.Edges[0].Curve else: curve = self.obj.Shape.Curve uNewPoint = curve.parameter(point) uPoints = [] for p in self.obj.Points: uPoints.append(curve.parameter(p)) for i in range(len(uPoints)-1): if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ): pts.insert(i+1, self.invpl.multVec(point)) break # DNC: fix: add points to last segment if curve is closed if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) : pts.append(self.invpl.multVec(point)) self.doc.openTransaction("Edit "+self.obj.Name) self.obj.Points = pts self.doc.commitTransaction() self.resetTrackers() def delPoint(self,point): if not (Draft.getType(self.obj) in ["Wire","BSpline"]): return if len(self.obj.Points) <= 2: msg(translate("draft", "Active object must have more than two points/nodes\n"),'warning') else: pts = self.obj.Points pts.pop(point) self.doc.openTransaction("Edit "+self.obj.Name) self.obj.Points = pts self.doc.commitTransaction() self.resetTrackers() def resetTrackers(self): for t in self.trackers: t.finalize() self.trackers = [] for ep in range(len(self.obj.Points)): objPoints = self.obj.Points[ep] if self.pl: objPoints = self.pl.multVec(objPoints) self.trackers.append(editTracker(objPoints,self.obj.Name,ep,self.obj.ViewObject.LineColor)) class AddToGroup(): "The AddToGroup FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_AddToGroup', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_AddToGroup", "Add to group..."), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_AddToGroup", "Adds the selected object(s) to an existing group")} def IsActive(self): if Draft.getSelection(): return True else: return False def Activated(self): self.groups = ["Ungroup"] self.groups.extend(Draft.getGroupNames()) self.labels = ["Ungroup"] for g in self.groups: o = FreeCAD.ActiveDocument.getObject(g) if o: self.labels.append(o.Label) self.ui = FreeCADGui.draftToolBar self.ui.sourceCmd = self self.ui.popupMenu(self.labels) def proceed(self,labelname): self.ui.sourceCmd = None if labelname == "Ungroup": for obj in Draft.getSelection(): try: Draft.ungroup(obj) except: pass else: if labelname in self.labels: i = self.labels.index(labelname) g = FreeCAD.ActiveDocument.getObject(self.groups[i]) for obj in Draft.getSelection(): try: g.addObject(obj) except: pass class AddPoint(Modifier): "The Draft_AddPoint FreeCAD command definition" def __init__(self): self.running = False def GetResources(self): return {'Pixmap' : 'Draft_AddPoint', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_AddPoint", "Add Point"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_AddPoint", "Adds a point to an existing wire/bspline")} def IsActive(self): self.selection = Draft.getSelection() if (Draft.getType(self.selection[0]) in ['Wire','BSpline']): return True else: return False def Activated(self): FreeCADGui.draftToolBar.vertUi(True) FreeCADGui.runCommand("Draft_Edit") class DelPoint(Modifier): "The Draft_DelPoint FreeCAD command definition" def __init__(self): self.running = False def GetResources(self): return {'Pixmap' : 'Draft_DelPoint', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_DelPoint", "Remove Point"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_DelPoint", "Removes a point from an existing wire or bspline")} def IsActive(self): self.selection = Draft.getSelection() if (Draft.getType(self.selection[0]) in ['Wire','BSpline']): return True else: return False def Activated(self): FreeCADGui.draftToolBar.vertUi(False) FreeCADGui.runCommand("Draft_Edit") class WireToBSpline(Modifier): "The Draft_Wire2BSpline FreeCAD command definition" def __init__(self): self.running = False def GetResources(self): return {'Pixmap' : 'Draft_WireToBSpline', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_WireToBSpline", "Wire to BSpline"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_WireToBSpline", "Converts between Wire and BSpline")} def IsActive(self): self.selection = Draft.getSelection() if (Draft.getType(self.selection[0]) in ['Wire','BSpline']): return True else: return False def Activated(self): if self.running: self.finish() else: Modifier.Activated(self,"Convert Curve Type") if self.doc: self.obj = Draft.getSelection() if self.obj: self.obj = self.obj[0] self.pl = None if "Placement" in self.obj.PropertiesList: self.pl = self.obj.Placement self.Points = self.obj.Points self.closed = self.obj.Closed n = None if (Draft.getType(self.selection[0]) == 'Wire'): n = Draft.makeBSpline(self.Points, self.closed, self.pl) elif (Draft.getType(self.selection[0]) == 'BSpline'): n = Draft.makeWire(self.Points, self.closed, self.pl) if n: Draft.formatObject(n,self.selection[0]) else: self.finish() def finish(self): Modifier.finish(self) class SelectGroup(): "The SelectGroup FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_SelectGroup', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_SelectGroup", "Select group"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_SelectGroup", "Selects all objects with the same parents as this group")} def IsActive(self): if Draft.getSelection(): return True else: return False def Activated(self): sellist = [] for ob in Draft.getSelection(): for child in ob.OutList: FreeCADGui.Selection.addSelection(child) for parent in ob.InList: FreeCADGui.Selection.addSelection(parent) for child in parent.OutList: FreeCADGui.Selection.addSelection(child) class Shape2DView(): "The Shape2DView FreeCAD command definition" def GetResources(self): return {'Pixmap' : 'Draft_2DShapeView', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Shape2DView", "Shape 2D view"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Shape2DView", "Creates Shape 2D views of selected objects")} def IsActive(self): if Draft.getSelection(): return True else: return False def Activated(self): sellist = [] for ob in Draft.getSelection(): Draft.makeShape2DView(ob) #--------------------------------------------------------------------------- # Adds the icons & commands to the FreeCAD command manager, and sets defaults #--------------------------------------------------------------------------- # drawing commands FreeCADGui.addCommand('Draft_SelectPlane',SelectPlane()) FreeCADGui.addCommand('Draft_Line',Line()) FreeCADGui.addCommand('Draft_Wire',Wire()) FreeCADGui.addCommand('Draft_Circle',Circle()) FreeCADGui.addCommand('Draft_Arc',Arc()) FreeCADGui.addCommand('Draft_Text',Text()) FreeCADGui.addCommand('Draft_Rectangle',Rectangle()) FreeCADGui.addCommand('Draft_Dimension',Dimension()) FreeCADGui.addCommand('Draft_Polygon',Polygon()) FreeCADGui.addCommand('Draft_BSpline',BSpline()) # modification commands FreeCADGui.addCommand('Draft_Move',Move()) FreeCADGui.addCommand('Draft_Rotate',Rotate()) FreeCADGui.addCommand('Draft_Offset',Offset()) FreeCADGui.addCommand('Draft_Upgrade',Upgrade()) FreeCADGui.addCommand('Draft_Downgrade',Downgrade()) FreeCADGui.addCommand('Draft_Trimex',Trimex()) FreeCADGui.addCommand('Draft_Scale',Scale()) FreeCADGui.addCommand('Draft_Drawing',Drawing()) FreeCADGui.addCommand('Draft_Edit',Edit()) FreeCADGui.addCommand('Draft_AddPoint',AddPoint()) FreeCADGui.addCommand('Draft_DelPoint',DelPoint()) FreeCADGui.addCommand('Draft_WireToBSpline',WireToBSpline()) # context commands FreeCADGui.addCommand('Draft_FinishLine',FinishLine()) FreeCADGui.addCommand('Draft_CloseLine',CloseLine()) FreeCADGui.addCommand('Draft_UndoLine',UndoLine()) FreeCADGui.addCommand('Draft_ToggleConstructionMode',ToggleConstructionMode()) FreeCADGui.addCommand('Draft_ToggleContinueMode',ToggleContinueMode()) FreeCADGui.addCommand('Draft_ApplyStyle',ApplyStyle()) FreeCADGui.addCommand('Draft_ToggleDisplayMode',ToggleDisplayMode()) FreeCADGui.addCommand('Draft_AddToGroup',AddToGroup()) FreeCADGui.addCommand('Draft_SelectGroup',SelectGroup()) FreeCADGui.addCommand('Draft_Shape2DView',Shape2DView()) # a global place to look for active draft Command FreeCAD.activeDraftCommand = None