Skip to content

Commit 45000a5

Browse files
jonahrbRobPasMue
andauthored
Cone Primitive, ConeEvaluation (#352)
Co-authored-by: Roberto Pastor Muela <roberto.pastormuela@ansys.com>
1 parent b5a6aa2 commit 45000a5

File tree

3 files changed

+296
-183
lines changed

3 files changed

+296
-183
lines changed

src/ansys/geometry/core/primitives/cone.py

Lines changed: 186 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33

44
from beartype import beartype as check_input_types
5-
from beartype.typing import Optional, Union
5+
from beartype.typing import Union
66
import numpy as np
7-
from pint import Unit
7+
from pint import Quantity
88

9-
from ansys.geometry.core.math import Point3D, UnitVector3D, Vector3D
10-
from ansys.geometry.core.misc import UNIT_ANGLE, UNIT_LENGTH, UNITS, check_pint_unit_compatibility
9+
from ansys.geometry.core.math import UNITVECTOR3D_X, UNITVECTOR3D_Z, Point3D, UnitVector3D, Vector3D
10+
from ansys.geometry.core.misc import Angle, Distance
11+
from ansys.geometry.core.primitives.line import Line
12+
from ansys.geometry.core.primitives.surface_evaluation import ParamUV, SurfaceEvaluation
1113
from ansys.geometry.core.typing import Real, RealSequence
1214

1315

@@ -18,117 +20,221 @@ class Cone:
1820
Parameters
1921
----------
2022
origin : Union[~numpy.ndarray, RealSequence, Point3D]
21-
Centered origin of the cone.
22-
direction_x : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
23-
X-plane direction.
24-
direction_y : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
25-
Y-plane direction.
26-
radius : Real
23+
Origin of the cone.
24+
radius : Union[Quantity, Distance, Real]
2725
Radius of the cone.
28-
half_angle : Real
26+
half_angle : Union[Quantity, Angle, Real]
2927
Half angle of the apex, determining the upward angle.
30-
length_unit : Unit, default: UNIT_LENGTH
31-
Units for defining the radius.
32-
angle_unit : Unit, default: UNIT_ANGLE
33-
Units for defining the half angle.
28+
reference : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
29+
X-plane direction.
30+
axis : Union[~numpy.ndarray, RealSequence, UnitVector3D, Vector3D]
31+
Z-plane direction.
3432
"""
3533

3634
@check_input_types
3735
def __init__(
3836
self,
3937
origin: Union[np.ndarray, RealSequence, Point3D],
40-
direction_x: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D],
41-
direction_y: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D],
42-
radius: Real,
43-
half_angle: Real,
44-
length_unit: Optional[Unit] = UNIT_LENGTH,
45-
angle_unit: Optional[Unit] = UNIT_ANGLE,
38+
radius: Union[Quantity, Distance, Real],
39+
half_angle: Union[Quantity, Angle, Real],
40+
reference: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D] = UNITVECTOR3D_X,
41+
axis: Union[np.ndarray, RealSequence, UnitVector3D, Vector3D] = UNITVECTOR3D_Z,
4642
):
4743
"""Constructor method for the ``Cone`` class."""
48-
check_pint_unit_compatibility(length_unit, UNIT_LENGTH)
49-
check_pint_unit_compatibility(angle_unit, UNIT_ANGLE)
50-
51-
self._length_unit = length_unit
52-
_, self._base_length_unit = UNITS.get_base_units(length_unit)
53-
54-
self._angle_unit = angle_unit
55-
_, self._base_angle_unit = UNITS.get_base_units(angle_unit)
5644

5745
self._origin = Point3D(origin) if not isinstance(origin, Point3D) else origin
58-
self._direction_x = (
59-
UnitVector3D(direction_x) if not isinstance(direction_x, UnitVector3D) else direction_x
60-
)
61-
self._direction_y = (
62-
UnitVector3D(direction_y) if not isinstance(direction_y, UnitVector3D) else direction_y
46+
self._reference = (
47+
UnitVector3D(reference) if not isinstance(reference, UnitVector3D) else reference
6348
)
49+
self._axis = UnitVector3D(axis) if not isinstance(axis, UnitVector3D) else axis
50+
if not self._reference.is_perpendicular_to(self._axis):
51+
raise ValueError("Cone reference (dir_x) and axis (dir_z) must be perpendicular.")
52+
53+
self._radius = radius if isinstance(radius, Distance) else Distance(radius)
54+
if self._radius.value <= 0:
55+
raise ValueError("Radius must be a real positive value.")
6456

65-
# Store values in base unit
66-
self._radius = UNITS.convert(radius, self._length_unit, self._base_length_unit)
67-
self._half_angle = UNITS.convert(half_angle, self._angle_unit, self._base_angle_unit)
57+
self._half_angle = half_angle if isinstance(half_angle, Angle) else Angle(half_angle)
6858

6959
@property
7060
def origin(self) -> Point3D:
7161
"""Origin of the cone."""
7262
return self._origin
7363

74-
@origin.setter
75-
@check_input_types
76-
def origin(self, origin: Point3D) -> None:
77-
self._origin = origin
78-
7964
@property
80-
def radius(self) -> Real:
65+
def radius(self) -> Quantity:
8166
"""Radius of the cone."""
82-
return UNITS.convert(self._radius, self._base_length_unit, self._length_unit)
83-
84-
@radius.setter
85-
@check_input_types
86-
def radius(self, radius: Real) -> None:
87-
self._radius = UNITS.convert(radius, self._length_unit, self._base_length_unit)
67+
return self._radius.value
8868

8969
@property
90-
def half_angle(self) -> Real:
70+
def half_angle(self) -> Quantity:
9171
"""Half angle of the apex."""
92-
return UNITS.convert(self._half_angle, self._base_angle_unit, self._angle_unit)
72+
return self._half_angle.value
9373

94-
@half_angle.setter
95-
@check_input_types
96-
def half_angle(self, half_angle: Real) -> None:
97-
self._half_angle = UNITS.convert(half_angle, self._angle_unit, self._base_angle_unit)
74+
@property
75+
def dir_x(self) -> UnitVector3D:
76+
"""X-direction of the cone."""
77+
return self._reference
9878

9979
@property
100-
def length_unit(self) -> Unit:
101-
"""Unit of the radius."""
102-
return self._length_unit
80+
def dir_y(self) -> UnitVector3D:
81+
"""Y-direction of the cone."""
82+
return self.dir_z.cross(self.dir_x)
10383

104-
@length_unit.setter
105-
@check_input_types
106-
def length_unit(self, length_unit: Unit) -> None:
107-
check_pint_unit_compatibility(length_unit, UNIT_LENGTH)
108-
self._length_unit = length_unit
84+
@property
85+
def dir_z(self) -> UnitVector3D:
86+
"""Z-direction of the cone."""
87+
return self._axis
10988

11089
@property
111-
def angle_unit(self) -> Unit:
112-
"""Unit of the angle."""
113-
return self._angle_unit
90+
def height(self) -> Quantity:
91+
"""Height of the cone."""
92+
return np.abs(self.radius / np.tan(self.half_angle))
11493

115-
@angle_unit.setter
116-
@check_input_types
117-
def angle_unit(self, angle_unit: Unit) -> None:
118-
check_pint_unit_compatibility(angle_unit, UNIT_ANGLE)
119-
self._angle_unit = angle_unit
94+
@property
95+
def surface_area(self) -> Quantity:
96+
"""Surface area of the cone."""
97+
return np.pi * self.radius * (self.radius + np.sqrt(self.height**2 + self.radius**2))
98+
99+
@property
100+
def volume(self) -> Quantity:
101+
"""Volume of the cone."""
102+
return np.pi * self.radius**2 * self.height / 3
103+
104+
@property
105+
def apex(self) -> Point3D:
106+
"""Apex point of the cone."""
107+
return self.origin + self.apex_param * self.dir_z
108+
109+
@property
110+
def apex_param(self) -> Real:
111+
"""Apex parameter of the cone."""
112+
return -np.abs(self.radius.m) / np.tan(self.half_angle.m)
120113

121114
@check_input_types
122-
def __eq__(self, other: object) -> bool:
115+
def __eq__(self, other: "Cone") -> bool:
123116
"""Equals operator for the ``Cone`` class."""
124117
return (
125-
self._origin == other.origin
126-
and self._radius == other.radius
127-
and self._half_angle == other.half_angle
128-
and self._direction_x == other._direction_x
129-
and self._direction_y == other._direction_y
118+
self._origin == other._origin
119+
and self._radius == other._radius
120+
and self._half_angle == other._half_angle
121+
and self._reference == other._reference
122+
and self._axis == other._axis
123+
)
124+
125+
def evaluate(self, parameter: ParamUV) -> "ConeEvaluation":
126+
"""Evaluate the cone at the given parameters."""
127+
return ConeEvaluation(self, parameter)
128+
129+
def project_point(self, point: Point3D) -> "ConeEvaluation":
130+
"""Project a point onto the cone and return its ``ConeEvaluation``."""
131+
u = np.arctan2(self.dir_y.dot(point - self.origin), self.dir_x.dot(point - self.origin))
132+
while u < 0:
133+
u += 2 * np.pi
134+
while u > 2 * np.pi:
135+
u -= 2 * np.pi
136+
axis = Line(self.origin, self.dir_z)
137+
line_eval = axis.project_point(point)
138+
v = line_eval.parameter
139+
140+
cone_radius = self.radius.m + v * np.tan(self.half_angle.m)
141+
point_radius = np.linalg.norm(point - line_eval.position())
142+
dist_to_cone = (point_radius - cone_radius) * np.cos(self.half_angle.m)
143+
v += dist_to_cone * np.sin(self.half_angle.m)
144+
145+
return ConeEvaluation(self, ParamUV(u, v))
146+
147+
148+
class ConeEvaluation(SurfaceEvaluation):
149+
"""
150+
Provides ``Cone`` evaluation at certain parameters.
151+
152+
Parameters
153+
----------
154+
cone: ~ansys.geometry.core.primitives.cone.Cone
155+
The ``Cone`` object to be evaluated.
156+
parameter: ParamUV
157+
The parameters (u, v) at which the ``Cone`` evaluation is requested.
158+
"""
159+
160+
def __init__(self, cone: Cone, parameter: ParamUV) -> None:
161+
"""``ConeEvaluation`` class constructor."""
162+
self._cone = cone
163+
self._parameter = parameter
164+
165+
@property
166+
def cone(self) -> Cone:
167+
"""The cone being evaluated."""
168+
return self._cone
169+
170+
@property
171+
def parameter(self) -> ParamUV:
172+
"""The parameter that the evaluation is based upon."""
173+
return self._parameter
174+
175+
def position(self) -> Point3D:
176+
"""The point on the cone, based on the evaluation."""
177+
return (
178+
self.cone.origin
179+
+ self.parameter.v * self.cone.dir_z
180+
+ self.__radius_v() * self.__cone_normal()
130181
)
131182

132-
def __ne__(self, other) -> bool:
133-
"""Not equals operator for the ``Cone`` class."""
134-
return not self == other
183+
def normal(self) -> UnitVector3D:
184+
"""The normal to the surface."""
185+
return UnitVector3D(
186+
self.__cone_normal() * np.cos(self.cone.half_angle.m)
187+
- self.cone.dir_z * np.sin(self.cone.half_angle.m)
188+
)
189+
190+
def __radius_v(self) -> Real:
191+
"""Private radius helper method."""
192+
return self.cone.radius.m + self.parameter.v * np.tan(self.cone.half_angle.m)
193+
194+
def __cone_normal(self) -> Vector3D:
195+
"""Private normal helper method."""
196+
return (
197+
np.cos(self.parameter.u) * self.cone.dir_x + np.sin(self.parameter.u) * self.cone.dir_y
198+
)
199+
200+
def __cone_tangent(self) -> Vector3D:
201+
"""Private tangent helper method."""
202+
return (
203+
-np.sin(self.parameter.u) * self.cone.dir_x + np.cos(self.parameter.u) * self.cone.dir_y
204+
)
205+
206+
def u_derivative(self) -> Vector3D:
207+
"""The first derivative with respect to u."""
208+
return self.__radius_v() * self.__cone_tangent()
209+
210+
def v_derivative(self) -> Vector3D:
211+
"""The first derivative with respect to v."""
212+
return self.cone.dir_z + np.tan(self.cone.half_angle.m) * self.__cone_normal()
213+
214+
def uu_derivative(self) -> Vector3D:
215+
"""The second derivative with respect to u."""
216+
return -self.__radius_v() * self.__cone_normal()
217+
218+
def uv_derivative(self) -> Vector3D:
219+
"""The second derivative with respect to u and v."""
220+
return np.tan(self.cone.half_angle.m) * self.__cone_tangent()
221+
222+
def vv_derivative(self) -> Vector3D:
223+
"""The second derivative with respect to v."""
224+
return Vector3D([0, 0, 0])
225+
226+
def min_curvature(self) -> Real:
227+
"""The minimum curvature."""
228+
return 0
229+
230+
def min_curvature_direction(self) -> UnitVector3D:
231+
"""The minimum curvature direction."""
232+
return UnitVector3D(self.v_derivative())
233+
234+
def max_curvature(self) -> Real:
235+
"""The maximum curvature."""
236+
return 1.0 / self.__radius_v()
237+
238+
def max_curvature_direction(self) -> UnitVector3D:
239+
"""The maximum curvature direction."""
240+
return UnitVector3D(self.u_derivative())

src/ansys/geometry/core/primitives/sphere.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,18 @@ def position(self) -> Point3D:
152152
def normal(self) -> UnitVector3D:
153153
"""The normal to the surface."""
154154
return UnitVector3D(
155-
np.cos(self.parameter.v) * self.cylinder_normal()
155+
np.cos(self.parameter.v) * self.__cylinder_normal()
156156
+ np.sin(self.parameter.v) * self.sphere.dir_z
157157
)
158158

159-
def cylinder_normal(self) -> Vector3D:
159+
def __cylinder_normal(self) -> Vector3D:
160160
"""Cylinder normal of the evaluation."""
161161
return (
162162
np.cos(self.parameter.u) * self.sphere.dir_x
163163
+ np.sin(self.parameter.u) * self.sphere.dir_y
164164
)
165165

166-
def cylinder_tangent(self) -> Vector3D:
166+
def __cylinder_tangent(self) -> Vector3D:
167167
"""Cylinder tangent of the evaluation."""
168168
return (
169169
-np.sin(self.parameter.u) * self.sphere.dir_x
@@ -172,28 +172,28 @@ def cylinder_tangent(self) -> Vector3D:
172172

173173
def u_derivative(self) -> Vector3D:
174174
"""The first derivative with respect to u."""
175-
return np.cos(self.parameter.v) * self.sphere.radius.m * self.cylinder_tangent()
175+
return np.cos(self.parameter.v) * self.sphere.radius.m * self.__cylinder_tangent()
176176

177177
def v_derivative(self) -> Vector3D:
178178
"""The first derivative with respect to v."""
179179
return self.sphere.radius.m * (
180180
np.cos(self.parameter.v) * self.sphere.dir_z
181-
- np.sin(self.parameter.v) * self.cylinder_normal()
181+
- np.sin(self.parameter.v) * self.__cylinder_normal()
182182
)
183183

184184
def uu_derivative(self) -> Vector3D:
185185
"""The second derivative with respect to u."""
186-
return -np.cos(self.parameter.v) * self.sphere.radius.m * self.cylinder_normal()
186+
return -np.cos(self.parameter.v) * self.sphere.radius.m * self.__cylinder_normal()
187187

188188
def uv_derivative(self) -> Vector3D:
189189
"""The second derivative with respect to u and v."""
190-
return -np.sin(self.parameter.v) * self.sphere.radius.m * self.cylinder_tangent()
190+
return -np.sin(self.parameter.v) * self.sphere.radius.m * self.__cylinder_tangent()
191191

192192
def vv_derivative(self) -> Vector3D:
193193
"""The second derivative with respect to v."""
194194
return self.sphere.radius.m * (
195195
-np.sin(self.parameter.v) * self.sphere.dir_z
196-
- np.cos(self.parameter.v) * self.cylinder_normal()
196+
- np.cos(self.parameter.v) * self.__cylinder_normal()
197197
)
198198

199199
def min_curvature(self) -> Real:

0 commit comments

Comments
 (0)