Skip to content

Commit

Permalink
New unit tests for collision detection api (#173)
Browse files Browse the repository at this point in the history
* New unit tests for collision detection api

* Added drawings of collision types to unit tests

* Clockwise region used for collision detection

* Fix for failing tests, added equivalency for entities independent of list index and direction

* Review updates

---------

Co-authored-by: JackDavies <[email protected]>
  • Loading branch information
matthewAnsys and jgsdavies authored Sep 19, 2023
1 parent 7dfe94c commit 6368914
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 5 deletions.
5 changes: 5 additions & 0 deletions doc/source/methods/geometry_functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Geometry Objects
:toctree: _autosummary_geometry_methods

Region
Coordinate
Entity
Line
Arc

Expand All @@ -18,5 +20,8 @@ Geometry Functions
.. autosummary::
:toctree: _autosummary_geometry_functions

get_entities_have_common_coordinate
entities_same
reverse_entities
xy_to_rt
rt_to_xy
80 changes: 77 additions & 3 deletions src/ansys/motorcad/core/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ def __eq__(self, other):
and self.colour == other.colour
# and self.area == other.area ->
# Already check entities - can't expect user to calculate area
and self.centroid == other.centroid
and self.region_coordinate == other.region_coordinate
# and self.centroid == other.centroid ->
# Centroid calculated from entities - can't expect user to calculate
# and self.region_coordinate == other.region_coordinate ->
# Region coordinate is an output, cannot guarantee will be same for identical regions
and self.duplications == other.duplications
and self.entities == other.entities
and (
entities_same(self.entities, other.entities, check_reverse=False)
or entities_same(self.entities, other.entities, check_reverse=True)
)
):
return True
else:
Expand Down Expand Up @@ -207,6 +212,10 @@ def __eq__(self, other):
else:
return False

def reverse(self):
"""Reverse Entity class."""
return Entity(self.end, self.start)


class Line(Entity):
"""Python representation of Motor-CAD line entity based upon start and end coordinates.
Expand Down Expand Up @@ -301,6 +310,10 @@ def get_length(self):
"""
return sqrt(pow(self.start.x - self.end.x, 2) + pow(self.start.y - self.end.y, 2))

def reverse(self):
"""Reverse Line entity."""
return Line(self.end, self.start)


class Arc(Entity):
"""Python representation of Motor-CAD arc entity based upon start, end, centre and radius.
Expand Down Expand Up @@ -414,6 +427,10 @@ def get_length(self):

return self.radius * radians(arc_angle)

def reverse(self):
"""Reverse Arc entity."""
return Arc(self.end, self.start, self.centre, -self.radius)


def _convert_entities_to_json(entities):
"""Get entities list as a json object.
Expand Down Expand Up @@ -516,6 +533,63 @@ def get_entities_have_common_coordinate(entity_1, entity_2):
return False


def entities_same(entities_a, entities_b, check_reverse=False):
"""Check whether entities in region are the same as entities a different region.
Parameters
----------
entities_a : list of Line or list of Arc
list of Line and Arc objects.
entities_b : list of Line or list of Arc
list of Line and Arc objects.
check_reverse : Boolean
Whether to reverse entities when checking entity equivalency.
Returns
----------
boolean
"""
if check_reverse:
entities_b = reverse_entities(entities_b)

start_index = 0

for count, entity in enumerate(entities_b):
if entity == entities_a[0]:
# start entity found
start_index = count
break

# regenerate entities_b from start index found from entities_a
entities = [entities_b[i] for i in range(start_index, len(entities_a))] + [
entities_b[i] for i in range(0, start_index)
]

if entities == entities_a:
return True
else:
return False


def reverse_entities(entities):
"""Reverse list of line/arc entities, including entity start end coordinates.
Parameters
----------
entities : list of Line or list of Arc
list of Line and Arc objects.
Returns
----------
list of Line or list of Arc
list of Line and Arc objects.
"""
entities.reverse()
return [entity.reverse() for entity in entities]


