Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/hypervehicle/components/fin.py: 96%
145 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-29 02:51 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-29 02:51 +0000
1from __future__ import annotations
2import numpy as np
3from scipy.optimize import bisect
4from hypervehicle.geometry.vector import Vector3
5from hypervehicle.geometry.surface import CoonsPatch
6from hypervehicle.geometry.path import Line, Polyline
7from typing import Callable, Optional
8from hypervehicle.components.component import Component
9from hypervehicle.components.constants import FIN_COMPONENT
10from hypervehicle.geometry.geometry import (
11 OffsetPatchFunction,
12 SubRangedPath,
13 ElipsePath,
14 ArcLengthParameterizedPath,
15 TrailingEdgePatch,
16 TrailingEdgePath,
17 RotatedPatch,
18 MeanLeadingEdgePatchFunction,
19 OffsetPathFunction,
20 GeometricMeanPathFunction,
21 ReversedPath,
22)
25class Fin(Component):
26 componenttype = FIN_COMPONENT
28 def __init__(
29 self,
30 p0: Vector3,
31 p1: Vector3,
32 p2: Vector3,
33 p3: Vector3,
34 fin_thickness: float,
35 fin_angle: float,
36 top_thickness_function: Callable,
37 bot_thickness_function: Callable,
38 LE_wf: Optional[Callable] = None,
39 mirror: Optional[bool] = False,
40 rudder_type: Optional[str] = "flat",
41 rudder_length: Optional[float] = 0,
42 rudder_angle: Optional[float] = 0,
43 pivot_angle: Optional[float] = 0,
44 pivot_point: Optional[Vector3] = Vector3(x=0, y=0),
45 offset_func: Optional[Callable] = None,
46 stl_resolution: Optional[int] = 2,
47 verbosity: Optional[int] = 1,
48 name: Optional[str] = None,
49 ) -> None:
50 """Creates a new fin component.
52 Fin geometry defined by 4 points and straight edges
53 between the points that define the fin planform.
54 Leading Edge runs p3->p2->p1.
56 p1--N--p2
57 | \
58 w e <---- FLOW
59 | \
60 p0-----S------p3
62 Parameters
63 ----------
64 p0 : Vector3
65 Point p0 of the fin geometry.
67 p1 : Vector3
68 Point p1 of the fin geometry.
70 p2 : Vector3
71 Point p2 of the fin geometry.
73 p3 : Vector3
74 Point p3 of the fin geometry.
76 fin_thickness : float
77 The thickness of the fin.
79 fin_angle : float
80 The axial position angle of the placement of the fin.
82 top_thickness_function : Callable
83 The thickness function for the top surface of the fin.
85 bot_thickness_function : Callable
86 The thickness function for the top surface of the fin.
88 LE_wf : Callable, optional
89 The thickness function for the leading edge of the fin.
91 mirror : bool, optional
92 Mirror the fin. The default is False.
94 rudder_type : str, optional
95 The type of rudder to use, either "flat" or "sharp". The
96 default is "flat".
98 rudder_length : float, optional
99 The length of the rudder. The default is 0.
101 pivot_angle : float, optional
102 The pivot angle of the entire fin, about its central axis.
103 The default is 0.
105 pivot_point : Vector3, optional
106 The point about which to apply the pivot_angle. The default
107 is Vector3(0,0,0).
109 offset_func : Callable, optional
110 The function to apply when offsetting the fin position.
111 The default is None.
113 stl_resolution : int, optional
114 The stl resolution to use when creating the mesh for this
115 component. The default is None.
117 verbosity : int, optional
118 The verbosity of the component. The default is 1.
120 name : str, optional
121 The name tag for the component. The default is None.
122 """
124 if LE_wf is None:
125 # Use default LE function
126 from hypervehicle.components.common import leading_edge_width_function
128 LE_wf = leading_edge_width_function
130 params = {
131 "p0": p0,
132 "p1": p1,
133 "p2": p2,
134 "p3": p3,
135 "FIN_THICKNESS": fin_thickness,
136 "FIN_ANGLE": fin_angle,
137 "FIN_TOP_THICKNESS_FUNC": top_thickness_function,
138 "FIN_BOTTOM_THICKNESS_FUNC": bot_thickness_function,
139 "FIN_LEADING_EDGE_FUNC": LE_wf,
140 "MIRROR_NEW_COMPONENT": mirror,
141 "rudder_type": rudder_type,
142 "rudder_length": rudder_length,
143 "rudder_angle": rudder_angle,
144 "pivot_angle": pivot_angle,
145 "pivot_point": pivot_point,
146 "offset_function": offset_func,
147 }
149 super().__init__(
150 params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name
151 )
153 def generate_patches(self):
154 # Initialise
155 temp_fin_patch_dict = {}
157 # Extract geometric properties
158 p0 = self.params["p0"]
159 p1 = self.params["p1"]
160 p2 = self.params["p2"]
161 p3 = self.params["p3"]
162 fin_thickness = self.params["FIN_THICKNESS"]
163 fin_angle = self.params["FIN_ANGLE"]
164 fin_thickness_function_top = self.params["FIN_TOP_THICKNESS_FUNC"]
165 fin_thickness_function_bot = self.params["FIN_BOTTOM_THICKNESS_FUNC"]
166 leading_edge_width_function = self.params["FIN_LEADING_EDGE_FUNC"]
168 p1p2 = Line(p1, p2)
169 p2p3 = Line(p2, p3)
170 p1p3 = Polyline(segments=[p1p2, p2p3])
172 if self.verbosity > 1:
173 print(" Creating fin planform.")
175 p0p1 = Line(p0, p1)
176 fin_patch = CoonsPatch(
177 north=Line(p1, p2),
178 east=Line(p3, p2),
179 south=Line(p0, p3),
180 west=Line(p0, p1),
181 )
182 flipped_fin_patch = CoonsPatch(
183 north=Line(p3, p2),
184 east=Line(p1, p2),
185 south=Line(p0, p1),
186 west=Line(p0, p3),
187 )
189 if self.verbosity > 1:
190 print(" Adding thickness to fin.")
191 top_patch = OffsetPatchFunction(flipped_fin_patch, fin_thickness_function_top)
192 bot_patch = OffsetPatchFunction(fin_patch, fin_thickness_function_bot)
193 temp_fin_patch_dict["top_patch"] = top_patch
194 temp_fin_patch_dict["bot_patch"] = bot_patch
196 if self.verbosity > 1:
197 print(" Adding Leading Edge to fin.")
199 top_edge_path = OffsetPathFunction(p1p3, fin_thickness_function_top)
200 bot_edge_path = OffsetPathFunction(p1p3, fin_thickness_function_bot)
201 mean_path = GeometricMeanPathFunction(top_edge_path, bot_edge_path)
203 # Find Locations
204 fun_B1 = lambda t: p1p3(t).x - p2.x
205 t_B1 = round(bisect(fun_B1, 0.0, 1.0), 6)
206 t_B2 = 1
208 LE_top_patch = [np.nan, np.nan, np.nan]
209 LE_bot_patch = [np.nan, np.nan, np.nan]
211 # Eliptical LE
212 LE_top_patch[0] = MeanLeadingEdgePatchFunction(
213 mean_path,
214 top_edge_path,
215 LE_width_function=leading_edge_width_function,
216 t0=0.0,
217 t1=t_B1,
218 side="top",
219 )
220 LE_top_patch[1] = MeanLeadingEdgePatchFunction(
221 mean_path,
222 top_edge_path,
223 LE_width_function=leading_edge_width_function,
224 t0=t_B1,
225 t1=t_B2,
226 side="top",
227 )
229 LE_bot_patch[0] = MeanLeadingEdgePatchFunction(
230 mean_path,
231 bot_edge_path,
232 LE_width_function=leading_edge_width_function,
233 t0=0.0,
234 t1=t_B1,
235 side="bot",
236 )
237 LE_bot_patch[1] = MeanLeadingEdgePatchFunction(
238 mean_path,
239 bot_edge_path,
240 LE_width_function=leading_edge_width_function,
241 t0=t_B1,
242 t1=t_B2,
243 side="bot",
244 )
246 temp_fin_patch_dict["LE_top_patch_0"] = LE_top_patch[0]
247 temp_fin_patch_dict["LE_top_patch_1"] = LE_top_patch[1]
248 temp_fin_patch_dict["LE_bot_patch_0"] = LE_bot_patch[0]
249 temp_fin_patch_dict["LE_bot_patch_1"] = LE_bot_patch[1]
251 if self.verbosity > 1:
252 print(" Adding bottom face.")
254 thickness_top = fin_thickness_function_top(x=p3.x, y=p3.y).z
255 thickness_bot = fin_thickness_function_bot(x=p3.x, y=p3.y).z
256 elipse_top = ElipsePath(
257 centre=p3,
258 thickness=thickness_top,
259 LE_width=leading_edge_width_function(1),
260 side="top",
261 )
262 elipse_bot = ElipsePath(
263 centre=p3,
264 thickness=thickness_bot,
265 LE_width=leading_edge_width_function(1),
266 side="bot",
267 )
268 elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top)
269 elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot)
271 p3p3_top = Line(p0=p3 - Vector3(0, 0, fin_thickness / 2), p1=p3)
272 p3p3_bot = Line(p0=p3, p1=p3 + Vector3(0, 0, fin_thickness / 2))
274 temp_bottom_ellipse_patch = CoonsPatch(
275 north=p3p3_bot, south=elipse_top, east=elipse_bot, west=p3p3_top
276 )
278 # Rotate patch into x-z plane about p3
279 bottom_ellipse_patch = RotatedPatch(
280 temp_bottom_ellipse_patch, np.deg2rad(-90), axis="z", point=p3
281 )
283 # Create rectangular patches for rest of fin bottom
284 p3p0 = Line(p3, p0)
285 p3p0_bot = Line(
286 p0=p3 + Vector3(0, 0, fin_thickness / 2),
287 p1=p0 + Vector3(0, 0, fin_thickness / 2),
288 )
289 p3p0_top = Line(
290 p0=p3 - Vector3(0, 0, fin_thickness / 2),
291 p1=p0 - Vector3(0, 0, fin_thickness / 2),
292 )
294 p0p0_top = Line(p0=p0, p1=p0 - Vector3(0, 0, fin_thickness / 2))
295 p0_botp0 = Line(p0=p0 + Vector3(0, 0, fin_thickness / 2), p1=p0)
296 # p3p3_top_reversed = SubRangedPath(p3p3_top, 1, 0)
297 # p3p3_bot_reversed = SubRangedPath(p3p3_bot, 1, 0)
298 p3p3_top_reversed = ReversedPath(p3p3_top)
299 p3p3_bot_reversed = ReversedPath(p3p3_bot)
301 bot_1 = CoonsPatch(
302 north=p3p0_top, south=p3p0, east=p0p0_top, west=p3p3_top_reversed
303 )
304 bot_2 = CoonsPatch(
305 north=p3p0, south=p3p0_bot, east=p0_botp0, west=p3p3_bot_reversed
306 )
308 temp_fin_patch_dict["bot_ellip"] = bottom_ellipse_patch
309 temp_fin_patch_dict["bot_1"] = bot_1
310 temp_fin_patch_dict["bot_2"] = bot_2
312 if self.verbosity > 1:
313 print(" Adding Trailing Edge.")
315 if self.params["rudder_type"] == "sharp":
316 # Create sharp trailing edge
318 # First create top and bottom paths connecting fin to TE
319 TE_top = TrailingEdgePath(
320 p0, p1, thickness_function=fin_thickness_function_top
321 )
322 TE_bot = TrailingEdgePath(
323 p0, p1, thickness_function=fin_thickness_function_bot
324 )
326 # Make top and bottom of rudder
327 TE_top_patch = TrailingEdgePatch(
328 A0=p0,
329 B0=p1,
330 TE_path=TE_top,
331 flap_length=self.params["rudder_length"],
332 flap_angle=self.params["rudder_angle"],
333 side="top",
334 )
335 TE_bot_patch = TrailingEdgePatch(
336 A0=p0,
337 B0=p1,
338 TE_path=TE_bot,
339 flap_length=self.params["rudder_length"],
340 flap_angle=self.params["rudder_angle"],
341 side="bot",
342 )
344 # Create corner patches
345 thickness_top = fin_thickness_function_top(x=p1.x, y=p1.y).z
346 thickness_bot = fin_thickness_function_bot(x=p1.x, y=p1.y).z
348 elipse_top = ElipsePath(
349 centre=p1,
350 thickness=thickness_top,
351 LE_width=leading_edge_width_function(0.0),
352 side="top",
353 )
354 elipse_bot = ElipsePath(
355 centre=p1,
356 thickness=thickness_bot,
357 LE_width=leading_edge_width_function(0.0),
358 side="bot",
359 )
360 elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top)
361 elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot)
363 south = Line(
364 p0=Vector3(x=p1.x, y=p1.y, z=thickness_top),
365 p1=Vector3(
366 x=p1.x - self.params["rudder_length"],
367 y=p1.y,
368 z=self.params["rudder_length"]
369 * np.sin(self.params["rudder_angle"]),
370 ),
371 )
372 east = Line(
373 p0=Vector3(
374 x=p1.x - self.params["rudder_length"],
375 y=p1.y,
376 z=self.params["rudder_length"]
377 * np.sin(self.params["rudder_angle"]),
378 ),
379 p1=Vector3(x=p1.x, y=p1.y, z=thickness_bot),
380 )
382 TE_ellip_patch = CoonsPatch(
383 north=elipse_bot, west=elipse_top, south=south, east=east
384 )
386 # Create bottom triangle patch
387 p0_top = p0 - Vector3(0, 0, fin_thickness / 2)
388 p0_bot = p0 + Vector3(0, 0, fin_thickness / 2)
389 TE = Vector3(
390 x=p0.x - self.params["rudder_length"],
391 y=p0.y,
392 z=self.params["rudder_length"] * np.sin(self.params["rudder_angle"]),
393 )
395 p0p0_top = Line(p0=p0 - Vector3(0, 0, fin_thickness / 2), p1=p0)
396 p0p0_bot = Line(p0=p0, p1=p0 + Vector3(0, 0, fin_thickness / 2))
397 p0_top_TE = Line(p0=p0_top, p1=TE)
398 p0_bot_TE = Line(p0=TE, p1=p0_bot)
400 TE_triangle_patch = CoonsPatch(
401 north=p0_bot_TE, south=p0p0_top, east=p0p0_bot, west=p0_top_TE
402 )
404 # Add to patch dict
405 temp_fin_patch_dict["TE_top"] = TE_top_patch
406 temp_fin_patch_dict["TE_bot"] = TE_bot_patch
407 temp_fin_patch_dict["TE_ellipse"] = TE_ellip_patch
408 temp_fin_patch_dict["TE_triangle"] = TE_triangle_patch
410 else:
411 # Create patch for top of fin TE (elliptical section)
412 thickness_top = fin_thickness_function_top(x=p1.x, y=p1.y).z
413 thickness_bot = fin_thickness_function_bot(x=p1.x, y=p1.y).z
414 elipse_top = ElipsePath(
415 centre=p1,
416 thickness=thickness_top,
417 LE_width=leading_edge_width_function(0.0),
418 side="top",
419 )
420 elipse_bot = ElipsePath(
421 centre=p1,
422 thickness=thickness_bot,
423 LE_width=leading_edge_width_function(0.0),
424 side="bot",
425 )
426 elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top)
427 elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot)
429 # Now reverse the paths for correct orientation
430 elipse_top = SubRangedPath(underlying_path=elipse_top, t0=1.0, t1=0.0)
431 elipse_bot = SubRangedPath(underlying_path=elipse_bot, t0=1.0, t1=0.0)
433 p1p1_top = Line(p0=p1, p1=p1 - Vector3(0, 0, fin_thickness / 2))
434 p1_botp1 = Line(p0=p1 + Vector3(0, 0, fin_thickness / 2), p1=p1)
436 back_ellipse_patch = CoonsPatch(
437 north=p1p1_top, south=elipse_bot, east=elipse_top, west=p1_botp1
438 )
440 # Create rectangular patches for rest of fin TE
441 p0_botp1_bot = Line(
442 p0=p0 + Vector3(0, 0, fin_thickness / 2),
443 p1=p1 + Vector3(0, 0, fin_thickness / 2),
444 )
445 p0_top1_top = Line(
446 p0=p0 - Vector3(0, 0, fin_thickness / 2),
447 p1=p1 - Vector3(0, 0, fin_thickness / 2),
448 )
450 p0p0_top = Line(p0=p0, p1=p0 - Vector3(0, 0, fin_thickness / 2))
451 p0_botp0 = Line(p0=p0 + Vector3(0, 0, fin_thickness / 2), p1=p0)
453 TE_back_1 = CoonsPatch(
454 north=p0_top1_top, south=p0p1, east=p1p1_top, west=p0p0_top
455 )
456 TE_back_2 = CoonsPatch(
457 north=p0p1, south=p0_botp1_bot, east=p1_botp1, west=p0_botp0
458 )
460 # Add to patch dict
461 temp_fin_patch_dict["TE_ellip"] = back_ellipse_patch
462 temp_fin_patch_dict["TE_1"] = TE_back_1
463 temp_fin_patch_dict["TE_2"] = TE_back_2
465 # Create fin patch dict
466 fin_patch_dict = temp_fin_patch_dict.copy()
468 # Rotate patches again for rudder angle
469 if "pivot_angle" in self.params:
470 for key, patch in fin_patch_dict.items():
471 fin_patch_dict[key] = RotatedPatch(
472 patch,
473 self.params["pivot_angle"],
474 axis="y",
475 point=self.params["pivot_point"],
476 )
478 # Rotate patches and add to fin_patch_dict
479 for key, patch in fin_patch_dict.items():
480 fin_patch_dict[key] = RotatedPatch(patch, fin_angle)
482 if (
483 "offset_function" in self.params
484 and self.params["offset_function"] is not None
485 ):
486 if self.verbosity > 1:
487 print(" Applying fin offset function.")
488 for patch in fin_patch_dict:
489 fin_patch_dict[patch] = OffsetPatchFunction(
490 fin_patch_dict[patch], self.params["offset_function"]
491 )
493 # Save patches
494 self.patches = fin_patch_dict