# *************************************************************************** # * Copyright (c) 2009, 2010 Yorik van Havre * # * Copyright (c) 2009, 2010 Ken Cline * # * * # * This file is part of the FreeCAD CAx development system. * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * # * 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. * # * * # * FreeCAD 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 FreeCAD; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * # *************************************************************************** """Provides various functions to work with fillets.""" ## @package fillets # \ingroup draftgeoutils # \brief Provides various functions to work with fillets. import math import lazy_loader.lazy_loader as lz import FreeCAD as App from draftgeoutils.general import precision from draftgeoutils.arcs import arcFrom2Pts from draftgeoutils.wires import isReallyClosed # Delay import of module until first use because it is heavy Part = lz.LazyLoader("Part", globals(), "Part") ## \addtogroup draftgeoutils # @{ def fillet(lEdges, r, chamfer=False, limit=0): """Return a list of sorted edges describing a round corner. Author: Jacques-Antoine Gaudin """ def getCurveType(edge, existingCurveType=None): """Build or complete a dictionary containing edges. The dictionary contains edges with keys 'Arc' and 'Line'. """ if not existingCurveType: existingCurveType = {'Line': [], 'Arc': []} if issubclass(type(edge.Curve), Part.LineSegment): existingCurveType['Line'] += [edge] elif issubclass(type(edge.Curve), Part.Line): existingCurveType['Line'] += [edge] elif issubclass(type(edge.Curve), Part.Circle): existingCurveType['Arc'] += [edge] 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 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) # Edges have same direction if (round(alpha, precision()) == 0 or round(alpha - math.pi, precision()) == 0): print("DraftGeomUtils.fillet: Warning: " "edges have same direction. Did nothing") return rndEdges dToCenter = r / math.sin(alpha/2.0) dToTangent = (dToCenter**2-r**2)**(0.5) dirVect = App.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 = App.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 if chamfer: newedge = Part.Edge(Part.LineSegment(arcPt1, arcPt3)) else: newedge = Part.Edge(Part.Arc(arcPt1, arcPt2, arcPt3)) if newedge.Length <= 100: return rndEdges rndEdges[1] = newedge if lVertexes[0].Point == arcPt1: # fillet consumes entire first edge rndEdges.pop(0) else: rndEdges[0] = Part.Edge(Part.LineSegment(lVertexes[0].Point, arcPt1)) if lVertexes[2].Point != arcPt3: # fillet does not consume entire second edge rndEdges += [Part.Edge(Part.LineSegment(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: lineEnd = lVertexes[0] arcEnd = lVertexes[2] arcFirst = False arcCenter = curveType['Arc'][0].Curve.Center 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 = App.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)): newRadius = arcRadius + r 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) 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.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: 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.LineSegment(V[-arcFirst], V[myTrick])) rndEdges[not arcFirst] = arcAsEdge rndEdges[arcFirst] = lineAsEdge if chamfer: rndEdges[1:1] = [Part.Edge(Part.LineSegment(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] arcRadius += [curveType['Arc'][i].Curve.Radius] arcAxis += [curveType['Arc'][i].Curve.Axis] arcLength += [curveType['Arc'][i].Length] toCenter += [arcCenter[i].sub(lVertexes[1].Point)] T += [arcAxis[0].cross(toCenter[0])] 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: if round(TcrossT.dot(arcAxis[0]), precision()) > 0: newRadius += [arcRadius[0] + r] newRadius += [arcRadius[1] + r] elif round(TcrossT.dot(arcAxis[0]), precision()) < 0: newRadius += [arcRadius[0] - r] newRadius += [arcRadius[1] - r] elif T[0].dot(T[1]) > 0: newRadius += [arcRadius[0] + r] newRadius += [arcRadius[1] + r] else: print("DraftGeomUtils.fillet: Warning: " "edges are already tangent. Did nothing") return rndEdges elif not sameDirection: if round(TcrossT.dot(arcAxis[0]), precision()) > 0: newRadius += [arcRadius[0] + r] newRadius += [arcRadius[1] - r] elif round(TcrossT.dot(arcAxis[0]), precision()) < 0: newRadius += [arcRadius[0] - r] newRadius += [arcRadius[1] + r] elif T[0].dot(T[1]) > 0: if arcRadius[0] > arcRadius[1]: newRadius += [arcRadius[0] - r] newRadius += [arcRadius[1] + r] elif arcRadius[1] > arcRadius[0]: newRadius += [arcRadius[0] + r] newRadius += [arcRadius[1] - r] else: print("DraftGeomUtils.fillet: Warning: " "arcs are coincident. Did nothing") return rndEdges 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])) else: normVect = T[0] normVect.normalize() CentToCent.scale(x, x, x) normVect.scale(y, y, y) newCent = arcCenter[0].add(CentToCent.add(normVect)) CentToNewCent = [newCent.sub(arcCenter[0]), newCent.sub(arcCenter[1])] for i in range(2): CentToNewCent[i].normalize() if newRadius[i] == arcRadius[i] + r: CentToNewCent[i].scale(-r, -r, -r) else: CentToNewCent[i].scale(r, r, r) toThirdPt = lVertexes[1].Point.sub(newCent) toThirdPt.normalize() toThirdPt.scale(r, r, r) arcPt1 = newCent.add(CentToNewCent[0]) 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) delLength = (arcRadius[i] * arcPt[-i].sub(arcCenter[i]).getAngle(toCenter[i])) if delLength > arcLength[i]: print("DraftGeomUtils.fillet: Error: radius value ", r, " is too high") 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.LineSegment(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): """Fillet each angle of a wire with r as radius. 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]] for i in range(len(edges) - 1): result = fillet([filEdges[-1], edges[i+1]], r, chamfer) if len(result) > 2: filEdges[-1:] = result[0:3] else: filEdges[-1:] = result[0:2] if isReallyClosed(aWire): result = fillet([filEdges[-1], filEdges[0]], r, chamfer) if len(result) > 2: filEdges[-1:] = result[0:2] filEdges[0] = result[2] return Part.Wire(filEdges) ## @}