Skip to content

Commit 7b41697

Browse files
RobPasMuepyansys-ci-botPipKat
authored
feat: add new arc constructors (#1208)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: Kathy Pippert <[email protected]>
1 parent 7a4762a commit 7b41697

File tree

11 files changed

+676
-5
lines changed

11 files changed

+676
-5
lines changed

doc/changelog.d/1208.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feat: add new arc constructors

doc/source/examples/02_sketching/basic_usage.mystnb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,31 @@ sketch.select("Arc")
105105
sketch.plot_selection()
106106
```
107107

108+
There are also additional ways to draw arcs, such as by specifying the start, center point, and angle.
109+
110+
```{code-cell} ipython3
111+
start_point = Point2D([2, 1], unit=u.m)
112+
center_point = Point2D([1, 1], unit=u.m)
113+
angle = 90
114+
sketch.arc_from_start_center_and_angle(
115+
start_point, center_point, angle=90, tag="Arc_from_start_center_angle"
116+
)
117+
sketch.select("Arc_from_start_center_angle")
118+
sketch.plot_selection()
119+
```
120+
121+
Or by specifying the start, end point, and radius.
122+
123+
```{code-cell} ipython3
124+
start_point, end_point = Point2D([2, 1], unit=u.m), Point2D([0, 1], unit=u.meter)
125+
radius = 1 * u.m
126+
sketch.arc_from_start_end_and_radius(
127+
start_point, end_point, radius, tag="Arc_from_start_end_radius"
128+
)
129+
sketch.select("Arc_from_start_end_radius")
130+
sketch.plot_selection()
131+
```
132+
108133
### Draw a slot
109134

110135
You draw a slot by specifying the center, width, and height.

doc/source/examples/04_applied/01_naca_airfoils.mystnb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def naca_airfoil_4digits(number: Union[int, str], n_points: int = 200) -> List[P
7878
# near the leading edge
7979
x = (1 - np.cos(i / (n_points - 1) * np.pi)) / 2
8080

81-
# Check if it is a symmetric airfoil or not
81+
# Check if it is a symmetric airfoil
8282
if p == 0 and m == 0:
8383
# Camber line is zero in this case
8484
yc = 0

src/ansys/geometry/core/connection/product_instance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ def _wait_for_backend(host: str, port: int, timeout: int):
363363

364364
def _is_port_available(port: int, host: str = "localhost") -> bool:
365365
"""
366-
Check whether the argument port is available or not.
366+
Check whether the argument port is available.
367367
368368
The optional argument is the ip address where to check port availability.
369369
Its default is ``localhost``.

src/ansys/geometry/core/math/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
)
4040
from ansys.geometry.core.math.frame import Frame
4141
from ansys.geometry.core.math.matrix import Matrix, Matrix33, Matrix44
42+
from ansys.geometry.core.math.misc import get_two_circle_intersections
4243
from ansys.geometry.core.math.plane import Plane
4344
from ansys.geometry.core.math.point import Point2D, Point3D
4445
from ansys.geometry.core.math.vector import UnitVector2D, UnitVector3D, Vector2D, Vector3D
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Provides auxiliary math functions for PyAnsys Geometry."""
23+
24+
25+
from beartype import beartype as check_input_types
26+
from beartype.typing import Tuple, Union
27+
import numpy as np
28+
29+
from ansys.geometry.core.typing import Real
30+
31+
32+
@check_input_types
33+
def get_two_circle_intersections(
34+
x0: Real, y0: Real, r0: Real, x1: Real, y1: Real, r1: Real
35+
) -> Union[Tuple[Tuple[Real, Real], Tuple[Real, Real]], None]:
36+
"""
37+
Get the intersection points of two circles.
38+
39+
Parameters
40+
----------
41+
x0 : Real
42+
x coordinate of the first circle.
43+
y0 : Real
44+
y coordinate of the first circle.
45+
r0 : Real
46+
Radius of the first circle.
47+
x1 : Real
48+
x coordinate of the second circle.
49+
y1 : Real
50+
y coordinate of the second circle.
51+
r1 : Real
52+
Radius of the second circle.
53+
54+
Notes
55+
-----
56+
This function is based on the following StackOverflow post:
57+
https://stackoverflow.com/questions/55816902/finding-the-intersection-of-two-circles
58+
59+
That post is based on the following implementation:
60+
https://paulbourke.net/geometry/circlesphere/
61+
62+
Returns
63+
-------
64+
Union[Tuple[Tuple[Real, Real], Tuple[Real, Real]], None]
65+
Intersection points of the two circles if they intersect.
66+
The points are returned as ``((x3, y3), (x4, y4))``, where ``(x3, y3)`` and ``(x4, y4)``
67+
are the intersection points of the two circles. If the circles do not
68+
intersect, then ``None`` is returned.
69+
"""
70+
# circle 1: (x0, y0), radius r0
71+
# circle 2: (x1, y1), radius r1
72+
d = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
73+
74+
# non-intersecting
75+
if d > r0 + r1:
76+
return None
77+
# one circle within other
78+
if d < abs(r0 - r1):
79+
return None
80+
# coincident circles
81+
if d == 0 and r0 == r1:
82+
return None
83+
else:
84+
a = (r0**2 - r1**2 + d**2) / (2 * d)
85+
h = np.sqrt(r0**2 - a**2)
86+
x2 = x0 + a * (x1 - x0) / d
87+
y2 = y0 + a * (y1 - y0) / d
88+
89+
dx = h * (y1 - y0) / d
90+
dy = h * (x1 - x0) / d
91+
x3 = x2 + dx
92+
y3 = y2 - dy
93+
94+
x4 = x2 - dx
95+
y4 = y2 + dy
96+
97+
return ((x3, y3), (x4, y4))

src/ansys/geometry/core/shapes/surfaces/torus.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
"""Provides for creating and managing a torus."""
2323

2424
from functools import cached_property
25-
from typing import Tuple
2625

2726
from beartype import beartype as check_input_types
2827
from beartype.typing import Tuple, Union

src/ansys/geometry/core/sketch/arc.py

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@
2222
"""Provides for creating and managing an arc."""
2323

2424
from beartype import beartype as check_input_types
25-
from beartype.typing import Optional
25+
from beartype.typing import Optional, Union
2626
import numpy as np
2727
from pint import Quantity
2828
import pyvista as pv
2929

30+
from ansys.geometry.core.math.matrix import Matrix
3031
from ansys.geometry.core.math.point import Point2D
3132
from ansys.geometry.core.math.vector import Vector2D
32-
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS
33+
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Angle, Distance
3334
from ansys.geometry.core.misc.units import UNITS
3435
from ansys.geometry.core.sketch.edge import SketchEdge
36+
from ansys.geometry.core.typing import Real
3537

3638

3739
class Arc(SketchEdge):
@@ -270,6 +272,7 @@ def __arc_pyvista_hack(self):
270272
return arc_sub1 + arc_sub2
271273

272274
@classmethod
275+
@check_input_types
273276
def from_three_points(cls, start: Point2D, inter: Point2D, end: Point2D):
274277
"""
275278
Create an arc from three given points.
@@ -342,3 +345,122 @@ def from_three_points(cls, start: Point2D, inter: Point2D, end: Point2D):
342345

343346
# Finally... you can create the arc
344347
return Arc(start=start, end=end, center=center, clockwise=is_clockwise)
348+
349+
@classmethod
350+
@check_input_types
351+
def from_start_end_and_radius(
352+
cls,
353+
start: Point2D,
354+
end: Point2D,
355+
radius: Union[Quantity, Distance, Real],
356+
convex_arc: Optional[bool] = False,
357+
clockwise: Optional[bool] = False,
358+
):
359+
"""
360+
Create an arc from a starting point, an ending point, and a radius.
361+
362+
Parameters
363+
----------
364+
start : Point2D
365+
Starting point of the arc.
366+
end : Point2D
367+
Ending point of the arc.
368+
radius : Union[Quantity, Distance, Real]
369+
Radius of the arc.
370+
convex_arc : bool, default: False
371+
Whether the arc is convex. The default is ``False``.
372+
When ``False``, the arc is concave. When ``True``, the arc is convex.
373+
clockwise : bool, default: False
374+
Whether the arc spans the clockwise angle between the start and end points.
375+
When ``False``, the arc spans the counter-clockwise angle.
376+
When ``True``, the arc spands the clockwise angle.
377+
378+
Returns
379+
-------
380+
Arc
381+
Arc generated from the three points.
382+
"""
383+
# Compute the potential centers of the circle
384+
# that could generate the arc
385+
from ansys.geometry.core.math.misc import get_two_circle_intersections
386+
387+
# Sanitize the radius
388+
radius = radius if isinstance(radius, Distance) else Distance(radius)
389+
if radius.value <= 0:
390+
raise ValueError("Radius must be a real positive value.")
391+
392+
# Unpack the points into its coordinates (in DEFAULT_UNITS.LENGTH)
393+
x_s, y_s = start.tolist()
394+
x_e, y_e = end.tolist()
395+
r0 = r1 = radius.value.m_as(DEFAULT_UNITS.LENGTH)
396+
397+
# Compute the potential centers of the circle
398+
centers = get_two_circle_intersections(x0=x_s, y0=y_s, r0=r0, x1=x_e, y1=y_e, r1=r1)
399+
if centers is None:
400+
raise ValueError("The provided points and radius do not yield a valid arc.")
401+
402+
# Choose the center depending on if the arc is convex
403+
center = Point2D(centers[1] if convex_arc else centers[0])
404+
405+
# Create the arc
406+
return Arc(start=start, end=end, center=center, clockwise=clockwise)
407+
408+
@classmethod
409+
@check_input_types
410+
def from_start_center_and_angle(
411+
cls,
412+
start: Point2D,
413+
center: Point2D,
414+
angle: Union[Angle, Quantity, Real],
415+
clockwise: Optional[bool] = False,
416+
):
417+
"""
418+
Create an arc from a starting point, a center point, and an angle.
419+
420+
Parameters
421+
----------
422+
start : Point2D
423+
Starting point of the arc.
424+
center : Point2D
425+
Center point of the arc.
426+
angle : Union[Angle, Quantity, Real]
427+
Angle of the arc.
428+
clockwise : bool, default: False
429+
Whether the provided angle should be considered clockwise.
430+
When ``False``, the angle is considered counter-clockwise.
431+
When ``True``, the angle is considered clockwise.
432+
433+
Returns
434+
-------
435+
Arc
436+
Arc generated from the three points.
437+
"""
438+
# Define a 2D vector from the center to the start point
439+
to_start_vector = Vector2D.from_points(center, start)
440+
441+
# Perform sanity check for the angle
442+
angle = angle if isinstance(angle, Angle) else Angle(angle)
443+
rad_angle = angle.value.m_as(UNITS.radian)
444+
cang = np.cos(rad_angle)
445+
sang = np.sin(rad_angle)
446+
447+
# Rotate the vector by the angle
448+
if clockwise:
449+
rot_matrix = Matrix([[cang, sang], [-sang, cang]])
450+
else:
451+
rot_matrix = Matrix([[cang, -sang], [sang, cang]])
452+
453+
# Compute the end vector
454+
to_end_vector = rot_matrix @ to_start_vector
455+
456+
# Define the end point
457+
end = Point2D(
458+
[
459+
to_end_vector[0] + center.x.to_base_units().m,
460+
to_end_vector[1] + center.y.to_base_units().m,
461+
],
462+
center.base_unit,
463+
)
464+
465+
# Create the arc
466+
return Arc(start=start, end=end, center=center, clockwise=clockwise)

0 commit comments

Comments
 (0)