Skip to content

Commit

Permalink
Add new class methods for returning intersection coordinates of entit…
Browse files Browse the repository at this point in the history
…ies (#422)

* Add new class methods for returning intersection coordinates of entities

* fixes and added tests for get_intersections

* Corrected docstrings

* Add special case handling for vertical line intersection with arc.

* Correct typo

* More tests.

* added comments and removed unnecessary lines

---------

Co-authored-by: Jack Davies <[email protected]>
Co-authored-by: James Packer <[email protected]>
  • Loading branch information
3 people authored Nov 26, 2024
1 parent d5112d3 commit e06a5e0
Show file tree
Hide file tree
Showing 2 changed files with 293 additions and 0 deletions.
169 changes: 169 additions & 0 deletions src/ansys/motorcad/core/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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.
Expand Down
124 changes: 124 additions & 0 deletions tests/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit e06a5e0

Please sign in to comment.