draftTools.py

Go to the documentation of this file.
00001 #***************************************************************************
00002 #*                                                                         *
00003 #*   Copyright (c) 2009, 2010                                              *  
00004 #*   Yorik van Havre <yorik@uncreated.net>, Ken Cline <cline@frii.com>     *  
00005 #*                                                                         *
00006 #*   This program is free software; you can redistribute it and/or modify  *
00007 #*   it under the terms of the GNU General Public License (GPL)            *
00008 #*   as published by the Free Software Foundation; either version 2 of     *
00009 #*   the License, or (at your option) any later version.                   *
00010 #*   for detail see the LICENCE text file.                                 *
00011 #*                                                                         *
00012 #*   This program is distributed in the hope that it will be useful,       *
00013 #*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00014 #*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00015 #*   GNU Library General Public License for more details.                  *
00016 #*                                                                         *
00017 #*   You should have received a copy of the GNU Library General Public     *
00018 #*   License along with this program; if not, write to the Free Software   *
00019 #*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
00020 #*   USA                                                                   *
00021 #*                                                                         *
00022 #***************************************************************************
00023 
00024 __title__="FreeCAD Draft Workbench GUI Tools"
00025 __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin"
00026 __url__ = "http://free-cad.sourceforge.net"
00027 
00028 #---------------------------------------------------------------------------
00029 # Generic stuff
00030 #---------------------------------------------------------------------------
00031 
00032 import os, FreeCAD, FreeCADGui, Part, WorkingPlane, math, re, importSVG, Draft, Draft_rc
00033 from functools import partial
00034 from draftlibs import fcvec,fcgeo
00035 from FreeCAD import Vector
00036 from draftGui import todo,QtCore,QtGui
00037 from pivy import coin
00038 
00039 # loads a translation engine
00040 #locale = QtCore.QLocale(eval("QtCore.QLocale."+FreeCADGui.getLocale())).name()
00041 #translator = QtCore.QTranslator()
00042 #translator.load('Draft_'+locale+'.qm',':/translations/')
00043 #QtGui.QApplication.installTranslator(translator)
00044 FreeCADGui.updateLocale()
00045 
00046 def translate(context,text):
00047     "convenience function for Qt translator"
00048     return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8).toUtf8()
00049                 
00050 def msg(text=None,mode=None):
00051     "prints the given message on the FreeCAD status bar"
00052     if not text: FreeCAD.Console.PrintMessage("")
00053     else:
00054         if mode == 'warning':
00055             FreeCAD.Console.PrintWarning(text)
00056         elif mode == 'error':
00057             FreeCAD.Console.PrintError(text)
00058         else:
00059             FreeCAD.Console.PrintMessage(text)
00060 
00061 # loads the fill patterns
00062 FreeCAD.svgpatterns = importSVG.getContents(Draft_rc.qt_resource_data,'pattern',True)
00063 altpat = Draft.getParam("patternFile")
00064 if os.path.isdir(altpat):
00065     for f in os.listdir(altpat):
00066         if '.svg' in f:
00067             p = importSVG.getContents(altpat+os.sep+f,'pattern')
00068             if p: FreeCAD.svgpatterns[p[0]]=p[1]
00069 
00070 # sets the default working plane
00071 plane = WorkingPlane.plane()
00072 FreeCAD.DraftWorkingPlane = plane
00073 defaultWP = Draft.getParam("defaultWP")
00074 if defaultWP == 1: plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,0,1), 0)
00075 elif defaultWP == 2: plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,1,0), 0)
00076 elif defaultWP == 3: plane.alignToPointAndAxis(Vector(0,0,0), Vector(1,0,0), 0)
00077 
00078 # last snapped objects, for quick intersection calculation
00079 lastObj = [0,0]
00080 
00081 # set modifier keys
00082 MODS = ["shift","ctrl","alt"]
00083 MODCONSTRAIN = MODS[Draft.getParam("modconstrain")]
00084 MODSNAP = MODS[Draft.getParam("modsnap")]
00085 MODALT = MODS[Draft.getParam("modalt")]
00086 
00087 #---------------------------------------------------------------------------
00088 # Snapping stuff
00089 #---------------------------------------------------------------------------
00090 
00091 def snapPoint(target,point,cursor,ctrl=False):
00092     '''
00093     Snap function used by the Draft tools
00094     
00095     Currently has two modes: passive and active. Pressing CTRL while 
00096     clicking puts you in active mode:
00097     
00098     - In passive mode (an open circle appears), your point is
00099     snapped to the nearest point on any underlying geometry.
00100     
00101     - In active mode (ctrl pressed, a filled circle appears), your point
00102     can currently be snapped to the following points:
00103         - Nodes and midpoints of all Part shapes
00104         - Nodes and midpoints of lines/wires
00105         - Centers and quadrant points of circles
00106         - Endpoints of arcs
00107         - Intersection between line, wires segments, arcs and circles
00108         - When constrained (SHIFT pressed), Intersections between
00109         constraining axis and lines/wires
00110     '''
00111         
00112     def getConstrainedPoint(edge,last,constrain):
00113         "check for constrained snappoint"
00114         p1 = edge.Vertexes[0].Point
00115         p2 = edge.Vertexes[-1].Point
00116         ar = []
00117         if (constrain == 0):
00118             if ((last.y > p1.y) and (last.y < p2.y) or (last.y > p2.y) and (last.y < p1.y)):
00119                 pc = (last.y-p1.y)/(p2.y-p1.y)
00120                 cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z)))
00121                 ar.append([cp,1,cp]) # constrainpoint
00122         if (constrain == 1):
00123             if ((last.x > p1.x) and (last.x < p2.x) or (last.x > p2.x) and (last.x < p1.x)):
00124                 pc = (last.x-p1.x)/(p2.x-p1.x)
00125                 cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z)))
00126                 ar.append([cp,1,cp]) # constrainpoint
00127         return ar
00128 
00129     def getPassivePoint(info):
00130         "returns a passive snap point"
00131         cur = Vector(info['x'],info['y'],info['z'])
00132         return [cur,2,cur]
00133 
00134     def getScreenDist(dist,cursor):
00135         "returns a 3D distance from a screen pixels distance"
00136         p1 = FreeCADGui.ActiveDocument.ActiveView.getPoint(cursor)
00137         p2 = FreeCADGui.ActiveDocument.ActiveView.getPoint((cursor[0]+dist,cursor[1]))
00138         return (p2.sub(p1)).Length
00139 
00140     def getGridSnap(target,point):
00141         "returns a grid snap point if available"
00142         if target.grid:
00143             return target.grid.getClosestNode(point)
00144         return None
00145 
00146     def getPerpendicular(edge,last):
00147         "returns a point on an edge, perpendicular to the given point"
00148         dv = last.sub(edge.Vertexes[0].Point)
00149         nv = fcvec.project(dv,fcgeo.vec(edge))
00150         np = (edge.Vertexes[0].Point).add(nv)
00151         return np
00152 
00153     # checking if alwaySnap setting is on
00154     extractrl = False
00155     if Draft.getParam("alwaysSnap"):
00156         extractrl = ctrl
00157         ctrl = True                
00158 
00159     # setting Radius
00160     radius =  getScreenDist(Draft.getParam("snapRange"),cursor)
00161         
00162     # checking if parallel to one of the edges of the last objects
00163     target.snap.off()
00164     target.extsnap.off()
00165     if (len(target.node) > 0):
00166         for o in [lastObj[1],lastObj[0]]:
00167             if o:
00168                 ob = target.doc.getObject(o)
00169                 if ob:
00170                     edges = ob.Shape.Edges
00171                     if len(edges)<10:
00172                         for e in edges:
00173                             if isinstance(e.Curve,Part.Line):
00174                                 last = target.node[len(target.node)-1]
00175                                 de = Part.Line(last,last.add(fcgeo.vec(e))).toShape()
00176                                 np = getPerpendicular(e,point)
00177                                 if (np.sub(point)).Length < radius:
00178                                     target.snap.coords.point.setValue((np.x,np.y,np.z))
00179                                     target.snap.setMarker("circle")
00180                                     target.snap.on()
00181                                     target.extsnap.p1(e.Vertexes[0].Point)
00182                                     target.extsnap.p2(np)
00183                                     target.extsnap.on()
00184                                     point = np
00185                                 else:
00186                                     last = target.node[len(target.node)-1]
00187                                     de = Part.Line(last,last.add(fcgeo.vec(e))).toShape()  
00188                                     np = getPerpendicular(de,point)
00189                                     if (np.sub(point)).Length < radius:
00190                                         target.snap.coords.point.setValue((np.x,np.y,np.z))
00191                                         target.snap.setMarker("circle")
00192                                         target.snap.on()
00193                                         point = np
00194 
00195     # check if we snapped to something
00196     snapped=target.view.getObjectInfo((cursor[0],cursor[1]))
00197 
00198     if (snapped == None):
00199         # nothing has been snapped, check fro grid snap
00200         gpt = getGridSnap(target,point)
00201         if gpt:
00202             if radius != 0:
00203                 dv = point.sub(gpt)
00204                 if dv.Length <= radius:
00205                     target.snap.coords.point.setValue((gpt.x,gpt.y,gpt.z))
00206                     target.snap.setMarker("point")
00207                     target.snap.on()  
00208                     return gpt
00209         return point
00210     else:
00211         # we have something to snap
00212         obj = target.doc.getObject(snapped['Object'])
00213         if hasattr(obj.ViewObject,"Selectable"):
00214                         if not obj.ViewObject.Selectable:
00215                                 return point
00216         if not ctrl:
00217                         # are we in passive snap?
00218                         snapArray = [getPassivePoint(snapped)]
00219         else:
00220             snapArray = []
00221             comp = snapped['Component']
00222             if obj.isDerivedFrom("Part::Feature"):
00223                 if "Edge" in comp:
00224                     # get the stored objects to calculate intersections
00225                     intedges = []
00226                     if lastObj[0]:
00227                         lo = target.doc.getObject(lastObj[0])
00228                         if lo:
00229                             if lo.isDerivedFrom("Part::Feature"):
00230                                 intedges = lo.Shape.Edges
00231                                                            
00232                     nr = int(comp[4:])-1
00233                     edge = obj.Shape.Edges[nr]
00234                     for v in edge.Vertexes:
00235                         snapArray.append([v.Point,0,v.Point])
00236                     if isinstance(edge.Curve,Part.Line):
00237                         # the edge is a line
00238                         midpoint = fcgeo.findMidpoint(edge)
00239                         snapArray.append([midpoint,1,midpoint])
00240                         if (len(target.node) > 0):
00241                             last = target.node[len(target.node)-1]
00242                             snapArray.extend(getConstrainedPoint(edge,last,target.constrain))
00243                             np = getPerpendicular(edge,last)
00244                             snapArray.append([np,1,np])
00245 
00246                     elif isinstance (edge.Curve,Part.Circle):
00247                         # the edge is an arc
00248                         rad = edge.Curve.Radius
00249                         pos = edge.Curve.Center
00250                         for i in [0,30,45,60,90,120,135,150,180,210,225,240,270,300,315,330]:
00251                             ang = math.radians(i)
00252                             cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z)
00253                             snapArray.append([cur,1,cur])
00254                         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]:
00255                             ang = math.radians(i)
00256                             cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z)
00257                             snapArray.append([cur,0,pos])
00258 
00259                     for e in intedges:
00260                         # get the intersection points
00261                         pt = fcgeo.findIntersection(e,edge)
00262                         if pt:
00263                             for p in pt:
00264                                 snapArray.append([p,3,p])
00265                 elif "Vertex" in comp:
00266                     # directly snapped to a vertex
00267                     p = Vector(snapped['x'],snapped['y'],snapped['z'])
00268                     snapArray.append([p,0,p])
00269                 elif comp == '':
00270                     # workaround for the new view provider
00271                     p = Vector(snapped['x'],snapped['y'],snapped['z'])
00272                     snapArray.append([p,2,p])
00273                 else:
00274                     snapArray = [getPassivePoint(snapped)]
00275             elif Draft.getType(obj) == "Dimension":
00276                 for pt in [obj.Start,obj.End,obj.Dimline]:
00277                     snapArray.append([pt,0,pt])
00278             elif Draft.getType(obj) == "Mesh":
00279                 for v in obj.Mesh.Points:
00280                     snapArray.append([v.Vector,0,v.Vector])
00281         if not lastObj[0]:
00282             lastObj[0] = obj.Name
00283             lastObj[1] = obj.Name
00284         if (lastObj[1] != obj.Name):
00285             lastObj[0] = lastObj[1]
00286             lastObj[1] = obj.Name
00287 
00288         # calculating shortest distance
00289         shortest = 1000000000000000000
00290         spt = Vector(snapped['x'],snapped['y'],snapped['z'])
00291         newpoint = [Vector(0,0,0),0,Vector(0,0,0)]
00292         for pt in snapArray:
00293             if pt[0] == None: print "snapPoint: debug 'i[0]' is 'None'"
00294             di = pt[0].sub(spt)
00295             if di.Length < shortest:
00296                 shortest = di.Length
00297                 newpoint = pt
00298         if radius != 0:
00299             dv = point.sub(newpoint[2])
00300             if (not extractrl) and (dv.Length > radius):
00301                 newpoint = getPassivePoint(snapped)
00302         target.snap.coords.point.setValue((newpoint[2].x,newpoint[2].y,newpoint[2].z))
00303         if (newpoint[1] == 1):
00304             target.snap.setMarker("square")
00305         elif (newpoint[1] == 0):
00306             target.snap.setMarker("point")
00307         elif (newpoint[1] == 3):
00308             target.snap.setMarker("square")
00309         else:
00310             target.snap.setMarker("circle")
00311         target.snap.on()                                
00312         return newpoint[2]
00313 
00314 def constrainPoint (target,pt,mobile=False,sym=False):
00315     '''
00316     Constrain function used by the Draft tools
00317     On commands that need to enter several points (currently only line/wire),
00318     you can constrain the next point to be picked to the last drawn point by
00319     pressing SHIFT. The vertical or horizontal constraining depends on the
00320     position of your mouse in relation to last point at the moment you press
00321     SHIFT. if mobile=True, mobile behaviour applies. If sym=True, x alway = y
00322     '''
00323     point = Vector(pt)
00324     if len(target.node) > 0:
00325         last = target.node[-1]
00326         dvec = point.sub(last)
00327         affinity = plane.getClosestAxis(dvec)
00328         if ((target.constrain == None) or mobile):
00329             if affinity == "x":
00330                 dv = fcvec.project(dvec,plane.u)
00331                 point = last.add(dv)
00332                 if sym:
00333                     l = dv.Length
00334                     if dv.getAngle(plane.u) > 1:
00335                         l = -l
00336                     point = last.add(plane.getGlobalCoords(Vector(l,l,l)))
00337                 target.constrain = 0 #x direction
00338                 target.ui.xValue.setEnabled(True)
00339                 target.ui.yValue.setEnabled(False)
00340                 target.ui.zValue.setEnabled(False)
00341                 target.ui.xValue.setFocus()
00342             elif affinity == "y":
00343                 dv = fcvec.project(dvec,plane.v)
00344                 point = last.add(dv)
00345                 if sym:
00346                     l = dv.Length
00347                     if dv.getAngle(plane.v) > 1:
00348                         l = -l
00349                     point = last.add(plane.getGlobalCoords(Vector(l,l,l)))
00350                 target.constrain = 1 #y direction
00351                 target.ui.xValue.setEnabled(False)
00352                 target.ui.yValue.setEnabled(True)
00353                 target.ui.zValue.setEnabled(False)
00354                 target.ui.yValue.setFocus()
00355             elif affinity == "z":
00356                 dv = fcvec.project(dvec,plane.axis)
00357                 point = last.add(dv)
00358                 if sym:
00359                     l = dv.Length
00360                     if dv.getAngle(plane.axis) > 1:
00361                         l = -l
00362                     point = last.add(plane.getGlobalCoords(Vector(l,l,l)))
00363                 target.constrain = 2 #z direction
00364                 target.ui.xValue.setEnabled(False)
00365                 target.ui.yValue.setEnabled(False)
00366                 target.ui.zValue.setEnabled(True)
00367                 target.ui.zValue.setFocus()
00368             else: target.constrain = 3
00369         elif (target.constrain == 0):
00370             dv = fcvec.project(dvec,plane.u)
00371             point = last.add(dv)
00372             if sym:
00373                 l = dv.Length
00374                 if dv.getAngle(plane.u) > 1:
00375                     l = -l
00376                 point = last.add(plane.getGlobalCoords(Vector(l,l,l)))
00377         elif (target.constrain == 1):
00378             dv = fcvec.project(dvec,plane.v)
00379             point = last.add(dv)
00380             if sym:
00381                 l = dv.Length
00382                 if dv.getAngle(plane.u) > 1:
00383                     l = -l
00384                 point = last.add(plane.getGlobalCoords(Vector(l,l,l)))
00385         elif (target.constrain == 2):
00386             dv = fcvec.project(dvec,plane.axis)
00387             point = last.add(dv)
00388             if sym:
00389                 l = dv.Length
00390                 if dv.getAngle(plane.u) > 1:
00391                     l = -l
00392                 point = last.add(plane.getGlobalCoords(Vector(l,l,l)))                  
00393     return point
00394 
00395 def selectObject(arg):
00396     '''this is a scene even handler, to be called from the Draft tools
00397     when they need to select an object'''
00398     if (arg["Type"] == "SoKeyboardEvent"):
00399         if (arg["Key"] == "ESCAPE"):
00400             FreeCAD.activeDraftCommand.finish()
00401             # TODO : this part raises a coin3D warning about scene traversal, to be fixed.
00402         if (arg["Type"] == "SoMouseButtonEvent"):
00403             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
00404                 cursor = arg["Position"]
00405                 snapped = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo((cursor[0],cursor[1]))
00406                 if snapped:
00407                     obj = FreeCAD.ActiveDocument.getObject(snapped['Object'])
00408                     FreeCADGui.Selection.addSelection(obj)
00409                     FreeCAD.activeDraftCommand.component=snapped['Component']
00410                     FreeCAD.activeDraftCommand.proceed()
00411 
00412 def getPoint(target,args,mobile=False,sym=False,workingplane=True):
00413     '''
00414     Function used by the Draft Tools.
00415     returns a constrained 3d point and its original point.
00416     if mobile=True, the constraining occurs from the location of
00417     mouse cursor when Shift is pressed, otherwise from last entered
00418     point. If sym=True, x and y values stay always equal. If workingplane=False,
00419     the point wont be projected on the Working Plane.
00420     '''
00421     ui = FreeCADGui.draftToolBar
00422     view = FreeCADGui.ActiveDocument.ActiveView
00423     point = view.getPoint(args["Position"][0],args["Position"][1])
00424     point = snapPoint(target,point,args["Position"],hasMod(args,MODSNAP))
00425 
00426     if (not plane.weak) and workingplane:
00427         # working plane was explicitely selected - project onto it
00428         viewDirection = view.getViewDirection()
00429         if FreeCADGui.ActiveDocument.ActiveView.getCameraType() == "Perspective":
00430             camera = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
00431             p = camera.getField("position").getValue()
00432             # view is from camera to point:
00433             viewDirection = point.sub(Vector(p[0],p[1],p[2]))
00434         # if we are not snapping to anything, project along view axis,
00435         # otherwise perpendicularly
00436         if view.getObjectInfo((args["Position"][0],args["Position"][1])):
00437             pass
00438             # point = plane.projectPoint(point)
00439         else:
00440             point = plane.projectPoint(point, viewDirection)
00441     ctrlPoint = Vector(point.x,point.y,point.z)
00442     if (hasMod(args,MODCONSTRAIN)): # constraining
00443         if mobile and (target.constrain == None):
00444             target.node.append(point)
00445         point = constrainPoint(target,point,mobile=mobile,sym=sym)
00446     else:
00447         target.constrain = None
00448         ui.xValue.setEnabled(True)
00449         ui.yValue.setEnabled(True)
00450         ui.zValue.setEnabled(True)
00451     if target.node:
00452         if target.featureName == "Rectangle":
00453             ui.displayPoint(point, target.node[0], plane=plane)
00454         else:
00455             ui.displayPoint(point, target.node[-1], plane=plane)
00456     else: ui.displayPoint(point, plane=plane)
00457     return point,ctrlPoint
00458 
00459 def getSupport(args):
00460     "returns the supporting object and sets the working plane"
00461     snapped = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo((args["Position"][0],args["Position"][1]))
00462     if not snapped: return None
00463     obj = None
00464     plane.save()
00465     try:
00466         obj = FreeCAD.ActiveDocument.getObject(snapped['Object'])
00467         shape = obj.Shape
00468         component = getattr(shape,snapped["Component"])
00469         if plane.alignToFace(component, 0) \
00470                 or plane.alignToCurve(component, 0):
00471             self.display(plane.axis)
00472     except:
00473         pass
00474     return obj
00475 
00476 def hasMod(args,mod):
00477     "checks if args has a specific modifier"
00478     if mod == "shift":
00479         return args["ShiftDown"]
00480     elif mod == "ctrl":
00481         return args["CtrlDown"]
00482     elif mod == "alt":
00483         return args["AltDown"]
00484 
00485 def setMod(args,mod,state):
00486     "sets a specific modifier state in args"
00487     if mod == "shift":
00488         args["ShiftDown"] = state
00489     elif mod == "ctrl":
00490         args["CtrlDown"] = state
00491     elif mod == "alt":
00492         args["AltDown"] = state
00493         
00494 #---------------------------------------------------------------------------
00495 # Trackers
00496 #---------------------------------------------------------------------------
00497 
00498 class Tracker:
00499     "A generic Draft Tracker, to be used by other specific trackers"
00500     def __init__(self,dotted=False,scolor=None,swidth=None,children=[],ontop=False):
00501         self.ontop = ontop
00502         color = coin.SoBaseColor()
00503         color.rgb = scolor or FreeCADGui.draftToolBar.getDefaultColor("ui")
00504         drawstyle = coin.SoDrawStyle()
00505         if swidth:
00506             drawstyle.lineWidth = swidth
00507         if dotted:
00508             drawstyle.style = coin.SoDrawStyle.LINES
00509             drawstyle.lineWeight = 3
00510             drawstyle.linePattern = 0x0f0f #0xaa
00511         node = coin.SoSeparator()
00512         for c in [drawstyle, color] + children:
00513             node.addChild(c)
00514         self.switch = coin.SoSwitch() # this is the on/off switch
00515         self.switch.addChild(node)
00516         self.switch.whichChild = -1
00517         self.Visible = False
00518         todo.delay(self._insertSwitch, self.switch)
00519 
00520     def finalize(self):
00521         todo.delay(self._removeSwitch, self.switch)
00522         self.switch = None
00523 
00524     def _insertSwitch(self, switch):
00525         '''insert self.switch into the scene graph.  Must not be called
00526         from an event handler (or other scene graph traversal).'''
00527         sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
00528         if self.ontop:
00529             sg.insertChild(switch,0)
00530         else:
00531             sg.addChild(switch)
00532 
00533     def _removeSwitch(self, switch):
00534         '''remove self.switch from the scene graph.  As with _insertSwitch,
00535         must not be called during scene graph traversal).'''
00536         sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
00537         sg.removeChild(switch)
00538 
00539     def on(self):
00540         self.switch.whichChild = 0
00541         self.Visible = True
00542 
00543     def off(self):
00544         self.switch.whichChild = -1
00545         self.Visible = False
00546                                 
00547 class snapTracker(Tracker):
00548     "A Snap Mark tracker, used by tools that support snapping"
00549     def __init__(self):
00550         color = coin.SoBaseColor()
00551         color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap")
00552         self.marker = coin.SoMarkerSet() # this is the marker symbol
00553         self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
00554         self.coords = coin.SoCoordinate3() # this is the coordinate
00555         self.coords.point.setValue((0,0,0))
00556         node = coin.SoAnnotation()
00557         node.addChild(self.coords)
00558         node.addChild(color)
00559         node.addChild(self.marker)
00560         Tracker.__init__(self,children=[node])
00561 
00562     def setMarker(self,style):
00563         if (style == "point"):
00564             self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
00565         elif (style == "square"):
00566             self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9
00567         elif (style == "circle"):
00568             self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_9_9
00569 
00570 class lineTracker(Tracker):
00571     "A Line tracker, used by the tools that need to draw temporary lines"
00572     def __init__(self,dotted=False,scolor=None,swidth=None):
00573         line = coin.SoLineSet()
00574         line.numVertices.setValue(2)
00575         self.coords = coin.SoCoordinate3() # this is the coordinate
00576         self.coords.point.setValues(0,2,[[0,0,0],[1,0,0]])
00577         Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line])
00578 
00579     def p1(self,point=None):
00580         "sets or gets the first point of the line"
00581         if point:
00582             self.coords.point.set1Value(0,point.x,point.y,point.z)
00583         else:
00584             return Vector(self.coords.point.getValues()[0].getValue())
00585 
00586     def p2(self,point=None):
00587         "sets or gets the second point of the line"
00588         if point:
00589             self.coords.point.set1Value(1,point.x,point.y,point.z)
00590         else:
00591             return Vector(self.coords.point.getValues()[-1].getValue())
00592                         
00593     def getLength(self):
00594         "returns the length of the line"
00595         p1 = Vector(self.coords.point.getValues()[0].getValue())
00596         p2 = Vector(self.coords.point.getValues()[-1].getValue())
00597         return (p2.sub(p1)).Length
00598 
00599 class rectangleTracker(Tracker):
00600     "A Rectangle tracker, used by the rectangle tool"
00601     def __init__(self,dotted=False,scolor=None,swidth=None):
00602         self.origin = Vector(0,0,0)
00603         line = coin.SoLineSet()
00604         line.numVertices.setValue(5)
00605         self.coords = coin.SoCoordinate3() # this is the coordinate
00606         self.coords.point.setValues(0,50,[[0,0,0],[2,0,0],[2,2,0],[0,2,0],[0,0,0]])
00607         Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line])
00608         self.u = plane.u
00609         self.v = plane.v
00610 
00611     def setorigin(self,point):
00612         "sets the base point of the rectangle"
00613         self.coords.point.set1Value(0,point.x,point.y,point.z)
00614         self.coords.point.set1Value(4,point.x,point.y,point.z)
00615         self.origin = point
00616 
00617     def update(self,point):
00618         "sets the opposite (diagonal) point of the rectangle"
00619         diagonal = point.sub(self.origin)
00620         inpoint1 = self.origin.add(fcvec.project(diagonal,self.v))
00621         inpoint2 = self.origin.add(fcvec.project(diagonal,self.u))
00622         self.coords.point.set1Value(1,inpoint1.x,inpoint1.y,inpoint1.z)
00623         self.coords.point.set1Value(2,point.x,point.y,point.z)
00624         self.coords.point.set1Value(3,inpoint2.x,inpoint2.y,inpoint2.z)
00625 
00626     def setPlane(self,u,v=None):
00627         '''sets given (u,v) vectors as working plane. You can give only u
00628         and v will be deduced automatically given current workplane'''
00629         self.u = u
00630         if v:
00631             self.v = v
00632         else:
00633             norm = plane.u.cross(plane.v)
00634             self.v = self.u.cross(norm)
00635 
00636     def p1(self,point=None):
00637         "sets or gets the base point of the rectangle"
00638         if point:
00639             self.setorigin(point)
00640         else:
00641             return Vector(self.coords.point.getValues()[0].getValue())
00642 
00643     def p2(self):
00644         "gets the second point (on u axis) of the rectangle"
00645         return Vector(self.coords.point.getValues()[3].getValue())
00646 
00647     def p3(self,point=None):
00648         "sets or gets the opposite (diagonal) point of the rectangle"
00649         if point:
00650             self.update(point)
00651         else:
00652             return Vector(self.coords.point.getValues()[2].getValue())
00653 
00654     def p4(self):
00655         "gets the fourth point (on v axis) of the rectangle"
00656         return Vector(self.coords.point.getValues()[1].getValue())
00657                 
00658     def getSize(self):
00659         "returns (length,width) of the rectangle"
00660         p1 = Vector(self.coords.point.getValues()[0].getValue())
00661         p2 = Vector(self.coords.point.getValues()[2].getValue())
00662         diag = p2.sub(p1)
00663         return ((fcvec.project(diag,self.u)).Length,(fcvec.project(diag,self.v)).Length)
00664 
00665     def getNormal(self):
00666         "returns the normal of the rectangle"
00667         return (self.u.cross(self.v)).normalize()
00668                 
00669 class dimTracker(Tracker):
00670     "A Dimension tracker, used by the dimension tool"
00671     def __init__(self,dotted=False,scolor=None,swidth=None):
00672         line = coin.SoLineSet()
00673         line.numVertices.setValue(4)
00674         self.coords = coin.SoCoordinate3() # this is the coordinate
00675         self.coords.point.setValues(0,4,[[0,0,0],[0,0,0],[0,0,0],[0,0,0]])
00676         Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line])
00677         self.p1 = self.p2 = self.p3 = None
00678 
00679     def update(self,pts):
00680         if len(pts) == 1:
00681             self.p3 = pts[0]
00682         else:
00683             self.p1 = pts[0]
00684             self.p2 = pts[1]
00685             if len(pts) > 2:
00686                 self.p3 = pts[2]
00687         self.calc()
00688         
00689     def calc(self):
00690         if (self.p1 != None) and (self.p2 != None):
00691             points = [fcvec.tup(self.p1,True),fcvec.tup(self.p2,True),\
00692                           fcvec.tup(self.p1,True),fcvec.tup(self.p2,True)]
00693             if self.p3 != None:
00694                 p1 = self.p1
00695                 p4 = self.p2
00696                 if fcvec.equals(p1,p4):
00697                     proj = None
00698                 else:
00699                     base = Part.Line(p1,p4).toShape()
00700                     proj = fcgeo.findDistance(self.p3,base)
00701                 if not proj:
00702                     p2 = p1
00703                     p3 = p4
00704                 else:
00705                     p2 = p1.add(fcvec.neg(proj))
00706                     p3 = p4.add(fcvec.neg(proj))
00707                 points = [fcvec.tup(p1),fcvec.tup(p2),fcvec.tup(p3),fcvec.tup(p4)]
00708             self.coords.point.setValues(0,4,points)
00709 
00710 class bsplineTracker(Tracker):
00711     "A bspline tracker"
00712     def __init__(self,dotted=False,scolor=None,swidth=None,points = []):
00713         self.bspline = None
00714         self.points = points
00715         self.trans = coin.SoTransform()
00716         self.sep = coin.SoSeparator()
00717         self.recompute()
00718         Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep])
00719         
00720     def update(self, points):
00721         self.points = points
00722         self.recompute()
00723             
00724     def recompute(self):
00725         if (len(self.points) >= 2):
00726             if self.bspline: self.sep.removeChild(self.bspline)
00727             self.bspline = None
00728             c =  Part.BSplineCurve()
00729             # DNC: allows to close the curve by placing ends close to each other
00730             if ( len(self.points) >= 3 ) and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ):
00731                 # YVH: Added a try to bypass some hazardous situations
00732                 try:
00733                     c.interpolate(self.points[:-1], True)
00734                 except:
00735                     pass
00736             elif self.points:
00737                 try:
00738                     c.interpolate(self.points, False)
00739                 except:
00740                     pass
00741             c = c.toShape()
00742             buf=c.writeInventor(2,0.01)
00743             #fp=open("spline.iv","w")
00744             #fp.write(buf)
00745             #fp.close()
00746             ivin = coin.SoInput()
00747             ivin.setBuffer(buf)
00748             ivob = coin.SoDB.readAll(ivin)
00749             # In case reading from buffer failed
00750             if ivob and ivob.getNumChildren() > 1:
00751                 self.bspline = ivob.getChild(1).getChild(0)
00752                 self.bspline.removeChild(self.bspline.getChild(0))
00753                 self.bspline.removeChild(self.bspline.getChild(0))
00754                 self.sep.addChild(self.bspline)
00755             else:
00756                 FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n")
00757 
00758 class arcTracker(Tracker):
00759     "An arc tracker"
00760     def __init__(self,dotted=False,scolor=None,swidth=None,start=0,end=math.pi*2):
00761         self.circle = None
00762         self.startangle = math.degrees(start)
00763         self.endangle = math.degrees(end)
00764         self.trans = coin.SoTransform()
00765         self.trans.translation.setValue([0,0,0])
00766         self.sep = coin.SoSeparator()
00767         self.recompute()
00768         Tracker.__init__(self,dotted,scolor,swidth,[self.trans, self.sep])
00769 
00770     def setCenter(self,cen):
00771         "sets the center point"
00772         self.trans.translation.setValue([cen.x,cen.y,cen.z])
00773 
00774     def setRadius(self,rad):
00775         "sets the radius"
00776         self.trans.scaleFactor.setValue([rad,rad,rad])
00777 
00778     def getRadius(self):
00779         "returns the current radius"
00780         return self.trans.scaleFactor.getValue()[0]
00781 
00782     def setStartAngle(self,ang):
00783         "sets the start angle"
00784         self.startangle = math.degrees(ang)
00785         self.recompute()
00786 
00787     def setEndAngle(self,ang):
00788         "sets the end angle"
00789         self.endangle = math.degrees(ang)
00790         self.recompute()
00791 
00792     def getAngle(self,pt):
00793         "returns the angle of a given vector"
00794         c = self.trans.translation.getValue()
00795         center = Vector(c[0],c[1],c[2])
00796         base = plane.u
00797         rad = pt.sub(center)
00798         return(fcvec.angle(rad,base,plane.axis))
00799 
00800     def getAngles(self):
00801         "returns the start and end angles"
00802         return(self.startangle,self.endangle)
00803                 
00804     def setStartPoint(self,pt):
00805         "sets the start angle from a point"
00806         self.setStartAngle(-self.getAngle(pt))
00807 
00808     def setEndPoint(self,pt):
00809         "sets the end angle from a point"
00810         self.setEndAngle(self.getAngle(pt))
00811                 
00812     def setApertureAngle(self,ang):
00813         "sets the end angle by giving the aperture angle"
00814         ap = math.degrees(ang)
00815         self.endangle = self.startangle + ap
00816         self.recompute()
00817 
00818     def recompute(self):
00819         if self.circle: self.sep.removeChild(self.circle)
00820         self.circle = None
00821         if self.endangle < self.startangle:
00822             c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.endangle,self.startangle)
00823         else:
00824             c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.startangle,self.endangle)
00825         buf=c.writeInventor(2,0.01)
00826         ivin = coin.SoInput()
00827         ivin.setBuffer(buf)
00828         ivob = coin.SoDB.readAll(ivin)
00829         # In case reading from buffer failed
00830         if ivob and ivob.getNumChildren() > 1:
00831             self.circle = ivob.getChild(1).getChild(0)
00832             self.circle.removeChild(self.circle.getChild(0))
00833             self.circle.removeChild(self.circle.getChild(0))
00834             self.sep.addChild(self.circle)
00835         else:
00836             FreeCAD.Console.PrintWarning("arcTracker.recompute() failed to read-in Inventor string\n")
00837 
00838 class ghostTracker(Tracker):
00839     '''A Ghost tracker, that allows to copy whole object representations.
00840     You can pass it an object or a list of objects, or a shape.'''
00841     def __init__(self,sel):
00842         self.trans = coin.SoTransform()
00843         self.trans.translation.setValue([0,0,0])
00844         self.children = [self.trans]
00845         self.ivsep = coin.SoSeparator()
00846         try:
00847             if isinstance(sel,Part.Shape):
00848                 ivin = coin.SoInput()
00849                 ivin.setBuffer(sel.writeInventor())
00850                 ivob = coin.SoDB.readAll(ivin)
00851                 self.ivsep.addChild(ivob.getChildren()[1])
00852             else:
00853                 if not isinstance(sel,list):
00854                     sel = [sel]
00855                 for obj in sel:
00856                     self.ivsep.addChild(obj.ViewObject.RootNode.copy())
00857         except:
00858             print "draft: Couldn't create ghost"
00859         self.children.append(self.ivsep)
00860         Tracker.__init__(self,children=self.children)
00861 
00862     def update(self,obj):
00863         obj.ViewObject.show()
00864         self.finalize()
00865         self.ivsep = coin.SoSeparator()
00866         self.ivsep.addChild(obj.ViewObject.RootNode.copy())
00867         Tracker.__init__(self,children=[self.ivsep])
00868         self.on()
00869         obj.ViewObject.hide()
00870 
00871 class editTracker(Tracker):
00872     "A node edit tracker"
00873     def __init__(self,pos=Vector(0,0,0),name="None",idx=0,objcol=None):
00874         color = coin.SoBaseColor()
00875         if objcol:
00876             color.rgb = objcol[:3]
00877         else:
00878             color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap")
00879         self.marker = coin.SoMarkerSet() # this is the marker symbol
00880         self.marker.markerIndex = coin.SoMarkerSet.SQUARE_FILLED_9_9
00881         self.coords = coin.SoCoordinate3() # this is the coordinate
00882         self.coords.point.setValue((pos.x,pos.y,pos.z))
00883         selnode = coin.SoType.fromName("SoFCSelection").createInstance()
00884         selnode.documentName.setValue(FreeCAD.ActiveDocument.Name)
00885         selnode.objectName.setValue(name)
00886         selnode.subElementName.setValue("EditNode"+str(idx))
00887         node = coin.SoAnnotation()
00888         selnode.addChild(self.coords)
00889         selnode.addChild(color)
00890         selnode.addChild(self.marker)
00891         node.addChild(selnode)
00892         Tracker.__init__(self,children=[node],ontop=True)
00893         self.on()
00894 
00895     def set(self,pos):
00896         self.coords.point.setValue((pos.x,pos.y,pos.z))
00897 
00898     def get(self):
00899         p = self.coords.point.getValues()[0]
00900         return Vector(p[0],p[1],p[2])
00901 
00902     def move(self,delta):
00903         self.set(self.get().add(delta))
00904 
00905 class PlaneTracker(Tracker):
00906     "A working plane tracker"
00907     def __init__(self):
00908         # getting screen distance
00909         p1 = FreeCADGui.ActiveDocument.ActiveView.getPoint((100,100))
00910         p2 = FreeCADGui.ActiveDocument.ActiveView.getPoint((110,100))
00911         bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange")/2)
00912         self.trans = coin.SoTransform()
00913         self.trans.translation.setValue([0,0,0])
00914         m1 = coin.SoMaterial()
00915         m1.transparency.setValue(0.8)
00916         m1.diffuseColor.setValue([0.4,0.4,0.6])
00917         c1 = coin.SoCoordinate3()
00918         c1.point.setValues([[-bl,-bl,0],[bl,-bl,0],[bl,bl,0],[-bl,bl,0]])
00919         f = coin.SoIndexedFaceSet()
00920         f.coordIndex.setValues([0,1,2,3])
00921         m2 = coin.SoMaterial()
00922         m2.transparency.setValue(0.7)
00923         m2.diffuseColor.setValue([0.2,0.2,0.3])
00924         c2 = coin.SoCoordinate3()
00925         c2.point.setValues([[0,bl,0],[0,0,0],[bl,0,0],[-.05*bl,.95*bl,0],[0,bl,0],
00926                             [.05*bl,.95*bl,0],[.95*bl,.05*bl,0],[bl,0,0],[.95*bl,-.05*bl,0]])
00927         l = coin.SoLineSet()
00928         l.numVertices.setValues([3,3,3])
00929         s = coin.SoSeparator()
00930         s.addChild(self.trans)
00931         s.addChild(m1)
00932         s.addChild(c1)
00933         s.addChild(f)
00934         s.addChild(m2)
00935         s.addChild(c2)
00936         s.addChild(l)
00937         Tracker.__init__(self,children=[s])
00938 
00939     def set(self,pos=None):
00940         if pos:                        
00941             Q = plane.getRotation().Rotation.Q
00942         else:
00943             plm = plane.getPlacement()
00944             Q = plm.Rotation.Q
00945             pos = plm.Base
00946         self.trans.translation.setValue([pos.x,pos.y,pos.z])
00947         self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]])
00948         self.on()
00949                         
00950 class wireTracker(Tracker):                
00951     "A wire tracker"
00952     def __init__(self,wire):
00953         self.line = coin.SoLineSet()
00954         self.closed = fcgeo.isReallyClosed(wire)
00955         if self.closed:
00956             self.line.numVertices.setValue(len(wire.Vertexes)+1)
00957         else:
00958             self.line.numVertices.setValue(len(wire.Vertexes))
00959         self.coords = coin.SoCoordinate3()
00960         self.update(wire)
00961         Tracker.__init__(self,children=[self.coords,self.line])
00962 
00963     def update(self,wire):
00964         if wire:
00965             self.line.numVertices.setValue(len(wire.Vertexes))
00966             for i in range(len(wire.Vertexes)):
00967                 p=wire.Vertexes[i].Point
00968                 self.coords.point.set1Value(i,[p.x,p.y,p.z])
00969             if self.closed:
00970                 t = len(wire.Vertexes)
00971                 p = wire.Vertexes[0].Point
00972                 self.coords.point.set1Value(t,[p.x,p.y,p.z])
00973 
00974 class gridTracker(Tracker):
00975     "A grid tracker"
00976     def __init__(self):
00977         # self.space = 1
00978         self.space = Draft.getParam("gridSpacing")
00979         # self.mainlines = 10
00980         self.mainlines = Draft.getParam("gridEvery")
00981         self.numlines = 100
00982         col = [0.2,0.2,0.3]
00983         
00984         self.trans = coin.SoTransform()
00985         self.trans.translation.setValue([0,0,0])
00986                 
00987         bound = (self.numlines/2)*self.space
00988         pts = []
00989         mpts = []
00990         for i in range(self.numlines+1):
00991             curr = -bound + i*self.space
00992             z = 0
00993             if i/float(self.mainlines) == i/self.mainlines:
00994                 mpts.extend([[-bound,curr,z],[bound,curr,z]])
00995                 mpts.extend([[curr,-bound,z],[curr,bound,z]])
00996             else:
00997                 pts.extend([[-bound,curr,z],[bound,curr,z]])
00998                 pts.extend([[curr,-bound,z],[curr,bound,z]])
00999         idx = []
01000         midx = []
01001         for p in range(0,len(pts),2):
01002             idx.append(2)
01003         for mp in range(0,len(mpts),2):
01004             midx.append(2)
01005 
01006         mat1 = coin.SoMaterial()
01007         mat1.transparency.setValue(0.7)
01008         mat1.diffuseColor.setValue(col)
01009         self.coords1 = coin.SoCoordinate3()
01010         self.coords1.point.setValues(pts)
01011         lines1 = coin.SoLineSet()
01012         lines1.numVertices.setValues(idx)
01013         mat2 = coin.SoMaterial()
01014         mat2.transparency.setValue(0.3)
01015         mat2.diffuseColor.setValue(col)
01016         self.coords2 = coin.SoCoordinate3()
01017         self.coords2.point.setValues(mpts)
01018         lines2 = coin.SoLineSet()
01019         lines2.numVertices.setValues(midx)
01020         s = coin.SoSeparator()
01021         s.addChild(self.trans)
01022         s.addChild(mat1)
01023         s.addChild(self.coords1)
01024         s.addChild(lines1)
01025         s.addChild(mat2)
01026         s.addChild(self.coords2)
01027         s.addChild(lines2)
01028         Tracker.__init__(self,children=[s])
01029         self.update()
01030 
01031     def update(self):
01032         bound = (self.numlines/2)*self.space
01033         pts = []
01034         mpts = []
01035         for i in range(self.numlines+1):
01036             curr = -bound + i*self.space
01037             if i/float(self.mainlines) == i/self.mainlines:
01038                 mpts.extend([[-bound,curr,0],[bound,curr,0]])
01039                 mpts.extend([[curr,-bound,0],[curr,bound,0]])
01040             else:
01041                 pts.extend([[-bound,curr,0],[bound,curr,0]])
01042                 pts.extend([[curr,-bound,0],[curr,bound,0]])
01043         self.coords1.point.setValues(pts)
01044         self.coords2.point.setValues(mpts)
01045 
01046     def setSpacing(self,space):
01047         self.space = space
01048         self.update()
01049 
01050     def setMainlines(self,ml):
01051         self.mainlines = ml
01052         self.update()
01053 
01054     def set(self):
01055         Q = plane.getRotation().Rotation.Q
01056         self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]])
01057         self.on()
01058 
01059     def getClosestNode(self,point):
01060         "returns the closest node from the given point"
01061         # get the 2D coords.
01062         point = plane.projectPoint(point)
01063         u = fcvec.project(point,plane.u)
01064         lu = u.Length
01065         if u.getAngle(plane.u) > 1.5:
01066             lu  = -lu
01067         v = fcvec.project(point,plane.v)
01068         lv = v.Length
01069         if v.getAngle(plane.v) > 1.5:
01070             lv = -lv
01071         # print "u = ",u," v = ",v
01072         # find nearest grid node
01073         pu = (round(lu/self.space,0))*self.space
01074         pv = (round(lv/self.space,0))*self.space
01075         rot = FreeCAD.Rotation()
01076         rot.Q = self.trans.rotation.getValue().getValue()
01077         return rot.multVec(Vector(pu,pv,0))
01078 
01079                 
01080 #---------------------------------------------------------------------------
01081 # Helper tools
01082 #---------------------------------------------------------------------------
01083                         
01084 class SelectPlane:
01085     "The Draft_SelectPlane FreeCAD command definition"
01086 
01087     def GetResources(self):
01088         return {'Pixmap'  : 'Draft_SelectPlane',
01089                 'Accel' : "W, P",
01090                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"),
01091                 'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_SelectPlane", "Select a working plane for geometry creation")}
01092 
01093     def IsActive(self):
01094         if FreeCADGui.ActiveDocument:
01095             return True
01096         else:
01097             return False
01098         
01099     def Activated(self):
01100         if FreeCAD.activeDraftCommand:
01101             FreeCAD.activeDraftCommand.finish()
01102         self.offset = 0
01103         self.ui = None
01104         self.call = None
01105         self.doc = FreeCAD.ActiveDocument
01106         if self.doc:
01107             FreeCAD.activeDraftCommand = self
01108             self.view = FreeCADGui.ActiveDocument.ActiveView
01109             self.ui = FreeCADGui.draftToolBar
01110             self.ui.selectPlaneUi()
01111             msg(translate("draft", "Pick a face to define the drawing plane\n"))
01112             self.ui.sourceCmd = self
01113             if plane.alignToSelection(self.offset):
01114                 FreeCADGui.Selection.clearSelection()
01115                 self.display(plane.axis)
01116                 self.finish()
01117             else:
01118                 self.call = self.view.addEventCallback("SoEvent", self.action)
01119 
01120     def action(self, arg):
01121         if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE":
01122             self.finish()
01123         if arg["Type"] == "SoMouseButtonEvent":
01124             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
01125                 cursor = arg["Position"]
01126                 doc = FreeCADGui.ActiveDocument
01127                 info = doc.ActiveView.getObjectInfo((cursor[0],cursor[1]))
01128                 if info:
01129                     try:
01130                         shape = doc.getObject(info["Object"]).Object.Shape
01131                         component = getattr(shape, info["Component"])
01132                         if plane.alignToFace(component, self.offset) \
01133                                 or plane.alignToCurve(component, self.offset):
01134                             self.display(plane.axis)
01135                             self.finish()
01136                     except:
01137                         pass
01138 
01139     def selectHandler(self, arg):
01140         try:
01141             self.offset = float(self.ui.offsetValue.text())
01142         except:
01143             self.offset = 0
01144         if arg == "XY":
01145             plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,0,1), self.offset)
01146             self.display('top')
01147             self.finish()
01148         elif arg == "XZ":
01149             plane.alignToPointAndAxis(Vector(0,0,0), Vector(0,-1,0), self.offset)
01150             self.display('front')
01151             self.finish()
01152         elif arg == "YZ":
01153             plane.alignToPointAndAxis(Vector(0,0,0), Vector(1,0,0), self.offset)
01154             self.display('side')
01155             self.finish()
01156         elif arg == "currentView":
01157             viewDirection = fcvec.neg(self.view.getViewDirection())
01158             plane.alignToPointAndAxis(Vector(0,0,0), viewDirection, self.offset)
01159             self.display(viewDirection)
01160             self.finish()
01161         elif arg == "reset":
01162             plane.reset()
01163             self.display('None')
01164             self.finish()
01165 
01166     def offsetHandler(self, arg):
01167         self.offset = arg
01168 
01169     def display(self,arg):
01170         if self.offset:
01171             if self.offset > 0: suffix = ' + '+str(self.offset)
01172             else: suffix = ' - '+str(self.offset)
01173         else: suffix = ''
01174         if type(arg).__name__  == 'str':
01175             self.ui.wplabel.setText(arg+suffix)
01176         elif type(arg).__name__ == 'Vector':
01177             plv = 'd('+str(arg.x)+','+str(arg.y)+','+str(arg.z)+')'
01178             self.ui.wplabel.setText(plv+suffix)
01179 
01180     def finish(self):
01181         if self.call:
01182             self.view.removeEventCallback("SoEvent",self.call)
01183         FreeCAD.activeDraftCommand = None
01184         if self.ui:
01185             self.ui.offUi()
01186 
01187 
01188 #---------------------------------------------------------------------------
01189 # Geometry constructors
01190 #---------------------------------------------------------------------------
01191 
01192 class Creator:
01193     "A generic Draft Creator Tool used by creation tools such as line or arc"
01194     
01195     def __init__(self):
01196         self.commitList = []
01197         
01198     def Activated(self,name="None"):
01199         if FreeCAD.activeDraftCommand:
01200             FreeCAD.activeDraftCommand.finish()
01201         self.ui = None
01202         self.call = None
01203         self.doc = None
01204         self.support = None
01205         self.commitList = []
01206         self.doc = FreeCAD.ActiveDocument
01207         self.view = FreeCADGui.ActiveDocument.ActiveView
01208         self.featureName = name
01209         if not self.doc:
01210             self.finish()
01211         else:
01212             FreeCAD.activeDraftCommand = self
01213             self.ui = FreeCADGui.draftToolBar
01214             self.ui.cross(True)
01215             self.ui.sourceCmd = self
01216             self.ui.setTitle(name)
01217             self.ui.show()
01218             rot = self.view.getCameraNode().getField("orientation").getValue()
01219             upv = Vector(rot.multVec(coin.SbVec3f(0,1,0)).getValue())
01220             plane.setup(fcvec.neg(self.view.getViewDirection()), Vector(0,0,0), upv)
01221             self.node = []
01222             self.pos = []
01223             self.constrain = None
01224             self.obj = None
01225             self.snap = snapTracker()
01226             self.extsnap = lineTracker(dotted=True)
01227             self.planetrack = PlaneTracker()
01228             if Draft.getParam("grid"):
01229                 self.grid = gridTracker()
01230                 self.grid.set()
01231             else:
01232                 self.grid = None
01233                         
01234     def IsActive(self):
01235         if FreeCADGui.ActiveDocument:
01236             return True
01237         else:
01238             return False
01239 
01240     def finish(self):
01241         self.snap.finalize()
01242         self.extsnap.finalize()
01243         self.node=[]
01244         self.planetrack.finalize()
01245         if self.grid: self.grid.finalize()
01246         if self.support: plane.restore()
01247         FreeCAD.activeDraftCommand = None
01248         if self.ui:
01249             self.ui.offUi()
01250             self.ui.cross(False)
01251             self.ui.sourceCmd = None
01252         msg("")
01253         if self.call:
01254             self.view.removeEventCallback("SoEvent",self.call)
01255             self.call = None
01256         if self.commitList:
01257             todo.delayCommit(self.commitList)
01258         self.commitList = []
01259 
01260     def commit(self,name,func):
01261         "stores partial actions to be committed to the FreeCAD document"
01262         self.commitList.append((name,func))
01263 
01264 class Line(Creator):
01265     "The Line FreeCAD command definition"
01266 
01267     def __init__(self, wiremode=False):
01268         self.isWire = wiremode
01269 
01270     def GetResources(self):
01271         return {'Pixmap'  : 'Draft_Line',
01272                 'Accel' : "L,I",
01273                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Line", "Line"),
01274                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Line", "Creates a 2-point line. CTRL to snap, SHIFT to constrain")}
01275 
01276     def Activated(self,name="Line"):
01277         Creator.Activated(self,name)
01278         if self.doc:
01279             self.obj = None
01280             self.ui.lineUi()
01281             self.linetrack = lineTracker()
01282             self.constraintrack = lineTracker(dotted=True)
01283             self.obj=self.doc.addObject("Part::Feature",self.featureName)
01284             # self.obj.ViewObject.Selectable = False
01285             Draft.formatObject(self.obj)
01286             if not Draft.getParam("UiMode"): self.makeDumbTask()
01287             self.call = self.view.addEventCallback("SoEvent",self.action)
01288             msg(translate("draft", "Pick first point:\n"))
01289 
01290     def makeDumbTask(self):
01291         "create a dumb taskdialog to prevent deleting the temp object"
01292         class TaskPanel:
01293             def __init__(self):
01294                 pass
01295             def getStandardButtons(self):
01296                 return 0
01297         panel = TaskPanel()
01298         FreeCADGui.Control.showDialog(panel)
01299 
01300     def finish(self,closed=False,cont=False):
01301         "terminates the operation and closes the poly if asked"
01302         if not Draft.getParam("UiMode"):
01303             FreeCADGui.Control.closeDialog()
01304         if self.obj:
01305             old = self.obj.Name
01306             todo.delay(self.doc.removeObject,old)
01307         self.obj = None
01308         if (len(self.node) > 1):
01309             self.commit(translate("draft","Create Wire"),
01310                         partial(Draft.makeWire,self.node,closed,
01311                                 face=self.ui.fillmode,support=self.support))
01312         if self.ui:
01313             self.linetrack.finalize()
01314             self.constraintrack.finalize()
01315         Creator.finish(self)
01316         if cont and self.ui:
01317             if self.ui.continueMode:
01318                 self.Activated()
01319 
01320     def action(self,arg):
01321         "scene event handler"
01322         if arg["Type"] == "SoKeyboardEvent":
01323             if arg["Key"] == "ESCAPE":
01324                 self.finish()
01325         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
01326             point,ctrlPoint = getPoint(self,arg)
01327             self.ui.cross(True)
01328             self.linetrack.p2(point)
01329             # Draw constraint tracker line.
01330             if hasMod(arg,MODCONSTRAIN):
01331                 self.constraintrack.p1(point)
01332                 self.constraintrack.p2(ctrlPoint)
01333                 self.constraintrack.on()
01334             else:
01335                 self.constraintrack.off()
01336         elif arg["Type"] == "SoMouseButtonEvent":
01337             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
01338                 if (arg["Position"] == self.pos):
01339                     self.finish(False,cont=True)
01340                 else:
01341                     if not self.node: self.support = getSupport(arg)
01342                     point,ctrlPoint = getPoint(self,arg)
01343                     self.pos = arg["Position"]
01344                     self.node.append(point)
01345                     self.linetrack.p1(point)
01346                     self.drawSegment(point)
01347                     if (not self.isWire and len(self.node) == 2):
01348                         self.finish(False,cont=True)
01349                     if (len(self.node) > 2):
01350                         # DNC: allows to close the curve
01351                         # by placing ends close to each other
01352                         # with tol = Draft tolerance
01353                         # old code has been to insensitive
01354                         # if fcvec.equals(point,self.node[0]):
01355                         if ((point-self.node[0]).Length < Draft.tolerance()):
01356                             self.undolast()
01357                             self.finish(True,cont=True)
01358                             msg(translate("draft", "Wire has been closed\n"))
01359 
01360     def undolast(self):
01361         "undoes last line segment"
01362         if (len(self.node) > 1):
01363             self.node.pop()
01364             last = self.node[len(self.node)-1]
01365             self.linetrack.p1(last)
01366             if self.obj.Shape.Edges:
01367                 edges = self.obj.Shape.Edges
01368                 if len(edges) > 1:
01369                     edges.pop()
01370                     newshape = Part.Wire(edges)
01371                 else:
01372                     newshape = Part.Shape()
01373                 self.obj.Shape = newshape
01374                 # DNC: report on removal
01375                 msg(translate("draft", "Last point has been removed\n"))
01376 
01377     def drawSegment(self,point):
01378         "draws a new segment"
01379         if (len(self.node) == 1):
01380             self.linetrack.on()
01381             msg(translate("draft", "Pick next point:\n"))
01382             self.planetrack.set(self.node[0])
01383         elif (len(self.node) == 2):
01384             last = self.node[len(self.node)-2]
01385             newseg = Part.Line(last,point).toShape()
01386             self.obj.Shape = newseg
01387             self.obj.ViewObject.Visibility = True
01388             if self.isWire:
01389                 msg(translate("draft", "Pick next point, or (F)inish or (C)lose:\n"))
01390         else:
01391             currentshape = self.obj.Shape
01392             last = self.node[len(self.node)-2]
01393             newseg = Part.Line(last,point).toShape()
01394             newshape=currentshape.fuse(newseg)
01395             self.obj.Shape = newshape
01396             msg(translate("draft", "Pick next point, or (F)inish or (C)lose:\n"))
01397 
01398     def wipe(self):
01399         "removes all previous segments and starts from last point"
01400         if len(self.node) > 1:
01401             print "nullifying"
01402             # self.obj.Shape.nullify() - for some reason this fails
01403             self.obj.ViewObject.Visibility = False
01404             self.node = [self.node[-1]]
01405             print "setting trackers"
01406             self.linetrack.p1(self.node[0])
01407             self.planetrack.set(self.node[0])
01408             msg(translate("draft", "Pick next point:\n"))
01409             print "done"
01410                         
01411     def numericInput(self,numx,numy,numz):
01412         "this function gets called by the toolbar when valid x, y, and z have been entered there"
01413         point = Vector(numx,numy,numz)
01414         self.node.append(point)
01415         self.linetrack.p1(point)
01416         self.drawSegment(point)
01417         if (not self.isWire and len(self.node) == 2):
01418             self.finish(False,cont=True)
01419         self.ui.setNextFocus()
01420             
01421 class Wire(Line):
01422     "a FreeCAD command for creating a wire"
01423     def __init__(self):
01424         Line.__init__(self,wiremode=True)
01425     def GetResources(self):
01426         return {'Pixmap'  : 'Draft_Wire',
01427                 'Accel' : "W, I",
01428                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Wire", "Wire"),
01429                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Wire", "Creates a multiple-point wire. CTRL to snap, SHIFT to constrain")}
01430 
01431     
01432 class BSpline(Line):
01433     "a FreeCAD command for creating a b-spline"
01434     
01435     def __init__(self):
01436         Line.__init__(self,wiremode=True)
01437 
01438     def GetResources(self):
01439         return {'Pixmap'  : 'Draft_BSpline',
01440                 'Accel' : "B, S",
01441                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_BSpline", "B-Spline"),
01442                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_BSpline", "Creates a multiple-point b-spline. CTRL to snap, SHIFT to constrain")}
01443 
01444     def Activated(self):
01445         Line.Activated(self,"BSpline")
01446         if self.doc:
01447             self.bsplinetrack = bsplineTracker()
01448 
01449     def action(self,arg):
01450         "scene event handler"
01451         if arg["Type"] == "SoKeyboardEvent":
01452             if arg["Key"] == "ESCAPE":
01453                 self.finish()
01454         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
01455             point,ctrlPoint = getPoint(self,arg)
01456             self.ui.cross(True)
01457             self.bsplinetrack.update(self.node + [point])
01458             # Draw constraint tracker line.
01459             if hasMod(arg,MODCONSTRAIN):
01460                 self.constraintrack.p1(point)
01461                 self.constraintrack.p2(ctrlPoint)
01462                 self.constraintrack.on()
01463             else: self.constraintrack.off()
01464         elif arg["Type"] == "SoMouseButtonEvent":
01465             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
01466                 if (arg["Position"] == self.pos):
01467                     self.finish(False,cont=True)
01468                 else:
01469                     if not self.node: self.support = getSupport(arg)
01470                     point,ctrlPoint = getPoint(self,arg)
01471                     self.pos = arg["Position"]
01472                     self.node.append(point)
01473                     self.drawUpdate(point)
01474                     if (not self.isWire and len(self.node) == 2):
01475                         self.finish(False,cont=True)
01476                     if (len(self.node) > 2):
01477                         # DNC: allows to close the curve
01478                         # by placing ends close to each other
01479                         # with tol = Draft tolerance
01480                         # old code has been to insensitive
01481                         if ((point-self.node[0]).Length < Draft.tolerance()):
01482                             self.undolast()
01483                             self.finish(True,cont=True)
01484                             msg(translate("draft", "Spline has been closed\n"))
01485 
01486     def undolast(self):
01487         "undoes last line segment"
01488         if (len(self.node) > 1):
01489             self.node.pop()
01490             self.bsplinetrack.update(self.node)
01491             spline = Part.BSplineCurve()
01492             spline.interpolate(self.node, False)
01493             self.obj.Shape = spline.toShape()
01494             msg(translate("draft", "Last point has been removed\n"))
01495 
01496     def drawUpdate(self,point):
01497         if (len(self.node) == 1):
01498             self.bsplinetrack.on()
01499             self.planetrack.set(self.node[0])
01500             msg(translate("draft", "Pick next point:\n"))
01501         else:
01502             spline = Part.BSplineCurve()
01503             spline.interpolate(self.node, False)
01504             self.obj.Shape = spline.toShape()
01505             msg(translate("draft", "Pick next point, or (F)inish or (C)lose:\n"))
01506         
01507     def finish(self,closed=False,cont=False):
01508         "terminates the operation and closes the poly if asked"
01509         if not Draft.getParam("UiMode"):
01510             FreeCADGui.Control.closeDialog()
01511         if (len(self.node) > 1):
01512             old = self.obj.Name
01513             self.doc.removeObject(old)
01514             try:
01515                 self.commit(translate("draft","Create BSpline"),
01516                             partial(Draft.makeBSpline,self.node,closed,
01517                                     face=self.ui.fillmode,support=self.support))
01518             except:
01519                 print "Draft: error delaying commit"
01520         if self.ui:
01521                         self.bsplinetrack.finalize()
01522                         self.constraintrack.finalize()
01523         Creator.finish(self)
01524         if cont and self.ui:
01525             if self.ui.continueMode:
01526                 self.Activated()
01527 
01528                 
01529 class FinishLine:
01530     "a FreeCAD command to finish any running Line drawing operation"
01531     
01532     def Activated(self):
01533         if (FreeCAD.activeDraftCommand != None):
01534             if (FreeCAD.activeDraftCommand.featureName == "Line"):
01535                 FreeCAD.activeDraftCommand.finish(False)
01536     def GetResources(self):
01537         return {'Pixmap'  : 'Draft_Finish',
01538                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_FinishLine", "Finish line"),
01539                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_FinishLine", "Finishes a line without closing it")}
01540     def IsActive(self):
01541         if FreeCAD.activeDraftCommand:
01542             if FreeCAD.activeDraftCommand.featureName == "Line":
01543                 return True
01544         return False
01545 
01546     
01547 class CloseLine:
01548     "a FreeCAD command to close any running Line drawing operation"
01549     
01550     def Activated(self):
01551         if (FreeCAD.activeDraftCommand != None):
01552             if (FreeCAD.activeDraftCommand.featureName == "Line"):
01553                 FreeCAD.activeDraftCommand.finish(True)
01554                 
01555     def GetResources(self):
01556         return {'Pixmap'  : 'Draft_Lock',
01557                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_CloseLine", "Close Line"),
01558                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_CloseLine", "Closes the line being drawn")}
01559     
01560     def IsActive(self):
01561         if FreeCAD.activeDraftCommand:
01562             if FreeCAD.activeDraftCommand.featureName == "Line":
01563                 return True
01564         return False
01565 
01566 
01567 class UndoLine:
01568     "a FreeCAD command to undo last drawn segment of a line"
01569     
01570     def Activated(self):
01571         if (FreeCAD.activeDraftCommand != None):
01572             if (FreeCAD.activeDraftCommand.featureName == "Line"):
01573                 FreeCAD.activeDraftCommand.undolast()
01574                 
01575     def GetResources(self):
01576         return {'Pixmap'  : 'Draft_Rotate',
01577                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_UndoLine", "Undo last segment"),
01578                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_UndoLine", "Undoes the last drawn segment of the line being drawn")}
01579     
01580     def IsActive(self):
01581         if FreeCAD.activeDraftCommand:
01582             if FreeCAD.activeDraftCommand.featureName == "Line":
01583                 return True
01584         return False
01585 
01586     
01587 class Rectangle(Creator):
01588     "the Draft_Rectangle FreeCAD command definition"
01589     
01590     def GetResources(self):
01591         return {'Pixmap'  : 'Draft_Rectangle',
01592                 'Accel' : "R, E",
01593                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Rectangle", "Rectangle"),
01594                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Rectangle", "Creates a 2-point rectangle. CTRL to snap")}
01595 
01596     def Activated(self):
01597         Creator.Activated(self,"Rectangle")
01598         if self.ui:
01599             self.refpoint = None
01600             self.ui.pointUi()
01601             self.ui.extUi()
01602             self.call = self.view.addEventCallback("SoEvent",self.action)
01603             self.rect = rectangleTracker()
01604             msg(translate("draft", "Pick first point:\n"))
01605 
01606     def finish(self,closed=False,cont=False):
01607         "terminates the operation and closes the poly if asked"
01608         Creator.finish(self) 
01609         if self.ui:
01610             self.rect.off()
01611             self.rect.finalize()
01612         if cont and self.ui:
01613             if self.ui.continueMode:
01614                 self.Activated()
01615 
01616     def createObject(self):
01617         "creates the final object in the current doc"
01618         p1 = self.node[0]
01619         p3 = self.node[-1]
01620         diagonal = p3.sub(p1)
01621         p2 = p1.add(fcvec.project(diagonal, plane.v))
01622         p4 = p1.add(fcvec.project(diagonal, plane.u))
01623         length = p4.sub(p1).Length
01624         if abs(fcvec.angle(p4.sub(p1),plane.u,plane.axis)) > 1: length = -length
01625         height = p2.sub(p1).Length
01626         if abs(fcvec.angle(p2.sub(p1),plane.v,plane.axis)) > 1: height = -height
01627         p = plane.getRotation()
01628         p.move(p1)
01629         try:
01630             self.commit(translate("draft","Create Rectangle"),
01631                         partial(Draft.makeRectangle,length,height,
01632                                 p,self.ui.fillmode,support=self.support))
01633         except:
01634             print "Draft: error delaying commit"
01635         self.finish(cont=True)
01636 
01637     def action(self,arg):
01638         "scene event handler"
01639         if arg["Type"] == "SoKeyboardEvent":
01640             if arg["Key"] == "ESCAPE":
01641                 self.finish()
01642         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
01643             point,ctrlPoint = getPoint(self,arg,mobile=True)
01644             self.rect.update(point)
01645             self.ui.cross(True)
01646         elif arg["Type"] == "SoMouseButtonEvent":
01647             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
01648                 if (arg["Position"] == self.pos):
01649                     self.finish()
01650                 else:
01651                     if not self.node: self.support = getSupport(arg)
01652                     point,ctrlPoint = getPoint(self,arg)
01653                     self.appendPoint(point)
01654 
01655     def numericInput(self,numx,numy,numz):
01656         "this function gets called by the toolbar when valid x, y, and z have been entered there"
01657         point = Vector(numx,numy,numz)
01658         self.appendPoint(point)
01659 
01660     def appendPoint(self,point):
01661         self.node.append(point)
01662         if (len(self.node) > 1):
01663             self.rect.update(point)
01664             self.createObject()
01665         else:
01666             msg(translate("draft", "Pick opposite point:\n"))
01667             self.ui.setRelative()
01668             self.rect.setorigin(point)
01669             self.rect.on()
01670             self.planetrack.set(point)
01671 
01672 
01673 class Arc(Creator):
01674     "the Draft_Arc FreeCAD command definition"
01675         
01676     def __init__(self):
01677         self.closedCircle=False
01678         self.featureName = "Arc"
01679 
01680     def GetResources(self):
01681         return {'Pixmap'  : 'Draft_Arc',
01682                 'Accel' : "A, R",
01683                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Arc", "Arc"),
01684                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Arc", "Creates an arc. CTRL to snap, SHIFT to constrain")}
01685 
01686     def Activated(self):
01687         Creator.Activated(self,self.featureName)
01688         if self.ui:
01689             self.step = 0
01690             self.center = None
01691             self.rad = None
01692             self.angle = 0 # angle inscribed by arc
01693             self.tangents = []
01694             self.tanpoints = []
01695             if self.featureName == "Arc": self.ui.arcUi()
01696             else: self.ui.circleUi()
01697             self.altdown = False
01698             self.ui.sourceCmd = self
01699             self.linetrack = lineTracker(dotted=True)
01700             self.constraintrack = lineTracker(dotted=True)
01701             self.arctrack = arcTracker()
01702             self.call = self.view.addEventCallback("SoEvent",self.action)
01703             msg(translate("draft", "Pick center point:\n"))
01704 
01705     def finish(self,closed=False,cont=False):
01706         "finishes the arc"
01707         Creator.finish(self)
01708         if self.ui:
01709             self.linetrack.finalize()
01710             self.constraintrack.finalize()
01711             self.arctrack.finalize()
01712             self.doc.recompute()
01713         if cont and self.ui:
01714             if self.ui.continueMode:
01715                 self.Activated()
01716 
01717     def updateAngle(self, angle):
01718         # previous absolute angle
01719         lastangle = self.firstangle + self.angle
01720         if lastangle <= -2*math.pi: lastangle += 2*math.pi
01721         if lastangle >= 2*math.pi: lastangle -= 2*math.pi
01722         # compute delta = change in angle:
01723         d0 = angle-lastangle
01724         d1 = d0 + 2*math.pi
01725         d2 = d0 - 2*math.pi
01726         if abs(d0) < min(abs(d1), abs(d2)):
01727             delta = d0
01728         elif abs(d1) < abs(d2):
01729             delta = d1
01730         else:
01731             delta = d2
01732         newangle = self.angle + delta
01733         # normalize angle, preserving direction
01734         if newangle >= 2*math.pi: newangle -= 2*math.pi
01735         if newangle <= -2*math.pi: newangle += 2*math.pi
01736         self.angle = newangle
01737 
01738     def action(self,arg):
01739         "scene event handler"
01740         if arg["Type"] == "SoKeyboardEvent":
01741             if arg["Key"] == "ESCAPE":
01742                 self.finish()
01743         elif arg["Type"] == "SoLocation2Event":
01744             point,ctrlPoint = getPoint(self,arg)
01745             # this is to make sure radius is what you see on screen
01746             self.ui.cross(True)
01747             if self.center and fcvec.dist(point,self.center) > 0:
01748                 viewdelta = fcvec.project(point.sub(self.center), plane.axis)
01749                 if not fcvec.isNull(viewdelta):
01750                     point = point.add(fcvec.neg(viewdelta))
01751             if (self.step == 0): # choose center
01752                 if hasMod(arg,MODALT):
01753                     if not self.altdown:
01754                         self.ui.cross(False)
01755                         self.altdown = True
01756                         self.ui.switchUi(True)
01757                     else:
01758                         if self.altdown:
01759                             self.ui.cross(True)
01760                             self.altdown = False
01761                             self.ui.switchUi(False)
01762             elif (self.step == 1): # choose radius
01763                 if len(self.tangents) == 2:
01764                     cir = fcgeo.circleFrom2tan1pt(self.tangents[0], self.tangents[1], point)
01765                     self.center = fcgeo.findClosestCircle(point,cir).Center
01766                     self.arctrack.setCenter(self.center)
01767                 elif self.tangents and self.tanpoints:
01768                     cir = fcgeo.circleFrom1tan2pt(self.tangents[0], self.tanpoints[0], point)
01769                     self.center = fcgeo.findClosestCircle(point,cir).Center
01770                     self.arctrack.setCenter(self.center)
01771                 if hasMod(arg,MODALT):
01772                     if not self.altdown:
01773                         self.ui.cross(False)
01774                         self.altdown = True
01775                     snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
01776                     if snapped:
01777                         ob = self.doc.getObject(snapped['Object'])
01778                         num = int(snapped['Component'].lstrip('Edge'))-1
01779                         ed = ob.Shape.Edges[num]
01780                         if len(self.tangents) == 2:
01781                             cir = fcgeo.circleFrom3tan(self.tangents[0], self.tangents[1], ed)
01782                             cl = fcgeo.findClosestCircle(point,cir)
01783                             self.center = cl.Center
01784                             self.rad = cl.Radius
01785                             self.arctrack.setCenter(self.center)
01786                         else:
01787                             self.rad = self.center.add(fcgeo.findDistance(self.center,ed).sub(self.center)).Length
01788                     else:
01789                         self.rad = fcvec.dist(point,self.center)
01790                 else:
01791                     if self.altdown:
01792                         self.ui.cross(True)
01793                         self.altdown = False
01794                     self.rad = fcvec.dist(point,self.center)
01795                 self.ui.setRadiusValue(self.rad)
01796                 self.arctrack.setRadius(self.rad)
01797                 # Draw constraint tracker line.
01798                 if hasMod(arg,MODCONSTRAIN):
01799                     self.constraintrack.p1(point)
01800                     self.constraintrack.p2(ctrlPoint)
01801                     self.constraintrack.on()
01802                 else:
01803                     self.constraintrack.off()
01804                 self.linetrack.p1(self.center)
01805                 self.linetrack.p2(point)
01806                 self.linetrack.on()
01807             elif (self.step == 2): # choose first angle
01808                 currentrad = fcvec.dist(point,self.center)
01809                 if currentrad != 0:
01810                     angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis)
01811                 else: angle = 0
01812                 self.linetrack.p2(fcvec.scaleTo(point.sub(self.center),self.rad).add(self.center))
01813                 # Draw constraint tracker line.
01814                 if hasMod(arg,MODCONSTRAIN):
01815                     self.constraintrack.p1(point)
01816                     self.constraintrack.p2(ctrlPoint)
01817                     self.constraintrack.on()
01818                 else:
01819                     self.constraintrack.off()
01820                 self.ui.setRadiusValue(math.degrees(angle))
01821                 self.firstangle = angle
01822             else: # choose second angle
01823                 currentrad = fcvec.dist(point,self.center)
01824                 if currentrad != 0:
01825                     angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis)
01826                 else: angle = 0
01827                 self.linetrack.p2(fcvec.scaleTo(point.sub(self.center),self.rad).add(self.center))
01828                 # Draw constraint tracker line.
01829                 if hasMod(arg,MODCONSTRAIN):
01830                     self.constraintrack.p1(point)
01831                     self.constraintrack.p2(ctrlPoint)
01832                     self.constraintrack.on()
01833                 else:
01834                     self.constraintrack.off()
01835                 self.ui.setRadiusValue(math.degrees(angle))
01836                 self.updateAngle(angle)
01837                 self.arctrack.setApertureAngle(self.angle)
01838 
01839         elif arg["Type"] == "SoMouseButtonEvent":
01840             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
01841                 point,ctrlPoint = getPoint(self,arg)
01842                 # this is to make sure radius is what you see on screen
01843                 if self.center and fcvec.dist(point,self.center) > 0:
01844                     viewdelta = fcvec.project(point.sub(self.center), plane.axis)
01845                     if not fcvec.isNull(viewdelta):
01846                         point = point.add(fcvec.neg(viewdelta))
01847                 if (self.step == 0): # choose center
01848                     self.support = getSupport(arg)
01849                     if hasMod(arg,MODALT):
01850                         snapped=self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
01851                         if snapped:
01852                             ob = self.doc.getObject(snapped['Object'])
01853                             num = int(snapped['Component'].lstrip('Edge'))-1
01854                             ed = ob.Shape.Edges[num]
01855                             self.tangents.append(ed)
01856                             if len(self.tangents) == 2:
01857                                 self.arctrack.on()
01858                                 self.ui.radiusUi()
01859                                 self.step = 1
01860                                 self.linetrack.on()
01861                                 msg(translate("draft", "Pick radius:\n"))
01862                     else:
01863                         if len(self.tangents) == 1:
01864                             self.tanpoints.append(point)
01865                         else:
01866                             self.center = point
01867                             self.node = [point]
01868                             self.arctrack.setCenter(self.center)
01869                             self.linetrack.p1(self.center)
01870                             self.linetrack.p2(self.view.getPoint(arg["Position"][0],arg["Position"][1]))
01871                         self.arctrack.on()
01872                         self.ui.radiusUi()
01873                         self.step = 1
01874                         self.linetrack.on()
01875                         msg(translate("draft", "Pick radius:\n"))
01876                         self.planetrack.set(point)                        
01877                 elif (self.step == 1): # choose radius
01878                     if self.closedCircle:
01879                         self.ui.cross(False)
01880                         self.drawArc()
01881                     else: 
01882                         self.ui.labelRadius.setText("Start angle")
01883                         self.linetrack.p1(self.center)
01884                         self.linetrack.on()
01885                         self.step = 2
01886                         msg(translate("draft", "Pick start angle:\n"))
01887                 elif (self.step == 2): # choose first angle
01888                     self.ui.labelRadius.setText("Aperture")
01889                     self.step = 3
01890                     # scale center->point vector for proper display
01891                     u = fcvec.scaleTo(point.sub(self.center), self.rad)
01892                     self.arctrack.setStartAngle(self.firstangle)
01893                     msg(translate("draft", "Pick aperture:\n"))
01894                 else: # choose second angle
01895                     self.step = 4
01896                     self.drawArc()
01897 
01898     def drawArc(self):
01899         "actually draws the FreeCAD object"
01900         p = plane.getRotation()
01901         p.move(self.center)
01902         if self.closedCircle:
01903             try:
01904                     self.commit(translate("draft","Create Circle"),
01905                                 partial(Draft.makeCircle,self.rad,p,
01906                                         self.ui.fillmode,support=self.support))
01907             except:
01908                     print "Draft: error delaying commit"
01909         else:
01910             sta = math.degrees(self.firstangle)
01911             end = math.degrees(self.firstangle+self.angle)
01912             if end < sta: sta,end = end,sta
01913             try:
01914                     self.commit(translate("draft","Create Arc"),
01915                                 partial(Draft.makeCircle,self.rad,p,self.ui.fillmode,
01916                                         sta,end,support=self.support))
01917             except:
01918                     print "Draft: error delaying commit"
01919         self.finish(cont=True)
01920 
01921     def numericInput(self,numx,numy,numz):
01922         "this function gets called by the toolbar when valid x, y, and z have been entered there"
01923         self.center = Vector(numx,numy,numz)
01924         self.node = [self.center]
01925         self.arctrack.setCenter(self.center)
01926         self.arctrack.on()
01927         self.ui.radiusUi()
01928         self.step = 1
01929         self.ui.setNextFocus()
01930         msg(translate("draft", "Pick radius:\n"))
01931                 
01932     def numericRadius(self,rad):
01933         "this function gets called by the toolbar when valid radius have been entered there"
01934         if (self.step == 1):
01935             self.rad = rad
01936             if len(self.tangents) == 2:
01937                 cir = fcgeo.circleFrom2tan1rad(self.tangents[0], self.tangents[1], rad)
01938                 if self.center:
01939                     self.center = fcgeo.findClosestCircle(self.center,cir).Center
01940                 else:
01941                     self.center = cir[-1].Center
01942             elif self.tangents and self.tanpoints:
01943                 cir = fcgeo.circleFrom1tan1pt1rad(self.tangents[0],self.tanpoints[0],rad)
01944                 if self.center:
01945                     self.center = fcgeo.findClosestCircle(self.center,cir).Center
01946                 else:
01947                     self.center = cir[-1].Center
01948             if self.closedCircle:
01949                 self.drawArc()
01950             else:
01951                 self.step = 2
01952                 self.arctrack.setCenter(self.center)
01953                 self.ui.labelRadius.setText(str(translate("draft", "Start Angle")))
01954                 self.linetrack.p1(self.center)
01955                 self.linetrack.on()
01956                 self.ui.radiusValue.setText("")
01957                 self.ui.radiusValue.setFocus()
01958                 msg(translate("draft", "Pick start angle:\n"))
01959         elif (self.step == 2):
01960             self.ui.labelRadius.setText(str(translate("draft", "Aperture")))
01961             self.firstangle = math.radians(rad)
01962             if fcvec.equals(plane.axis, Vector(1,0,0)): u = Vector(0,self.rad,0)
01963             else: u = fcvec.scaleTo(Vector(1,0,0).cross(plane.axis), self.rad)
01964             urotated = fcvec.rotate(u, math.radians(rad), plane.axis)
01965             self.arctrack.setStartAngle(self.firstangle)
01966             self.step = 3
01967             self.ui.radiusValue.setText("")
01968             self.ui.radiusValue.setFocus()
01969             msg(translate("draft", "Aperture angle:\n"))
01970         else:
01971             self.updateAngle(rad)
01972             self.angle = math.radians(rad)
01973             self.step = 4
01974             self.drawArc()
01975 
01976             
01977 class Circle(Arc):
01978     "The Draft_Circle FreeCAD command definition"
01979         
01980     def __init__(self):
01981         self.closedCircle=True
01982         self.featureName = "Circle"
01983         
01984     def GetResources(self):
01985         return {'Pixmap'  : 'Draft_Circle',
01986                 'Accel' : "C, I",
01987                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Circle", "Circle"),
01988                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Circle", "Creates a circle. CTRL to snap, ALT to select tangent objects")}
01989 
01990 
01991 class Polygon(Creator):
01992     "the Draft_Polygon FreeCAD command definition"
01993         
01994     def GetResources(self):
01995         return {'Pixmap'  : 'Draft_Polygon',
01996                 'Accel' : "P, G",
01997                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Polygon", "Polygon"),
01998                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Polygon", "Creates a regular polygon. CTRL to snap, SHIFT to constrain")}
01999 
02000     def Activated(self):
02001         Creator.Activated(self,"Polygon")
02002         if self.ui:
02003             self.step = 0
02004             self.center = None
02005             self.rad = None
02006             self.tangents = []
02007             self.tanpoints = []
02008             self.ui.pointUi()
02009             self.ui.extUi()
02010             self.ui.numFaces.show()
02011             self.altdown = False
02012             self.ui.sourceCmd = self
02013             self.linetrack = lineTracker(dotted=True)
02014             self.constraintrack = lineTracker(dotted=True)
02015             self.arctrack = arcTracker()
02016             self.call = self.view.addEventCallback("SoEvent",self.action)
02017             msg(translate("draft", "Pick center point:\n"))
02018 
02019     def finish(self,closed=False,cont=False):
02020         "finishes the arc"
02021         Creator.finish(self)
02022         if self.ui:
02023             self.linetrack.finalize()
02024             self.constraintrack.finalize()
02025             self.arctrack.finalize()
02026             self.doc.recompute()
02027         if cont and self.ui:
02028             if self.ui.continueMode:
02029                 self.Activated()
02030 
02031     def action(self,arg):
02032         "scene event handler"
02033         if arg["Type"] == "SoKeyboardEvent":
02034             if arg["Key"] == "ESCAPE":
02035                 self.finish()
02036         elif arg["Type"] == "SoLocation2Event":
02037             point,ctrlPoint = getPoint(self,arg)
02038             # this is to make sure radius is what you see on screen
02039             self.ui.cross(True)
02040             if self.center and fcvec.dist(point,self.center) > 0:
02041                 viewdelta = fcvec.project(point.sub(self.center), plane.axis)
02042                 if not fcvec.isNull(viewdelta):
02043                     point = point.add(fcvec.neg(viewdelta))
02044             if (self.step == 0): # choose center
02045                 if hasMod(arg,MODALT):
02046                     if not self.altdown:
02047                         self.ui.cross(False)
02048                         self.altdown = True
02049                         self.ui.switchUi(True)
02050                 else:
02051                     if self.altdown:
02052                         self.ui.cross(True)
02053                         self.altdown = False
02054                         self.ui.switchUi(False)
02055             else: # choose radius
02056                 if len(self.tangents) == 2:
02057                     cir = fcgeo.circleFrom2tan1pt(self.tangents[0], self.tangents[1], point)
02058                     self.center = fcgeo.findClosestCircle(point,cir).Center
02059                     self.arctrack.setCenter(self.center)
02060                 elif self.tangents and self.tanpoints:
02061                     cir = fcgeo.circleFrom1tan2pt(self.tangents[0], self.tanpoints[0], point)
02062                     self.center = fcgeo.findClosestCircle(point,cir).Center
02063                     self.arctrack.setCenter(self.center)
02064                 if hasMod(arg,MODALT):
02065                     if not self.altdown:
02066                         self.ui.cross(False)
02067                         self.altdown = True
02068                     snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
02069                     if snapped:
02070                         ob = self.doc.getObject(snapped['Object'])
02071                         num = int(snapped['Component'].lstrip('Edge'))-1
02072                         ed = ob.Shape.Edges[num]
02073                         if len(self.tangents) == 2:
02074                             cir = fcgeo.circleFrom3tan(self.tangents[0], self.tangents[1], ed)
02075                             cl = fcgeo.findClosestCircle(point,cir)
02076                             self.center = cl.Center
02077                             self.rad = cl.Radius
02078                             self.arctrack.setCenter(self.center)
02079                         else:
02080                             self.rad = self.center.add(fcgeo.findDistance(self.center,ed).sub(self.center)).Length
02081                     else:
02082                         self.rad = fcvec.dist(point,self.center)
02083                 else:
02084                     if self.altdown:
02085                         self.ui.cross(True)
02086                         self.altdown = False
02087                     self.rad = fcvec.dist(point,self.center)
02088                 self.ui.setRadiusValue(self.rad)
02089                 self.arctrack.setRadius(self.rad)
02090                 # Draw constraint tracker line.
02091                 if hasMod(arg,MODCONSTRAIN):
02092                     self.constraintrack.p1(point)
02093                     self.constraintrack.p2(ctrlPoint)
02094                     self.constraintrack.on()
02095                 else: self.constraintrack.off()
02096                 self.linetrack.p1(self.center)
02097                 self.linetrack.p2(point)
02098                 self.linetrack.on()
02099 
02100         elif arg["Type"] == "SoMouseButtonEvent":
02101             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
02102                 point,ctrlPoint = getPoint(self,arg)
02103                 # this is to make sure radius is what you see on screen
02104                 if self.center and fcvec.dist(point,self.center) > 0:
02105                     viewdelta = fcvec.project(point.sub(self.center), plane.axis)
02106                     if not fcvec.isNull(viewdelta):
02107                         point = point.add(fcvec.neg(viewdelta))
02108                 if (self.step == 0): # choose center
02109                     if not self.node: self.support = getSupport(arg)
02110                     if hasMod(arg,MODALT):
02111                         snapped=self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
02112                         if snapped:
02113                             ob = self.doc.getObject(snapped['Object'])
02114                             num = int(snapped['Component'].lstrip('Edge'))-1
02115                             ed = ob.Shape.Edges[num]
02116                             self.tangents.append(ed)
02117                             if len(self.tangents) == 2:
02118                                 self.arctrack.on()
02119                                 self.ui.radiusUi()
02120                                 self.step = 1
02121                                 self.linetrack.on()
02122                                 msg(translate("draft", "Pick radius:\n"))
02123                     else:
02124                         if len(self.tangents) == 1:
02125                             self.tanpoints.append(point)
02126                         else:
02127                             self.center = point
02128                             self.node = [point]
02129                             self.arctrack.setCenter(self.center)
02130                             self.linetrack.p1(self.center)
02131                             self.linetrack.p2(self.view.getPoint(arg["Position"][0],arg["Position"][1]))
02132                         self.arctrack.on()
02133                         self.ui.radiusUi()
02134                         self.step = 1
02135                         self.linetrack.on()
02136                         msg(translate("draft", "Pick radius:\n"))
02137                         self.planetrack.set(point)
02138                 elif (self.step == 1): # choose radius
02139                     self.ui.cross(False)
02140                     self.drawPolygon()
02141 
02142     def drawPolygon(self):
02143         "actually draws the FreeCAD object"
02144         p = plane.getRotation()
02145         p.move(self.center)
02146         self.commit(translate("draft","Create Polygon"),
02147                     partial(Draft.makePolygon,self.ui.numFaces.value(),self.rad,
02148                             True,p,face=self.ui.fillmode,support=self.support))
02149         self.finish(cont=True)
02150 
02151     def numericInput(self,numx,numy,numz):
02152         "this function gets called by the toolbar when valid x, y, and z have been entered there"
02153         self.center = Vector(numx,numy,numz)
02154         self.node = [self.center]
02155         self.arctrack.setCenter(self.center)
02156         self.arctrack.on()
02157         self.ui.radiusUi()
02158         self.step = 1
02159         self.ui.radiusValue.setFocus()
02160         msg(translate("draft", "Pick radius:\n"))
02161                 
02162     def numericRadius(self,rad):
02163         "this function gets called by the toolbar when valid radius have been entered there"
02164         self.rad = rad
02165         if len(self.tangents) == 2:
02166             cir = fcgeo.circleFrom2tan1rad(self.tangents[0], self.tangents[1], rad)
02167             if self.center:
02168                 self.center = fcgeo.findClosestCircle(self.center,cir).Center
02169             else:
02170                 self.center = cir[-1].Center
02171         elif self.tangents and self.tanpoints:
02172             cir = fcgeo.circleFrom1tan1pt1rad(self.tangents[0],self.tanpoints[0],rad)
02173             if self.center:
02174                 self.center = fcgeo.findClosestCircle(self.center,cir).Center
02175             else:
02176                 self.center = cir[-1].Center
02177         self.drawPolygon()
02178 
02179         
02180 class Text(Creator):
02181     "This class creates an annotation feature."
02182 
02183     def GetResources(self):
02184         return {'Pixmap'  : 'Draft_Text',
02185                 'Accel' : "T, E",
02186                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Text", "Text"),
02187                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Text", "Creates an annotation. CTRL to snap")}
02188 
02189     def Activated(self):
02190         Creator.Activated(self,"Text")
02191         if self.ui:
02192             self.dialog = None
02193             self.text = ''
02194             self.ui.sourceCmd = self
02195             self.ui.pointUi()
02196             self.call = self.view.addEventCallback("SoEvent",self.action)
02197             self.ui.xValue.setFocus()
02198             self.ui.xValue.selectAll()
02199             msg(translate("draft", "Pick location point:\n"))
02200             FreeCADGui.draftToolBar.show()
02201 
02202     def finish(self,closed=False,cont=False):
02203         "terminates the operation"
02204         Creator.finish(self)
02205         if self.ui:
02206             del self.dialog
02207         if cont and self.ui:
02208             if self.ui.continueMode:
02209                 self.Activated()
02210 
02211     def createObject(self):
02212         "creates an object in the current doc"
02213         self.commit(translate("draft","Create Text"),
02214                     partial(Draft.makeText,self.text,self.node[0]))
02215         self.finish(cont=True)
02216 
02217     def action(self,arg):
02218         "scene event handler"
02219         if arg["Type"] == "SoKeyboardEvent":
02220             if arg["Key"] == "ESCAPE":
02221                 self.finish()
02222         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
02223             point,ctrlPoint = getPoint(self,arg)
02224         elif arg["Type"] == "SoMouseButtonEvent":
02225             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
02226                 point,dtrlPoint = getPoint(self,arg)
02227                 self.node.append(point)
02228                 self.ui.textUi()
02229                 self.ui.textValue.setFocus()
02230                 self.ui.cross(False)
02231 
02232     def numericInput(self,numx,numy,numz):
02233         '''this function gets called by the toolbar when valid
02234         x, y, and z have been entered there'''
02235         point = Vector(numx,numy,numz)
02236         self.node.append(point)
02237         self.ui.textUi()
02238         self.ui.textValue.setFocus()
02239         self.ui.cross(False)
02240 
02241         
02242 class Dimension(Creator):
02243     "The Draft_Dimension FreeCAD command definition"
02244         
02245     def __init__(self):
02246         self.max=2
02247         self.cont = None
02248         self.dir = None
02249 
02250     def GetResources(self):
02251         return {'Pixmap'  : 'Draft_Dimension',
02252                 'Accel' : "D, I",
02253                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Dimension", "Dimension"),
02254                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Dimension", "Creates a dimension. CTRL to snap, SHIFT to constrain, ALT to select a segment")}
02255 
02256     def Activated(self):
02257         if self.cont:
02258             self.finish()
02259         elif self.hasMeasures():
02260             Creator.Activated(self,"Dimension")
02261             self.dimtrack = dimTracker()
02262             self.arctrack = arcTracker()
02263             self.constraintrack = lineTracker(dotted=True)
02264             self.createOnMeasures()
02265             self.finish()
02266         else:
02267             Creator.Activated(self,"Dimension")
02268             if self.ui:
02269                 self.ui.pointUi()
02270                 self.ui.continueCmd.show()
02271                 self.altdown = False
02272                 self.call = self.view.addEventCallback("SoEvent",self.action)
02273                 self.dimtrack = dimTracker()
02274                 self.arctrack = arcTracker()
02275                 self.link = None
02276                 self.edges = []
02277                 self.pts = []
02278                 self.angledata = None
02279                 self.indices = []
02280                 self.center = None
02281                 self.arcmode = False
02282                 self.point2 = None
02283                 self.constraintrack = lineTracker(dotted=True)
02284                 msg(translate("draft", "Pick first point:\n"))
02285                 FreeCADGui.draftToolBar.show()
02286 
02287     def hasMeasures(self):
02288         "checks if only measurements objects are selected"
02289         sel = FreeCADGui.Selection.getSelection()
02290         if not sel:
02291             return False
02292         for o in sel:
02293             if not o.isDerivedFrom("App::MeasureDistance"):
02294                 return False
02295         return True
02296 
02297     def finish(self,closed=False):
02298         "terminates the operation"
02299         self.cont = None
02300         self.dir = None
02301         Creator.finish(self)
02302         if self.ui:
02303             self.dimtrack.finalize()
02304             self.arctrack.finalize()
02305             self.constraintrack.finalize()
02306 
02307     def createOnMeasures(self):
02308         for o in FreeCADGui.Selection.getSelection():
02309             p1 = o.P1
02310             p2 = o.P2
02311             pt = o.ViewObject.RootNode.getChildren()[1].getChildren()[0].getChildren()[0].getChildren()[3]
02312             p3 = Vector(pt.point.getValues()[2].getValue())
02313             self.commit(translate("draft","Create Dimension"),
02314                         partial(Draft.makeDimension,p1,p2,p3))
02315             self.commit(translate("draft","Delete Measurement"),
02316                         partial(FreeCAD.ActiveDocument.removeObject,o.Name))
02317 
02318     def createObject(self):
02319         "creates an object in the current doc"
02320         if self.angledata:
02321             self.commit(translate("draft","Create Dimension"),
02322                         partial(Draft.makeAngularDimension,self.center,
02323                                 self.angledata,self.node[-1]))
02324         elif self.link and (not self.arcmode):
02325             self.commit(translate("draft","Create Dimension"),
02326                         partial(Draft.makeDimension,self.link[0],self.link[1],
02327                                 self.link[2],self.node[2]))
02328         elif self.arcmode:
02329             self.commit(translate("draft","Create Dimension"),
02330                         partial(Draft.makeDimension,self.link[0],self.link[1],
02331                                 self.arcmode,self.node[2]))
02332         else:
02333             self.commit(translate("draft","Create Dimension"),
02334                         partial(Draft.makeDimension,self.node[0],self.node[1],
02335                                 self.node[2]))
02336         if self.ui.continueMode:
02337             self.cont = self.node[2]
02338             if not self.dir:
02339                 if self.link:
02340                     v1 = self.link[0].Shape.Vertexes[self.link[1]].Point
02341                     v2 = self.link[0].Shape.Vertexes[self.link[2]].Point
02342                     self.dir = v2.sub(v1)
02343                 else:
02344                     self.dir = self.node[1].sub(self.node[0])
02345             self.node = [self.node[1]]
02346         self.link = None
02347 
02348     def action(self,arg):
02349         "scene event handler"
02350         if arg["Type"] == "SoKeyboardEvent":
02351             if arg["Key"] == "ESCAPE":
02352                 self.finish()
02353         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
02354             shift = hasMod(arg,MODCONSTRAIN)
02355             if self.arcmode or self.point2:
02356                 setMod(arg,MODCONSTRAIN,False)
02357             point,ctrlPoint = getPoint(self,arg)
02358             self.ui.cross(True)
02359             if hasMod(arg,MODALT) and (len(self.node)<3):
02360                 self.ui.cross(False)
02361                 self.dimtrack.off()
02362                 if not self.altdown:
02363                     self.altdown = True
02364                     self.ui.switchUi(True)
02365                 snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
02366                 if snapped:
02367                     ob = self.doc.getObject(snapped['Object'])
02368                     if "Edge" in snapped['Component']:
02369                         num = int(snapped['Component'].lstrip('Edge'))-1
02370                         ed = ob.Shape.Edges[num]
02371                         v1 = ed.Vertexes[0].Point
02372                         v2 = ed.Vertexes[-1].Point
02373                         self.dimtrack.update([v1,v2,self.cont])
02374             else:
02375                 self.ui.cross(True)
02376                 if self.node and (len(self.edges) < 2):
02377                     self.dimtrack.on()
02378                 if len(self.edges) == 2:
02379                     # angular dimension
02380                     self.dimtrack.off()
02381                     r = point.sub(self.center)
02382                     self.arctrack.setRadius(r.Length)
02383                     a = self.arctrack.getAngle(point)
02384                     pair = fcgeo.getBoundaryAngles(a,self.pts)
02385                     if not (pair[0] < a < pair[1]):
02386                         self.angledata = [4*math.pi-pair[0],2*math.pi-pair[1]]
02387                     else:
02388                         self.angledata = [2*math.pi-pair[0],2*math.pi-pair[1]]
02389                     self.arctrack.setStartAngle(self.angledata[0])
02390                     self.arctrack.setEndAngle(self.angledata[1])
02391                 if self.altdown:
02392                     self.altdown = False
02393                     self.ui.switchUi(False)
02394                 if self.dir:
02395                     point = self.node[0].add(fcvec.project(point.sub(self.node[0]),self.dir))
02396                 if len(self.node) == 2:
02397                     if self.arcmode and self.edges:
02398                         cen = self.edges[0].Curve.Center
02399                         rad = self.edges[0].Curve.Radius
02400                         baseray = point.sub(cen)
02401                         v2 = fcvec.scaleTo(baseray,rad)
02402                         v1 = fcvec.neg(v2)
02403                         if shift:
02404                             self.node = [cen,cen.add(v2)]
02405                             self.arcmode = "radius"
02406                         else:
02407                             self.node = [cen.add(v1),cen.add(v2)]
02408                             self.arcmode = "diameter"
02409                         self.dimtrack.update(self.node)
02410                 # Draw constraint tracker line.
02411                 if shift and (not self.arcmode):
02412                     if len(self.node) == 2:
02413                         if not self.point2:
02414                             self.point2 = self.node[1]
02415                         else:
02416                             self.node[1] = self.point2
02417                         a=abs(point.sub(self.node[0]).getAngle(plane.u))
02418                         if (a > math.pi/4) and (a <= 0.75*math.pi):
02419                             self.node[1] = Vector(self.node[0].x,self.node[1].y,self.node[0].z)
02420                         else:
02421                             self.node[1] = Vector(self.node[1].x,self.node[0].y,self.node[0].z)
02422                     self.constraintrack.p1(point)
02423                     self.constraintrack.p2(ctrlPoint)
02424                     self.constraintrack.on()
02425                 else:
02426                     if self.point2:
02427                         self.node[1] = self.point2
02428                         self.point2 = None
02429                     self.constraintrack.off()
02430                 # update the dimline
02431                 if self.node and (not self.arcmode):
02432                     self.dimtrack.update(self.node+[point]+[self.cont])
02433         elif arg["Type"] == "SoMouseButtonEvent":
02434             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
02435                 point,ctrlPoint = getPoint(self,arg)
02436                 if not self.node: self.support = getSupport(arg)
02437                 if hasMod(arg,MODALT) and (len(self.node)<3):
02438                     snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
02439                     if snapped:
02440                         ob = self.doc.getObject(snapped['Object'])
02441                         if 'Edge' in snapped['Component']:
02442                             num = int(snapped['Component'].lstrip('Edge'))-1
02443                             ed = ob.Shape.Edges[num]
02444                             v1 = ed.Vertexes[0].Point
02445                             v2 = ed.Vertexes[-1].Point
02446                             i1 = i2 = None
02447                             for i in range(len(ob.Shape.Vertexes)):
02448                                 if v1 == ob.Shape.Vertexes[i].Point:
02449                                     i1 = i
02450                                 if v2 == ob.Shape.Vertexes[i].Point:
02451                                     i2 = i
02452                             if (i1 != None) and (i2 != None):
02453                                 self.indices.append(num)
02454                                 if not self.edges:
02455                                     # nothing snapped yet, we treat it as normal edge-snapped dimension
02456                                     self.node = [v1,v2]
02457                                     self.link = [ob,i1,i2]
02458                                     self.edges.append(ed)
02459                                     if isinstance(ed.Curve,Part.Circle):
02460                                         # snapped edge is an arc
02461                                         self.arcmode = "diameter"
02462                                         self.link = [ob,num]
02463                                 else:
02464                                     # there is already a snapped edge, so we start angular dimension
02465                                     self.edges.append(ed)
02466                                     self.node.extend([v1,v2]) # self.node now has the 4 endpoints
02467                                     c = fcgeo.findIntersection(self.node[0],
02468                                                                self.node[1],
02469                                                                self.node[2],
02470                                                                self.node[3],
02471                                                                True,True)
02472                                     if c:
02473                                         self.center = c[0]
02474                                         self.arctrack.setCenter(self.center)
02475                                         self.arctrack.on()
02476                                         for e in self.edges:
02477                                             for v in e.Vertexes:
02478                                                 self.pts.append(self.arctrack.getAngle(v.Point))
02479                                         self.link = [self.link[0],ob]
02480                                     else:
02481                                         msg(translate("draft", "Edges don't intersect!\n"))
02482                                         self.finish()                                
02483                             self.dimtrack.on()
02484                 else:
02485                     if self.dir:
02486                         point = self.node[0].add(fcvec.project(point.sub(self.node[0]),self.dir))
02487                     self.node.append(point)
02488                 self.dimtrack.update(self.node)
02489                 if (len(self.node) == 2):
02490                     self.point2 = self.node[1]
02491                 if (len(self.node) == 1):
02492                     self.dimtrack.on()
02493                     self.planetrack.set(self.node[0])
02494                 elif (len(self.node) == 2) and self.cont:
02495                     self.node.append(self.cont)
02496                     self.createObject()
02497                     if not self.cont: self.finish()
02498                 elif (len(self.node) == 3):
02499                     # for unlinked arc mode:
02500                     # if self.arcmode:
02501                     #        v = self.node[1].sub(self.node[0])
02502                     #        v = fcvec.scale(v,0.5)
02503                     #        cen = self.node[0].add(v)
02504                     #        self.node = [self.node[0],self.node[1],cen]
02505                     self.createObject()
02506                     if not self.cont: self.finish()
02507                 elif self.angledata:
02508                     self.node.append(point)
02509                     self.createObject()
02510                     self.finish()
02511 
02512     def numericInput(self,numx,numy,numz):
02513         "this function gets called by the toolbar when valid x, y, and z have been entered there"
02514         point = Vector(numx,numy,numz)
02515         self.node.append(point)
02516         self.dimtrack.update(self.node)
02517         if (len(self.node) == 1):
02518             self.dimtrack.on()
02519         elif (len(self.node) == 3):
02520             self.createObject()
02521             if not self.cont: self.finish()
02522 
02523 
02524 #---------------------------------------------------------------------------
02525 # Modifier functions
02526 #---------------------------------------------------------------------------
02527 
02528 class Modifier:
02529     "A generic Modifier Tool, used by modification tools such as move"
02530     
02531     def __init__(self):
02532         self.commitList = []
02533         
02534     def Activated(self,name="None"):
02535         if FreeCAD.activeDraftCommand:
02536             FreeCAD.activeDraftCommand.finish()
02537         self.ui = None
02538         self.call = None
02539         self.commitList = []
02540         self.doc = FreeCAD.ActiveDocument
02541         if not self.doc:
02542             self.finish()
02543         else:
02544             FreeCAD.activeDraftCommand = self
02545             self.view = FreeCADGui.ActiveDocument.ActiveView
02546             self.ui = FreeCADGui.draftToolBar
02547             FreeCADGui.draftToolBar.show()
02548             rot = self.view.getCameraNode().getField("orientation").getValue()
02549             upv = Vector(rot.multVec(coin.SbVec3f(0,1,0)).getValue())
02550             plane.setup(fcvec.neg(self.view.getViewDirection()), Vector(0,0,0), upv)
02551             self.node = []
02552             self.ui.sourceCmd = self
02553             self.constrain = None
02554             self.obj = None
02555             self.extendedCopy = False
02556             self.ui.setTitle(name)
02557             self.featureName = name
02558             self.snap = snapTracker()
02559             self.extsnap = lineTracker(dotted=True)
02560             self.planetrack = PlaneTracker()
02561             if Draft.getParam("grid"):
02562                 self.grid = gridTracker()
02563                 self.grid.set()
02564             else:
02565                 self.grid = None
02566 
02567     def IsActive(self):
02568         if FreeCADGui.ActiveDocument:
02569             return True
02570         else:
02571             return False
02572                 
02573     def finish(self):
02574         self.node = []
02575         self.snap.finalize()
02576         self.extsnap.finalize()
02577         FreeCAD.activeDraftCommand = None
02578         if self.ui:
02579             self.ui.offUi()
02580             self.ui.sourceCmd=None
02581             self.ui.cross(False)
02582         msg("")
02583         self.planetrack.finalize()
02584         if self.grid: self.grid.finalize()
02585         if self.call:
02586             self.view.removeEventCallback("SoEvent",self.call)
02587             self.call = None
02588         if self.commitList:
02589             todo.delayCommit(self.commitList)
02590         self.commitList = []
02591 
02592     def commit(self,name,func):
02593         "stores partial actions to be committed to the FreeCAD document"
02594         # print "committing"
02595         self.commitList.append((name,func))
02596 
02597 
02598 class Move(Modifier):
02599     "The Draft_Move FreeCAD command definition"
02600 
02601     def GetResources(self):
02602         return {'Pixmap'  : 'Draft_Move',
02603                 'Accel' : "M, V",
02604                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Move", "Move"),
02605                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Move", "Moves the selected objects between 2 points. CTRL to snap, SHIFT to constrain, ALT to copy")}
02606     
02607     def Activated(self):
02608         Modifier.Activated(self,"Move")
02609         if self.ui:
02610             if not Draft.getSelection():
02611                 self.ghost = None
02612                 self.linetrack = None
02613                 self.constraintrack = None
02614                 self.ui.selectUi()
02615                 msg(translate("draft", "Select an object to move\n"))
02616                 self.call = self.view.addEventCallback("SoEvent",selectObject)
02617             else:
02618                 self.proceed()
02619 
02620     def proceed(self):
02621         if self.call: self.view.removeEventCallback("SoEvent",self.call)
02622         self.sel = Draft.getSelection()
02623         self.sel = Draft.getGroupContents(self.sel)
02624         self.ui.pointUi()
02625         self.ui.modUi()
02626         self.ui.xValue.setFocus()
02627         self.ui.xValue.selectAll()
02628         self.linetrack = lineTracker()
02629         self.constraintrack = lineTracker(dotted=True)
02630         self.ghost = ghostTracker(self.sel)
02631         self.call = self.view.addEventCallback("SoEvent",self.action)
02632         msg(translate("draft", "Pick start point:\n"))
02633         self.ui.cross(True)
02634 
02635     def finish(self,closed=False,cont=False):
02636         if self.ui:
02637             self.ghost.finalize()
02638             self.linetrack.finalize()
02639             self.constraintrack.finalize()
02640         Modifier.finish(self)
02641         if cont and self.ui:
02642             if self.ui.continueMode:
02643                 FreeCADGui.Selection.clearSelection()
02644                 self.Activated()
02645 
02646     def move(self,delta,copy=False):
02647         "moving the real shapes"
02648         if copy:
02649             self.commit(translate("draft","Copy"),partial(Draft.move,self.sel,delta,copy))
02650         else:
02651             self.commit(translate("draft","Move"),partial(Draft.move,self.sel,delta,copy))
02652         self.doc.recompute()
02653 
02654     def action(self,arg):
02655         "scene event handler"
02656         if arg["Type"] == "SoKeyboardEvent":
02657             if arg["Key"] == "ESCAPE":
02658                 self.finish()
02659         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
02660             point,ctrlPoint = getPoint(self,arg)
02661             self.linetrack.p2(point)
02662             self.ui.cross(True)
02663             # Draw constraint tracker line.
02664             if hasMod(arg,MODCONSTRAIN):
02665                 self.constraintrack.p1(point)
02666                 self.constraintrack.p2(ctrlPoint)
02667                 self.constraintrack.on()
02668             else: self.constraintrack.off()
02669             if (len(self.node) > 0):
02670                 last = self.node[len(self.node)-1]
02671                 delta = point.sub(last)
02672                 self.ghost.trans.translation.setValue([delta.x,delta.y,delta.z])
02673             if self.extendedCopy:
02674                 if not hasMod(arg,MODALT): self.finish()
02675         elif arg["Type"] == "SoMouseButtonEvent":
02676             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
02677                 point,ctrlPoint = getPoint(self,arg)
02678                 if (self.node == []):
02679                     self.node.append(point)
02680                     self.ui.isRelative.show()
02681                     self.linetrack.on()
02682                     self.ghost.on()
02683                     self.linetrack.p1(point)
02684                     msg(translate("draft", "Pick end point:\n"))
02685                     self.planetrack.set(point)
02686                 else:
02687                     last = self.node[0]
02688                     if self.ui.isCopy.isChecked() or hasMod(arg,MODALT):
02689                         self.move(point.sub(last),True)
02690                     else:
02691                         self.move(point.sub(last))
02692                     if hasMod(arg,MODALT):
02693                         self.extendedCopy = True
02694                     else:
02695                         self.finish(cont=True)
02696 
02697     def numericInput(self,numx,numy,numz):
02698         "this function gets called by the toolbar when valid x, y, and z have been entered there"
02699         point = Vector(numx,numy,numz)
02700         if not self.node:
02701             self.node.append(point)
02702             self.ui.isRelative.show()
02703             self.ui.isCopy.show()
02704             self.linetrack.p1(point)
02705             self.linetrack.on()
02706             self.ghost.on()
02707             msg(translate("draft", "Pick end point:\n"))
02708         else:
02709             last = self.node[-1]
02710             if self.ui.isCopy.isChecked():
02711                 self.move(point.sub(last),True)
02712             else:
02713                 self.move(point.sub(last))
02714             self.finish()
02715 
02716                         
02717 class ApplyStyle(Modifier):
02718     "The Draft_ApplyStyle FreeCA command definition"
02719 
02720     def GetResources(self):
02721         return {'Pixmap'  : 'Draft_Apply',
02722                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ApplyStyle", "Apply Current Style"),
02723                 'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_ApplyStyle", "Applies current line width and color to selected objects")}
02724 
02725     def IsActive(self):
02726         if Draft.getSelection():
02727             return True
02728         else:
02729             return False
02730 
02731     def Activated(self):
02732         Modifier.Activated(self)
02733         if self.ui:
02734             self.sel = Draft.getSelection()
02735             if (len(self.sel)>0):
02736                 for ob in self.sel:
02737                     if (ob.Type == "App::DocumentObjectGroup"):
02738                         self.formatGroup(ob)
02739                     else:
02740                         self.commit(translate("draft","Change Style"),partial(Draft.formatObject,ob))
02741 
02742     def formatGroup(self,grpob):
02743         for ob in grpob.Group:
02744             if (ob.Type == "App::DocumentObjectGroup"):
02745                 self.formatGroup(ob)
02746             else:
02747                 self.commit(translate("draft","Change Style"),partial(Draft.formatObject,ob))
02748 
02749                         
02750 class Rotate(Modifier):
02751     "The Draft_Rotate FreeCAD command definition"
02752     
02753     def GetResources(self):
02754         return {'Pixmap'  : 'Draft_Rotate',
02755                 'Accel' : "R, O",
02756                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Rotate", "Rotate"),
02757                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Rotate", "Rotates the selected objects. CTRL to snap, SHIFT to constrain, ALT creates a copy")}
02758 
02759     def Activated(self):
02760         Modifier.Activated(self,"Rotate")
02761         if self.ui:
02762             if not Draft.getSelection():
02763                 self.ghost = None
02764                 self.linetrack = None
02765                 self.arctrack = None
02766                 self.constraintrack = None
02767                 self.ui.selectUi()
02768                 msg(translate("draft", "Select an object to rotate\n"))
02769                 self.call = self.view.addEventCallback("SoEvent",selectObject)
02770             else:
02771                 self.proceed()
02772 
02773     def proceed(self):
02774         if self.call: self.view.removeEventCallback("SoEvent",self.call)
02775         self.sel = Draft.getSelection()
02776         self.sel = Draft.getGroupContents(self.sel)
02777         self.step = 0
02778         self.center = None
02779         self.ui.arcUi()
02780         self.ui.isCopy.show()
02781         self.ui.setTitle("Rotate")
02782         self.linetrack = lineTracker()
02783         self.constraintrack = lineTracker(dotted=True)
02784         self.arctrack = arcTracker()
02785         self.ghost = ghostTracker(self.sel)
02786         self.call = self.view.addEventCallback("SoEvent",self.action)
02787         msg(translate("draft", "Pick rotation center:\n"))
02788         self.ui.cross(True)
02789                                 
02790     def finish(self,closed=False,cont=False):
02791         "finishes the arc"
02792         Modifier.finish(self)
02793         if self.ui:
02794             self.linetrack.finalize()
02795             self.constraintrack.finalize()
02796             self.arctrack.finalize()
02797             self.ghost.finalize()
02798             self.doc.recompute()
02799         if cont and self.ui:
02800             if self.ui.continueMode:
02801                 FreeCADGui.Selection.clearSelection()
02802                 self.Activated()
02803 
02804     def rot (self,angle,copy=False):
02805         "rotating the real shapes"
02806         if copy:
02807             self.commit(translate("draft","Copy"),
02808                         partial(Draft.rotate,self.sel,
02809                                 math.degrees(angle),self.center,plane.axis,copy))
02810         else:
02811             self.commit(translate("draft","Rotate"),
02812                         partial(Draft.rotate,self.sel,
02813                                 math.degrees(angle),self.center,plane.axis,copy))
02814 
02815     def action(self,arg):
02816         "scene event handler"
02817         if arg["Type"] == "SoKeyboardEvent":
02818             if arg["Key"] == "ESCAPE":
02819                 self.finish()
02820         elif arg["Type"] == "SoLocation2Event":
02821             point,ctrlPoint = getPoint(self,arg)
02822             self.ui.cross(True)
02823             # this is to make sure radius is what you see on screen
02824             if self.center and fcvec.dist(point,self.center):
02825                 viewdelta = fcvec.project(point.sub(self.center), plane.axis)
02826                 if not fcvec.isNull(viewdelta):
02827                     point = point.add(fcvec.neg(viewdelta))
02828             if self.extendedCopy:
02829                 if not hasMod(arg,MODALT):
02830                     self.step = 3
02831                     self.finish()
02832             if (self.step == 0):
02833                 pass
02834             elif (self.step == 1):
02835                 currentrad = fcvec.dist(point,self.center)
02836                 if (currentrad != 0):
02837                     angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis)
02838                 else: angle = 0
02839                 self.linetrack.p2(point)
02840                 # Draw constraint tracker line.
02841                 if hasMod(arg,MODCONSTRAIN):
02842                     self.constraintrack.p1(point)
02843                     self.constraintrack.p2(ctrlPoint)
02844                     self.constraintrack.on()
02845                 else:
02846                     self.constraintrack.off()
02847                 self.ui.radiusValue.setText("%.2f" % math.degrees(angle))
02848                 self.firstangle = angle
02849                 self.ui.radiusValue.setFocus()
02850                 self.ui.radiusValue.selectAll()
02851             elif (self.step == 2):
02852                 currentrad = fcvec.dist(point,self.center)
02853                 if (currentrad != 0):
02854                     angle = fcvec.angle(plane.u, point.sub(self.center), plane.axis)
02855                 else: angle = 0
02856                 if (angle < self.firstangle): 
02857                     sweep = (2*math.pi-self.firstangle)+angle
02858                 else:
02859                     sweep = angle - self.firstangle
02860                 self.arctrack.setApertureAngle(sweep)
02861                 self.ghost.trans.rotation.setValue(coin.SbVec3f(fcvec.tup(plane.axis)),sweep)
02862                 self.linetrack.p2(point)
02863                 # Draw constraint tracker line.
02864                 if hasMod(arg,MODCONSTRAIN):
02865                     self.constraintrack.p1(point)
02866                     self.constraintrack.p2(ctrlPoint)
02867                     self.constraintrack.on()
02868                 else:
02869                     self.constraintrack.off()
02870                 self.ui.radiusValue.setText("%.2f" % math.degrees(sweep))
02871                 self.ui.radiusValue.setFocus()
02872                 self.ui.radiusValue.selectAll()
02873                 
02874         elif arg["Type"] == "SoMouseButtonEvent":
02875             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
02876                 point,ctrlPoint = getPoint(self,arg)
02877                 if self.center and fcvec.dist(point,self.center):
02878                     viewdelta = fcvec.project(point.sub(self.center), plane.axis)
02879                     if not fcvec.isNull(viewdelta): point = point.add(fcvec.neg(viewdelta))
02880                 if (self.step == 0):
02881                     self.center = point
02882                     self.node = [point]
02883                     self.ui.radiusUi()
02884                     self.ui.hasFill.hide()
02885                     self.ui.labelRadius.setText("Base angle")
02886                     self.linetrack.p1(self.center)
02887                     self.arctrack.setCenter(self.center)
02888                     self.ghost.trans.center.setValue(self.center.x,self.center.y,self.center.z)
02889                     self.linetrack.on()
02890                     self.step = 1
02891                     msg(translate("draft", "Pick base angle:\n"))
02892                     self.planetrack.set(point)
02893                 elif (self.step == 1):
02894                     self.ui.labelRadius.setText("Rotation")
02895                     self.rad = fcvec.dist(point,self.center)
02896                     self.arctrack.on()
02897                     self.arctrack.setStartPoint(point)
02898                     self.ghost.on()
02899                     self.step = 2
02900                     msg(translate("draft", "Pick rotation angle:\n"))
02901                 else:
02902                     currentrad = fcvec.dist(point,self.center)
02903                     angle = point.sub(self.center).getAngle(plane.u)
02904                     if fcvec.project(point.sub(self.center), plane.v).getAngle(plane.v) > 1:
02905                         angle = -angle
02906                     if (angle < self.firstangle): 
02907                         sweep = (2*math.pi-self.firstangle)+angle
02908                     else:
02909                         sweep = angle - self.firstangle
02910                     if self.ui.isCopy.isChecked() or hasMod(arg,MODALT):
02911                         self.rot(sweep,True)
02912                     else:
02913                         self.rot(sweep)
02914                     if hasMod(arg,MODALT):
02915                         self.extendedCopy = True
02916                     else:
02917                         self.finish(cont=True)
02918 
02919     def numericInput(self,numx,numy,numz):
02920         "this function gets called by the toolbar when valid x, y, and z have been entered there"
02921         self.center = Vector(numx,numy,numz)
02922         self.node = [self.center]
02923         self.arctrack.setCenter(self.center)
02924         self.ghost.trans.center.setValue(self.center.x,self.center.y,self.center.z)
02925         self.linetrack.p1(self.center)
02926         # self.arctrack.on()
02927         self.linetrack.on()
02928         self.ui.radiusUi()
02929         self.ui.hasFill.hide()
02930         self.ui.labelRadius.setText("Base angle")
02931         self.step = 1
02932         msg(translate("draft", "Pick base angle:\n"))
02933 
02934     def numericRadius(self,rad):
02935         "this function gets called by the toolbar when valid radius have been entered there"
02936         if (self.step == 1):
02937             self.ui.labelRadius.setText("Rotation")
02938             self.firstangle = math.radians(rad)
02939             self.arctrack.setStartAngle(self.firstangle)
02940             self.arctrack.on()
02941             self.ghost.on()
02942             self.step = 2
02943             msg(translate("draft", "Pick rotation angle:\n"))
02944         else:
02945             self.rot(math.radians(rad),self.ui.isCopy.isChecked())
02946             self.finish(cont=True)
02947 
02948 
02949 class Offset(Modifier):
02950     "The Draft_Offset FreeCAD command definition"
02951 
02952     def GetResources(self):
02953         return {'Pixmap'  : 'Draft_Offset',
02954                 'Accel' : "O, S",
02955                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Offset", "Offset"),
02956                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Offset", "Offsets the active object. CTRL to snap, SHIFT to constrain, ALT to copy")}
02957 
02958     def Activated(self):
02959         self.running = False
02960         Modifier.Activated(self,"Offset")
02961         if self.ui:
02962             if not Draft.getSelection():
02963                 self.ghost = None
02964                 self.linetrack = None
02965                 self.arctrack = None
02966                 self.constraintrack = None
02967                 self.ui.selectUi()
02968                 msg(translate("draft", "Select an object to offset\n"))
02969                 self.call = self.view.addEventCallback("SoEvent",selectObject)
02970             elif len(Draft.getSelection()) > 1:
02971                 msg(translate("draft", "Offset only works on one object at a time\n"),"warning")
02972             else:
02973                 self.proceed()
02974 
02975     def proceed(self):
02976         if self.call: self.view.removeEventCallback("SoEvent",self.call)
02977         self.sel = Draft.getSelection()[0]
02978         if not self.sel.isDerivedFrom("Part::Feature"):
02979             msg(translate("draft", "Cannot offset this object type\n"),"warning")
02980             self.finish()
02981         else:
02982             self.step = 0
02983             self.dvec = None
02984             self.constrainSeg = None
02985             self.ui.offsetUi()
02986             self.linetrack = lineTracker()
02987             self.constraintrack = lineTracker(dotted=True)
02988             self.faces = False
02989             self.shape = self.sel.Shape
02990             self.mode = None
02991             if Draft.getType(self.sel) in ["Circle","Arc"]:
02992                 self.ghost = arcTracker()
02993                 self.mode = "Circle"
02994                 self.center = self.shape.Edges[0].Curve.Center
02995                 self.ghost.setCenter(self.center)
02996                 self.ghost.setStartAngle(math.radians(self.sel.FirstAngle))
02997                 self.ghost.setEndAngle(math.radians(self.sel.LastAngle))
02998             else:
02999                 self.ghost = wireTracker(self.shape)
03000                 self.mode = "Wire"
03001             self.call = self.view.addEventCallback("SoEvent",self.action)
03002             msg(translate("draft", "Pick distance:\n"))
03003             self.ui.cross(True)
03004             self.planetrack.set(self.shape.Vertexes[0].Point)
03005             self.running = True
03006 
03007     def action(self,arg):
03008         "scene event handler"
03009         if arg["Type"] == "SoKeyboardEvent":
03010             if arg["Key"] == "ESCAPE":
03011                 self.finish()
03012         elif arg["Type"] == "SoLocation2Event":
03013             self.ui.cross(True)
03014             point,ctrlPoint = getPoint(self,arg)
03015             if hasMod(arg,MODCONSTRAIN) and self.constrainSeg:
03016                 dist = fcgeo.findPerpendicular(point,self.shape,self.constrainSeg[1])
03017                 e = self.shape.Edges[self.constrainSeg[1]]
03018                 self.constraintrack.p1(e.Vertexes[0].Point)
03019                 self.constraintrack.p2(point.add(dist[0]))
03020                 self.constraintrack.on()
03021             else:
03022                 dist = fcgeo.findPerpendicular(point,self.shape.Edges)
03023                 self.constraintrack.off()
03024             if dist:
03025                 self.ghost.on()
03026                 if self.mode == "Wire":
03027                     d = fcvec.neg(dist[0])
03028                     v1 = fcgeo.getTangent(self.shape.Edges[0],point)
03029                     v2 = fcgeo.getTangent(self.shape.Edges[dist[1]],point)
03030                     a = -fcvec.angle(v1,v2)
03031                     self.dvec = fcvec.rotate(d,a,plane.axis)
03032                     self.ghost.update(fcgeo.offsetWire(self.shape,self.dvec,occ=self.ui.occOffset.isChecked()))
03033                 elif self.mode == "Circle":
03034                     self.dvec = point.sub(self.center).Length
03035                     self.ghost.setRadius(self.dvec)
03036                 self.constrainSeg = dist
03037                 self.linetrack.on()
03038                 self.linetrack.p1(point)
03039                 self.linetrack.p2(point.add(dist[0]))
03040                 self.ui.radiusValue.setText("%.2f" % dist[0].Length)
03041             else:
03042                 self.dvec = None
03043                 self.ghost.off()
03044                 self.constrainSeg = None
03045                 self.linetrack.off()
03046                 self.ui.radiusValue.setText("off")
03047             self.ui.radiusValue.setFocus()
03048             self.ui.radiusValue.selectAll()
03049             if self.extendedCopy:
03050                 if not hasMod(arg,MODALT): self.finish()
03051                                 
03052         elif arg["Type"] == "SoMouseButtonEvent":
03053             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
03054                 copymode = False
03055                 occmode = self.ui.occOffset.isChecked()
03056                 if hasMod(arg,MODALT) or self.ui.isCopy.isChecked(): copymode = True
03057                 if self.dvec:
03058                     self.commit(translate("draft","Offset"),
03059                                 partial(Draft.offset,self.sel,
03060                                         self.dvec,copymode,occ=occmode))
03061                 if hasMod(arg,MODALT):
03062                     self.extendedCopy = True
03063                 else:
03064                     self.finish()
03065                                         
03066     def finish(self,closed=False):
03067         Modifier.finish(self)
03068         if self.ui and self.running:
03069             self.linetrack.finalize()
03070             self.constraintrack.finalize()
03071             self.ghost.finalize()
03072 
03073     def numericRadius(self,rad):
03074         '''this function gets called by the toolbar when
03075         valid radius have been entered there'''
03076         if self.dvec:
03077             self.dvec.normalize()
03078             self.dvec.multiply(rad)
03079             copymode = False
03080             occmode = self.ui.occOffset.isChecked()
03081             if self.ui.isCopy.isChecked(): copymode = True
03082             self.commit(translate("draft","Offset"),
03083                         partial(Draft.offset,self.sel,
03084                                 self.dvec,copymode,occ=occmode))
03085             self.finish()
03086 
03087             
03088 class Upgrade(Modifier):
03089     '''The Draft_Upgrade FreeCAD command definition.
03090     This class upgrades selected objects in different ways,
03091     following this list (in order):
03092     - if there are more than one faces, the faces are merged (union)
03093     - if there is only one face, nothing is done
03094     - if there are closed wires, they are transformed in a face
03095     - otherwise join all edges into a wire (closed if applicable)
03096     - if nothing of the above is possible, a Compound is created
03097     '''
03098 
03099     def GetResources(self):
03100         return {'Pixmap'  : 'Draft_Upgrade',
03101                 'Accel' : "U, P",
03102                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Upgrade", "Upgrade"),
03103                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Upgrade", "Joins the selected objects into one, or converts closed wires to filled faces, or unite faces")}
03104 
03105     def Activated(self):
03106         Modifier.Activated(self,"Upgrade")
03107         if self.ui:
03108             if not Draft.getSelection():
03109                 self.ui.selectUi()
03110                 msg(translate("draft", "Select an object to upgrade\n"))
03111                 self.call = self.view.addEventCallback("SoEvent",selectObject)
03112             else:
03113                 self.proceed()
03114 
03115     def compound(self):
03116         # shapeslist = []
03117         # for ob in self.sel: shapeslist.append(ob.Shape)
03118         # newob = self.doc.addObject("Part::Feature","Compound")
03119         # newob.Shape = Part.makeCompound(shapeslist)
03120         newob = Draft.makeBlock(self.sel)
03121         self.nodelete = True
03122         return newob
03123                 
03124     def proceed(self):
03125         if self.call: self.view.removeEventCallback("SoEvent",self.call)
03126         self.sel = Draft.getSelection()
03127         newob = None
03128         self.nodelete = False
03129         edges = []
03130         wires = []
03131         openwires = []
03132         faces = []
03133         groups = []
03134         curves = []
03135         facewires = []
03136         
03137         # determining what we have in our selection
03138         for ob in self.sel:
03139             if ob.Type == "App::DocumentObjectGroup":
03140                 groups.append(ob)
03141             else:
03142                 if ob.Shape.ShapeType == 'Edge': openwires.append(ob.Shape)
03143                 for f in ob.Shape.Faces:
03144                     faces.append(f)
03145                     facewires.extend(f.Wires)
03146                 for w in ob.Shape.Wires:
03147                     if w.isClosed():
03148                         wires.append(w)
03149                     else:
03150                         openwires.append(w)
03151                 for e in ob.Shape.Edges:
03152                     if not isinstance(e.Curve,Part.Line):
03153                         curves.append(e)
03154                 lastob = ob
03155         # print "objects:",self.sel," edges:",edges," wires:",wires," openwires:",openwires," faces:",faces
03156         # print "groups:",groups," curves:",curves," facewires:",facewires
03157                 
03158         # applying transformation
03159         self.doc.openTransaction("Upgrade")
03160         
03161         if groups:
03162             # if we have a group: turn each closed wire inside into a face
03163             msg(translate("draft", "Found groups: closing each open object inside\n"))
03164             for grp in groups: 
03165                 for ob in grp.Group:
03166                     if not ob.Shape.Faces:
03167                         for w in ob.Shape.Wires:
03168                             newob = Draft.makeWire(w,closed=w.isClosed())
03169                             self.sel.append(ob)
03170                             grp.addObject(newob)
03171                             
03172         elif faces and (len(wires)+len(openwires)==len(facewires)):
03173             # we have only faces here, no lone edges
03174             
03175             if (len(self.sel) == 1) and (len(faces) > 1):
03176                 # we have a shell: we try to make a solid
03177                 sol = Part.makeSolid(self.sel[0].Shape)
03178                 if sol.isClosed():
03179                     msg(translate("draft", "Found 1 solidificable object: solidifying it\n"))
03180                     newob = self.doc.addObject("Part::Feature","Solid")
03181                     newob.Shape = sol
03182                     Draft.formatObject(newob,lastob)
03183                         
03184             elif (len(self.sel) == 2) and (not curves):
03185                 # we have exactly 2 objects: we fuse them
03186                 msg(translate("draft", "Found 2 objects: fusing them\n"))
03187                 newob = Draft.fuse(self.sel[0],self.sel[1])
03188                 self.nodelete = True
03189                                 
03190             elif (len(self.sel) > 2) and (len(faces) > 10):
03191                 # we have many separate faces: we try to make a shell
03192                 sh = Part.makeShell(faces)
03193                 newob = self.doc.addObject("Part::Feature","Shell")
03194                 newob.Shape = sh
03195                 Draft.formatObject(newob,lastob)
03196                 
03197             elif (len(self.sel) > 2) or (len(faces) > 1):
03198                 # more than 2 objects or faces: we try the draft way: make one face out of them
03199                 u = faces.pop(0)
03200                 for f in faces:
03201                     u = u.fuse(f)
03202                 if fcgeo.isCoplanar(faces):
03203                     if self.sel[0].ViewObject.DisplayMode == "Wireframe":
03204                         f = False
03205                     else:
03206                         f = True
03207                     u = fcgeo.concatenate(u)
03208                     if not curves:
03209                         msg(translate("draft", "Found several objects or faces: making a parametric face\n"))
03210                         newob = Draft.makeWire(u.Wires[0],closed=True,face=f)
03211                         Draft.formatObject(newob,lastob)
03212                     else:
03213                         # if not possible, we do a non-parametric union
03214                         msg(translate("draft", "Found objects containing curves: fusing them\n"))
03215                         newob = self.doc.addObject("Part::Feature","Union")
03216                         newob.Shape = u
03217                         Draft.formatObject(newob,lastob)
03218                 else:
03219                     # if not possible, we do a non-parametric union
03220                     msg(translate("draft", "Found several objects: fusing them\n"))
03221                     # if we have a solid, make sure we really return a solid
03222                     if (len(u.Faces) > 1) and u.isClosed():
03223                         u = Part.makeSolid(u)
03224                     newob = self.doc.addObject("Part::Feature","Union")
03225                     newob.Shape = u
03226                     Draft.formatObject(newob,lastob)
03227             elif len(self.sel) == 1:
03228                 # only one object: if not parametric, we "draftify" it
03229                 self.nodelete = True
03230                 if (not curves) and (Draft.getType(self.sel[0]) == "Part"):
03231                     msg(translate("draft", "Found 1 non-parametric objects: draftifying it\n"))
03232                     Draft.draftify(self.sel[0])
03233                                         
03234         elif wires and (not faces) and (not openwires):
03235             # we have only wires, no faces
03236             
03237             if (len(self.sel) == 1) and self.sel[0].isDerivedFrom("Sketcher::SketchObject") and (not curves):
03238                 # we have a sketch
03239                 msg(translate("draft", "Found 1 closed sketch object: making a face from it\n"))
03240                 newob = Draft.makeWire(self.sel[0].Shape,closed=True)
03241                 newob.Base = self.sel[0]
03242                 self.sel[0].ViewObject.Visibility = False
03243                 self.nodelete = True
03244                 
03245             else:
03246                 # only closed wires
03247                 for w in wires:
03248                     f = Part.Face(w)
03249                     faces.append(f)
03250                 for f in faces:
03251                     if not curves: 
03252                         newob = Draft.makeWire(f.Wire,closed=True)
03253                     else:
03254                         # if there are curved segments, we do a non-parametric face
03255                         msg(translate("draft", "Found closed wires: making faces\n"))
03256                         newob = self.doc.addObject("Part::Feature","Face")
03257                         newob.Shape = f
03258                         Draft.formatObject(newob,lastob)
03259                         
03260         elif (len(openwires) == 1) and (not faces) and (not wires):
03261             # special case, we have only one open wire. We close it!"
03262             p0 = openwires[0].Vertexes[0].Point
03263             p1 = openwires[0].Vertexes[-1].Point
03264             edges = openwires[0].Edges
03265             edges.append(Part.Line(p1,p0).toShape())
03266             w = Part.Wire(fcgeo.sortEdges(edges))
03267             msg(translate("draft", "Found 1 open wire: closing it\n"))
03268             if not curves:
03269                 newob = Draft.makeWire(w,closed=True)
03270             else:
03271                 # if not possible, we do a non-parametric union
03272                 newob = self.doc.addObject("Part::Feature","Wire")
03273                 newob.Shape = w
03274                 Draft.formatObject(newob,lastob)
03275                 
03276         elif openwires and (not wires) and (not faces):
03277             # only open wires and edges: we try to join their edges
03278             for ob in self.sel:
03279                 for e in ob.Shape.Edges:
03280                     edges.append(e)
03281             newob = None
03282             nedges = fcgeo.sortEdges(edges[:])
03283             # for e in nedges: print "debug: ",e.Curve,e.Vertexes[0].Point,e.Vertexes[-1].Point
03284             w = Part.Wire(nedges)
03285             if len(w.Edges) == len(edges):
03286                 msg(translate("draft", "Found several edges: wiring them\n"))
03287                 if not curves:
03288                     newob = Draft.makeWire(w)
03289                 else:
03290                     newob = self.doc.addObject("Part::Feature","Wire")
03291                     newob.Shape = w
03292                     Draft.formatObject(newob,lastob)
03293             if not newob:
03294                 print "no new object found"
03295                 msg(translate("draft", "Found several non-connected edges: making compound\n"))
03296                 newob = self.compound()
03297                 Draft.formatObject(newob,lastob)
03298         else:
03299             # all other cases
03300             msg(translate("draft", "Found several non-treatable objects: making compound\n"))
03301             newob = self.compound()
03302             Draft.formatObject(newob,lastob)
03303             
03304         if not self.nodelete:
03305             # deleting original objects, if needed
03306             for ob in self.sel:
03307                 if not ob.Type == "App::DocumentObjectGroup":
03308                     self.doc.removeObject(ob.Name)
03309                     
03310         self.doc.commitTransaction()
03311         if newob: Draft.select(newob)
03312         Modifier.finish(self)
03313 
03314         
03315 class Downgrade(Modifier):
03316     '''
03317     The Draft_Downgrade FreeCAD command definition.
03318     This class downgrades selected objects in different ways,
03319     following this list (in order):
03320     - if there are more than one faces, the subsequent
03321     faces are subtracted from the first one
03322     - if there is only one face, it gets converted to a wire
03323     - otherwise wires are exploded into single edges
03324     '''
03325 
03326     def GetResources(self):
03327         return {'Pixmap'  : 'Draft_Downgrade',
03328                 'Accel' : "D, N",
03329                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Downgrade", "Downgrade"),
03330                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Downgrade", "Explodes the selected objects into simpler objects, or subtract faces")}
03331 
03332     def Activated(self):
03333         Modifier.Activated(self,"Downgrade")
03334         if self.ui:
03335             if not Draft.getSelection():
03336                 self.ui.selectUi()
03337                 msg(translate("draft", "Select an object to upgrade\n"))
03338                 self.call = self.view.addEventCallback("SoEvent",selectObject)
03339             else:
03340                 self.proceed()
03341 
03342     def proceed(self):
03343         self.sel = Draft.getSelection()
03344         edges = []
03345         faces = []
03346 
03347         # scanning objects
03348         for ob in self.sel:
03349             for f in ob.Shape.Faces:
03350                 faces.append(f)
03351         for ob in self.sel:
03352             for e in ob.Shape.Edges:
03353                 edges.append(e)
03354             lastob = ob
03355                         
03356         # applying transformation
03357         self.doc.openTransaction("Downgrade")
03358         
03359         if (len(self.sel) == 1) and (Draft.getType(self.sel[0]) == "Block"):
03360             # a block, we explode it
03361             pl = self.sel[0].Placement
03362             newob = []
03363             for ob in self.sel[0].Components:
03364                 ob.ViewObject.Visibility = True
03365                 ob.Placement = ob.Placement.multiply(pl)
03366                 newob.append(ob)
03367             self.doc.removeObject(self.sel[0].Name)
03368             
03369         elif (len(self.sel) == 1) and (self.sel[0].isDerivedFrom("Part::Feature")) and ("Base" in self.sel[0].PropertiesList):
03370             # special case, we have one parametric object: we "de-parametrize" it
03371             msg(translate("draft", "Found 1 parametric object: breaking its dependencies\n"))
03372             newob = Draft.shapify(self.sel[0])
03373             
03374         elif len(self.sel) == 2:
03375             # we have only 2 objects: cut 2nd from 1st
03376             msg(translate("draft", "Found 2 objects: subtracting them\n"))
03377             newob = Draft.cut(self.sel[0],self.sel[1])
03378             
03379         elif (len(faces) > 1):
03380             
03381             if len(self.sel) == 1:
03382                 # one object with several faces: split it
03383                 for f in faces:
03384                     msg(translate("draft", "Found several faces: splitting them\n"))
03385                     newob = self.doc.addObject("Part::Feature","Face")
03386                     newob.Shape = f
03387                     Draft.formatObject(newob,self.sel[0])
03388                 self.doc.removeObject(ob.Name)
03389                 
03390             else:
03391                 # several objects: remove all the faces from the first one
03392                 msg(translate("draft", "Found several objects: subtracting them from the first one\n"))
03393                 u = faces.pop(0)
03394                 for f in faces:
03395                     u = u.cut(f)
03396                 newob = self.doc.addObject("Part::Feature","Subtraction")
03397                 newob.Shape = u
03398                 for ob in self.sel:
03399                     Draft.formatObject(newob,ob)
03400                     self.doc.removeObject(ob.Name)
03401                     
03402         elif (len(faces) > 0):
03403             # only one face: we extract its wires
03404             msg(translate("draft", "Found 1 face: extracting its wires\n"))
03405             for w in faces[0].Wires:
03406                 newob = self.doc.addObject("Part::Feature","Wire")
03407                 newob.Shape = w
03408                 Draft.formatObject(newob,lastob)
03409             for ob in self.sel:
03410                 self.doc.removeObject(ob.Name)
03411                 
03412         else:
03413             # no faces: split wire into single edges
03414             msg(translate("draft", "Found only wires: extracting their edges\n"))
03415             for ob in self.sel:
03416                 for e in edges:
03417                     newob = self.doc.addObject("Part::Feature","Edge")
03418                     newob.Shape = e
03419                     Draft.formatObject(newob,ob)
03420                 self.doc.removeObject(ob.Name)
03421         self.doc.commitTransaction()
03422         Draft.select(newob)
03423         Modifier.finish(self)
03424 
03425 
03426 class Trimex(Modifier):
03427     ''' The Draft_Trimex FreeCAD command definition.
03428     This tool trims or extends lines, wires and arcs,
03429     or extrudes single faces. SHIFT constrains to the last point
03430     or extrudes in direction to the face normal.'''
03431 
03432     def GetResources(self):
03433         return {'Pixmap' : 'Draft_Trimex',
03434                 'Accel' : "T, R",
03435                 'MenuText' : QtCore.QT_TRANSLATE_NOOP("Draft_Trimex", "Trimex"),
03436                 '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")}
03437 
03438     def Activated(self):
03439         Modifier.Activated(self,"Trimex")
03440         self.edges = []
03441         self.placement = None
03442         self.ghost = None
03443         self.linetrack = None
03444         self.constraintrack = None
03445         if self.ui:
03446             if not Draft.getSelection():
03447                 self.ui.selectUi()
03448                 msg(translate("draft", "Select an object to trim/extend\n"))
03449                 self.call = self.view.addEventCallback("SoEvent",selectObject)
03450             else:
03451                 self.proceed()
03452 
03453     def proceed(self):
03454         if self.call: self.view.removeEventCallback("SoEvent",self.call)
03455         self.obj = Draft.getSelection()[0]
03456         self.ui.trimUi()
03457         self.linetrack = lineTracker()
03458         self.constraintrack = lineTracker(dotted=True)
03459         if not "Shape" in self.obj.PropertiesList: return
03460         if "Placement" in self.obj.PropertiesList:
03461             self.placement = self.obj.Placement
03462         if len(self.obj.Shape.Faces) == 1:
03463             # simple extrude mode, the object itself is extruded
03464             self.extrudeMode = True
03465             self.ghost = [ghostTracker([self.obj])]
03466             self.normal = self.obj.Shape.Faces[0].normalAt(.5,.5)
03467             for v in self.obj.Shape.Vertexes:
03468                 self.ghost.append(lineTracker())
03469         elif len(self.obj.Shape.Faces) > 1:
03470             # face extrude mode, a new object is created
03471             ss =  FreeCADGui.Selection.getSelectionEx()[0]
03472             if len(ss.SubObjects) == 1:
03473                 if ss.SubObjects[0].ShapeType == "Face":
03474                     self.obj = self.doc.addObject("Part::Feature","Face")
03475                     self.obj.Shape = ss.SubObjects[0]
03476                     self.extrudeMode = True
03477                     self.ghost = [ghostTracker([self.obj])]
03478                     self.normal = self.obj.Shape.Faces[0].normalAt(.5,.5)
03479                     for v in self.obj.Shape.Vertexes:
03480                         self.ghost.append(lineTracker())
03481         else:
03482             # normal wire trimex mode
03483             self.obj.ViewObject.Visibility = False
03484             self.extrudeMode = False
03485             if self.obj.Shape.Wires:
03486                 self.edges = self.obj.Shape.Wires[0].Edges
03487                 self.edges = fcgeo.sortEdges(self.edges)
03488             else:
03489                 self.edges = self.obj.Shape.Edges       
03490             self.ghost = []
03491             lc = self.obj.ViewObject.LineColor
03492             sc = (lc[0],lc[1],lc[2])
03493             sw = self.obj.ViewObject.LineWidth
03494             for e in self.edges:
03495                 if isinstance(e.Curve,Part.Line):
03496                     self.ghost.append(lineTracker(scolor=sc,swidth=sw))
03497                 else:
03498                     self.ghost.append(arcTracker(scolor=sc,swidth=sw))
03499         if not self.ghost: self.finish()
03500         for g in self.ghost: g.on()
03501         self.activePoint = 0
03502         self.nodes = []
03503         self.shift = False
03504         self.alt = False
03505         self.force = None
03506         self.cv = None
03507         self.call = self.view.addEventCallback("SoEvent",self.action)
03508         msg(translate("draft", "Pick distance:\n"))
03509         self.ui.cross(True)
03510                                 
03511     def action(self,arg):
03512         "scene event handler"
03513         if arg["Type"] == "SoKeyboardEvent":
03514             if arg["Key"] == "ESCAPE":
03515                 self.finish()
03516         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
03517             self.ui.cross(True)
03518             self.shift = hasMod(arg,MODCONSTRAIN)
03519             self.alt = hasMod(arg,MODALT)
03520             wp = not(self.extrudeMode and self.shift)
03521             self.point = getPoint(self,arg,workingplane=wp)[0]
03522             if hasMod(arg,MODSNAP): self.snapped = None
03523             else: self.snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
03524             if self.extrudeMode:
03525                 dist = self.extrude(self.shift)
03526             else:
03527                 dist = self.redraw(self.point,self.snapped,self.shift,self.alt)
03528             self.constraintrack.p1(self.point)
03529             self.constraintrack.p2(self.newpoint)
03530             self.constraintrack.on()
03531             self.ui.radiusValue.setText("%.2f" % dist)
03532             self.ui.radiusValue.setFocus()
03533             self.ui.radiusValue.selectAll()
03534                         
03535         elif arg["Type"] == "SoMouseButtonEvent":
03536             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
03537                 cursor = arg["Position"]
03538                 self.shift = hasMod(arg,MODCONSTRAIN)
03539                 self.alt = hasMod(arg,MODALT)
03540                 if hasMod(arg,MODSNAP): self.snapped = None
03541                 else: self.snapped = self.view.getObjectInfo((cursor[0],cursor[1]))
03542                 self.trimObject()
03543                 self.finish()
03544 
03545     def extrude(self,shift=False,real=False):
03546         "redraws the ghost in extrude mode"
03547         self.newpoint = self.obj.Shape.Faces[0].CenterOfMass
03548         dvec = self.point.sub(self.newpoint)
03549         if shift: delta = fcvec.project(dvec,self.normal)
03550         else: delta = dvec
03551         if self.force:
03552             ratio = self.force/delta.Length
03553             delta.multiply(ratio)
03554         if real: return delta
03555         self.ghost[0].trans.translation.setValue([delta.x,delta.y,delta.z])
03556         for i in range(1,len(self.ghost)):
03557             base = self.obj.Shape.Vertexes[i-1].Point
03558             self.ghost[i].p1(base)
03559             self.ghost[i].p2(base.add(delta))
03560         return delta.Length
03561         
03562     def redraw(self,point,snapped=None,shift=False,alt=False,real=None):
03563         "redraws the ghost"
03564         
03565         # initializing
03566         reverse = False
03567         for g in self.ghost: g.off()
03568         if real: newedges = []
03569         
03570         # finding the active point
03571         vlist = []
03572         for e in self.edges: vlist.append(e.Vertexes[0].Point)
03573         vlist.append(self.edges[-1].Vertexes[-1].Point)
03574         if shift: npoint = self.activePoint
03575         else: npoint = fcgeo.findClosest(point,vlist)
03576         if npoint > len(self.edges)/2: reverse = True
03577         if alt: reverse = not reverse
03578         self.activePoint = npoint
03579                 
03580         # sorting out directions
03581         if reverse and (npoint > 0): npoint = npoint-1
03582         if (npoint > len(self.edges)-1):
03583             edge = self.edges[-1]
03584             ghost = self.ghost[-1]
03585         else:
03586             edge = self.edges[npoint]
03587             ghost = self.ghost[npoint]
03588         if reverse:
03589             v1 = edge.Vertexes[-1].Point
03590             v2 = edge.Vertexes[0].Point
03591         else:
03592             v1 = edge.Vertexes[0].Point
03593             v2 = edge.Vertexes[-1].Point
03594                         
03595         # snapping
03596         if snapped:
03597             snapped = self.doc.getObject(snapped['Object'])
03598             pts = []
03599             for e in snapped.Shape.Edges:
03600                 int = fcgeo.findIntersection(edge,e,True,True)
03601                 if int: pts.extend(int)
03602             if pts:
03603                 point = pts[fcgeo.findClosest(point,pts)]
03604 
03605         # modifying active edge
03606         if isinstance(edge.Curve,Part.Line):
03607             perp = fcgeo.vec(edge).cross(Vector(0,0,1))
03608             chord = v1.sub(point)
03609             proj = fcvec.project(chord,perp)
03610             self.newpoint = Vector.add(point,proj)
03611             dist = v1.sub(self.newpoint).Length
03612             ghost.p1(self.newpoint)
03613             ghost.p2(v2)
03614             self.ui.labelRadius.setText("Distance")
03615             if real:
03616                 if self.force:
03617                     ray = self.newpoint.sub(v1)
03618                     ray = fcvec.scale(ray,self.force/ray.Length)
03619                     self.newpoint = Vector.add(v1,ray)
03620                 newedges.append(Part.Line(self.newpoint,v2).toShape())
03621         else:
03622             center = edge.Curve.Center
03623             rad = edge.Curve.Radius
03624             ang1 = fcvec.angle(v2.sub(center))
03625             ang2 = fcvec.angle(point.sub(center))
03626             self.newpoint=Vector.add(center,fcvec.rotate(Vector(rad,0,0),-ang2))
03627             self.ui.labelRadius.setText("Angle")
03628             dist = math.degrees(-ang2)
03629             # if ang1 > ang2: ang1,ang2 = ang2,ang1
03630             print "last calculated:",math.degrees(-ang1),math.degrees(-ang2)
03631             ghost.setEndAngle(-ang2)
03632             ghost.setStartAngle(-ang1)
03633             ghost.setCenter(center)
03634             ghost.setRadius(rad)
03635             if real:
03636                 if self.force:
03637                     angle = math.radians(self.force)
03638                     newray = fcvec.rotate(Vector(rad,0,0),-angle)
03639                     self.newpoint = Vector.add(center,newray)
03640                 chord = self.newpoint.sub(v2)
03641                 perp = chord.cross(Vector(0,0,1))
03642                 scaledperp = fcvec.scaleTo(perp,rad)
03643                 midpoint = Vector.add(center,scaledperp)
03644                 newedges.append(Part.Arc(self.newpoint,midpoint,v2).toShape())
03645         ghost.on()
03646 
03647         # resetting the visible edges
03648         if not reverse: list = range(npoint+1,len(self.edges))
03649         else: list = range(npoint-1,-1,-1)
03650         for i in list:
03651             edge = self.edges[i]
03652             ghost = self.ghost[i]
03653             if isinstance(edge.Curve,Part.Line):
03654                 ghost.p1(edge.Vertexes[0].Point)
03655                 ghost.p2(edge.Vertexes[-1].Point)
03656             else:
03657                 ang1 = fcvec.angle(edge.Vertexes[0].Point.sub(center))
03658                 ang2 = fcvec.angle(edge.Vertexes[-1].Point.sub(center))
03659                 # if ang1 > ang2: ang1,ang2 = ang2,ang1
03660                 ghost.setEndAngle(-ang2)
03661                 ghost.setStartAngle(-ang1)
03662                 ghost.setCenter(edge.Curve.Center)
03663                 ghost.setRadius(edge.Curve.Radius)
03664             if real: newedges.append(edge)
03665             ghost.on()
03666                         
03667         # finishing
03668         if real: return newedges
03669         else: return dist
03670 
03671     def trimObject(self):
03672         "trims the actual object"
03673         if self.extrudeMode:
03674             delta = self.extrude(self.shift,real=True)
03675             print "delta",delta
03676             self.doc.openTransaction("Extrude")
03677             obj = Draft.extrude(self.obj,delta)
03678             self.doc.commitTransaction()
03679             self.obj = obj
03680         else:
03681             edges = self.redraw(self.point,self.snapped,self.shift,self.alt,real=True)
03682             newshape = Part.Wire(edges)
03683             self.doc.openTransaction("Trim/extend")
03684             if Draft.getType(self.obj) in ["Wire","BSpline"]:
03685                 p = []
03686                 if self.placement: invpl = self.placement.inverse()
03687                 for v in newshape.Vertexes:
03688                     np = v.Point
03689                     if self.placement: np = invpl.multVec(np)
03690                     p.append(np)
03691                 self.obj.Points = p
03692             elif Draft.getType(self.obj) == "Circle":
03693                 angles = self.ghost[0].getAngles()
03694                 print "original",self.obj.FirstAngle," ",self.obj.LastAngle
03695                 print "new",angles
03696                 if angles[0] > angles[1]: angles = (angles[1],angles[0])
03697                 self.obj.FirstAngle = angles[0]
03698                 self.obj.LastAngle = angles[1]
03699             else:
03700                 self.obj.Shape = newshape
03701             self.doc.commitTransaction()
03702         for g in self.ghost: g.off()
03703 
03704     def finish(self,closed=False):              
03705         Modifier.finish(self)
03706         self.force = None
03707         if self.ui:
03708             self.linetrack.finalize()
03709             self.constraintrack.finalize()
03710             if self.ghost:
03711                 for g in self.ghost:
03712                     g.finalize()
03713             self.obj.ViewObject.Visibility = True
03714             Draft.select(self.obj)
03715 
03716     def numericRadius(self,dist):
03717         "this function gets called by the toolbar when valid distance have been entered there"
03718         self.force = dist
03719         self.trimObject()
03720         self.finish()
03721 
03722         
03723 class Scale(Modifier):
03724     '''The Draft_Scale FreeCAD command definition.
03725     This tool scales the selected objects from a base point.'''
03726 
03727     def GetResources(self):
03728         return {'Pixmap'  : 'Draft_Scale',
03729                 'Accel' : "S, C",
03730                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Scale", "Scale"),
03731                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Scale", "Scales the selected objects from a base point. CTRL to snap, SHIFT to constrain, ALT to copy")}
03732 
03733     def Activated(self):
03734         Modifier.Activated(self,"Scale")
03735         if self.ui:
03736             if not Draft.getSelection():
03737                 self.ghost = None
03738                 self.linetrack = None
03739                 self.constraintrack = None
03740                 self.ui.selectUi()
03741                 msg(translate("draft", "Select an object to scale\n"))
03742                 self.call = self.view.addEventCallback("SoEvent",selectObject)
03743             else:
03744                 self.proceed()
03745 
03746     def proceed(self):
03747         if self.call: self.view.removeEventCallback("SoEvent",self.call)
03748         self.sel = Draft.getSelection()
03749         self.sel = Draft.getGroupContents(self.sel)
03750         self.ui.pointUi()
03751         self.ui.modUi()
03752         self.ui.xValue.setFocus()
03753         self.ui.xValue.selectAll()
03754         self.linetrack = lineTracker()
03755         self.constraintrack = lineTracker(dotted=True)
03756         self.ghost = ghostTracker(self.sel)
03757         self.call = self.view.addEventCallback("SoEvent",self.action)
03758         msg(translate("draft", "Pick base point:\n"))
03759         self.ui.cross(True)
03760 
03761     def finish(self,closed=False,cont=False):
03762         Modifier.finish(self)
03763         if self.ui:
03764             self.ghost.finalize()
03765             self.linetrack.finalize()
03766             self.constraintrack.finalize()
03767         if cont and self.ui:
03768             if self.ui.continueMode:
03769                 FreeCADGui.Selection.clearSelection()
03770                 self.Activated()
03771 
03772     def scale(self,delta,copy=False):
03773         "moving the real shapes"
03774         if copy:
03775             self.commit(translate("draft","Copy"),
03776                         partial(Draft.scale,self.sel,delta,self.node[0],copy))
03777         else:
03778             self.commit(translate("draft","Scale"),
03779                         partial(Draft.scale,self.sel,delta,self.node[0],copy))
03780 
03781     def action(self,arg):
03782         "scene event handler"
03783         if arg["Type"] == "SoKeyboardEvent":
03784             if arg["Key"] == "ESCAPE":
03785                 self.finish()
03786         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
03787             point,ctrlPoint = getPoint(self,arg,sym=True)
03788             self.linetrack.p2(point)
03789             self.ui.cross(True)
03790             # Draw constraint tracker line.
03791             if hasMod(arg,MODCONSTRAIN):
03792                 self.constraintrack.p1(point)
03793                 self.constraintrack.p2(ctrlPoint)
03794                 self.constraintrack.on()
03795             else: self.constraintrack.off()
03796             if (len(self.node) > 0):
03797                 last = self.node[len(self.node)-1]
03798                 delta = point.sub(last)
03799                 self.ghost.trans.scaleFactor.setValue([delta.x,delta.y,delta.z])
03800                 corr = Vector(self.node[0].x,self.node[0].y,self.node[0].z)
03801                 corr.scale(delta.x,delta.y,delta.z)
03802                 corr = fcvec.neg(corr.sub(self.node[0]))
03803                 self.ghost.trans.translation.setValue([corr.x,corr.y,corr.z])
03804             if self.extendedCopy:
03805                 if not hasMod(arg,MODALT): self.finish()
03806         elif arg["Type"] == "SoMouseButtonEvent":
03807             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
03808                 point,ctrlPoint = getPoint(self,arg,sym=True)
03809                 if (self.node == []):
03810                     self.node.append(point)
03811                     self.ui.isRelative.show()
03812                     self.ui.isCopy.show()
03813                     self.linetrack.on()
03814                     self.ghost.on()
03815                     self.linetrack.p1(point)
03816                     msg(translate("draft", "Pick scale factor:\n"))
03817                 else:
03818                     last = self.node[0]
03819                     if self.ui.isCopy.isChecked() or hasMod(arg,MODALT):
03820                         self.scale(point.sub(last),True)
03821                     else:
03822                         self.scale(point.sub(last))
03823                     if hasMod(arg,MODALT):
03824                         self.extendedCopy = True
03825                     else:
03826                         self.finish(cont=True)
03827 
03828     def numericInput(self,numx,numy,numz):
03829         "this function gets called by the toolbar when valid x, y, and z have been entered there"
03830         point = Vector(numx,numy,numz)
03831         if not self.node:
03832             self.node.append(point)
03833             self.ui.isRelative.show()
03834             self.ui.isCopy.show()
03835             self.linetrack.p1(point)
03836             self.linetrack.on()
03837             self.ghost.on()
03838             msg(translate("draft", "Pick scale factor:\n"))
03839         else:
03840             last = self.node[-1]
03841             if self.ui.isCopy.isChecked():
03842                 self.scale(point.sub(last),True)
03843             else:
03844                 self.scale(point.sub(last))
03845             self.finish(cont=True)
03846 
03847             
03848 class ToggleConstructionMode():
03849     "The Draft_ToggleConstructionMode FreeCAD command definition"
03850 
03851     def GetResources(self):
03852         return {'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode", "Toggle construcion Mode"),
03853                 'Accel' : "C, M",
03854                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleConstructionMode", "Toggles the Construction Mode for next objects.")}
03855     
03856     def Activated(self):
03857         FreeCADGui.draftToolBar.constrButton.toggle()
03858 
03859         
03860 class ToggleContinueMode():
03861     "The Draft_ToggleContinueMode FreeCAD command definition"
03862 
03863     def GetResources(self):
03864         return {'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleContinueMode", "Toggle continue Mode"),
03865                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleContinueMode", "Toggles the Continue Mode for next commands.")}
03866 
03867     def Activated(self):
03868         FreeCADGui.draftToolBar.continueCmd.toggle()
03869 
03870         
03871 class Drawing(Modifier):
03872     "The Draft Drawing command definition"
03873     
03874     def GetResources(self):
03875         return {'Pixmap'  : 'Draft_Drawing',
03876                 'Accel' : "D, D",
03877                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Drawing", "Drawing"),
03878                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Drawing", "Puts the selected objects on a Drawing sheet.")}
03879 
03880     def IsActive(self):
03881         if Draft.getSelection():
03882             return True
03883         else:
03884             return False
03885 
03886     def Activated(self):
03887         Modifier.Activated(self,"Drawing")
03888         sel = Draft.getSelection()
03889         if not sel:
03890             self.page = self.createDefaultPage()
03891         else:
03892             self.page = None
03893             for obj in sel:
03894                 if obj.isDerivedFrom("Drawing::FeaturePage"):
03895                     self.page = obj
03896                     sel.pop(sel.index(obj))
03897             if not self.page:
03898                 for obj in self.doc.Objects:
03899                     if obj.isDerivedFrom("Drawing::FeaturePage"):
03900                         self.page = obj
03901             if not self.page:
03902                 self.page = self.createDefaultPage()
03903             sel.reverse() 
03904             for obj in sel:
03905                 self.insertPattern(obj)
03906                 if obj.ViewObject.isVisible():
03907                     name = 'View'+obj.Name
03908                     oldobj = self.page.getObject(name)
03909                     if oldobj: self.doc.removeObject(oldobj.Name)
03910                     Draft.makeDrawingView(obj,self.page)
03911             self.doc.recompute()
03912 
03913     def insertPattern(self,obj):
03914         "inserts a pattern object on the page"
03915         if 'FillStyle' in obj.ViewObject.PropertiesList:
03916             if obj.ViewObject.FillStyle != 'shape color':
03917                 hatch = obj.ViewObject.FillStyle
03918                 vobj = self.page.getObject('Pattern'+hatch)
03919                 if not vobj:
03920                     if hatch in FreeCAD.svgpatterns:
03921                         view = self.doc.addObject('Drawing::FeatureView','Pattern'+hatch)
03922                         svg = FreeCAD.svgpatterns[hatch]
03923                         view.ViewResult = svg
03924                         view.X = 0
03925                         view.Y = 0
03926                         view.Scale = 1
03927                         self.page.addObject(view)
03928 
03929     def createDefaultPage(self):
03930         "created a default page"
03931         template = Draft.getParam("template")
03932         if not template:
03933             template = FreeCAD.getResourceDir()+'Mod/Drawing/Templates/A3_Landscape.svg'
03934         page = self.doc.addObject('Drawing::FeaturePage','Page')
03935         page.ViewObject.HintOffsetX = 200
03936         page.ViewObject.HintOffsetY = 100
03937         page.ViewObject.HintScale = 20
03938         page.Template = template
03939         self.doc.recompute()
03940         return page
03941 
03942     
03943 class ToggleDisplayMode():
03944     "The ToggleDisplayMode FreeCAD command definition"
03945 
03946     def GetResources(self):
03947         return {'Pixmap'  : 'Draft_SwitchMode',
03948                 'Accel' : "Shift+Space",
03949                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode", "Toggle display mode"),
03950                 'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Draft_ToggleDisplayMode", "Swaps display mode of selected objects between wireframe and flatlines")}
03951 
03952     def IsActive(self):
03953         if Draft.getSelection():
03954             return True
03955         else:
03956             return False
03957         
03958     def Activated(self):
03959         for obj in Draft.getSelection():
03960             if obj.ViewObject.DisplayMode == "Flat Lines":
03961                 if "Wireframe" in obj.ViewObject.listDisplayModes():
03962                     obj.ViewObject.DisplayMode = "Wireframe"
03963             elif obj.ViewObject.DisplayMode == "Wireframe":
03964                 if "Flat Lines" in obj.ViewObject.listDisplayModes():
03965                     obj.ViewObject.DisplayMode = "Flat Lines"
03966 
03967 
03968 class Edit(Modifier):
03969     "The Draft_Edit FreeCAD command definition"
03970 
03971     def __init__(self):
03972         self.running = False
03973 
03974     def GetResources(self):
03975         return {'Pixmap'  : 'Draft_Edit',
03976                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edit"),
03977                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edits the active object")}
03978 
03979     def IsActive(self):
03980         if Draft.getSelection():
03981             self.selection = Draft.getSelection()
03982             if "Proxy" in self.selection[0].PropertiesList:
03983                 if hasattr(self.selection[0].Proxy,"Type"):
03984                     return True
03985         return False
03986 
03987     def Activated(self):
03988         if self.running:
03989             self.finish()
03990         else:
03991             Modifier.Activated(self,"Edit")
03992             self.ui.editUi()
03993             if self.doc:
03994                 self.obj = Draft.getSelection()
03995                 if self.obj:
03996                     self.obj = self.obj[0]
03997                     # store selectable state of the object
03998                     self.selectstate = self.obj.ViewObject.Selectable
03999                     self.obj.ViewObject.Selectable = False
04000                     if not Draft.getType(self.obj) in ["Wire","BSpline"]:
04001                         self.ui.setEditButtons(False)
04002                     else:
04003                         self.ui.setEditButtons(True)
04004                     self.editing = None
04005                     self.editpoints = []
04006                     self.pl = None
04007                     if "Placement" in self.obj.PropertiesList:
04008                         self.pl = self.obj.Placement
04009                         self.invpl = self.pl.inverse()
04010                     if Draft.getType(self.obj) in ["Wire","BSpline"]:
04011                         for p in self.obj.Points:
04012                             if self.pl: p = self.pl.multVec(p)
04013                             self.editpoints.append(p)
04014                     elif Draft.getType(self.obj) == "Circle":
04015                         self.editpoints.append(self.obj.Placement.Base)
04016                         if self.obj.FirstAngle == self.obj.LastAngle:
04017                             self.editpoints.append(self.obj.Shape.Vertexes[0].Point)
04018                     elif Draft.getType(self.obj) == "Rectangle":
04019                         self.editpoints.append(self.obj.Placement.Base)
04020                         self.editpoints.append(self.obj.Shape.Vertexes[2].Point)
04021                         v = self.obj.Shape.Vertexes
04022                         self.bx = v[1].Point.sub(v[0].Point)
04023                         if self.obj.Length < 0: self.bx = fcvec.neg(self.bx)
04024                         self.by = v[2].Point.sub(v[1].Point)
04025                         if self.obj.Height < 0: self.by = fcvec.neg(self.by)
04026                     elif Draft.getType(self.obj) == "Polygon":
04027                         self.editpoints.append(self.obj.Placement.Base)
04028                         self.editpoints.append(self.obj.Shape.Vertexes[0].Point)
04029                     elif Draft.getType(self.obj) == "Dimension":
04030                         p = self.obj.ViewObject.Proxy.textpos.translation.getValue()
04031                         self.editpoints.append(self.obj.Start)
04032                         self.editpoints.append(self.obj.End)
04033                         self.editpoints.append(self.obj.Dimline)
04034                         self.editpoints.append(Vector(p[0],p[1],p[2]))
04035                     self.trackers = []
04036                     self.constraintrack = None
04037                     if self.editpoints:
04038                         for ep in range(len(self.editpoints)):
04039                             self.trackers.append(editTracker(self.editpoints[ep],self.obj.Name,
04040                                                              ep,self.obj.ViewObject.LineColor))
04041                             self.constraintrack = lineTracker(dotted=True)
04042                             self.call = self.view.addEventCallback("SoEvent",self.action)
04043                             self.running = True
04044                             plane.save()
04045                             if "Shape" in self.obj.PropertiesList:
04046                                 plane.alignToFace(self.obj.Shape)
04047                             self.planetrack.set(self.editpoints[0])
04048                     else:
04049                         msg(translate("draft", "This object type is not editable\n"),'warning')
04050                         self.finish()
04051                 else:
04052                     self.finish()
04053 
04054     def finish(self,closed=False):
04055         "terminates the operation"
04056         if closed:
04057             if "Closed" in self.obj.PropertiesList:
04058                 if not self.obj.Closed:
04059                     self.obj.Closed = True
04060         if self.ui:
04061             if self.trackers:
04062                 for t in self.trackers:
04063                     t.finalize()
04064             if self.constraintrack:
04065                 self.constraintrack.finalize()
04066         self.obj.ViewObject.Selectable = self.selectstate
04067         Modifier.finish(self)
04068         plane.restore()
04069         self.running = False
04070 
04071     def action(self,arg):
04072         "scene event handler"
04073         if arg["Type"] == "SoKeyboardEvent":
04074             if arg["Key"] == "ESCAPE":
04075                 self.finish()
04076         elif arg["Type"] == "SoLocation2Event": #mouse movement detection
04077             if self.editing != None:
04078                 point,ctrlPoint = getPoint(self,arg)
04079                 # Draw constraint tracker line.
04080                 if hasMod(arg,MODCONSTRAIN):
04081                     self.constraintrack.p1(point)
04082                     self.constraintrack.p2(ctrlPoint)
04083                     self.constraintrack.on()
04084                 else:
04085                     self.constraintrack.off()
04086                 self.trackers[self.editing].set(point)
04087                 self.update(self.trackers[self.editing].get())
04088         elif arg["Type"] == "SoMouseButtonEvent":
04089             if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"):
04090                 if self.editing == None:
04091                     snapped = self.view.getObjectInfo((arg["Position"][0],arg["Position"][1]))
04092                     if snapped:
04093                         if snapped['Object'] == self.obj.Name:
04094                             if self.ui.addButton.isChecked():
04095                                 point,ctrlPoint = getPoint(self,arg)
04096                                 self.pos = arg["Position"]
04097                                 self.addPoint(point)
04098                             elif self.ui.delButton.isChecked():
04099                                 if 'EditNode' in snapped['Component']:
04100                                     self.delPoint(int(snapped['Component'][8:]))
04101                             elif 'EditNode' in snapped['Component']:
04102                                 self.ui.pointUi()
04103                                 self.ui.isRelative.show()
04104                                 self.editing = int(snapped['Component'][8:])
04105                                 self.trackers[self.editing].off()
04106                                 self.obj.ViewObject.Selectable = False
04107                                 if "Points" in self.obj.PropertiesList:
04108                                     self.node.append(self.obj.Points[self.editing])
04109                 else:
04110                     print "finishing edit"
04111                     self.trackers[self.editing].on()
04112                     self.obj.ViewObject.Selectable = True
04113                     self.numericInput(self.trackers[self.editing].get())
04114 
04115     def update(self,v):
04116         if Draft.getType(self.obj) in ["Wire","BSpline"]:
04117             pts = self.obj.Points
04118             editPnt = self.invpl.multVec(v)
04119             # DNC: allows to close the curve by placing ends close to each other
04120             tol = 0.001
04121             if ( ( self.editing == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or ( self.editing == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol):
04122                 self.obj.Closed = True
04123             # DNC: fix error message if edited point coinsides with one of the existing points
04124             if ( editPnt in pts ) == False:
04125                 pts[self.editing] = editPnt
04126                 self.obj.Points = pts
04127                 self.trackers[self.editing].set(v)
04128         elif Draft.getType(self.obj) == "Circle":
04129             delta = v.sub(self.obj.Placement.Base)
04130             if self.editing == 0:
04131                 p = self.obj.Placement
04132                 p.move(delta)
04133                 self.obj.Placement = p
04134                 self.trackers[0].set(self.obj.Placement.Base)
04135             elif self.editing == 1:
04136                 self.obj.Radius = delta.Length
04137             self.trackers[1].set(self.obj.Shape.Vertexes[0].Point)
04138         elif Draft.getType(self.obj) == "Rectangle":
04139             delta = v.sub(self.obj.Placement.Base)
04140             if self.editing == 0:
04141                 p = self.obj.Placement
04142                 p.move(delta)
04143                 self.obj.Placement = p
04144             elif self.editing == 1:
04145                 diag = v.sub(self.obj.Placement.Base)
04146                 nx = fcvec.project(diag,self.bx)
04147                 ny = fcvec.project(diag,self.by)
04148                 ax = nx.Length
04149                 ay = ny.Length
04150                 if ax and ay:
04151                     if abs(nx.getAngle(self.bx)) > 0.1:
04152                         ax = -ax
04153                     if abs(ny.getAngle(self.by)) > 0.1:
04154                         ay = -ay
04155                     self.obj.Length = ax
04156                     self.obj.Height = ay
04157             self.trackers[0].set(self.obj.Placement.Base)
04158             self.trackers[1].set(self.obj.Shape.Vertexes[2].Point)
04159         elif Draft.getType(self.obj) == "Polygon":
04160             delta = v.sub(self.obj.Placement.Base)
04161             if self.editing == 0:
04162                 p = self.obj.Placement
04163                 p.move(delta)
04164                 self.obj.Placement = p
04165                 self.trackers[0].set(self.obj.Placement.Base)
04166             elif self.editing == 1:
04167                 if self.obj.DrawMode == 'inscribed':
04168                     self.obj.Radius = delta.Length
04169                 else:
04170                     halfangle = ((math.pi*2)/self.obj.FacesNumber)/2
04171                     rad = math.cos(halfangle)*delta.Length
04172                     self.obj.Radius = rad
04173             self.trackers[1].set(self.obj.Shape.Vertexes[0].Point)
04174         elif Draft.getType(self.obj) == "Dimension":
04175             if self.editing == 0:
04176                 self.obj.Start = v
04177             elif self.editing == 1:
04178                 self.obj.End = v
04179             elif self.editing == 2:
04180                 self.obj.Dimline = v
04181             elif self.editing == 3:
04182                 self.obj.ViewObject.TextPosition = v        
04183 
04184     def numericInput(self,v,numy=None,numz=None):
04185         '''this function gets called by the toolbar
04186         when valid x, y, and z have been entered there'''
04187         if (numy != None):
04188             v = Vector(v,numy,numz)
04189         self.doc.openTransaction("Edit "+self.obj.Name)
04190         self.update(v)
04191         self.doc.commitTransaction()
04192         self.editing = None
04193         self.ui.editUi()
04194         self.node = []
04195        
04196     def addPoint(self,point):
04197         if not (Draft.getType(self.obj) in ["Wire","BSpline"]): return
04198         pts = self.obj.Points
04199         if ( Draft.getType(self.obj) == "Wire" ):
04200             if (self.obj.Closed == True):
04201                 # DNC: work around.... seems there is a
04202                 # bug in approximate method for closed wires...
04203                 edges = self.obj.Shape.Wires[0].Edges
04204                 e1 = edges[-1] # last edge
04205                 v1 = e1.Vertexes[0].Point
04206                 v2 = e1.Vertexes[1].Point
04207                 v2.multiply(0.9999)
04208                 edges[-1] = Part.makeLine(v1,v2)
04209                 edges.reverse()
04210                 wire = Part.Wire(edges)
04211                 curve = wire.approximate(0.0001,0.0001,100,25)
04212             else:
04213                 # DNC: this version is much more reliable near sharp edges!
04214                 curve = self.obj.Shape.Wires[0].approximate(0.0001,0.0001,100,25)
04215         elif ( Draft.getType(self.obj) == "BSpline" ):
04216             if (self.obj.Closed == True):
04217                 curve = self.obj.Shape.Edges[0].Curve
04218             else:
04219                 curve = self.obj.Shape.Curve
04220         uNewPoint = curve.parameter(point)
04221         uPoints = []
04222         for p in self.obj.Points:
04223             uPoints.append(curve.parameter(p))
04224         for i in range(len(uPoints)-1):
04225             if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ):
04226                 pts.insert(i+1, self.invpl.multVec(point))
04227                 break
04228         # DNC: fix: add points to last segment if curve is closed 
04229         if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) :
04230             pts.append(self.invpl.multVec(point))
04231         self.doc.openTransaction("Edit "+self.obj.Name)
04232         self.obj.Points = pts
04233         self.doc.commitTransaction()
04234         self.resetTrackers()
04235         
04236     def delPoint(self,point):
04237         if not (Draft.getType(self.obj) in ["Wire","BSpline"]): return
04238         if len(self.obj.Points) <= 2:
04239             msg(translate("draft", "Active object must have more than two points/nodes\n"),'warning')
04240         else: 
04241             pts = self.obj.Points
04242             pts.pop(point)
04243             self.doc.openTransaction("Edit "+self.obj.Name)
04244             self.obj.Points = pts
04245             self.doc.commitTransaction()
04246             self.resetTrackers()
04247 
04248     def resetTrackers(self):
04249         for t in self.trackers:
04250             t.finalize()
04251         self.trackers = []
04252         for ep in range(len(self.obj.Points)):
04253             objPoints = self.obj.Points[ep]
04254             if self.pl: objPoints = self.pl.multVec(objPoints)
04255             self.trackers.append(editTracker(objPoints,self.obj.Name,ep,self.obj.ViewObject.LineColor))
04256 
04257             
04258 class AddToGroup():
04259     "The AddToGroup FreeCAD command definition"
04260 
04261     def GetResources(self):
04262         return {'Pixmap'  : 'Draft_AddToGroup',
04263                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_AddToGroup", "Add to group..."),
04264                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_AddToGroup", "Adds the selected object(s) to an existing group")}
04265 
04266     def IsActive(self):
04267         if Draft.getSelection():
04268             return True
04269         else:
04270             return False
04271         
04272     def Activated(self):
04273         self.groups = ["Ungroup"]
04274         self.groups.extend(Draft.getGroupNames())
04275         self.labels = ["Ungroup"]
04276         for g in self.groups:
04277             o = FreeCAD.ActiveDocument.getObject(g)
04278             if o: self.labels.append(o.Label)
04279         self.ui = FreeCADGui.draftToolBar
04280         self.ui.sourceCmd = self
04281         self.ui.popupMenu(self.labels)
04282 
04283     def proceed(self,labelname):
04284         self.ui.sourceCmd = None
04285         if labelname == "Ungroup":
04286             for obj in Draft.getSelection():
04287                 try:
04288                     Draft.ungroup(obj)
04289                 except:
04290                     pass
04291         else:
04292             if labelname in self.labels:
04293                 i = self.labels.index(labelname)
04294                 g = FreeCAD.ActiveDocument.getObject(self.groups[i])
04295                 for obj in Draft.getSelection():
04296                     try:
04297                         g.addObject(obj)
04298                     except:
04299                         pass
04300 
04301                     
04302 class AddPoint(Modifier):
04303     "The Draft_AddPoint FreeCAD command definition"
04304 
04305     def __init__(self):
04306         self.running = False
04307 
04308     def GetResources(self):
04309         return {'Pixmap'  : 'Draft_AddPoint',
04310                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_AddPoint", "Add Point"),
04311                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_AddPoint", "Adds a point to an existing wire/bspline")}
04312 
04313     def IsActive(self):
04314         self.selection = Draft.getSelection()
04315         if (Draft.getType(self.selection[0]) in ['Wire','BSpline']):
04316             return True
04317         else:
04318             return False
04319 
04320     def Activated(self):
04321         FreeCADGui.draftToolBar.vertUi(True)
04322         FreeCADGui.runCommand("Draft_Edit")
04323 
04324         
04325 class DelPoint(Modifier):
04326     "The Draft_DelPoint FreeCAD command definition"
04327 
04328     def __init__(self):
04329         self.running = False
04330         
04331     def GetResources(self):
04332         return {'Pixmap'  : 'Draft_DelPoint',
04333                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_DelPoint", "Remove Point"),
04334                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_DelPoint", "Removes a point from an existing wire or bspline")}
04335 
04336     def IsActive(self):
04337         self.selection = Draft.getSelection()
04338         if (Draft.getType(self.selection[0]) in ['Wire','BSpline']):
04339             return True
04340         else:
04341             return False
04342 
04343     def Activated(self):
04344         FreeCADGui.draftToolBar.vertUi(False)
04345         FreeCADGui.runCommand("Draft_Edit")
04346 
04347         
04348 class WireToBSpline(Modifier):
04349     "The Draft_Wire2BSpline FreeCAD command definition"
04350 
04351     def __init__(self):
04352         self.running = False
04353         
04354     def GetResources(self):
04355         return {'Pixmap'  : 'Draft_WireToBSpline',
04356                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_WireToBSpline", "Wire to BSpline"),
04357                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_WireToBSpline", "Converts between Wire and BSpline")}
04358 
04359     def IsActive(self):
04360         self.selection = Draft.getSelection()
04361         if (Draft.getType(self.selection[0]) in ['Wire','BSpline']):
04362             return True
04363         else:
04364             return False
04365 
04366     def Activated(self):
04367         if self.running:
04368             self.finish()
04369         else:
04370             Modifier.Activated(self,"Convert Curve Type")
04371             if self.doc:
04372                 self.obj = Draft.getSelection()
04373                 if self.obj:
04374                     self.obj = self.obj[0]
04375                     self.pl = None
04376                     if "Placement" in self.obj.PropertiesList:
04377                         self.pl = self.obj.Placement
04378                     self.Points = self.obj.Points
04379                     self.closed = self.obj.Closed
04380                     n = None
04381                     if (Draft.getType(self.selection[0]) == 'Wire'):
04382                         n = Draft.makeBSpline(self.Points, self.closed, self.pl)
04383                     elif (Draft.getType(self.selection[0]) == 'BSpline'):
04384                         n = Draft.makeWire(self.Points, self.closed, self.pl)
04385                     if n:
04386                         Draft.formatObject(n,self.selection[0])
04387                 else:
04388                     self.finish()
04389         def finish(self):
04390                 Modifier.finish(self)
04391 
04392                 
04393 class SelectGroup():
04394     "The SelectGroup FreeCAD command definition"
04395 
04396     def GetResources(self):
04397         return {'Pixmap'  : 'Draft_SelectGroup',
04398                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_SelectGroup", "Select group"),
04399                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_SelectGroup", "Selects all objects with the same parents as this group")}
04400 
04401     def IsActive(self):
04402         if Draft.getSelection():
04403             return True
04404         else:
04405             return False
04406         
04407     def Activated(self):
04408         sellist = []
04409         for ob in Draft.getSelection():
04410             for child in ob.OutList:
04411                 FreeCADGui.Selection.addSelection(child)
04412                 for parent in ob.InList:
04413                     FreeCADGui.Selection.addSelection(parent)
04414                     for child in parent.OutList:
04415                         FreeCADGui.Selection.addSelection(child)
04416 
04417                         
04418 class Shape2DView():
04419     "The Shape2DView FreeCAD command definition"
04420     def GetResources(self):
04421         return {'Pixmap'  : 'Draft_2DShapeView',
04422                 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Shape2DView", "Shape 2D view"),
04423                 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Shape2DView", "Creates Shape 2D views of selected objects")}
04424 
04425     def IsActive(self):
04426         if Draft.getSelection():
04427             return True
04428         else:
04429             return False
04430         
04431     def Activated(self):
04432         sellist = []
04433         for ob in Draft.getSelection():
04434             Draft.makeShape2DView(ob)
04435         
04436 #---------------------------------------------------------------------------
04437 # Adds the icons & commands to the FreeCAD command manager, and sets defaults
04438 #---------------------------------------------------------------------------
04439                 
04440 # drawing commands
04441 FreeCADGui.addCommand('Draft_SelectPlane',SelectPlane())
04442 FreeCADGui.addCommand('Draft_Line',Line())
04443 FreeCADGui.addCommand('Draft_Wire',Wire())
04444 FreeCADGui.addCommand('Draft_Circle',Circle())
04445 FreeCADGui.addCommand('Draft_Arc',Arc())
04446 FreeCADGui.addCommand('Draft_Text',Text())
04447 FreeCADGui.addCommand('Draft_Rectangle',Rectangle())
04448 FreeCADGui.addCommand('Draft_Dimension',Dimension())
04449 FreeCADGui.addCommand('Draft_Polygon',Polygon())
04450 FreeCADGui.addCommand('Draft_BSpline',BSpline())
04451 
04452 # modification commands
04453 FreeCADGui.addCommand('Draft_Move',Move())
04454 FreeCADGui.addCommand('Draft_Rotate',Rotate())
04455 FreeCADGui.addCommand('Draft_Offset',Offset())
04456 FreeCADGui.addCommand('Draft_Upgrade',Upgrade())
04457 FreeCADGui.addCommand('Draft_Downgrade',Downgrade())
04458 FreeCADGui.addCommand('Draft_Trimex',Trimex())
04459 FreeCADGui.addCommand('Draft_Scale',Scale())
04460 FreeCADGui.addCommand('Draft_Drawing',Drawing())
04461 FreeCADGui.addCommand('Draft_Edit',Edit())
04462 FreeCADGui.addCommand('Draft_AddPoint',AddPoint())
04463 FreeCADGui.addCommand('Draft_DelPoint',DelPoint())
04464 FreeCADGui.addCommand('Draft_WireToBSpline',WireToBSpline())
04465 
04466 # context commands
04467 FreeCADGui.addCommand('Draft_FinishLine',FinishLine())
04468 FreeCADGui.addCommand('Draft_CloseLine',CloseLine())
04469 FreeCADGui.addCommand('Draft_UndoLine',UndoLine())
04470 FreeCADGui.addCommand('Draft_ToggleConstructionMode',ToggleConstructionMode())
04471 FreeCADGui.addCommand('Draft_ToggleContinueMode',ToggleContinueMode())
04472 FreeCADGui.addCommand('Draft_ApplyStyle',ApplyStyle())
04473 FreeCADGui.addCommand('Draft_ToggleDisplayMode',ToggleDisplayMode())
04474 FreeCADGui.addCommand('Draft_AddToGroup',AddToGroup())
04475 FreeCADGui.addCommand('Draft_SelectGroup',SelectGroup())
04476 FreeCADGui.addCommand('Draft_Shape2DView',Shape2DView())
04477 
04478 # a global place to look for active draft Command
04479 FreeCAD.activeDraftCommand = None
04480 
04481 

Generated on Wed Nov 23 19:00:09 2011 for FreeCAD by  doxygen 1.6.1