| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """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 |
| |
|
| | |
| | Part = lz.LazyLoader("Part", globals(), "Part") |
| |
|
| | |
| | |
| |
|
| |
|
| | def fillet(lEdges, r, chamfer=False): |
| | """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) |
| |
|
| | |
| | |
| | edge1_sta, edge1_end = [rndEdges[0].Vertexes[i].Point for i in [0, -1]] |
| | edge2_sta, edge2_end = [rndEdges[1].Vertexes[i].Point for i in [0, -1]] |
| | tol = 1e-7 |
| | if edge1_sta.isEqual(edge2_sta, tol): |
| | lVertexes = [rndEdges[0].Vertexes[-1], rndEdges[0].Vertexes[0], rndEdges[1].Vertexes[-1]] |
| | elif edge1_sta.isEqual(edge2_end, tol): |
| | lVertexes = [rndEdges[0].Vertexes[-1], rndEdges[0].Vertexes[0], rndEdges[1].Vertexes[0]] |
| | elif edge1_end.isEqual(edge2_sta, tol): |
| | lVertexes = [rndEdges[0].Vertexes[0], rndEdges[0].Vertexes[-1], rndEdges[1].Vertexes[-1]] |
| | else: |
| | lVertexes = [rndEdges[0].Vertexes[0], rndEdges[0].Vertexes[-1], rndEdges[1].Vertexes[0]] |
| |
|
| | if len(curveType["Line"]) == 2: |
| | |
| | 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 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 > rndEdges[0].Length) or (dToTangent > rndEdges[1].Length): |
| | print("DraftGeomUtils.fillet: Error: radius value ", r, " is too high") |
| | return rndEdges |
| |
|
| | if chamfer: |
| | rndEdges[1] = Part.Edge(Part.LineSegment(arcPt1, arcPt3)) |
| | else: |
| | rndEdges[1] = Part.Edge(Part.Arc(arcPt1, arcPt2, arcPt3)) |
| |
|
| | if lVertexes[0].Point == arcPt1: |
| | |
| | rndEdges.pop(0) |
| | else: |
| | rndEdges[0] = Part.Edge(Part.LineSegment(lVertexes[0].Point, arcPt1)) |
| |
|
| | if lVertexes[2].Point != arcPt3: |
| | |
| | rndEdges += [Part.Edge(Part.LineSegment(arcPt3, lVertexes[2].Point))] |
| |
|
| | return rndEdges |
| |
|
| | elif len(curveType["Arc"]) == 1: |
| | |
| | if rndEdges[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: |
| | 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] |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | 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: |
| | |
| | (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) |
| |
|
| |
|
| | |
| |
|