def xy_to_rt(x, y):
"""Convert Motor-CAD Cartesian coordinates to polar coordinates in degrees.
Expand Down
212 changes: 210 additions & 2 deletions tests/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ def generate_constant_region():
region.entities.append(geometry.Line(geometry.Coordinate(-1, 0), geometry.Coordinate(1, 0)))
region.entities.append(
geometry.Arc(
geometry.Coordinate(1, 0), geometry.Coordinate(1, 1), geometry.Coordinate(0, 0), 1
geometry.Coordinate(1, 0), geometry.Coordinate(0, 1), geometry.Coordinate(0, 0), 1
)
)
region.entities.append(geometry.Line(geometry.Coordinate(1, 1), geometry.Coordinate(-1, 0)))
region.entities.append(geometry.Line(geometry.Coordinate(0, 1), geometry.Coordinate(-1, 0)))

return region

Expand Down Expand Up @@ -302,6 +302,73 @@ def test_region_is_closed():
assert region.is_closed()


def test_region_contains_same_entities():
region = generate_constant_region()

expected_region = region
expected_region.entities = geometry.reverse_entities(region.entities)

assert region == expected_region


def test_reverse_entity():
entity = geometry.Entity(geometry.Coordinate(0, 0), geometry.Coordinate(1, 1))
expected_entity = geometry.Entity(geometry.Coordinate(1, 1), geometry.Coordinate(0, 0))

assert entity.reverse() == expected_entity


def test_reverse_line():
region = generate_constant_region()
line = region.entities[0]
expected_line = geometry.Line(line.end, line.start)

assert line.reverse() == expected_line


def test_reverse_arc():
region = generate_constant_region()
arc = region.entities[1]
expected_line = geometry.Arc(arc.end, arc.start, arc.centre, -arc.radius)

assert arc.reverse() == expected_line


def test_entities_same():
region = generate_constant_region()
region_expected = generate_constant_region()

assert geometry.entities_same(region.entities, region_expected.entities)


def test_entities_same_1():
region = generate_constant_region()

entities = [region.entities[i] for i in range(1, len(region.entities))] + [
region.entities[i] for i in range(0, 1)
]

assert geometry.entities_same(region.entities, entities)


def test_reverse_entities():
region = generate_constant_region()

expected_entities = []

for entity in region.entities:
if isinstance(entity, geometry.Line):
expected_entities.append(geometry.Line(entity.end, entity.start))
elif isinstance(entity, geometry.Arc):
expected_entities.append(
geometry.Arc(entity.end, entity.start, entity.centre, -entity.radius)
)

expected_entities.reverse()

assert geometry.reverse_entities(region.entities) == expected_entities


def test_line_get_coordinate_from_percentage_distance():
line = geometry.Line(geometry.Coordinate(0, 0), geometry.Coordinate(2, 0))

Expand Down Expand Up @@ -541,3 +608,144 @@ def test_unite_regions_2():
union = mc.unite_regions(square, [triangle])

assert expected_region == union


def test_check_collisions():
"""Collision Type : Collision detected.
No vertices from the other region within the other region."""
# Before After
#
# |---|
# | |
# |--|---|--|
# | | | | -> Collision detected between regions
# | | | |
# |--|---|--|
# | |
# |---|
#
region_a = generate_constant_region()

region_b = geometry.Region()
region_b.add_entity(geometry.Line(geometry.Coordinate(0, -2), geometry.Coordinate(1, 2)))
region_b.add_entity(geometry.Line(geometry.Coordinate(1, 2), geometry.Coordinate(5, -3)))
region_b.add_entity(geometry.Line(geometry.Coordinate(5, -3), geometry.Coordinate(0, -2)))

collisions = mc.check_collisions(region_a, [region_b, mc.get_region("Stator")])
num_collisions = len(collisions)

assert num_collisions == 1
assert collisions[0] == region_b


def test_check_collisions_1():
"""Collision Type : Collision Detected.
Two vertices from the other region within the other region."""
# Before After
#
# |---------|
# | | -> Collision detected between regions
# | |---| |
# |--|---|--|
# | |
# |---|
#
region_a = generate_constant_region()

region_b = geometry.Region()
region_b.add_entity(
geometry.Line(geometry.Coordinate(-0.2, -2), geometry.Coordinate(-0.2, 0.2))
)
region_b.add_entity(
geometry.Line(geometry.Coordinate(-0.2, 0.2), geometry.Coordinate(0.2, 0.2))
)
region_b.add_entity(geometry.Line(geometry.Coordinate(0.2, 0.2), geometry.Coordinate(0.2, -2)))
region_b.add_entity(geometry.Line(geometry.Coordinate(0.2, -2), geometry.Coordinate(-0.2, -2)))

collisions = mc.check_collisions(region_a, [region_b, mc.get_region("Stator")])
num_collisions = len(collisions)

assert num_collisions == 1
assert collisions[0] == region_b


def test_check_collisions_2():
"""Collision Type : No collision.
Regions touching on single entity"""
# Before After
#
# |---------|
# | | -> No collision detected between regions
# | |
# |--|---|--|
# | |
# |---|
#
region_a = generate_constant_region()

region_b = geometry.Region()
region_b.add_entity(geometry.Line(geometry.Coordinate(-0.2, -2), geometry.Coordinate(-0.2, 0)))
region_b.add_entity(geometry.Line(geometry.Coordinate(-0.2, 0), geometry.Coordinate(0.2, 0)))
region_b.add_entity(geometry.Line(geometry.Coordinate(0.2, 0), geometry.Coordinate(0.2, -2)))
region_b.add_entity(geometry.Line(geometry.Coordinate(0.2, -2), geometry.Coordinate(-0.2, -2)))

collisions = mc.check_collisions(region_a, [region_b, mc.get_region("Stator")])
num_collisions = len(collisions)

assert num_collisions == 0


def test_check_collisions_3():
"""Collision Type : Collision detected.
No vertices from the other region within the other region.
Square region drawn clockwise."""
# Before After
#
# |---|
# | |
# |--|---|--|
# | | | | -> Collision detected between regions
# | | | |
# |--|---|--|
# | |
# |---|
#
s_p = []
s_l = []

s_p.append(geometry.Coordinate(0, 0))
s_p.append(geometry.Coordinate(0, 2))
s_p.append(geometry.Coordinate(2, 2))
s_p.append(geometry.Coordinate(2, 0))

s_l.append(geometry.Line(s_p[0], s_p[1]))
s_l.append(geometry.Line(s_p[1], s_p[2]))
s_l.append(geometry.Line(s_p[2], s_p[3]))
s_l.append(geometry.Line(s_p[3], s_p[0]))

square = geometry.Region()
for entity in s_l:
square.add_entity(entity)

t_p = []
t_l = []

t_p.append(geometry.Coordinate(1, 2.2))
t_p.append(geometry.Coordinate(2.2, 1))
t_p.append(geometry.Coordinate(4, 4))

t_l.append(geometry.Line(t_p[0], t_p[1]))
t_l.append(geometry.Line(t_p[1], t_p[2]))
t_l.append(geometry.Line(t_p[2], t_p[0]))

triangle = geometry.Region()
for entity in t_l:
triangle.add_entity(entity)

collisions = mc.check_collisions(triangle, [square])
assert len(collisions) == 1
assert collisions[0] == square

collisions = mc.check_collisions(square, [triangle])
assert len(collisions) == 1
assert collisions[0] == triangle

0 comments on commit 6368914

Please sign in to comment.