Files
create/src/Mod/Draft/DraftSnap.py
2011-12-17 14:47:11 +00:00

814 lines
35 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2011 *
#* Yorik van Havre <yorik@uncreated.net> *
#* *
#* 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 Snap tools"
__author__ = "Yorik van Havre"
__url__ = "http://free-cad.sourceforge.net"
import FreeCAD, FreeCADGui, math, Draft, DraftGui, DraftTrackers, Part, SketcherGui
from DraftGui import todo
from draftlibs import fcvec,fcgeo
from FreeCAD import Vector
from pivy import coin
from PyQt4 import QtCore,QtGui
class Snapper:
"""The Snapper objects contains all the functionality used by draft
and arch module to manage object snapping. It is responsible for
finding snap points and displaying snap markers. Usually You
only need to invoke it's snap() function, all the rest is taken
care of."""
def __init__(self):
self.lastObj = [None,None]
self.views = []
self.maxEdges = 0
self.radius = 0
self.constraintAxis = None
self.basepoint = None
self.affinity = None
self.cursorMode = None
if Draft.getParam("maxSnap"):
self.maxEdges = Draft.getParam("maxSnapEdges")
# we still have no 3D view when the draft module initializes
self.tracker = None
self.extLine = None
self.grid = None
self.constrainLine = None
# the snapmarker has "dot","circle" and "square" available styles
self.mk = {'passive':'circle',
'extension':'circle',
'parallel':'circle',
'grid':'circle',
'endpoint':'dot',
'midpoint':'dot',
'perpendicular':'dot',
'angle':'dot',
'center':'dot',
'ortho':'dot',
'intersection':'dot'}
self.cursors = {'passive':None,
'extension':':/icons/Constraint_Parallel.svg',
'parallel':':/icons/Constraint_Parallel.svg',
'grid':':/icons/Constraint_PointOnPoint.svg',
'endpoint':':/icons/Constraint_PointOnEnd.svg',
'midpoint':':/icons/Constraint_PointOnObject.svg',
'perpendicular':':/icons/Constraint_PointToObject.svg',
'angle':':/icons/Constraint_ExternalAngle.svg',
'center':':/icons/Constraint_Concentric.svg',
'ortho':':/icons/Constraint_Perpendicular.svg',
'intersection':':/icons/Constraint_Tangent.svg'}
def snap(self,screenpos,lastpoint=None,active=True,constrain=None):
"""snap(screenpos,lastpoint=None,active=True,constrain=None): returns a snapped
point from the given (x,y) screenpos (the position of the mouse cursor), active is to
activate active point snapping or not (passive), lastpoint is an optional
other point used to draw an imaginary segment and get additional snap locations. Constrain can
be set to 0 (horizontal) or 1 (vertical), for more additional snap locations.
Screenpos can be a list, a tuple or a coin.SbVec2s object."""
# type conversion if needed
if isinstance(screenpos,list):
screenpos = tuple(screenpos)
elif isinstance(screenpos,coin.SbVec2s):
screenpos = tuple(screenpos.getValue())
elif not isinstance(screenpos,tuple):
print "snap needs valid screen position (list, tuple or sbvec2s)"
return None
# setup trackers if needed
if not self.tracker:
self.tracker = DraftTrackers.snapTracker()
if not self.extLine:
self.extLine = DraftTrackers.lineTracker(dotted=True)
if (not self.grid) and Draft.getParam("grid"):
self.grid = DraftTrackers.gridTracker()
# getting current snap Radius
if not self.radius:
self.radius = self.getScreenDist(Draft.getParam("snapRange"),screenpos)
# set the grid
if self.grid and Draft.getParam("grid"):
self.grid.set()
# checking if alwaySnap setting is on
oldActive = False
if Draft.getParam("alwaysSnap"):
oldActive = active
active = True
self.setCursor('passive')
if self.tracker:
self.tracker.off()
if self.extLine:
self.extLine.off()
point = FreeCADGui.ActiveDocument.ActiveView.getPoint(screenpos[0],screenpos[1])
# check if we snapped to something
info = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo((screenpos[0],screenpos[1]))
# checking if parallel to one of the edges of the last objects
point = self.snapToExtensions(point,lastpoint)
if not info:
# nothing has been snapped, check fro grid snap
point = self.snapToGrid(point)
return point
else:
# we have an object to snap to
snaps = []
obj = FreeCAD.ActiveDocument.getObject(info['Object'])
if not obj:
return point
if hasattr(obj.ViewObject,"Selectable"):
if not obj.ViewObject.Selectable:
return point
if not active:
# passive snapping
snaps = [self.snapToVertex(info)]
else:
# active snapping
comp = info['Component']
if obj.isDerivedFrom("Part::Feature"):
if (not self.maxEdges) or (len(obj.Edges) <= self.maxEdges):
if "Edge" in comp:
# we are snapping to an edge
edge = obj.Shape.Edges[int(comp[4:])-1]
snaps.extend(self.snapToEndpoints(edge))
snaps.extend(self.snapToMidpoint(edge))
snaps.extend(self.snapToPerpendicular(edge,lastpoint))
snaps.extend(self.snapToOrtho(edge,lastpoint,constrain))
snaps.extend(self.snapToIntersection(edge))
if isinstance (edge.Curve,Part.Circle):
# the edge is an arc, we have extra options
snaps.extend(self.snapToAngles(edge))
snaps.extend(self.snapToCenter(edge))
elif "Vertex" in comp:
# directly snapped to a vertex
snaps.append(self.snapToVertex(info,active=True))
elif comp == '':
# workaround for the new view provider
snaps.append(self.snapToVertex(info,active=True))
else:
# all other cases (face, etc...) default to passive snap
snapArray = [self.snapToVertex(info)]
elif Draft.getType(obj) == "Dimension":
# for dimensions we snap to their 3 points
for pt in [obj.Start,obj.End,obj.Dimline]:
snaps.append([pt,'endpoint',pt])
elif Draft.getType(obj) == "Mesh":
# for meshes we only snap to vertices
snaps.extend(self.snapToEndpoints(obj.Mesh))
# updating last objects list
if not self.lastObj[1]:
self.lastObj[1] = obj.Name
elif self.lastObj[1] != obj.Name:
self.lastObj[0] = self.lastObj[1]
self.lastObj[1] = obj.Name
# calculating the nearest snap point
shortest = 1000000000000000000
origin = Vector(info['x'],info['y'],info['z'])
winner = [Vector(0,0,0),None,Vector(0,0,0)]
for snap in snaps:
# if snap[0] == None: print "debug: Snapper: 'i[0]' is 'None'"
delta = snap[0].sub(origin)
if delta.Length < shortest:
shortest = delta.Length
winner = snap
# see if we are out of the max radius, if any
if self.radius:
dv = point.sub(winner[2])
if (dv.Length > self.radius):
if not oldActive:
winner = self.snapToVertex(info)
# setting the cursors
if self.tracker:
self.tracker.setCoords(winner[2])
self.tracker.setMarker(self.mk[winner[1]])
self.tracker.on()
self.setCursor(winner[1])
# return the final point
return winner[2]
def snapToExtensions(self,point,last):
"returns a point snapped to extension or parallel line to last object, if any"
for o in [self.lastObj[1],self.lastObj[0]]:
if o:
ob = FreeCAD.ActiveDocument.getObject(o)
if ob:
if ob.isDerivedFrom("Part::Feature"):
edges = ob.Shape.Edges
if (not self.maxEdges) or (len(edges) <= self.maxEdges):
for e in edges:
if isinstance(e.Curve,Part.Line):
np = self.getPerpendicular(e,point)
if not fcgeo.isPtOnEdge(np,e):
if (np.sub(point)).Length < self.radius:
if self.tracker:
self.tracker.setCoords(np)
self.tracker.setMarker(self.mk['extension'])
self.tracker.on()
if self.extLine:
self.extLine.p1(e.Vertexes[0].Point)
self.extLine.p2(np)
self.extLine.on()
self.setCursor('extension')
return np
else:
if last:
de = Part.Line(last,last.add(fcgeo.vec(e))).toShape()
np = self.getPerpendicular(de,point)
if (np.sub(point)).Length < self.radius:
if self.tracker:
self.tracker.setCoords(np)
self.tracker.setMarker(self.mk['parallel'])
self.tracker.on()
self.setCursor('extension')
return np
return point
def snapToGrid(self,point):
"returns a grid snap point if available"
if self.grid:
np = self.grid.getClosestNode(point)
if np:
if self.radius != 0:
dv = point.sub(np)
if dv.Length <= self.radius:
if self.tracker:
self.tracker.setCoords(np)
self.tracker.setMarker(self.mk['grid'])
self.tracker.on()
self.setCursor('grid')
return np
return point
def snapToEndpoints(self,shape):
"returns a list of enpoints snap locations"
snaps = []
if hasattr(shape,"Vertexes"):
for v in shape.Vertexes:
snaps.append([v.Point,'endpoint',v.Point])
elif hasattr(shape,"Point"):
snaps.append([shape.Point,'endpoint',shape.Point])
elif hasattr(shape,"Points"):
for v in shape.Points:
snaps.append([v.Vector,'endpoint',v.Vector])
return snaps
def snapToMidpoint(self,shape):
"returns a list of midpoints snap locations"
snaps = []
if isinstance(shape,Part.Edge):
mp = fcgeo.findMidpoint(shape)
if mp:
snaps.append([mp,'midpoint',mp])
return snaps
def snapToPerpendicular(self,shape,last):
"returns a list of perpendicular snap locations"
snaps = []
if last:
if isinstance(shape,Part.Edge):
if isinstance(shape.Curve,Part.Line):
np = self.getPerpendicular(shape,last)
elif isinstance(shape.Curve,Part.Circle):
dv = last.sub(shape.Curve.Center)
dv = fcvec.scaleTo(dv,shape.Curve.Radius)
np = (shape.Curve.Center).add(dv)
snaps.append([np,'perpendicular',np])
return snaps
def snapToOrtho(self,shape,last,constrain):
"returns a list of ortho snap locations"
snaps = []
if constrain != None:
if isinstance(shape,Part.Edge):
if last:
if isinstance(shape.Curve,Part.Line):
p1 = shape.Vertexes[0].Point
p2 = shape.Vertexes[-1].Point
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)))
snaps.append([cp,'ortho',cp])
elif (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)))
snaps.append([cp,'ortho',cp])
return snaps
def snapToAngles(self,shape):
"returns a list of angle snap locations"
snaps = []
rad = shape.Curve.Radius
pos = shape.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)
snaps.append([cur,'angle',cur])
return snaps
def snapToCenter(self,shape):
"returns a list of center snap locations"
snaps = []
rad = shape.Curve.Radius
pos = shape.Curve.Center
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)
snaps.append([cur,'center',pos])
return snaps
def snapToIntersection(self,shape):
"returns a list of intersection snap locations"
snaps = []
# get the stored objects to calculate intersections
if self.lastObj[0]:
obj = FreeCAD.ActiveDocument.getObject(self.lastObj[0])
if obj:
if obj.isDerivedFrom("Part::Feature"):
if (not self.maxEdges) or (len(obj.Shape.Edges) <= self.maxEdges):
for e in obj.Shape.Edges:
# get the intersection points
pt = fcgeo.findIntersection(e,shape)
if pt:
for p in pt:
snaps.append([p,'intersection',p])
return snaps
def snapToVertex(self,info,active=False):
p = Vector(info['x'],info['y'],info['z'])
if active:
return [p,'endpoint',p]
else:
return [p,'passive',p]
def getScreenDist(self,dist,cursor):
"returns a distance in 3D space 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 getPerpendicular(self,edge,pt):
"returns a point on an edge, perpendicular to the given point"
dv = pt.sub(edge.Vertexes[0].Point)
nv = fcvec.project(dv,fcgeo.vec(edge))
np = (edge.Vertexes[0].Point).add(nv)
return np
def setCursor(self,mode=None):
"setCursor(self,mode=None): sets or resets the cursor to the given mode or resets"
if not mode:
for v in self.views:
v.unsetCursor()
self.views = []
else:
if mode != self.cursorMode:
if not self.views:
mw = DraftGui.getMainWindow()
self.views = mw.findChildren(QtGui.QWidget,"QtGLArea")
baseicon = QtGui.QPixmap(":/icons/Draft_Cursor.svg")
newicon = QtGui.QPixmap(32,24)
newicon.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter()
qp.begin(newicon)
qp.drawPixmap(0,0,baseicon)
if not (mode == 'passive'):
tp = QtGui.QPixmap(self.cursors[mode]).scaledToWidth(16)
qp.drawPixmap(QtCore.QPoint(16, 8), tp);
qp.end()
cur = QtGui.QCursor(newicon,8,8)
for v in self.views:
v.setCursor(cur)
self.cursorMode = mode
def off(self):
"finishes snapping"
if self.tracker:
self.tracker.off()
if self.extLine:
self.extLine.off()
if self.grid:
self.grid.off()
if self.constrainLine:
self.constrainLine.off()
self.radius = 0
self.setCursor()
self.cursorMode = None
def constrain(self,point,basepoint=None,axis=None):
'''constrain(point,basepoint=None,axis=None: Returns a
constrained point. Axis can be "x","y" or "z" or a custom vector. If None,
the closest working plane axis will be picked.
Basepoint is the base point used to figure out from where the point
must be constrained. If no basepoint is given, the current point is
used as basepoint.'''
point = Vector(point)
# setting basepoint
if not basepoint:
if not self.basepoint:
self.basepoint = point
else:
self.basepoint = basepoint
delta = point.sub(basepoint)
# setting constraint axis
self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta)
if isinstance(axis,FreeCAD.Vector):
self.constraintAxis = axis
elif axis == "x":
self.constraintAxis = FreeCAD.DraftWorkingPlane.u
elif axis == "y":
self.constraintAxis = FreeCAD.DraftWorkingPlane.v
elif axis == "z":
self.constraintAxis = FreeCAD.DraftWorkingPlane.axis
else:
if self.affinity == "x":
self.constraintAxis = FreeCAD.DraftWorkingPlane.u
elif self.affinity == "y":
self.constraintAxis = FreeCAD.DraftWorkingPlane.v
else:
self.constraintAxis = FreeCAD.DraftWorkingPlane.axis
# calculating constrained point
cdelta = fcvec.project(delta,self.constraintAxis)
npoint = self.basepoint.add(cdelta)
# setting constrain line
if point != npoint:
self.constrainLine.p1(point)
self.constrainLine.p2(npoint)
self.constrainLine.on()
else:
self.constrainLine.off()
return npoint
def unconstrain(self):
self.basepoint = None
self.affinity = None
if self.constrainLine:
self.constrainLine.off()
# deprecated ##################################################################
# last snapped objects, for quick intersection calculation
lastObj = [0,0]
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:
if ob.isDerivedFrom("Part::Feature"):
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 = FreeCAD.DraftWorkingPlane.getClosestAxis(dvec)
if ((target.constrain == None) or mobile):
if affinity == "x":
dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.u)
point = last.add(dv)
if sym:
l = dv.Length
if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1:
l = -l
point = last.add(FreeCAD.DraftWorkingPlane.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,FreeCAD.DraftWorkingPlane.v)
point = last.add(dv)
if sym:
l = dv.Length
if dv.getAngle(FreeCAD.DraftWorkingPlane.v) > 1:
l = -l
point = last.add(FreeCAD.DraftWorkingPlane.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,FreeCAD.DraftWorkingPlane.axis)
point = last.add(dv)
if sym:
l = dv.Length
if dv.getAngle(FreeCAD.DraftWorkingPlane.axis) > 1:
l = -l
point = last.add(FreeCAD.DraftWorkingPlane.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,FreeCAD.DraftWorkingPlane.u)
point = last.add(dv)
if sym:
l = dv.Length
if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1:
l = -l
point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l)))
elif (target.constrain == 1):
dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.v)
point = last.add(dv)
if sym:
l = dv.Length
if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1:
l = -l
point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l)))
elif (target.constrain == 2):
dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.axis)
point = last.add(dv)
if sym:
l = dv.Length
if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1:
l = -l
point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l)))
return point
if not hasattr(FreeCADGui,"Snapper"):
FreeCADGui.Snapper = Snapper()