Smooth path with fillets.

This commit is contained in:
ml 2016-10-16 23:46:00 -07:00 committed by Markus Lampert
parent 0ba1030163
commit 23713a2c51
3 changed files with 244 additions and 181 deletions

View File

@ -1,7 +1,7 @@
#***************************************************************************
#* *
#* Copyright (c) 2009, 2010 *
#* Yorik van Havre <yorik@uncreated.net>, Ken Cline <cline@frii.com> *
#* 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 Lesser General Public License (LGPL) *
@ -133,7 +133,7 @@ def isAligned(edge,axis="x"):
if edge.StartPoint.z == edge.EndPoint.z:
return True
return False
def getQuad(face):
"""getQuad(face): returns a list of 3 vectors (basepoint, Xdir, Ydir) if the face
is a quad, or None if not."""
@ -162,7 +162,7 @@ def areColinear(e1,e2):
return False
v1 = vec(e1)
v2 = vec(e2)
a = round(v1.getAngle(v2),precision())
a = round(v1.getAngle(v2),precision())
if (a == 0) or (a == round(math.pi,precision())):
v3 = e2.Vertexes[0].Point.sub(e1.Vertexes[0].Point)
if DraftVecUtils.isNull(v3):
@ -225,7 +225,7 @@ def findEdge(anEdge,aList):
if DraftVecUtils.equals(anEdge.Vertexes[-1].Point,aList[e].Vertexes[-1].Point):
return(e)
return None
def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=False,dts=True) :
'''findIntersection(edge1,edge2,infinite1=False,infinite2=False,dts=True):
@ -268,13 +268,13 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
(pt3.x-pt1.x)*(vec2.y-vec2.z))/(norm3.x+norm3.y+norm3.z)
vec1.scale(k,k,k)
intp = pt1.add(vec1)
if infinite1 == False and not isPtOnEdge(intp,edge1) :
return []
if infinite2 == False and not isPtOnEdge(intp,edge2) :
return []
return [intp]
else :
return [] # Lines have same direction
@ -303,26 +303,26 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
infinite2 = ex2
return getLineIntersections(pt1,pt2,pt3,pt4,infinite1,infinite2)
elif (geomType(edge1) == "Line") and (geomType(edge2) == "Line") :
# we have 2 straight lines
elif (geomType(edge1) == "Line") and (geomType(edge2) == "Line") :
# we have 2 straight lines
pt1, pt2, pt3, pt4 = [edge1.Vertexes[0].Point,
edge1.Vertexes[1].Point,
edge2.Vertexes[0].Point,
edge2.Vertexes[1].Point]
return getLineIntersections(pt1,pt2,pt3,pt4,infinite1,infinite2)
elif (geomType(edge1) == "Circle") and (geomType(edge2) == "Line") \
or (geomType(edge1) == "Line") and (geomType(edge2) == "Circle") :
# deals with an arc or circle and a line
edges = [edge1,edge2]
for edge in edges :
if geomType(edge) == "Line":
line = edge
else :
arc = edge
dirVec = vec(line) ; dirVec.normalize()
pt1 = line.Vertexes[0].Point
pt2 = line.Vertexes[1].Point
@ -335,15 +335,15 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
return [pt1]
elif (pt2 in [pt3,pt4]):
return [pt2]
if DraftVecUtils.isNull(pt1.sub(center).cross(pt2.sub(center)).cross(arc.Curve.Axis)) :
# Line and Arc are on same plane
dOnLine = center.sub(pt1).dot(dirVec)
onLine = Vector(dirVec)
onLine.scale(dOnLine,dOnLine,dOnLine)
toLine = pt1.sub(center).add(onLine)
if toLine.Length < arc.Curve.Radius :
dOnLine = (arc.Curve.Radius**2 - toLine.Length**2)**(0.5)
onLine = Vector(dirVec)
@ -356,8 +356,8 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
int = [center.add(toLine)]
else :
return []
else :
else :
# Line isn't on Arc's plane
if dirVec.dot(arc.Curve.Axis) != 0 :
toPlane = Vector(arc.Curve.Axis) ; toPlane.normalize()
@ -374,7 +374,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
return []
else :
return []
if infinite1 == False :
for i in range(len(int)-1,-1,-1) :
if not isPtOnEdge(int[i],edge1) :
@ -384,16 +384,16 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
if not isPtOnEdge(int[i],edge2) :
del int[i]
return int
elif (geomType(edge1) == "Circle") and (geomType(edge2) == "Circle") :
# deals with 2 arcs or circles
cent1, cent2 = edge1.Curve.Center, edge2.Curve.Center
rad1 , rad2 = edge1.Curve.Radius, edge2.Curve.Radius
axis1, axis2 = edge1.Curve.Axis , edge2.Curve.Axis
c2c = cent2.sub(cent1)
if DraftVecUtils.isNull(axis1.cross(axis2)) :
if round(c2c.dot(axis1),precision()) == 0 :
# circles are on same plane
@ -432,7 +432,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
for pt in intTemp :
if round(pt.sub(cent2).Length-rad2,precision()) == 0 :
int += [pt]
if infinite1 == False :
for i in range(len(int)-1,-1,-1) :
if not isPtOnEdge(int[i],edge1) :
@ -441,12 +441,12 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F
for i in range(len(int)-1,-1,-1) :
if not isPtOnEdge(int[i],edge2) :
del int[i]
return int
else:
# print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")")
return []
def wiresIntersect(wire1,wire2):
"wiresIntersect(wire1,wire2): returns True if some of the edges of the wires are intersecting otherwise False"
for e1 in wire1.Edges:
@ -454,7 +454,7 @@ def wiresIntersect(wire1,wire2):
if findIntersection(e1,e2,dts=False):
return True
return False
def pocket2d(shape,offset):
"""pocket2d(shape,offset): return a list of wires obtained from offsetting the wires from the given shape
by the given offset, and intersection if needed."""
@ -551,7 +551,7 @@ def mirror (point, edge):
return refl
else:
return None
def isClockwise(edge,ref=None):
"""Returns True if a circle-based edge has a clockwise direction"""
if not geomType(edge) == "Circle":
@ -572,7 +572,7 @@ def isClockwise(edge,ref=None):
if n.z < 0:
return False
return True
def isSameLine(e1,e2):
"""isSameLine(e1,e2): return True if the 2 edges are lines and have the same
points"""
@ -587,7 +587,7 @@ def isSameLine(e1,e2):
(DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[-1].Point)):
return True
return False
def isWideAngle(edge):
"""returns True if the given edge is an arc with angle > 180 degrees"""
if geomType(edge) != "Circle":
@ -657,9 +657,9 @@ def isLine(bsp):
def sortEdges(edges):
"Deprecated. Use Part.__sortEdges__ instead"
raise DeprecationWarning("Deprecated. Use Part.__sortEdges__ instead")
# Build a dictionary of edges according to their end points.
# Each entry is a set of edges that starts, or ends, at the
# given vertex hash.
@ -685,7 +685,7 @@ def sortEdges(edges):
if v not in edict and len(se) == 1:
startedge = se
break
# The above may not find a start vertex; if the start edge is reversed,
# The above may not find a start vertex; if the start edge is reversed,
# the start vertex will appear in edict (and not sdict).
if not startedge:
for v, se in edict.items():
@ -735,9 +735,9 @@ def sortEdges(edges):
def sortEdgesOld(lEdges, aVertex=None):
"Deprecated. Use Part.__sortEdges__ instead"
raise DeprecationWarning("Deprecated. Use Part.__sortEdges__ instead")
#There is no reason to limit this to lines only because every non-closed edge always
#has exactly two vertices (wmayer)
#for e in lEdges:
@ -781,7 +781,7 @@ def sortEdgesOld(lEdges, aVertex=None):
else:
return lEdges
olEdges = [] # ol stands for ordered list
olEdges = [] # ol stands for ordered list
if aVertex == None:
for i in range(len(lEdges)*2) :
if len(lEdges[i/2].Vertexes) > 1:
@ -791,7 +791,7 @@ def sortEdgesOld(lEdges, aVertex=None):
return olEdges
# if the wire is closed there is no end so choose 1st Vertex
# print("closed wire, starting from ",lEdges[0].Vertexes[0].Point)
return sortEdgesOld(lEdges, lEdges[0].Vertexes[0])
return sortEdgesOld(lEdges, lEdges[0].Vertexes[0])
else :
#print("looking ",aVertex.Point)
result = lookfor(aVertex,lEdges)
@ -876,7 +876,7 @@ def findWires(edgeslist):
if DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[-1].Point):
return True
return False
edges = edgeslist[:]
wires = []
lost = []
@ -916,7 +916,7 @@ def findWires(edgeslist):
else:
nwires.append(wi)
return nwires
def superWire(edgeslist,closed=False):
'''superWire(edges,[closed]): forces a wire between edges that don't necessarily
have coincident endpoints. If closed=True, wire will always be closed'''
@ -1089,7 +1089,7 @@ def getNormal(shape):
if FreeCAD.GuiUp:
import Draft
vdir = Draft.get3DView().getViewDirection()
if n.getAngle(vdir) < 0.78:
if n.getAngle(vdir) < 0.78:
n = n.negative()
return n
@ -1193,7 +1193,7 @@ def connect(edges,closed=False):
i = findIntersection(curr,prev,True,True)
if i:
v1 = i[DraftVecUtils.closest(curr.Vertexes[0].Point,i)]
else:
else:
v1 = curr.Vertexes[0].Point
else:
v1 = curr.Vertexes[0].Point
@ -1203,7 +1203,7 @@ def connect(edges,closed=False):
if i:
v2 = i[DraftVecUtils.closest(curr.Vertexes[-1].Point,i)]
else:
v2 = curr.Vertexes[-1].Point
v2 = curr.Vertexes[-1].Point
else:
v2 = curr.Vertexes[-1].Point
if geomType(curr) == "Line":
@ -1444,7 +1444,7 @@ def cleanFaces(shape):
if ee.hashCode() in eset:
return i
return None
# build lookup table
lut = {}
for face in faceset:
@ -1655,7 +1655,7 @@ def fillet(lEdges,r,chamfer=False):
Returns a list of sorted edges describing a round corner'''
def getCurveType(edge,existingCurveType = None):
'''Builds or completes a dictionnary containing edges with keys "Arc" and "Line"'''
'''Builds or completes a dictionnary containing edges with keys "Arc" and "Line"'''
if not existingCurveType :
existingCurveType = { 'Line' : [], 'Arc' : [] }
if issubclass(type(edge.Curve),Part.Line) :
@ -1665,51 +1665,51 @@ def fillet(lEdges,r,chamfer=False):
else :
raise ValueError("Edge's curve must be either Line or Arc")
return existingCurveType
rndEdges = lEdges[0:2]
rndEdges = Part.__sortEdges__(rndEdges)
if len(rndEdges) < 2 :
return rndEdges
if r <= 0 :
print("DraftGeomUtils.fillet : Error : radius is negative.")
return rndEdges
curveType = getCurveType(rndEdges[0])
curveType = getCurveType(rndEdges[1],curveType)
lVertexes = rndEdges[0].Vertexes + [rndEdges[1].Vertexes[-1]]
if len(curveType['Line']) == 2:
# Deals with 2-line-edges lists --------------------------------------
# Deals with 2-line-edges lists --------------------------------------
U1 = lVertexes[0].Point.sub(lVertexes[1].Point) ; U1.normalize()
U2 = lVertexes[2].Point.sub(lVertexes[1].Point) ; U2.normalize()
alpha = U1.getAngle(U2)
if chamfer:
# correcting r value so the size of the chamfer = r
beta = math.pi - alpha/2
r = (r/2)/math.cos(beta)
if round(alpha,precision()) == 0 or round(alpha - math.pi,precision()) == 0: # Edges have same direction
print("DraftGeomUtils.fillet : Warning : edges have same direction. Did nothing")
return rndEdges
dToCenter = r / math.sin(alpha/2.)
dToCenter = r / math.sin(alpha/2.)
dToTangent = (dToCenter**2-r**2)**(0.5)
dirVect = Vector(U1) ; dirVect.scale(dToTangent,dToTangent,dToTangent)
arcPt1 = lVertexes[1].Point.add(dirVect)
dirVect = U2.add(U1) ; dirVect.normalize()
dirVect.scale(dToCenter-r,dToCenter-r,dToCenter-r)
arcPt2 = lVertexes[1].Point.add(dirVect)
dirVect = Vector(U2) ; dirVect.scale(dToTangent,dToTangent,dToTangent)
arcPt3 = lVertexes[1].Point.add(dirVect)
if (dToTangent>lEdges[0].Length) or (dToTangent>lEdges[1].Length) :
print("DraftGeomUtils.fillet : Error : radius value ", r," is too high")
return rndEdges
@ -1717,15 +1717,23 @@ def fillet(lEdges,r,chamfer=False):
rndEdges[1] = Part.Edge(Part.Line(arcPt1,arcPt3))
else:
rndEdges[1] = Part.Edge(Part.Arc(arcPt1,arcPt2,arcPt3))
rndEdges[0] = Part.Edge(Part.Line(lVertexes[0].Point,arcPt1))
rndEdges += [Part.Edge(Part.Line(arcPt3,lVertexes[2].Point))]
if lVertexes[0].Point == arcPt1:
# fillet consumes entire first edge
rndEdges.pop(0)
else:
rndEdges[0] = Part.Edge(Part.Line(lVertexes[0].Point,arcPt1))
if lVertexes[2].Point != arcPt3:
# fillet does not consume entire second edge
rndEdges += [Part.Edge(Part.Line(arcPt3,lVertexes[2].Point))]
return rndEdges
elif len(curveType['Arc']) == 1 :
# Deals with lists containing an arc and a line ----------------------------------
if lEdges[0] in curveType['Arc'] :
lineEnd = lVertexes[2] ; arcEnd = lVertexes[0] ; arcFirst = True
else :
@ -1734,23 +1742,23 @@ def fillet(lEdges,r,chamfer=False):
arcRadius = curveType['Arc'][0].Curve.Radius
arcAxis = curveType['Arc'][0].Curve.Axis
arcLength = curveType['Arc'][0].Length
U1 = lineEnd.Point.sub(lVertexes[1].Point) ; U1.normalize()
toCenter = arcCenter.sub(lVertexes[1].Point)
if arcFirst : # make sure the tangent points towards the arc
T = arcAxis.cross(toCenter)
else :
T = toCenter.cross(arcAxis)
projCenter = toCenter.dot(U1)
if round(abs(projCenter),precision()) > 0 :
normToLine = U1.cross(T).cross(U1)
else :
normToLine = Vector(toCenter)
normToLine = Vector(toCenter)
normToLine.normalize()
dCenterToLine = toCenter.dot(normToLine) - r
if round(projCenter,precision()) > 0 :
newRadius = arcRadius - r
elif round(projCenter,precision()) < 0 or (round(projCenter,precision()) == 0 and U1.dot(T) > 0):
@ -1758,65 +1766,65 @@ def fillet(lEdges,r,chamfer=False):
else :
print("DraftGeomUtils.fillet : Warning : edges are already tangent. Did nothing")
return rndEdges
toNewCent = newRadius**2-dCenterToLine**2
if toNewCent > 0 :
toNewCent = abs(abs(projCenter) - toNewCent**(0.5))
else :
print("DraftGeomUtils.fillet : Error : radius value ", r," is too high")
return rndEdges
U1.scale(toNewCent,toNewCent,toNewCent)
normToLine.scale(r,r,r)
normToLine.scale(r,r,r)
newCent = lVertexes[1].Point.add(U1).add(normToLine)
arcPt1= lVertexes[1].Point.add(U1)
arcPt2= lVertexes[1].Point.sub(newCent); arcPt2.normalize()
arcPt2.scale(r,r,r) ; arcPt2 = arcPt2.add(newCent)
if newRadius == arcRadius - r :
arcPt3= newCent.sub(arcCenter)
else :
arcPt3= arcCenter.sub(newCent)
arcPt3= arcCenter.sub(newCent)
arcPt3.normalize()
arcPt3.scale(r,r,r) ; arcPt3 = arcPt3.add(newCent)
arcPt = [arcPt1,arcPt2,arcPt3]
# Warning : In the following I used a trick for calling the right element
# in arcPt or V : arcFirst is a boolean so - not arcFirst is -0 or -1
# list[-1] is the last element of a list and list[0] the first
# this way I don't have to proceed tests to know the position of the arc
myTrick = not arcFirst
V = [arcPt3]
V += [arcEnd.Point]
toCenter.scale(-1,-1,-1)
delLength = arcRadius * V[0].sub(arcCenter).getAngle(toCenter)
if delLength > arcLength or toNewCent > curveType['Line'][0].Length:
if delLength > arcLength or toNewCent > curveType['Line'][0].Length:
print("DraftGeomUtils.fillet : Error : radius value ", r," is too high")
return rndEdges
arcAsEdge = arcFrom2Pts(V[-arcFirst],V[-myTrick],arcCenter,arcAxis)
V = [lineEnd.Point,arcPt1]
lineAsEdge = Part.Edge(Part.Line(V[-arcFirst],V[myTrick]))
rndEdges[not arcFirst] = arcAsEdge
rndEdges[arcFirst] = lineAsEdge
if chamfer:
rndEdges[1:1] = [Part.Edge(Part.Line(arcPt[- arcFirst],arcPt[- myTrick]))]
else:
rndEdges[1:1] = [Part.Edge(Part.Arc(arcPt[- arcFirst],arcPt[1],arcPt[- myTrick]))]
return rndEdges
elif len(curveType['Arc']) == 2 :
# Deals with lists of 2 arc-edges --------------------------------------------
arcCenter, arcRadius, arcAxis, arcLength, toCenter, T, newRadius = [], [], [], [], [], [], []
for i in range(2) :
arcCenter += [curveType['Arc'][i].Curve.Center]
@ -1828,7 +1836,7 @@ def fillet(lEdges,r,chamfer=False):
T += [toCenter[1].cross(arcAxis[1])]
CentToCent = toCenter[1].sub(toCenter[0])
dCentToCent = CentToCent.Length
sameDirection = (arcAxis[0].dot(arcAxis[1]) > 0)
TcrossT = T[0].cross(T[1])
if sameDirection :
@ -1844,7 +1852,7 @@ def fillet(lEdges,r,chamfer=False):
else :
print("DraftGeomUtils.fillet : Warning : edges are already tangent. Did nothing")
return rndEdges
elif not sameDirection :
elif not sameDirection :
if round(TcrossT.dot(arcAxis[0]),precision()) > 0 :
newRadius += [arcRadius[0]+r]
newRadius += [arcRadius[1]-r]
@ -1864,16 +1872,16 @@ def fillet(lEdges,r,chamfer=False):
else :
print("DraftGeomUtils.fillet : Warning : edges are already tangent. Did nothing")
return rndEdges
if newRadius[0]+newRadius[1] < dCentToCent or \
newRadius[0]-newRadius[1] > dCentToCent or \
newRadius[1]-newRadius[0] > dCentToCent :
print("DraftGeomUtils.fillet : Error : radius value ", r," is too high")
return rndEdges
x = (dCentToCent**2+newRadius[0]**2-newRadius[1]**2)/(2*dCentToCent)
y = (newRadius[0]**2-x**2)**(0.5)
CentToCent.normalize() ; toCenter[0].normalize() ; toCenter[1].normalize()
if abs(toCenter[0].dot(toCenter[1])) != 1 :
normVect = CentToCent.cross(CentToCent.cross(toCenter[0]))
@ -1895,7 +1903,7 @@ def fillet(lEdges,r,chamfer=False):
arcPt2 = newCent.add(toThirdPt)
arcPt3 = newCent.add(CentToNewCent[1])
arcPt = [arcPt1,arcPt2,arcPt3]
arcAsEdge = []
for i in range(2) :
toCenter[i].scale(-1,-1,-1)
@ -1905,21 +1913,21 @@ def fillet(lEdges,r,chamfer=False):
return rndEdges
V = [arcPt[-i],lVertexes[-i].Point]
arcAsEdge += [arcFrom2Pts(V[i-1],V[-i],arcCenter[i],arcAxis[i])]
rndEdges[0] = arcAsEdge[0]
rndEdges[1] = arcAsEdge[1]
if chamfer:
rndEdges[1:1] = [Part.Edge(Part.Line(arcPt[0],arcPt[2]))]
else:
rndEdges[1:1] = [Part.Edge(Part.Arc(arcPt[0],arcPt[1],arcPt[2]))]
return rndEdges
def filletWire(aWire,r,chamfer=False):
''' Fillets each angle of a wire with r as radius value
if chamfer is true, a chamfer is made instead and r is the
size of the chamfer'''
edges = aWire.Edges
edges = Part.__sortEdges__(edges)
filEdges = [edges[0]]
@ -1935,7 +1943,7 @@ def filletWire(aWire,r,chamfer=False):
filEdges[-1:] = result[0:2]
filEdges[0] = result[2]
return Part.Wire(filEdges)
def getCircleFromSpline(edge):
"returns a circle-based edge from a bspline-based edge"
if geomType(edge) != "BSplineCurve":
@ -2018,7 +2026,7 @@ def cleanProjection(shape,tessellate=True,seglength=.05):
except:
print("Debug: error cleaning edge ",e)
return Part.makeCompound(newedges)
def curvetosegment(curve,seglen):
points = curve.discretize(seglen)
p0 = points[0]
@ -2049,10 +2057,10 @@ def tessellateProjection(shape,seglen):
except:
print("Debug: error cleaning edge ",e)
return Part.makeCompound(newedges)
def rebaseWire(wire,vidx):
"""rebaseWire(wire,vidx): returns a new wire which is a copy of the
current wire, but where the first vertex is the vertex indicated by the given
index vidx, starting from 1. 0 will return an exact copy of the wire."""
@ -2109,7 +2117,7 @@ def getBoundaryAngles(angle,alist):
if a < higher:
higher = a
return (lower,higher)
def circleFrom2tan1pt(tan1, tan2, point):
"circleFrom2tan1pt(edge, edge, Vector)"
@ -2237,7 +2245,7 @@ def circleFrom3LineTangents (edge1, edge2, edge3):
bis23 = angleBisection(edge2,edge3)
bis31 = angleBisection(edge3,edge1)
intersections = []
int = findIntersection(bis12, bis23, True, True)
int = findIntersection(bis12, bis23, True, True)
if int:
radius = findDistance(int[0],edge1).Length
intersections.append(Part.Circle(int[0],NORM,radius))
@ -2457,7 +2465,7 @@ def innerSoddyCircle(circle1, circle2, circle3):
print("debug: innerSoddyCircle bad parameters!\n")
# FreeCAD.Console.PrintMessage("debug: innerSoddyCircle bad parameters!\n")
return None
def circleFrom3CircleTangents(circle1, circle2, circle3):
'''
http://en.wikipedia.org/wiki/Problem_of_Apollonius#Inversive_methods
@ -2560,7 +2568,7 @@ def determinant (mat,n):
else:
return 0
def findHomotheticCenterOfCircles(circle1, circle2):
'''
findHomotheticCenterOfCircles(circle1, circle2)

