From ce209917455d25dcea578480f09300f3ae8569f2 Mon Sep 17 00:00:00 2001 From: gumyr Date: Mon, 24 Jun 2024 13:47:52 -0400 Subject: [PATCH] Replaced Axis.as_infinite_edge with Edge __init__ method Issue #648 --- src/build123d/geometry.py | 30 ++++++- src/build123d/topology.py | 176 ++++++++++++++++---------------------- tests/test_direct_api.py | 8 +- 3 files changed, 107 insertions(+), 107 deletions(-) diff --git a/src/build123d/geometry.py b/src/build123d/geometry.py index 39a3516..1720fa4 100644 --- a/src/build123d/geometry.py +++ b/src/build123d/geometry.py @@ -37,6 +37,8 @@ from __future__ import annotations import copy import json import logging +import numpy as np + from math import degrees, pi, radians from typing import ( Any, @@ -798,9 +800,31 @@ class Axis(metaclass=AxisMeta): if self.is_coaxial(axis): return self else: - pnt = self.as_infinite_edge().intersect(axis.as_infinite_edge()) - if pnt is not None: - return Vector(pnt) + # Extract points and directions to numpy arrays + p1 = np.array([*self.position]) + d1 = np.array([*self.direction]) + p2 = np.array([*axis.position]) + d2 = np.array([*axis.direction]) + + # Compute the cross product of directions + cross_d1_d2 = np.cross(d1, d2) + cross_d1_d2_norm = np.linalg.norm(cross_d1_d2) + + if cross_d1_d2_norm < TOLERANCE: + # The directions are parallel + return None + + # Solve the system of equations to find the intersection + system_of_equations = np.array([d1, -d2, cross_d1_d2]).T + origin_diff = p2 - p1 + try: + t1, t2, _ = np.linalg.solve(system_of_equations, origin_diff) + except np.linalg.LinAlgError: + return None # The lines do not intersect + + # Calculate the intersection point + intersection_point = p1 + t1 * d1 + return Vector(*intersection_point) elif plane is not None: return plane.intersect(self) diff --git a/src/build123d/topology.py b/src/build123d/topology.py index 27b66d9..73fa314 100644 --- a/src/build123d/topology.py +++ b/src/build123d/topology.py @@ -2570,7 +2570,7 @@ class Shape(NodeMixin): return return_value def _intersect_with_axis(self, *axes: Axis) -> Shape: - lines = [a.as_infinite_edge() for a in axes] + lines = [Edge(a) for a in axes] return self.intersect(*lines) def _intersect_with_plane(self, *planes: Plane) -> Shape: @@ -2594,7 +2594,7 @@ class Shape(NodeMixin): if isinstance(obj, Vector): objs.append(Vertex(obj)) elif isinstance(obj, Axis): - objs.append(obj.as_infinite_edge()) + objs.append(Edge(obj)) elif isinstance(obj, Plane): objs.append(Face.make_plane(obj)) elif isinstance(obj, Location): @@ -4401,6 +4401,77 @@ class Edge(Mixin1D, Shape): _dim = 1 + @overload + def __init__( + self, + obj: TopoDS_Shape, + label: str = "", + color: Color = None, + parent: Compound = None, + ): + """Build an Edge from an OCCT TopoDS_Shape/TopoDS_Edge + + Args: + obj (TopoDS_Shape, optional): OCCT Face. + label (str, optional): Defaults to ''. + color (Color, optional): Defaults to None. + parent (Compound, optional): assembly parent. Defaults to None. + """ + + @overload + def __init__( + self, + axis: Axis, + label: str = "", + color: Color = None, + parent: Compound = None, + ): + """Build an infinite Edge from an Axis + + Args: + axis (Axis): Axis to be converted to an infinite Edge + label (str, optional): Defaults to ''. + color (Color, optional): Defaults to None. + parent (Compound, optional): assembly parent. Defaults to None. + """ + + def __init__(self, *args, **kwargs): + axis, obj, label, color, parent = (None,) * 5 + + if args: + l_a = len(args) + if isinstance(args[0], TopoDS_Shape): + obj, label, color, parent = args[:4] + (None,) * (4 - l_a) + elif isinstance(args[0], Axis): + axis, label, color, parent = args[:4] + (None,) * (4 - l_a) + + unknown_args = ", ".join( + set(kwargs.keys()).difference(["axis", "obj", "label", "color", "parent"]) + ) + if unknown_args: + raise ValueError(f"Unexpected argument(s) {unknown_args}") + + obj = kwargs.get("obj", obj) + axis = kwargs.get("axis", axis) + label = kwargs.get("label", label) + color = kwargs.get("color", color) + parent = kwargs.get("parent", parent) + + if axis is not None: + obj = BRepBuilderAPI_MakeEdge( + Geom_Line( + axis.position.to_pnt(), + axis.direction.to_dir(), + ) + ).Edge() + + super().__init__( + obj=obj, + label="" if label is None else label, + color=color, + parent=parent, + ) + def _geom_adaptor(self) -> BRepAdaptor_Curve: """ """ return BRepAdaptor_Curve(self.wrapped) @@ -4513,14 +4584,10 @@ class Edge(Mixin1D, Shape): # Find Edge/Edge overlaps intersect_op = BRepAlgoAPI_Common() - edge_intersections = self._bool_op( - (self,), (axis.as_infinite_edge(),), intersect_op - ).edges() + edge_intersections = self._bool_op((self,), (Edge(axis),), intersect_op).edges() return Compound(vertex_intersections + edge_intersections) - # return self._intersect_with_edge(axis.as_infinite_edge()) - def find_intersection_points( self, edge: Union[Axis, Edge] = None, tolerance: float = TOLERANCE ) -> ShapeList[Vector]: @@ -8665,98 +8732,3 @@ class SkipClean: def __exit__(self, exception_type, exception_value, traceback): SkipClean.clean = True - - -# Monkey-patched Axis and Plane methods that take Shapes as arguments -def _axis_as_infinite_edge(self: Axis) -> Edge: - """return an edge with infinite length along self""" - return Edge( - BRepBuilderAPI_MakeEdge( - Geom_Line( - self.position.to_pnt(), - self.direction.to_dir(), - ) - ).Edge() - ) - - -Axis.as_infinite_edge = _axis_as_infinite_edge - - -# def _axis_intersect(self: Axis, *to_intersect: Union[Shape, Axis, Plane]) -> Shape: -# """axis intersect - -# Args: -# to_intersect (sequence of Union[Shape, Axis, Plane]): objects to intersect -# with Axis. - -# Returns: -# Shape: result of intersection -# """ -# self_i_edge: Edge = self.as_infinite_edge() -# self_as_curve = Geom_Line(self.position.to_pnt(), self.direction.to_dir()) - -# intersections = [] -# for intersector in to_intersect: -# if isinstance(intersector, Axis): -# intersector_as_edge: Edge = intersector.as_infinite_edge() -# distance, point1, _point2 = self_i_edge.distance_to_with_closest_points( -# intersector_as_edge -# ) -# if distance <= TOLERANCE: -# intersections.append(Vertex(*point1.to_tuple())) -# elif isinstance(intersector, Plane): -# geom_plane: Geom_Surface = Face.make_plane(intersector)._geom_adaptor() - -# # Create a GeomAPI_IntCS object and compute the intersection -# int_cs = GeomAPI_IntCS(self_as_curve, geom_plane) -# # Check if there is an intersection point -# if int_cs.NbPoints() > 0: -# intersections.append(Vertex(*Vector(int_cs.Point(1)).to_tuple())) -# elif isinstance(intersector, Shape): -# intersections.extend(self_i_edge.intersect(intersector)) - -# return ( -# intersections[0] -# if len(intersections) == 1 -# else Compound(children=intersections) -# ) - - -# Axis.intersect = _axis_intersect - - -# def _axis_and(self: Axis, other: Union[Shape, Axis, Plane]) -> Shape: -# """intersect shape with self operator &""" -# return self.intersect(other) - - -# Axis.__and__ = _axis_and - - -# def _plane_intersect(self: Plane, *to_intersect: Union[Shape, Axis, Plane]) -> Shape: -# """plane intersect - -# Args: -# to_intersect (sequence of Union[Shape, Axis, Plane]): objects to intersect -# with Plane. - -# Returns: -# Shape: result of intersection -# """ -# self_as_face: Face = Face.make_plane(self) -# intersections = [ -# self_as_face.intersect(intersector) for intersector in to_intersect -# ] -# return Compound(children=intersections) - - -# Plane.intersect = _plane_intersect - - -# def _plane_and(self: Plane, other: Union[Shape, Axis, Plane]) -> Shape: -# """intersect shape with self operator &""" -# return self.intersect(other) - - -# Plane.__and__ = _plane_and diff --git a/tests/test_direct_api.py b/tests/test_direct_api.py index 895199a..df77ab9 100644 --- a/tests/test_direct_api.py +++ b/tests/test_direct_api.py @@ -300,7 +300,7 @@ class TestAxis(DirectApiTestCase): self.assertVectorAlmostEquals(axis.direction, (-1, 0, 0), 5) def test_axis_as_edge(self): - edge = Axis.X.as_infinite_edge() + edge = Edge(Axis.X) self.assertTrue(isinstance(edge, Edge)) common = (edge & Edge.make_line((0, 0, 0), (1, 0, 0))).edge() self.assertAlmostEqual(common.length, 1, 5) @@ -994,7 +994,7 @@ class TestEdge(DirectApiTestCase): self.assertAlmostEqual(e3_trim.length, 7, 5) a4 = Axis((0, 0, 0), (1, 1, 1)) - e4_trim = a4.as_infinite_edge().trim_to_length(0.5, 2) + e4_trim = Edge(a4).trim_to_length(0.5, 2) self.assertAlmostEqual(e4_trim.length, 2, 5) def test_bezier(self): @@ -1068,6 +1068,10 @@ class TestEdge(DirectApiTestCase): e2r = e2.reversed() self.assertAlmostEqual((e2 @ 0.1).X, -(e2r @ 0.1).X, 5) + def test_init(self): + with self.assertRaises(ValueError): + Edge(direction=(1, 0, 0)) + class TestFace(DirectApiTestCase): def test_make_surface_from_curves(self):