diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 0cd227a0e..64a9088df 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -900,6 +900,47 @@ def translate(self, x, y): self.start.translate(x, y) self.end.translate(x, y) + def get_intersection(self, entity): + """Get intersection Coordinate of entity with another entity. + + Returns None if intersection not found. + + Parameters + ---------- + entity : ansys.motorcad.core.geometry.Line or ansys.motorcad.core.geometry.Arc + + Returns + ------- + ansys.motorcad.core.geometry.Coordinate or list of Coordinate or None + """ + if isinstance(self, Line): + if isinstance(entity, Line): + points = self.get_line_intersection(entity) + elif isinstance(entity, Arc): + points = entity.get_line_intersection(self) + else: + raise Exception("Entity type is not Arc or Line") + else: + if isinstance(entity, Line): + points = self.get_line_intersection(entity) + elif isinstance(entity, Arc): + points = self.get_arc_intersection(entity) + if points: + intersections = [] + if type(points) == list: + for point in points: + if self.coordinate_on_entity(point): + intersections.append(point) + else: + if self.coordinate_on_entity(points): + intersections.append(points) + if intersections: + return intersections + else: + return None + else: + return None + class Line(Entity): """Python representation of Motor-CAD line entity based upon start and end coordinates. @@ -1129,6 +1170,21 @@ def get_line_intersection(self, line): # Lines don't intersect return None + def get_arc_intersection(self, arc): + """Get intersection Coordinates of line with an arc. + + Returns None if intersection not found. + + Parameters + ---------- + arc : ansys.motorcad.core.geometry.Arc + + Returns + ------- + ansys.motorcad.core.geometry.Coordinate or list of Coordinate or None + """ + return arc.get_line_intersection(self) + class _BaseArc(Entity): """Internal class to allow creation of Arcs.""" @@ -1287,6 +1343,119 @@ def coordinate_on_entity(self, coordinate): abs(radius), abs(self.radius), abs_tol=GEOM_TOLERANCE ) + def get_line_intersection(self, line): + """Get intersection Coordinates of arc with a line. + + Returns None if intersection not found. + + Parameters + ---------- + line : ansys.motorcad.core.geometry.Line + + Returns + ------- + ansys.motorcad.core.geometry.Coordinate or list of Coordinate or None + """ + # circle of the arc + a = self.centre.x + b = self.centre.y + r = self.radius + + if line.is_vertical: + # line x coordinate is constant + x = line.start.x + + A = 1 + B = -2 * b + C = x**2 - 2 * x * a + a**2 + b**2 - r**2 + D = B**2 - 4 * A * C + + if D < 0: + # line and circle do not intersect + return None + elif D == 0: + # line and circle intersect at 1 point + y = -B / (2 * A) + return Coordinate(x, y) + else: + # line and circle intersect at 2 points + y1 = (-B + sqrt(D)) / (2 * A) + y2 = (-B - sqrt(D)) / (2 * A) + return [Coordinate(x, y1), Coordinate(x, y2)] + else: + # Normal case, line y is a function of x + m = line.gradient + c = line.y_intercept + + A = 1 + m**2 + B = 2 * (m * (c - b) - a) + C = a**2 + (c - b) ** 2 - r**2 + + D = B**2 - 4 * A * C + if D < 0: + # line and circle do not intersect + return None + elif D == 0: + # line and circle intersect at 1 point + x = -B / (2 * A) + y = m * x + c + return Coordinate(x, y) + else: + # line and circle intersect at 2 points + x1 = (-B + sqrt(D)) / (2 * A) + x2 = (-B - sqrt(D)) / (2 * A) + y1 = m * x1 + c + y2 = m * x2 + c + return [Coordinate(x1, y1), Coordinate(x2, y2)] + + def get_arc_intersection(self, arc): + """Get intersection Coordinates of arc with another arc. + + Returns None if intersection not found. + + Parameters + ---------- + arc : ansys.motorcad.core.geometry.Arc + + Returns + ------- + list of Coordinate or None + """ + # circle of self + a1 = self.centre.x + b1 = self.centre.y + r1 = abs(self.radius) + + # circle of other arc + a2 = arc.centre.x + b2 = arc.centre.y + r2 = abs(arc.radius) + + d = sqrt((a2 - a1) ** 2 + (b2 - b1) ** 2) + + if d > (r1 + r2): + # if they don't intersect + return None + if d < abs(r1 - r2): + # if one circle is inside the other + return None + if d == 0 and r1 == r2: + # coincident circles + return None + else: + a = (r1**2 - r2**2 + d**2) / (2 * d) + h = sqrt(r1**2 - a**2) + + x = a1 + a * (a2 - a1) / d + y = b1 + a * (b2 - b1) / d + x1 = x + h * (b2 - b1) / d + y1 = y - h * (a2 - a1) / d + + x2 = x - h * (b2 - b1) / d + y2 = y + h * (a2 - a1) / d + + return [Coordinate(x1, y1), Coordinate(x2, y2)] + @property def start_angle(self): """Get angle of start point from centre point coordinates. diff --git a/tests/test_geometry.py b/tests/test_geometry.py index b05963e0c..3ef5c3e7d 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -1685,6 +1685,130 @@ def test_get_line_intersection(): assert l1.get_line_intersection(l2) is None +def test_get_intersection(): + arc_1 = Arc(Coordinate(0, 0), Coordinate(5, 5), radius=15) + arc_2 = Arc(Coordinate(0, 6), Coordinate(5, 1), radius=-21) + + coordinate = Coordinate(0, 0) + + # test get_intersection + + # 2 arcs, 1 intersection + intersection_1_2 = arc_1.get_intersection(arc_2) + intersection_2_1 = arc_2.get_intersection(arc_1) + assert len(intersection_1_2) == 1 + assert len(intersection_2_1) == 1 + assert intersection_1_2[0] == intersection_2_1[0] + assert arc_1.coordinate_on_entity(intersection_1_2[0]) + assert arc_2.coordinate_on_entity(intersection_1_2[0]) + # draw_objects_debug([arc_1, arc_2, intersection_1_2[0]]) + + # 2 arcs, 2 intersections + arc_3 = Arc(Coordinate(1, 0), Coordinate(5, 4), radius=-5) + intersection_1_3 = arc_1.get_arc_intersection(arc_3) + intersection_3_1 = arc_3.get_arc_intersection(arc_1) + assert len(intersection_1_3) == 2 + assert len(intersection_3_1) == 2 + for point in intersection_1_3: + assert arc_1.coordinate_on_entity(point) + assert arc_3.coordinate_on_entity(point) + assert point in intersection_3_1 + # draw_objects_debug([arc_1, arc_3, intersection_1_3[0], intersection_1_3[1]]) + + # 2 arcs, 0 intersection + arc_4 = Arc(Coordinate(0, -1), Coordinate(0, 1), radius=1) + arc_5 = Arc(Coordinate(0, -0.5), Coordinate(0, 0.5), radius=0.5) + intersection_4_5 = arc_4.get_intersection(arc_5) + intersection_5_4 = arc_5.get_intersection(arc_4) + assert intersection_4_5 is None + assert intersection_5_4 is None + # draw_objects_debug([arc_4, arc_5]) + + # 2 arcs, same radius, 0 intersection + intersection_4_4 = arc_4.get_intersection(arc_4) + assert intersection_4_4 is None + # draw_objects_debug([arc_4]) + + # 1 arc, 1 line, 1 intersection + line_4 = Line(Coordinate(0, 6), Coordinate(5, 1)) + intersection_1_4 = arc_1.get_intersection(line_4) + intersection_4_1 = line_4.get_intersection(arc_1) + intersection_4_1_alt = line_4.get_arc_intersection(arc_1) + assert len(intersection_1_4) == 1 + assert len(intersection_4_1) == 1 + assert len(intersection_4_1_alt) >= len(intersection_4_1) + assert intersection_1_4[0] == intersection_4_1[0] + assert intersection_4_1[0] in intersection_4_1_alt + assert arc_1.coordinate_on_entity(intersection_1_4[0]) + assert line_4.coordinate_on_entity(intersection_1_4[0]) + # draw_objects_debug([arc_1, line_4, intersection_1_4[0]]) + + # 1 arc, 1 line, 2 intersections + line_5 = Line(Coordinate(1, 0), Coordinate(5, 4)) + arc_6 = Arc(Coordinate(0, 0), Coordinate(5, 5), radius=8) + intersection_5_6 = line_5.get_intersection(arc_6) + intersection_6_5 = arc_6.get_intersection(line_5) + assert len(intersection_5_6) == 2 + assert len(intersection_6_5) == 2 + for point in intersection_5_6: + assert line_5.coordinate_on_entity(point) + assert arc_6.coordinate_on_entity(point) + assert point in intersection_6_5 + # draw_objects_debug([arc_6, line_5, intersection_5_6[0], intersection_5_6[1]]) + + # 1 arc, 1 line, 1 intersection (vertical tangent line) + arc_7 = Arc(Coordinate(0, 0), Coordinate(0, 2), radius=1) + line_6 = Line(Coordinate(1, 2), Coordinate(1, 0)) + intersection_7_6 = arc_7.get_intersection(line_6) + intersection_6_7 = line_6.get_intersection(arc_7) + assert len(intersection_7_6) == 1 + assert len(intersection_6_7) == 1 + assert intersection_7_6[0] == intersection_6_7[0] + assert arc_7.coordinate_on_entity(intersection_7_6[0]) + assert line_6.coordinate_on_entity(intersection_7_6[0]) + # draw_objects_debug([arc_7, line_6, intersection_7_6[0]]) + + # 1 arc, 1 line, 2 intersections (vertical line) + line_7 = Line(Coordinate(0.5, 2), Coordinate(0.5, 0)) + intersection_7a_7l = arc_7.get_intersection(line_7) + intersection_7l_7a = line_7.get_intersection(arc_7) + assert len(intersection_7a_7l) == 2 + assert len(intersection_7l_7a) == 2 + assert intersection_7a_7l[0] == intersection_7l_7a[0] + assert arc_7.coordinate_on_entity(intersection_7l_7a[0]) + assert line_7.coordinate_on_entity(intersection_7l_7a[0]) + # draw_objects_debug([arc_7, line_7, intersection_7a_7l[0], intersection_7a_7l[1]]) + + # 1 arc, 1 line, 1 intersection (horizontal tangent line) + line_8 = Line(Coordinate(-1, 2), Coordinate(1, 2)) + intersection_7_8 = arc_7.get_intersection(line_8) + intersection_8_7 = line_8.get_intersection(arc_7) + assert len(intersection_7_8) == 1 + assert len(intersection_8_7) == 1 + assert intersection_7_8[0] == intersection_8_7[0] + assert arc_7.coordinate_on_entity(intersection_7_8[0]) + assert line_8.coordinate_on_entity(intersection_7_8[0]) + # draw_objects_debug([arc_7, line_8, intersection_7_8[0]]) + + # 2 lines, 1 intersection + intersection_4_5 = line_4.get_intersection(line_5) + intersection_5_4 = line_5.get_intersection(line_4) + assert len(intersection_4_5) == 1 + assert len(intersection_5_4) == 1 + assert intersection_4_5[0] == intersection_5_4[0] + assert line_4.coordinate_on_entity(intersection_4_5[0]) + assert line_5.coordinate_on_entity(intersection_4_5[0]) + # draw_objects_debug([line_4, line_5, intersection_4_5[0]]) + + # Arc intersection with point, not valid, should raise exception + with pytest.raises(Exception) as e_info: + arc_1.get_intersection(coordinate) # noqa + + # Line intersection with point, not valid, should raise exception + with pytest.raises(Exception) as e_info: + line_1.get_intersection(coordinate) # noqa + + def test_arc_from_coordinates(): c1 = Coordinate(1, 0) c2 = Coordinate(sin(pi / 4), sin(pi / 4))