FreeCAD/src/Mod/Draft/draftTools.py
2011-10-24 17:06:03 +00:00

4480 lines
185 KiB
Python
Executable File

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