diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 0cd227a0e..cd6274f1d 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -24,7 +24,7 @@ from cmath import polar, rect from copy import deepcopy from enum import Enum -from math import atan2, cos, degrees, inf, isclose, radians, sin, sqrt +from math import atan2, cos, degrees, inf, isclose, pi, radians, sin, sqrt GEOM_TOLERANCE = 1e-6 @@ -404,6 +404,74 @@ def unite(self, regions): united_region = self.motorcad_instance.unite_regions(self, regions) self.update(united_region) + def boundary_split(self, region=None): + """Split and repeat a region that overhangs the boundary. + + Parameters + ---------- + region : ansys.motorcad.core.geometry.Region + Motor-CAD region object whose boundary self overhangs + """ + if not region: + try: + region = self.parent + except: + raise Exception( + "You must set the parent region of " + + str(self.name) + + "or provide a region whose boundary " + + str(self.name) + + " overhangs." + ) + + duplication_number = region.duplications + mc = self.motorcad_instance + + # get the portion of self that overhangs the region boundary + self_overhang = deepcopy(self) + try: + self_overhang.subtract(region) + except Exception as e: + if "Unable to subtract" in str(e): + raise Exception( + str(self.name) + " does not overhang the boundary of " + str(region.name) + ) + + # get the portion of self that is within the region boundary + self_within = deepcopy(self) + self_within.subtract(self_overhang) + + # check whether self crosses the upper or lower boundary + # if it crosses the lower boundary, set e = 1 so that we rotate by a positive angle + # if it doesn't cross the lower boundary, it must cross the upper boundary so rotate by + # a negative angle + e = -1 + self_name = self.name + self_within.name = self_name + self_number = int("".join(filter(str.isdigit, self_name))) + string_1 = "_" + str(self_number) + string_2 = "_" + str(self_number + 1) + + if string_1 in self_name: + self_overhang.name = self_name.replace(string_1, string_2) + else: + self_overhang.name = self_name + "_boundary_split" + + for entity in self.entities: + if entity.start.y < 0 or entity.end.y < 0: + e = 1 + if string_1 in self_name: + self_within.name = self_name.replace(string_1, string_2) + else: + self_within.name = self_name + "_boundary_split" + self_overhang.name = self_name + + # rotate the portion of self that overhangs the region boundary to the opposite boundary + self_overhang.rotate(Coordinate(0, 0), e * 360 / duplication_number) + + self.update(self_within) + return self_overhang + def collides(self, regions): """Check whether any of the specified regions collide with self. @@ -570,6 +638,88 @@ def edit_point(self, old_coordinates, new_coordinates): # Check Arc is still valid _ = entity.centre + def round_corner(self, corner_coordinate, radius): + """Round the corner of a region. + + Parameters + ---------- + corner_coordinate : ansys.motorcad.core.geometry.Coordinate + Coordinate of the corner to be rounded. + radius : float + Radius by which the corner will be rounded. + """ + adj_entities = [] + entity_indicies = [] + coordinates = [] + # Get entities at corner + for index in range(len(self.entities)): + i = self.entities[index] + if i.coordinate_on_entity(corner_coordinate): + # coordinates.append(i.get_coordinate_from_distance(corner_coordinate, distance)) + adj_entities.append(i) + entity_indicies.append(index) + if not adj_entities: + raise Exception( + "Failed to find point on entity in region. " + "You must specify a corner in this region." + ) + if len(adj_entities) == 1: + raise Exception( + "Point found on only one entity in region. " + "You must specify a corner in this region." + ) + # if first and last entities, swap the positions + if entity_indicies[0] == 0 and entity_indicies[1] == len(self.entities) - 1: + entity_indicies[0], entity_indicies[1] = entity_indicies[1], entity_indicies[0] + adj_entities[0], adj_entities[1] = adj_entities[1], adj_entities[0] + # Calculate distances for adjacent entities + if type(adj_entities[0]) == Line: + angle_1 = degrees( + atan2( + adj_entities[0].start.y - adj_entities[0].end.y, + adj_entities[0].start.x - adj_entities[0].end.x, + ) + ) + else: # Arc + point_on_curve = adj_entities[0].get_coordinate_from_distance(corner_coordinate, 0.01) + angle_1 = degrees( + atan2( + point_on_curve.y - adj_entities[0].end.y, + point_on_curve.x - adj_entities[0].end.x, + ) + ) + if type(adj_entities[1]) == Line: + angle_2 = degrees( + atan2( + adj_entities[1].end.y - adj_entities[1].start.y, + adj_entities[1].end.x - adj_entities[1].start.x, + ) + ) + else: # Arc + point_on_curve = adj_entities[1].get_coordinate_from_distance(corner_coordinate, 0.01) + angle_2 = degrees( + atan2( + point_on_curve.y - adj_entities[1].start.y, + point_on_curve.x - adj_entities[1].start.x, + ) + ) + + corner_angle = abs(angle_1 - angle_2) + arc_angle = 90 + (90 - corner_angle) + half_chord = radius * sin(radians(arc_angle) / 2) + distance = abs(half_chord / (sin(radians(corner_angle) / 2))) + + for index in range(len(adj_entities)): + j = adj_entities[index] + coordinates.append(j.get_coordinate_from_distance(corner_coordinate, distance)) + if j.start == corner_coordinate: + j.start = coordinates[index] + elif j.end == corner_coordinate: + j.end = coordinates[index] + + corner_arc = Arc(coordinates[0], coordinates[1], radius=radius) + self.insert_entity(entity_indicies[0] + 1, corner_arc) + def find_entity_from_coordinates(self, coordinate_1, coordinate_2): """Search through region to find an entity with start and end coordinates. @@ -1210,6 +1360,34 @@ def get_coordinate_from_distance(self, ref_coordinate, distance): ) # sign of the radius accounts for clockwise/anticlockwise arcs return self.centre + Coordinate(*rt_to_xy(abs(self.radius), degrees(angle))) + def get_intersects_with_line(self, line, tolerance=0.0001): + """Get if a line intersects with this arc. + + Parameters + ---------- + line : ansys.motorcad.core.geometry.Line + Check if this Line entity intersects with self + + Returns + ------- + bool + """ + start = self.start + end = self.end + points = 1 / tolerance # 10000 + length = 2 * pi * self.radius * (abs(self.total_angle) / 360) + line_crossed = False + for i in range(int(points)): + point_1 = self.get_coordinate_from_distance(start, (i * (length / points))) + point_2 = self.get_coordinate_from_distance(point_1, length / points) + tangent = Line(point_1, point_2) + intersection = tangent.get_line_intersection(line) + if intersection == None: + pass + elif tangent.coordinate_on_entity(intersection): + line_crossed = True + return line_crossed + def mirror(self, mirror_line): """Mirror arc about a line. diff --git a/tests/test_geometry.py b/tests/test_geometry.py index b05963e0c..e67eab4b8 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -42,6 +42,7 @@ _orientation_of_three_points, rt_to_xy, ) +from ansys.motorcad.core.geometry_shapes import eq_triangle_h import ansys.motorcad.core.rpc_client_core as rpc_client_core from ansys.motorcad.core.rpc_client_core import DEFAULT_INSTANCE, set_default_instance @@ -1232,6 +1233,37 @@ def test_edit_point(): assert region.entities[2].start == ref_region.entities[2].start + translate +def test_round_corner(): + radius = 0.5 + triangle_1 = eq_triangle_h(5, 15, 45) + triangle_2 = eq_triangle_h(5, 15, 45) + for index in reversed(range(3)): + triangle_1.round_corner(triangle_1.entities[index].end, radius) + # draw_objects([triangle_1, triangle_2]) + + assert triangle_1.is_closed() + for i in range(3): + # check that the entities making up the rounded triangle are of the expected types + assert type(triangle_1.entities[2 * i]) == Line + assert type(triangle_1.entities[2 * i + 1]) == Arc + # check that the midpoints of the shortened lines are the same as the original lines + assert triangle_1.entities[2 * i].midpoint == triangle_2.entities[i].midpoint + + # check that the original corner coordinates are not on any of the rounded triangle's entities + corners = [] + for i in range(3): + corners.append(triangle_2.entities[i].end) + for entity in triangle_1.entities: + for i in range(3): + assert not entity.coordinate_on_entity(corners[i]) + + # check exception is raised when a point that is not a corner is specified + with pytest.raises(Exception): + triangle_1.round_corner(corners[0], radius) + with pytest.raises(Exception): + triangle_1.round_corner(triangle_1.entities[0].midpoint, radius) + + def test_subtract_regions(mc): """Test subtract rectangle from square to create cut out in square as shown below""" # Before After