View File

@ -27,6 +27,8 @@ import Path
from PathScripts import PathUtils
from PySide import QtCore, QtGui
import math
import Part
import DraftGeomUtils
"""Dogbone Dressup object and FreeCAD command"""
@ -45,6 +47,20 @@ except AttributeError:
movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03']
movestraight = ['G1', 'G01']
def debugMarker(vector, label):
obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label)
obj.Label = label
obj.Radius = 0.5
obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0))
def debugCircle(vector, r, label):
obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label)
obj.Label = label
obj.Radius = r
obj.Height = 1
obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0))
obj.ViewObject.Transparency = 95
class Style:
Dogbone = 'Dogbone'
Tbone_H = 'T-bone horizontal'
@ -155,6 +171,9 @@ class Chord (object):
def g1Command(self):
return Path.Command("G1", {"X": self.End.x, "Y": self.End.y})
def arcCommand(self, orientation):
return self.g1Command()
def isAPlungeMove(self):
return self.End.z != self.Start.z
@ -195,7 +214,14 @@ class Chord (object):
else:
print("Now this really sucks")
return (x, y)
return FreeCAD.Vector(x, y, self.End.z)
def perpendicular(self):
v = self.asVector()
return FreeCAD.Vector(-v.y, v.x, 0)
def footOfPerpendicularFrom(self, vector):
return self.intersection(Chord(vector, vector + self.perpendicular()))
class ObjectDressup:
@ -235,21 +261,11 @@ class ObjectDressup:
def shouldInsertDogbone(self, obj, inChord, outChord):
return outChord.foldsBackOrTurns(inChord, self.theOtherSideOf(obj.Side))
# draw circles where dogbones go, easier to spot during testing
def debugCircleBone(self, obj,inChord, outChord):
di = 0.
dj = 0.5
if inChord.Start.x < outChord.End.x:
dj = -dj
circle = Path.Command("G1", {"I": di, "J": dj}).Parameters
circle.update({"X": inChord.End.x, "Y": inChord.End.y})
return [ Path.Command("G3", circle) ]
def adaptiveBoneLength(self, obj, inChord, outChord, angle):
iChord = inChord.offsetBy(self.toolRadius)
oChord = outChord.offsetBy(self.toolRadius)
x,y = iChord.intersection(oChord, self.toolRadius)
dest = inChord.moveTo(FreeCAD.Vector(x, y, inChord.End.z))
v = iChord.intersection(oChord, self.toolRadius)
dest = inChord.moveTo(FreeCAD.Vector(v.x, v.y, v.z))
destAngle = dest.getAngleXY()
distance = dest.getLength() - self.toolRadius * math.fabs(math.cos(destAngle - angle))
#print("adapt")
@ -258,13 +274,43 @@ class ObjectDressup:
#print(" = (%.2f, %.2f) -> %.2f (%.2f %.2f) -> %.2f" % (x, y, dest.getLength(), destAngle/math.pi, angle/math.pi, distance))
return distance
def smoothChordCommands(self, inChord, outChord, smooth):
def smoothChordCommands(self, inChord, outChord, side, smooth):
if smooth == 0:
return [ inChord.g1Command(), outChord.g1Command() ]
print("(%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (inChord.Start.x, inChord.Start.y, inChord.End.x, inChord.End.y, outChord.End.x, outChord.End.y))
inAngle = inChord.getAngleXY()
outAngle = outChord.getAngleXY()
if inAngle == outAngle: # straight line, combine g1
return [ Chord(inChord.Start, outChord.End).g1Command() ]
print(" inAngle = %.2f outAngle = %.2f" % (inAngle/math.pi, outAngle/math.pi))
if inAngle == outAngle: # straight line, outChord includes inChord
print(" ---> (%.2f, %.2f)" %(outChord.End.x, outChord.End.y))
return [ outChord.g1Command() ]
print("%s :: %s" % (inChord, outChord))
inEdge = DraftGeomUtils.edg(inChord.Start, inChord.End)
outEdge = DraftGeomUtils.edg(outChord.Start, outChord.End)
#wire = Part.Wire([inEdge, outEdge])
#print(" => %s" % wire)
#wire = wire.makeOffset2D(self.toolRadius)
#print(" ==> %s" % wire)
#wire = wire.makeOffset2D(-self.toolRadius)
#print(" ===> %s" % wire)
radius = self.toolRadius
while radius > 0:
lastpt = None
commands = ""
edges = DraftGeomUtils.fillet([inEdge, outEdge], radius)
if DraftGeomUtils.isSameLine(edges[0], inEdge) or DraftGeomUtils.isSameLine(edges[1], inEdge):
print("Oh, we got a problem, try smaller radius")
radius = radius - 0.1 * self.toolRadius
continue
print("we're good")
#for edge in wire.Edges[:-1]: # makeOffset2D closes the wire
for edge in edges:
if not lastpt:
lastpt = edge.Vertexes[0].Point
lastpt, cmds = PathUtils.edge_to_path(lastpt, edge, 0)
commands += cmds
path = Path.Path(commands)
return path.Commands
return [ inChord.g1Command(), outChord.g1Command() ]
def inOutBoneCommands(self, obj, inChord, outChord, angle, fixedLength, smooth):
@ -279,9 +325,12 @@ class ObjectDressup:
boneInChord = inChord.moveBy(x, y, 0)
boneOutChord = boneInChord.moveTo(outChord.Start)
debugCircle(boneInChord.Start, self.toolRadius, 'boneStart')
debugCircle(boneInChord.End, self.toolRadius, 'boneEnd')
bones = []
bones.extend(self.smoothChordCommands(inChord, boneInChord, smooth & Smooth.In))
bones.extend(self.smoothChordCommands(boneOutChord, outChord, smooth & Smooth.Out))
bones.extend(self.smoothChordCommands(inChord, boneInChord, obj.Side, smooth & Smooth.In))
bones.extend(self.smoothChordCommands(boneOutChord, outChord, obj.Side, smooth & Smooth.Out))
return bones
def dogboneAngle(self, obj, inChord, outChord):
@ -349,12 +398,7 @@ class ObjectDressup:
return (blacklisted, parentConsumed)
# Generate commands necessary to execute the dogbone
def boneCommands(self, obj, boneId, inChord, outChord, smooth):
loc = (inChord.End.x, inChord.End.y)
blacklisted, inaccessible = self.boneIsBlacklisted(obj, boneId, loc)
enabled = not blacklisted
self.bones.append((boneId, loc, enabled, inaccessible))
def boneCommands(self, obj, enabled, inChord, outChord, smooth):
if enabled:
if obj.Style == Style.Dogbone:
return self.dogbone(obj, inChord, outChord, smooth)
@ -366,13 +410,22 @@ class ObjectDressup:
return self.tboneLongEdge(obj, inChord, outChord, smooth)
if obj.Style == Style.Tbone_S:
return self.tboneShortEdge(obj, inChord, outChord, smooth)
return self.debugCircleBone(obj, inChord, outChord)
else:
return []
return [ inChord.g1Command(), outChord.g1Command() ]
def insertBone(self, boneId, obj, inChord, outChord, commands, smooth):
bones = self.boneCommands(obj, boneId, inChord, outChord, smooth)
print(">----------------------------------- %d --------------------------------------" % boneId)
loc = (inChord.End.x, inChord.End.y)
blacklisted, inaccessible = self.boneIsBlacklisted(obj, boneId, loc)
enabled = not blacklisted
self.bones.append((boneId, loc, enabled, inaccessible))
if boneId < 9:
bones = self.boneCommands(obj, enabled, inChord, outChord, smooth)
else:
bones = self.boneCommands(obj, False, inChord, outChord, smooth)
commands.extend(bones[:-1])
print("<----------------------------------- %d --------------------------------------" % boneId)
return boneId + 1, bones[-1]
def execute(self, obj):
@ -424,6 +477,8 @@ class ObjectDressup:
commands.append(lastCommand)
lastCommand = None
commands.append(thisCmd)
#for cmd in commands:
# print("cmd = '%s'" % cmd)
path = Path.Path(commands)
obj.Path = path

View File

@ -233,6 +233,51 @@ def reverseEdge(e):
return newedge
def edge_to_path(lastpt, edge, Z, hf=2.0):
if isinstance(edge.Curve, Part.Circle):
# FreeCAD.Console.PrintMessage("arc\n")
arcstartpt = edge.valueAt(edge.FirstParameter)
midpt = edge.valueAt(
(edge.FirstParameter + edge.LastParameter) * 0.5)
arcendpt = edge.valueAt(edge.LastParameter)
# arcchkpt = edge.valueAt(edge.LastParameter * .99)
if DraftVecUtils.equals(lastpt, arcstartpt):
startpt = arcstartpt
endpt = arcendpt
else:
startpt = arcendpt
endpt = arcstartpt
center = edge.Curve.Center
relcenter = center.sub(lastpt)
# FreeCAD.Console.PrintMessage("arc startpt= " + str(startpt)+ "\n")
# FreeCAD.Console.PrintMessage("arc midpt= " + str(midpt)+ "\n")
# FreeCAD.Console.PrintMessage("arc endpt= " + str(endpt)+ "\n")
arc_cw = check_clockwise(
[(startpt.x, startpt.y), (midpt.x, midpt.y), (endpt.x, endpt.y)])
# FreeCAD.Console.PrintMessage("arc_cw="+ str(arc_cw)+"\n")
if arc_cw:
output = "G2"
else:
output = "G3"
output += " X" + str(fmt(endpt.x)) + " Y" + \
str(fmt(endpt.y)) + " Z" + str(fmt(Z)) + " F" + str(hf)
output += " I" + str(fmt(relcenter.x)) + " J" + \
str(fmt(relcenter.y)) + " K" + str(fmt(relcenter.z))
output += "\n"
lastpt = endpt
# FreeCAD.Console.PrintMessage("last pt arc= " + str(lastpt)+ "\n")
else:
point = edge.Vertexes[-1].Point
if DraftVecUtils.equals(point, lastpt): # edges can come flipped
point = edge.Vertexes[0].Point
output = "G1 X" + str(fmt(point.x)) + " Y" + str(fmt(point.y)) + \
" Z" + str(fmt(Z)) + " F" + str(hf) + "\n"
lastpt = point
# FreeCAD.Console.PrintMessage("line\n")
# FreeCAD.Console.PrintMessage("last pt line= " + str(lastpt)+ "\n")
return lastpt, output
def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None, vf=1.0, hf=2.0) :
'''convert(toolpath,Z=0.0,vf=1.0,hf=2.0,PlungeAngle=90.0,Zprevious=None,StopLength=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.'''
@ -248,51 +293,6 @@ def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None,
else:
Zprevious = Z
def edge_to_path(lastpt, edge, Z):
if isinstance(edge.Curve, Part.Circle):
# FreeCAD.Console.PrintMessage("arc\n")
arcstartpt = edge.valueAt(edge.FirstParameter)
midpt = edge.valueAt(
(edge.FirstParameter + edge.LastParameter) * 0.5)
arcendpt = edge.valueAt(edge.LastParameter)
# arcchkpt = edge.valueAt(edge.LastParameter * .99)
if DraftVecUtils.equals(lastpt, arcstartpt):
startpt = arcstartpt
endpt = arcendpt
else:
startpt = arcendpt
endpt = arcstartpt
center = edge.Curve.Center
relcenter = center.sub(lastpt)
# FreeCAD.Console.PrintMessage("arc startpt= " + str(startpt)+ "\n")
# FreeCAD.Console.PrintMessage("arc midpt= " + str(midpt)+ "\n")
# FreeCAD.Console.PrintMessage("arc endpt= " + str(endpt)+ "\n")
arc_cw = check_clockwise(
[(startpt.x, startpt.y), (midpt.x, midpt.y), (endpt.x, endpt.y)])
# FreeCAD.Console.PrintMessage("arc_cw="+ str(arc_cw)+"\n")
if arc_cw:
output = "G2"
else:
output = "G3"
output += " X" + str(fmt(endpt.x)) + " Y" + \
str(fmt(endpt.y)) + " Z" + str(fmt(Z)) + " F" + str(hf)
output += " I" + str(fmt(relcenter.x)) + " J" + \
str(fmt(relcenter.y)) + " K" + str(fmt(relcenter.z))
output += "\n"
lastpt = endpt
# FreeCAD.Console.PrintMessage("last pt arc= " + str(lastpt)+ "\n")
else:
point = edge.Vertexes[-1].Point
if DraftVecUtils.equals(point, lastpt): # edges can come flipped
point = edge.Vertexes[0].Point
output = "G1 X" + str(fmt(point.x)) + " Y" + str(fmt(point.y)) + \
" Z" + str(fmt(Z)) + " F" + str(hf) + "\n"
lastpt = point
# FreeCAD.Console.PrintMessage("line\n")
# FreeCAD.Console.PrintMessage("last pt line= " + str(lastpt)+ "\n")
return lastpt, output
lastpt = None
output = ""
path_length = 0.0
@ -334,13 +334,13 @@ def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None,
subwire = edge.split(t)
assert(len(subwire.Edges) == 2)
Z_cur = Z
lastpt, codes = edge_to_path(lastpt, subwire.Edges[0], Z_cur)
lastpt, codes = edge_to_path(lastpt, subwire.Edges[0], Z_cur, hf)
output += codes
edge = subwire.Edges[1]
else:
Z_cur = Z_next
lastpt, codes = edge_to_path(lastpt, edge, Z_cur)
lastpt, codes = edge_to_path(lastpt, edge, Z_cur, hf)
output += codes
if StopLength: