Skip to content

Commit 01f56d5

Browse files
jonahrbRobPasMue
andauthored
Add Cylinder Primitive/Surface and its CylinderEvaluation (#357)
Co-authored-by: Roberto Pastor Muela <[email protected]>
1 parent b4eb511 commit 01f56d5

File tree

2 files changed

+229
-137
lines changed

2 files changed

+229
-137
lines changed
Lines changed: 159 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
""" Provides the ``Cylinder`` class."""
22

33
from beartype import beartype as check_input_types
4-
from beartype.typing import Optional, Union
4+
from beartype.typing import Union
55
import numpy as np
6-
from pint import Unit
6+
from pint import Quantity
77

8-
from ansys.geometry.core.math import Point3D, UnitVector3D, Vector3D
9-
from ansys.geometry.core.misc import UNIT_LENGTH, UNITS, check_pint_unit_compatibility
8+
from ansys.geometry.core.math import UNITVECTOR3D_X, UNITVECTOR3D_Z, Point3D, UnitVector3D, Vector3D
9+
from ansys.geometry.core.misc import Distance
10+
from ansys.geometry.core.primitives.circle import Circle
11+
from ansys.geometry.core.primitives.line import Line
12+
from ansys.geometry.core.primitives.surface_evaluation import ParamUV, SurfaceEvaluation
1013
from ansys.geometry.core.typing import Real, RealSequence
1114

1215

@@ -18,101 +21,188 @@ class Cylinder:
1821
----------
1922
origin : Union[~numpy.ndarray, RealSequence, Point3D]
2023
Origin of the cylinder.
21-
direction_x : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
22-
X-plane direction.
23-
direction_y : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
24-
Y-plane direction.
25-
radius : Real
24+
radius : Union[Quantity, Distance, Real]
2625
Radius of the cylinder.
27-
height : Real
28-
Height of the cylinder.
29-
unit : Unit, default: UNIT_LENGTH
30-
Units for defining the radius and height.
26+
reference : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
27+
X-axis direction.
28+
axis : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
29+
Z-axis direction.
3130
"""
3231

3332
@check_input_types
3433
def __init__(
3534
self,
3635
origin: Union[np.ndarray, RealSequence, Point3D],
37-
direction_x: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D],
38-
direction_y: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D],
39-
radius: Real,
40-
height: Real,
41-
unit: Optional[Unit] = UNIT_LENGTH,
36+
radius: Union[Quantity, Distance, Real],
37+
reference: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D] = UNITVECTOR3D_X,
38+
axis: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D] = UNITVECTOR3D_Z,
4239
):
4340
"""Constructor method for the ``Cylinder`` class."""
4441

45-
check_pint_unit_compatibility(unit, UNIT_LENGTH)
46-
self._unit = unit
47-
_, self._base_unit = UNITS.get_base_units(unit)
48-
4942
self._origin = Point3D(origin) if not isinstance(origin, Point3D) else origin
50-
self._direction_x = (
51-
UnitVector3D(direction_x) if not isinstance(direction_x, UnitVector3D) else direction_x
52-
)
53-
self._direction_y = (
54-
UnitVector3D(direction_y) if not isinstance(direction_y, UnitVector3D) else direction_y
43+
self._reference = (
44+
UnitVector3D(reference) if not isinstance(reference, UnitVector3D) else reference
5545
)
46+
self._axis = UnitVector3D(axis) if not isinstance(axis, UnitVector3D) else axis
47+
self._axis = UnitVector3D(axis) if not isinstance(axis, UnitVector3D) else axis
48+
if not self._reference.is_perpendicular_to(self._axis):
49+
raise ValueError("Cylinder reference (dir_x) and axis (dir_z) must be perpendicular.")
5650

57-
# Store values in base unit
58-
self._radius = UNITS.convert(radius, self._unit, self._base_unit)
59-
self._height = UNITS.convert(height, self._unit, self._base_unit)
51+
self._radius = radius if isinstance(radius, Distance) else Distance(radius)
52+
if self._radius.value <= 0:
53+
raise ValueError("Radius must be a real positive value.")
6054

6155
@property
6256
def origin(self) -> Point3D:
6357
"""Origin of the cylinder."""
6458
return self._origin
6559

66-
@origin.setter
67-
@check_input_types
68-
def origin(self, origin: Point3D) -> None:
69-
self._origin = origin
70-
7160
@property
72-
def radius(self) -> Real:
61+
def radius(self) -> Quantity:
7362
"""Radius of the cylinder."""
74-
return UNITS.convert(self._radius, self._base_unit, self._unit)
75-
76-
@radius.setter
77-
@check_input_types
78-
def radius(self, radius: Real) -> None:
79-
"""Set the radius of the cylinder."""
80-
self._radius = UNITS.convert(radius, self._unit, self._base_unit)
63+
return self._radius.value
8164

8265
@property
83-
def height(self) -> Real:
84-
"""Height of the cylinder."""
85-
return UNITS.convert(self._height, self._base_unit, self._unit)
66+
def dir_x(self) -> UnitVector3D:
67+
"""X-direction of the cylinder."""
68+
return self._reference
8669

87-
@height.setter
88-
@check_input_types
89-
def height(self, height: Real) -> None:
90-
"""Set the height of the cylinder."""
91-
self._height = UNITS.convert(height, self._unit, self._base_unit)
70+
@property
71+
def dir_y(self) -> UnitVector3D:
72+
"""Y-direction of the cylinder."""
73+
return self.dir_z.cross(self.dir_x)
9274

9375
@property
94-
def unit(self) -> Unit:
95-
"""Unit of the radius and height."""
96-
return self._unit
76+
def dir_z(self) -> UnitVector3D:
77+
"""Z-direction of the cylinder."""
78+
return self._axis
9779

98-
@unit.setter
99-
@check_input_types
100-
def unit(self, unit: Unit) -> None:
101-
"""Set the unit of the object."""
102-
check_pint_unit_compatibility(unit, UNIT_LENGTH)
103-
self._unit = unit
80+
def surface_area(self, height: Union[Quantity, Distance, Real]) -> Quantity:
81+
"""Surface area of the cylinder."""
82+
height = height if isinstance(height, Distance) else Distance(height)
83+
if height.value <= 0:
84+
raise ValueError("Height must be a real positive value.")
85+
86+
return 2 * np.pi * self.radius * height.value + 2 * np.pi * self.radius**2
87+
88+
def volume(self, height: Union[Quantity, Distance, Real]) -> Quantity:
89+
"""Volume of the cylinder."""
90+
height = height if isinstance(height, Distance) else Distance(height)
91+
if height.value <= 0:
92+
raise ValueError("Height must be a real positive value.")
93+
94+
return np.pi * self.radius**2 * height.value
10495

10596
@check_input_types
106-
def __eq__(self, other: object) -> bool:
97+
def __eq__(self, other: "Cylinder") -> bool:
10798
"""Equals operator for the ``Cylinder`` class."""
10899
return (
109-
self._origin == other.origin
110-
and self._radius == other.radius
111-
and self._height == other.height
112-
and self._direction_x == other._direction_x
113-
and self._direction_y == other._direction_y
100+
self._origin == other._origin
101+
and self._radius == other._radius
102+
and self._reference == other._reference
103+
and self._axis == other._axis
104+
)
105+
106+
def evaluate(self, parameter: ParamUV) -> "CylinderEvaluation":
107+
"""Evaluate the cylinder at the given parameters."""
108+
return CylinderEvaluation(self, parameter)
109+
110+
def project_point(self, point: Point3D) -> "CylinderEvaluation":
111+
"""Project a point onto the cylinder and return its ``CylinderEvaluation``."""
112+
circle = Circle(self.origin, self.radius, self.dir_x, self.dir_z)
113+
u = circle.project_point(point).parameter
114+
115+
line = Line(self.origin, self.dir_z)
116+
v = line.project_point(point).parameter
117+
118+
return CylinderEvaluation(self, ParamUV(u, v))
119+
120+
121+
class CylinderEvaluation(SurfaceEvaluation):
122+
"""
123+
Provides ``Cylinder`` evaluation at certain parameters.
124+
125+
Parameters
126+
----------
127+
cylinder: ~ansys.geometry.core.primitives.cylinder.Cylinder
128+
The ``Cylinder`` object to be evaluated.
129+
parameter: ParamUV
130+
The parameters (u, v) at which the ``Cylinder`` evaluation is requested.
131+
"""
132+
133+
def __init__(self, cylinder: Cylinder, parameter: ParamUV) -> None:
134+
"""``CylinderEvaluation`` class constructor."""
135+
self._cylinder = cylinder
136+
self._parameter = parameter
137+
138+
@property
139+
def cylinder(self) -> Cylinder:
140+
"""The cylinder being evaluated."""
141+
return self._cylinder
142+
143+
@property
144+
def parameter(self) -> ParamUV:
145+
"""The parameter that the evaluation is based upon."""
146+
return self._parameter
147+
148+
def position(self) -> Point3D:
149+
"""The point on the cylinder, based on the evaluation."""
150+
return (
151+
self.cylinder.origin
152+
+ self.cylinder.radius.m * self.__cylinder_normal()
153+
+ self.parameter.v * self.cylinder.dir_z
154+
)
155+
156+
def normal(self) -> UnitVector3D:
157+
"""The normal to the surface."""
158+
return UnitVector3D(self.__cylinder_normal())
159+
160+
def __cylinder_normal(self) -> Vector3D:
161+
"""The normal to the cylinder."""
162+
return (
163+
np.cos(self.parameter.u) * self.cylinder.dir_x
164+
+ np.sin(self.parameter.u) * self.cylinder.dir_y
114165
)
115166

116-
def __ne__(self, other) -> bool:
117-
"""Not equals operator for the ``Cylinder`` class."""
118-
return not self == other
167+
def __cylinder_tangent(self) -> Vector3D:
168+
"""The tangent to the cylinder."""
169+
return (
170+
-np.sin(self.parameter.u) * self.cylinder.dir_x
171+
+ np.cos(self.parameter.u) * self.cylinder.dir_y
172+
)
173+
174+
def u_derivative(self) -> Vector3D:
175+
"""The first derivative with respect to u."""
176+
return self.cylinder.radius.m * self.__cylinder_tangent()
177+
178+
def v_derivative(self) -> Vector3D:
179+
"""The first derivative with respect to v."""
180+
return self.cylinder.dir_z
181+
182+
def uu_derivative(self) -> Vector3D:
183+
"""The second derivative with respect to u."""
184+
return -self.cylinder.radius.m * self.__cylinder_normal()
185+
186+
def uv_derivative(self) -> Vector3D:
187+
"""The second derivative with respect to u and v."""
188+
return Vector3D([0, 0, 0])
189+
190+
def vv_derivative(self) -> Vector3D:
191+
"""The second derivative with respect to v."""
192+
return Vector3D([0, 0, 0])
193+
194+
def min_curvature(self) -> Real:
195+
"""The minimum curvature."""
196+
return 0
197+
198+
def min_curvature_direction(self) -> UnitVector3D:
199+
"""The minimum curvature direction."""
200+
return UnitVector3D(self.cylinder.dir_z)
201+
202+
def max_curvature(self) -> Real:
203+
"""The maximum curvature."""
204+
return 1.0 / self.cylinder.radius.m
205+
206+
def max_curvature_direction(self) -> UnitVector3D:
207+
"""The maximum curvature direction."""
208+
return UnitVector3D(self.u_derivative())

0 commit comments

Comments
 (0)