diff --git a/src/ansys/geometry/core/shapes/arc.py b/src/ansys/geometry/core/shapes/arc.py new file mode 100644 index 0000000000..9403ade304 --- /dev/null +++ b/src/ansys/geometry/core/shapes/arc.py @@ -0,0 +1,152 @@ +"""``ArcSketch`` class module.""" +from typing import List, Optional + +import numpy as np + +from ansys.geometry.core.math import Point, Vector +from ansys.geometry.core.math.vector import UnitVector +from ansys.geometry.core.shapes.base import BaseShape +from ansys.geometry.core.typing import Real + + +class Arc(BaseShape): + """A class for modeling arcs.""" + + def __init__( + self, + origin: Point, + start_point: Point, + end_point: Point, + ): + """Initializes the arc shape. + + Parameters + ---------- + origin : Point + A :class:``Point`` representing the center of the arc. + start_point : Point + A :class:``Point`` representing the start of the arc. + end_points : Point + A :class:``Point`` representing the end of the arc. + + """ + # Verify both points are not the same + if start_point == end_point: + raise ValueError("Start and end points must be different.") + if origin == start_point: + raise ValueError("Center and start points must be different.") + if origin == end_point: + raise ValueError("Center and end points must be different.") + + self._origin, self._start_point, self._end_point = (origin, start_point, end_point) + + self._start_vector = Vector.from_points(self._origin, self._start_point) + self._end_vector = Vector.from_points(self._origin, self._end_point) + + super().__init__( + origin, + dir_1=UnitVector(self._start_vector / self._start_vector.norm), + dir_2=UnitVector(self._end_vector / self._end_vector.norm), + ) + + self._radius = self._start_vector.norm + + @property + def start_point(self) -> Point: + """Return the start of the arc line. + + Returns + ------- + Point + Starting point of the arc line. + + """ + return self._start_point + + @property + def end_point(self) -> Point: + """Return the end of the arc line. + + Returns + ------- + Point + Ending point of the arc line. + + """ + return self._end_point + + @property + def radius(self) -> Real: + """The radius of the arc. + + Returns + ------- + Real + The radius of the arc. + + """ + return self._radius + + @property + def angle(self) -> Real: + """The angle of the arc. + + Returns + ------- + Real + The angle of the arc. + + """ + + self._angle = np.arccos(self._start_vector * self._end_vector / self.radius**2) + return self._angle + + @property + def length(self) -> Real: + """Return the length of the arc. + + Returns + ------- + Real + The length of the arc. + + """ + return 2 * np.pi * self.radius * self.angle + + @property + def sector_area(self) -> Real: + """Return the area of the sector of the arc. + + Returns + ------- + Real + The area of the sector of the arc. + + """ + return self.radius**2 * self.angle / 2 + + def local_points(self, num_points: Optional[int] = 100) -> List[Point]: + """Returns al list containing all the points belonging to the shape. + + Parameters + ---------- + num_points : int + Desired number of points belonging to the shape. + + Returns + ------- + List[Point] + A list of points representing the shape. + + """ + theta = np.linspace(0, self.angle, num_points) + return [ + Point( + [ + self.radius * np.cos(ang) + self.origin[0], + self.radius * np.sin(ang) + self.origin[1], + self.origin[2], + ] + ) + for ang in theta + ] diff --git a/src/ansys/geometry/core/sketch.py b/src/ansys/geometry/core/sketch.py index 222d80fd31..a559e080e2 100644 --- a/src/ansys/geometry/core/sketch.py +++ b/src/ansys/geometry/core/sketch.py @@ -5,6 +5,7 @@ from ansys.geometry.core.math import UNIT_VECTOR_X, UNIT_VECTOR_Y, ZERO_VECTOR3D from ansys.geometry.core.math.point import Point from ansys.geometry.core.math.vector import UnitVector, Vector +from ansys.geometry.core.shapes.arc import Arc from ansys.geometry.core.shapes.base import BaseShape from ansys.geometry.core.shapes.circle import Circle from ansys.geometry.core.shapes.ellipse import Ellipse @@ -232,3 +233,30 @@ def draw_polygon( polygon = Polygon(radius, sides, origin, dir_1=dir_1, dir_2=dir_2) self.append_shape(polygon) return polygon + + def draw_arc( + self, + center: Point, + start_point: Point, + end_point: Point, + ): + """Create an arc shape on the sketch. + + Parameters + ---------- + center : Point + A :class:``Point`` representing the center of the arc. + start_point : Point + A :class:``Point`` representing the start of the shape. + end_points : Point + A :class:``Point`` representing the end of the shape. + + Returns + ------- + Arc + An object representing the arc added to the sketch. + + """ + arc = Arc(center, start_point, end_point) + self.append_shape(arc) + return arc diff --git a/tests/test_shapes.py b/tests/test_shapes.py index 90ad90c6a9..73a3e1a2c2 100644 --- a/tests/test_shapes.py +++ b/tests/test_shapes.py @@ -249,3 +249,27 @@ def test_errors_segment(): match="Parameters 'origin' and 'end' have the same values. No segment can be created.", ): Segment(Point([10, 20, 30]), Point([10, 20, 30])) + + +def test_create_arc(): + """Test arc shape creation in a sketch.""" + + # Create a Sketch instance + sketch = Sketch() + + # Draw an arc in previous sketch + origin = Point([1, 1, 0], unit=UNITS.meter) + start_point = Point([1, 3, 0], unit=UNITS.meter) + end_point = Point([3, 1, 0], unit=UNITS.meter) + arc = sketch.draw_arc(origin, start_point, end_point) + + # Check attributes are expected ones + assert_allclose(arc.radius, 2) + assert_allclose(arc.sector_area, np.pi) + assert_allclose(arc.length, 2 * np.pi**2) + + # Check points are expected ones + local_points = arc.local_points(num_points=5) + assert abs(all(local_points[0] - Point([3, 1, 0]))) <= DOUBLE_EPS + assert abs(all(local_points[1] - Point([2.8477, 1.7653, 0]))) <= DOUBLE_EPS + assert abs(all(local_points[4] - Point([1, 3, 0]))) <= DOUBLE_EPS