Download src/Mod/Draft/draftgeoutils/intersections.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 17.3 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftgeoutils/intersections.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftgeoutils/intersections.py
-
curl -L -o intersections.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftgeoutils/intersections.py
17.3 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> * | |
| # * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> * | |
| # * * | |
| # * 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 calculate intersections of shapes.""" | |
| ## @package intersections | |
| # \ingroup draftgeoutils | |
| # \brief Provides various functions to calculate intersections of shapes. | |
| import lazy_loader.lazy_loader as lz | |
| import FreeCAD as App | |
| import DraftVecUtils | |
| from draftgeoutils.general import precision, vec, geomType, isPtOnEdge | |
| from draftgeoutils.edges import findMidpoint | |
| # Delay import of module until first use because it is heavy | |
| Part = lz.LazyLoader("Part", globals(), "Part") | |
| ## \addtogroup draftgeoutils | |
| # @{ | |
| def findIntersection( | |
| edge1, edge2, infinite1=False, infinite2=False, ex1=False, ex2=False, dts=True, findAll=False | |
| ): | |
| """Return a list containing the intersection points of 2 edges. | |
| You can also feed 4 points instead of `edge1` and `edge2`. | |
| If `dts` is used, `Shape.section()` is used. | |
| Parameters | |
| ---------- | |
| edge1 | |
| Part.Edge, Circle, Line -> the first edge. | |
| Base::Vector3 -> the starting point of the first line. In which case | |
| `infinite1` must also be a point. | |
| edge2 | |
| Part.Edge, Circle, Line -> the second edge. | |
| Base::Vector3 -> the ending point of the second line. In which case | |
| `infinite2` must also be a point. | |
| the second edge. In case of a point, `infinite2` must also be a point. | |
| infinite1 | |
| bool, optional -> whether `edge1` should be continued to infinity. | |
| Default to `False`. | |
| Base::Vector3 -> if `edge1` is a point, must also be a point. | |
| infinite2 | |
| bool, optional -> whether `edge2` should be continued to infinity. | |
| Default to `False`. | |
| Base::Vector3 -> if `edge2` is a point, must also be a point. | |
| ex1: bool, optional | |
| In case `edge1` is a point, indicate whether the line should be | |
| continued to infinity. Default to `False` | |
| ex2: bool, optional | |
| In case `edge2` is a point, indicate whether the line should be | |
| continued to infinity. Default to `False` | |
| dts: bool, optional | |
| NOT_DOCUMENTED. Default to `True` | |
| findAll: bool, optional | |
| In case either `edge1` or `edge2` is a circle, indicates whether | |
| to find all intersection points. Default to `False` | |
| Returns | |
| ------- | |
| list | |
| A list of intersection points | |
| """ | |
| def getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2): | |
| if pt1: | |
| # first check if we don't already have coincident endpoints | |
| if pt1 in [pt3, pt4]: | |
| return [pt1] | |
| elif pt2 in [pt3, pt4]: | |
| return [pt2] | |
| norm1 = pt2.sub(pt1).cross(pt3.sub(pt1)) | |
| norm2 = pt2.sub(pt4).cross(pt3.sub(pt4)) | |
| if not DraftVecUtils.isNull(norm1): | |
| try: | |
| norm1.normalize() | |
| except Part.OCCError: | |
| return [] | |
| if not DraftVecUtils.isNull(norm2): | |
| try: | |
| norm2.normalize() | |
| except Part.OCCError: | |
| return [] | |
| if DraftVecUtils.isNull(norm1.cross(norm2)): | |
| vec1 = pt2.sub(pt1) | |
| vec2 = pt4.sub(pt3) | |
| if DraftVecUtils.isNull(vec1) or DraftVecUtils.isNull(vec2): | |
| return [] # One of the lines has zero-length | |
| try: | |
| vec1.normalize() | |
| vec2.normalize() | |
| except Part.OCCError: | |
| return [] | |
| norm3 = vec1.cross(vec2) | |
| denom = norm3.x + norm3.y + norm3.z | |
| if not DraftVecUtils.isNull(norm3) and denom != 0: | |
| k = ( | |
| (pt3.z - pt1.z) * (vec2.x - vec2.y) | |
| + (pt3.y - pt1.y) * (vec2.z - vec2.x) | |
| + (pt3.x - pt1.x) * (vec2.y - vec2.z) | |
| ) / denom | |
| vec1.scale(k, k, k) | |
| intp = pt1.add(vec1) | |
| if infinite1 is False and not isPtOnEdge(intp, edge1): | |
| return [] | |
| if infinite2 is False and not isPtOnEdge(intp, edge2): | |
| return [] | |
| return [intp] | |
| else: | |
| return [] # Lines have same direction | |
| else: | |
| return [] # Lines aren't on same plane | |
| tol = pow(10, -precision()) | |
| # First, check bound boxes | |
| if ( | |
| isinstance(edge1, Part.Edge) | |
| and isinstance(edge2, Part.Edge) | |
| and (not infinite1) | |
| and (not infinite2) | |
| ): | |
| bb1 = edge1.BoundBox | |
| bb1.enlarge(tol) # enlarge one box to account for rounding errors | |
| if not bb1.intersect(edge2.BoundBox): | |
| return [] # bound boxes don't intersect | |
| # First, try to use Shape.section if possible | |
| if ( | |
| dts | |
| and ( | |
| (isinstance(edge1, Part.Edge) and isinstance(edge2, (Part.Edge, Part.Face))) | |
| or (isinstance(edge1, (Part.Edge, Part.Face)) and isinstance(edge2, Part.Edge)) | |
| ) | |
| and (not infinite1) | |
| and (not infinite2) | |
| ): | |
| return [v.Point for v in edge1.section((edge2), tol).Vertexes] | |
| pt1 = None | |
| if isinstance(edge1, App.Vector) and isinstance(edge2, App.Vector): | |
| # we got points directly | |
| pt1 = edge1 | |
| pt2 = edge2 | |
| pt3 = infinite1 | |
| pt4 = infinite2 | |
| infinite1 = ex1 | |
| infinite2 = ex2 | |
| return getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2) | |
| 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 | |
| pt3 = arc.Vertexes[0].Point | |
| pt4 = arc.Vertexes[-1].Point | |
| center = arc.Curve.Center | |
| int = [] | |
| # first check for coincident endpoints | |
| if DraftVecUtils.equals(pt1, pt3) or DraftVecUtils.equals(pt1, pt4): | |
| if findAll: | |
| int.append(pt1) | |
| else: | |
| return [pt1] | |
| elif pt2 in [pt3, pt4]: | |
| if findAll: | |
| int.append(pt2) | |
| else: | |
| 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 = App.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 = App.Vector(dirVec) | |
| onLine.scale(dOnLine, dOnLine, dOnLine) | |
| int += [center.add(toLine).add(onLine)] | |
| onLine = App.Vector(dirVec) | |
| onLine.scale(-dOnLine, -dOnLine, -dOnLine) | |
| int += [center.add(toLine).add(onLine)] | |
| elif round(toLine.Length - arc.Curve.Radius, precision()) == 0: | |
| int = [center.add(toLine)] | |
| else: | |
| return [] | |
| else: | |
| # Line isn't on Arc's plane | |
| if dirVec.dot(arc.Curve.Axis) != 0: | |
| toPlane = App.Vector(arc.Curve.Axis) | |
| toPlane.normalize() | |
| d = pt1.dot(toPlane) | |
| if not d: | |
| return [] | |
| dToPlane = center.sub(pt1).dot(toPlane) | |
| toPlane = App.Vector(pt1) | |
| toPlane.scale(dToPlane / d, dToPlane / d, dToPlane / d) | |
| ptOnPlane = toPlane.add(pt1) | |
| if round(ptOnPlane.sub(center).Length - arc.Curve.Radius, precision()) == 0: | |
| int = [ptOnPlane] | |
| else: | |
| return [] | |
| else: | |
| return [] | |
| if infinite1 is False: | |
| for i in range(len(int) - 1, -1, -1): | |
| if not isPtOnEdge(int[i], edge1): | |
| del int[i] | |
| if infinite2 is False: | |
| for i in range(len(int) - 1, -1, -1): | |
| 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 cent1.sub(cent2).Length == 0: | |
| # circles are concentric | |
| return [] | |
| if DraftVecUtils.isNull(axis1.cross(axis2)): | |
| if round(c2c.dot(axis1), precision()) == 0: | |
| # circles are on same plane | |
| dc2c = c2c.Length | |
| if not DraftVecUtils.isNull(c2c): | |
| c2c.normalize() | |
| if ( | |
| round(rad1 + rad2 - dc2c, precision()) < 0 | |
| or round(rad1 - dc2c - rad2, precision()) > 0 | |
| or round(rad2 - dc2c - rad1, precision()) > 0 | |
| ): | |
| return [] | |
| else: | |
| norm = c2c.cross(axis1) | |
| if not DraftVecUtils.isNull(norm): | |
| norm.normalize() | |
| if DraftVecUtils.isNull(norm): | |
| x = 0 | |
| else: | |
| x = (dc2c**2 + rad1**2 - rad2**2) / (2 * dc2c) | |
| y = abs(rad1**2 - x**2) ** (0.5) | |
| c2c.scale(x, x, x) | |
| if round(y, precision()) != 0: | |
| norm.scale(y, y, y) | |
| int = [cent1.add(c2c).add(norm)] | |
| int += [cent1.add(c2c).sub(norm)] | |
| else: | |
| int = [cent1.add(c2c)] | |
| else: | |
| return [] # circles are on parallel planes | |
| else: | |
| # circles aren't on same plane | |
| axis1.normalize() | |
| axis2.normalize() | |
| U = axis1.cross(axis2) | |
| V = axis1.cross(U) | |
| dToPlane = c2c.dot(axis2) | |
| d = V.add(cent1).dot(axis2) | |
| V.scale(dToPlane / d, dToPlane / d, dToPlane / d) | |
| PtOn2Planes = V.add(cent1) | |
| planeIntersectionVector = U.add(PtOn2Planes) | |
| intTemp = findIntersection(planeIntersectionVector, edge1, True, True) | |
| int = [] | |
| for pt in intTemp: | |
| if round(pt.sub(cent2).Length - rad2, precision()) == 0: | |
| int += [pt] | |
| if infinite1 is False: | |
| for i in range(len(int) - 1, -1, -1): | |
| if not isPtOnEdge(int[i], edge1): | |
| del int[i] | |
| if infinite2 is False: | |
| 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): | |
| """Return True if some of the edges of the wires are intersecting. | |
| Otherwise return `False`. | |
| """ | |
| for e1 in wire1.Edges: | |
| for e2 in wire2.Edges: | |
| if findIntersection(e1, e2, dts=False): | |
| return True | |
| return False | |
| def connect(edges, closed=False, wireNedge=False): | |
| """Connect the edges in the given list by their intersections.""" | |
| inters_list = [] # List of intersections (with the previous edge). | |
| for i, curr in enumerate(edges): | |
| if i > 0: | |
| prev = edges[i - 1] | |
| elif closed: | |
| prev = edges[-1] | |
| else: | |
| inters_list.append(None) | |
| continue | |
| curr_inters_list = findIntersection(prev, curr, True, True) | |
| if len(curr_inters_list) == 0: | |
| inters_list.append(None) | |
| elif len(curr_inters_list) == 1: | |
| inters_list.append(curr_inters_list[0]) | |
| else: | |
| inters = curr_inters_list[ | |
| DraftVecUtils.closest(curr.Vertexes[0].Point, curr_inters_list) | |
| ] | |
| inters_list.append(inters) | |
| new_edges_full = [] | |
| new_edges = [] | |
| for i, curr in enumerate(edges): | |
| curr_sta = inters_list[i] | |
| if i < (len(edges) - 1): | |
| curr_end = inters_list[i + 1] | |
| elif closed: | |
| curr_end = inters_list[0] | |
| else: | |
| curr_end = None | |
| if curr_sta is None: | |
| curr_sta = curr.Vertexes[0].Point | |
| if i > 0: | |
| prev = edges[i - 1] | |
| elif closed: | |
| prev = edges[-1] | |
| else: | |
| prev = None | |
| if prev is not None: | |
| prev_end = prev.Vertexes[-1].Point | |
| new_edges_full.append(Part.LineSegment(prev_end, curr_sta).toShape()) | |
| if curr_end is None: | |
| curr_end = curr.Vertexes[-1].Point | |
| if curr_sta != curr_end: | |
| if geomType(curr) == "Line": | |
| n = Part.LineSegment(curr_sta, curr_end).toShape() | |
| new_edges.append(n) | |
| new_edges_full.append(n) | |
| elif geomType(curr) == "Circle": | |
| n = Part.Arc(curr_sta, findMidpoint(curr), curr_end).toShape() | |
| new_edges.append(n) | |
| new_edges_full.append(n) | |
| try: | |
| wire = Part.Wire(new_edges_full) | |
| # TODO May phase out wire if bind() can do without it later and do with | |
| # only connectEdges so no need bind() to find 'touching edges' there | |
| if wireNedge: | |
| return (wire, new_edges_full, new_edges) | |
| else: | |
| return wire | |
| except Part.OCCError: | |
| print("DraftGeomUtils.connect: unable to connect edges") | |
| for edge in new_edges: | |
| print(edge.Curve, " ", edge.Vertexes[0].Point, " ", edge.Vertexes[-1].Point) | |
| return None | |
| def angleBisection(edge1, edge2): | |
| """Return an edge that bisects the angle between the 2 straight edges.""" | |
| if geomType(edge1) != "Line" or geomType(edge2) != "Line": | |
| return None | |
| p1 = edge1.Vertexes[0].Point | |
| p2 = edge1.Vertexes[-1].Point | |
| p3 = edge2.Vertexes[0].Point | |
| p4 = edge2.Vertexes[-1].Point | |
| intersect = findIntersection(edge1, edge2, True, True) | |
| if intersect: | |
| line1Dir = p2.sub(p1) | |
| angleDiff = DraftVecUtils.angle(line1Dir, p4.sub(p3)) | |
| ang = angleDiff * 0.5 | |
| origin = intersect[0] | |
| line1Dir.normalize() | |
| direction = DraftVecUtils.rotate(line1Dir, ang) | |
| else: | |
| diff = p3.sub(p1) | |
| origin = p1.add(diff.multiply(0.5)) | |
| direction = p2.sub(p1) | |
| direction.normalize() | |
| return Part.LineSegment(origin, origin.add(direction)).toShape() | |
| ## @} | |