from __future__ import annotations
import numpy as np
from scipy.optimize import bisect
from hypervehicle.geometry.vector import Vector3
from hypervehicle.geometry.surface import CoonsPatch
from hypervehicle.geometry.path import Line, Polyline
from typing import Callable, Optional
from hypervehicle.components.component import Component
from hypervehicle.components.constants import FIN_COMPONENT
from hypervehicle.geometry.geometry import (
OffsetPatchFunction,
SubRangedPath,
ElipsePath,
ArcLengthParameterizedPath,
TrailingEdgePatch,
TrailingEdgePath,
RotatedPatch,
MeanLeadingEdgePatchFunction,
OffsetPathFunction,
GeometricMeanPathFunction,
ReversedPath,
)
[docs]
class Fin(Component):
componenttype = FIN_COMPONENT
[docs]
def __init__(
self,
p0: Vector3,
p1: Vector3,
p2: Vector3,
p3: Vector3,
fin_thickness: float,
fin_angle: float,
top_thickness_function: Callable,
bot_thickness_function: Callable,
LE_wf: Optional[Callable] = None,
mirror: Optional[bool] = False,
rudder_type: Optional[str] = "flat",
rudder_length: Optional[float] = 0,
rudder_angle: Optional[float] = 0,
pivot_angle: Optional[float] = 0,
pivot_point: Optional[Vector3] = Vector3(x=0, y=0),
offset_func: Optional[Callable] = None,
stl_resolution: Optional[int] = 2,
verbosity: Optional[int] = 1,
name: Optional[str] = None,
) -> None:
"""Creates a new fin component.
Fin geometry defined by 4 points and straight edges
between the points that define the fin planform.
Leading Edge runs p3->p2->p1.
p1--N--p2
| \
w e <---- FLOW
| \
p0-----S------p3
Parameters
----------
p0 : Vector3
Point p0 of the fin geometry.
p1 : Vector3
Point p1 of the fin geometry.
p2 : Vector3
Point p2 of the fin geometry.
p3 : Vector3
Point p3 of the fin geometry.
fin_thickness : float
The thickness of the fin.
fin_angle : float
The axial position angle of the placement of the fin.
top_thickness_function : Callable
The thickness function for the top surface of the fin.
bot_thickness_function : Callable
The thickness function for the top surface of the fin.
LE_wf : Callable, optional
The thickness function for the leading edge of the fin.
mirror : bool, optional
Mirror the fin. The default is False.
rudder_type : str, optional
The type of rudder to use, either "flat" or "sharp". The
default is "flat".
rudder_length : float, optional
The length of the rudder. The default is 0.
pivot_angle : float, optional
The pivot angle of the entire fin, about its central axis.
The default is 0.
pivot_point : Vector3, optional
The point about which to apply the pivot_angle. The default
is Vector3(0,0,0).
offset_func : Callable, optional
The function to apply when offsetting the fin position.
The default is None.
stl_resolution : int, optional
The stl resolution to use when creating the mesh for this
component. The default is None.
verbosity : int, optional
The verbosity of the component. The default is 1.
name : str, optional
The name tag for the component. The default is None.
"""
if LE_wf is None:
# Use default LE function
from hypervehicle.components.common import leading_edge_width_function
LE_wf = leading_edge_width_function
params = {
"p0": p0,
"p1": p1,
"p2": p2,
"p3": p3,
"FIN_THICKNESS": fin_thickness,
"FIN_ANGLE": fin_angle,
"FIN_TOP_THICKNESS_FUNC": top_thickness_function,
"FIN_BOTTOM_THICKNESS_FUNC": bot_thickness_function,
"FIN_LEADING_EDGE_FUNC": LE_wf,
"MIRROR_NEW_COMPONENT": mirror,
"rudder_type": rudder_type,
"rudder_length": rudder_length,
"rudder_angle": rudder_angle,
"pivot_angle": pivot_angle,
"pivot_point": pivot_point,
"offset_function": offset_func,
}
super().__init__(
params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name
)
[docs]
def generate_patches(self):
# Initialise
temp_fin_patch_dict = {}
# Extract geometric properties
p0 = self.params["p0"]
p1 = self.params["p1"]
p2 = self.params["p2"]
p3 = self.params["p3"]
fin_thickness = self.params["FIN_THICKNESS"]
fin_angle = self.params["FIN_ANGLE"]
fin_thickness_function_top = self.params["FIN_TOP_THICKNESS_FUNC"]
fin_thickness_function_bot = self.params["FIN_BOTTOM_THICKNESS_FUNC"]
leading_edge_width_function = self.params["FIN_LEADING_EDGE_FUNC"]
p1p2 = Line(p1, p2)
p2p3 = Line(p2, p3)
p1p3 = Polyline(segments=[p1p2, p2p3])
if self.verbosity > 1:
print(" Creating fin planform.")
p0p1 = Line(p0, p1)
fin_patch = CoonsPatch(
north=Line(p1, p2),
east=Line(p3, p2),
south=Line(p0, p3),
west=Line(p0, p1),
)
flipped_fin_patch = CoonsPatch(
north=Line(p3, p2),
east=Line(p1, p2),
south=Line(p0, p1),
west=Line(p0, p3),
)
if self.verbosity > 1:
print(" Adding thickness to fin.")
top_patch = OffsetPatchFunction(flipped_fin_patch, fin_thickness_function_top)
bot_patch = OffsetPatchFunction(fin_patch, fin_thickness_function_bot)
temp_fin_patch_dict["top_patch"] = top_patch
temp_fin_patch_dict["bot_patch"] = bot_patch
if self.verbosity > 1:
print(" Adding Leading Edge to fin.")
top_edge_path = OffsetPathFunction(p1p3, fin_thickness_function_top)
bot_edge_path = OffsetPathFunction(p1p3, fin_thickness_function_bot)
mean_path = GeometricMeanPathFunction(top_edge_path, bot_edge_path)
# Find Locations
fun_B1 = lambda t: p1p3(t).x - p2.x
t_B1 = round(bisect(fun_B1, 0.0, 1.0), 6)
t_B2 = 1
LE_top_patch = [np.nan, np.nan, np.nan]
LE_bot_patch = [np.nan, np.nan, np.nan]
# Eliptical LE
LE_top_patch[0] = MeanLeadingEdgePatchFunction(
mean_path,
top_edge_path,
LE_width_function=leading_edge_width_function,
t0=0.0,
t1=t_B1,
side="top",
)
LE_top_patch[1] = MeanLeadingEdgePatchFunction(
mean_path,
top_edge_path,
LE_width_function=leading_edge_width_function,
t0=t_B1,
t1=t_B2,
side="top",
)
LE_bot_patch[0] = MeanLeadingEdgePatchFunction(
mean_path,
bot_edge_path,
LE_width_function=leading_edge_width_function,
t0=0.0,
t1=t_B1,
side="bot",
)
LE_bot_patch[1] = MeanLeadingEdgePatchFunction(
mean_path,
bot_edge_path,
LE_width_function=leading_edge_width_function,
t0=t_B1,
t1=t_B2,
side="bot",
)
temp_fin_patch_dict["LE_top_patch_0"] = LE_top_patch[0]
temp_fin_patch_dict["LE_top_patch_1"] = LE_top_patch[1]
temp_fin_patch_dict["LE_bot_patch_0"] = LE_bot_patch[0]
temp_fin_patch_dict["LE_bot_patch_1"] = LE_bot_patch[1]
if self.verbosity > 1:
print(" Adding bottom face.")
thickness_top = fin_thickness_function_top(x=p3.x, y=p3.y).z
thickness_bot = fin_thickness_function_bot(x=p3.x, y=p3.y).z
elipse_top = ElipsePath(
centre=p3,
thickness=thickness_top,
LE_width=leading_edge_width_function(1),
side="top",
)
elipse_bot = ElipsePath(
centre=p3,
thickness=thickness_bot,
LE_width=leading_edge_width_function(1),
side="bot",
)
elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top)
elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot)
p3p3_top = Line(p0=p3 - Vector3(0, 0, fin_thickness / 2), p1=p3)
p3p3_bot = Line(p0=p3, p1=p3 + Vector3(0, 0, fin_thickness / 2))
temp_bottom_ellipse_patch = CoonsPatch(
north=p3p3_bot, south=elipse_top, east=elipse_bot, west=p3p3_top
)
# Rotate patch into x-z plane about p3
bottom_ellipse_patch = RotatedPatch(
temp_bottom_ellipse_patch, np.deg2rad(-90), axis="z", point=p3
)
# Create rectangular patches for rest of fin bottom
p3p0 = Line(p3, p0)
p3p0_bot = Line(
p0=p3 + Vector3(0, 0, fin_thickness / 2),
p1=p0 + Vector3(0, 0, fin_thickness / 2),
)
p3p0_top = Line(
p0=p3 - Vector3(0, 0, fin_thickness / 2),
p1=p0 - Vector3(0, 0, fin_thickness / 2),
)
p0p0_top = Line(p0=p0, p1=p0 - Vector3(0, 0, fin_thickness / 2))
p0_botp0 = Line(p0=p0 + Vector3(0, 0, fin_thickness / 2), p1=p0)
# p3p3_top_reversed = SubRangedPath(p3p3_top, 1, 0)
# p3p3_bot_reversed = SubRangedPath(p3p3_bot, 1, 0)
p3p3_top_reversed = ReversedPath(p3p3_top)
p3p3_bot_reversed = ReversedPath(p3p3_bot)
bot_1 = CoonsPatch(
north=p3p0_top, south=p3p0, east=p0p0_top, west=p3p3_top_reversed
)
bot_2 = CoonsPatch(
north=p3p0, south=p3p0_bot, east=p0_botp0, west=p3p3_bot_reversed
)
temp_fin_patch_dict["bot_ellip"] = bottom_ellipse_patch
temp_fin_patch_dict["bot_1"] = bot_1
temp_fin_patch_dict["bot_2"] = bot_2
if self.verbosity > 1:
print(" Adding Trailing Edge.")
if self.params["rudder_type"] == "sharp":
# Create sharp trailing edge
# First create top and bottom paths connecting fin to TE
TE_top = TrailingEdgePath(
p0, p1, thickness_function=fin_thickness_function_top
)
TE_bot = TrailingEdgePath(
p0, p1, thickness_function=fin_thickness_function_bot
)
# Make top and bottom of rudder
TE_top_patch = TrailingEdgePatch(
A0=p0,
B0=p1,
TE_path=TE_top,
flap_length=self.params["rudder_length"],
flap_angle=self.params["rudder_angle"],
side="top",
)
TE_bot_patch = TrailingEdgePatch(
A0=p0,
B0=p1,
TE_path=TE_bot,
flap_length=self.params["rudder_length"],
flap_angle=self.params["rudder_angle"],
side="bot",
)
# Create corner patches
thickness_top = fin_thickness_function_top(x=p1.x, y=p1.y).z
thickness_bot = fin_thickness_function_bot(x=p1.x, y=p1.y).z
elipse_top = ElipsePath(
centre=p1,
thickness=thickness_top,
LE_width=leading_edge_width_function(0.0),
side="top",
)
elipse_bot = ElipsePath(
centre=p1,
thickness=thickness_bot,
LE_width=leading_edge_width_function(0.0),
side="bot",
)
elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top)
elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot)
south = Line(
p0=Vector3(x=p1.x, y=p1.y, z=thickness_top),
p1=Vector3(
x=p1.x - self.params["rudder_length"],
y=p1.y,
z=self.params["rudder_length"]
* np.sin(self.params["rudder_angle"]),
),
)
east = Line(
p0=Vector3(
x=p1.x - self.params["rudder_length"],
y=p1.y,
z=self.params["rudder_length"]
* np.sin(self.params["rudder_angle"]),
),
p1=Vector3(x=p1.x, y=p1.y, z=thickness_bot),
)
TE_ellip_patch = CoonsPatch(
north=elipse_bot, west=elipse_top, south=south, east=east
)
# Create bottom triangle patch
p0_top = p0 - Vector3(0, 0, fin_thickness / 2)
p0_bot = p0 + Vector3(0, 0, fin_thickness / 2)
TE = Vector3(
x=p0.x - self.params["rudder_length"],
y=p0.y,
z=self.params["rudder_length"] * np.sin(self.params["rudder_angle"]),
)
p0p0_top = Line(p0=p0 - Vector3(0, 0, fin_thickness / 2), p1=p0)
p0p0_bot = Line(p0=p0, p1=p0 + Vector3(0, 0, fin_thickness / 2))
p0_top_TE = Line(p0=p0_top, p1=TE)
p0_bot_TE = Line(p0=TE, p1=p0_bot)
TE_triangle_patch = CoonsPatch(
north=p0_bot_TE, south=p0p0_top, east=p0p0_bot, west=p0_top_TE
)
# Add to patch dict
temp_fin_patch_dict["TE_top"] = TE_top_patch
temp_fin_patch_dict["TE_bot"] = TE_bot_patch
temp_fin_patch_dict["TE_ellipse"] = TE_ellip_patch
temp_fin_patch_dict["TE_triangle"] = TE_triangle_patch
else:
# Create patch for top of fin TE (elliptical section)
thickness_top = fin_thickness_function_top(x=p1.x, y=p1.y).z
thickness_bot = fin_thickness_function_bot(x=p1.x, y=p1.y).z
elipse_top = ElipsePath(
centre=p1,
thickness=thickness_top,
LE_width=leading_edge_width_function(0.0),
side="top",
)
elipse_bot = ElipsePath(
centre=p1,
thickness=thickness_bot,
LE_width=leading_edge_width_function(0.0),
side="bot",
)
elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top)
elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot)
# Now reverse the paths for correct orientation
elipse_top = SubRangedPath(underlying_path=elipse_top, t0=1.0, t1=0.0)
elipse_bot = SubRangedPath(underlying_path=elipse_bot, t0=1.0, t1=0.0)
p1p1_top = Line(p0=p1, p1=p1 - Vector3(0, 0, fin_thickness / 2))
p1_botp1 = Line(p0=p1 + Vector3(0, 0, fin_thickness / 2), p1=p1)
back_ellipse_patch = CoonsPatch(
north=p1p1_top, south=elipse_bot, east=elipse_top, west=p1_botp1
)
# Create rectangular patches for rest of fin TE
p0_botp1_bot = Line(
p0=p0 + Vector3(0, 0, fin_thickness / 2),
p1=p1 + Vector3(0, 0, fin_thickness / 2),
)
p0_top1_top = Line(
p0=p0 - Vector3(0, 0, fin_thickness / 2),
p1=p1 - Vector3(0, 0, fin_thickness / 2),
)
p0p0_top = Line(p0=p0, p1=p0 - Vector3(0, 0, fin_thickness / 2))
p0_botp0 = Line(p0=p0 + Vector3(0, 0, fin_thickness / 2), p1=p0)
TE_back_1 = CoonsPatch(
north=p0_top1_top, south=p0p1, east=p1p1_top, west=p0p0_top
)
TE_back_2 = CoonsPatch(
north=p0p1, south=p0_botp1_bot, east=p1_botp1, west=p0_botp0
)
# Add to patch dict
temp_fin_patch_dict["TE_ellip"] = back_ellipse_patch
temp_fin_patch_dict["TE_1"] = TE_back_1
temp_fin_patch_dict["TE_2"] = TE_back_2
# Create fin patch dict
fin_patch_dict = temp_fin_patch_dict.copy()
# Rotate patches again for rudder angle
if "pivot_angle" in self.params:
for key, patch in fin_patch_dict.items():
fin_patch_dict[key] = RotatedPatch(
patch,
self.params["pivot_angle"],
axis="y",
point=self.params["pivot_point"],
)
# Rotate patches and add to fin_patch_dict
for key, patch in fin_patch_dict.items():
fin_patch_dict[key] = RotatedPatch(patch, fin_angle)
if (
"offset_function" in self.params
and self.params["offset_function"] is not None
):
if self.verbosity > 1:
print(" Applying fin offset function.")
for patch in fin_patch_dict:
fin_patch_dict[patch] = OffsetPatchFunction(
fin_patch_dict[patch], self.params["offset_function"]
)
# Save patches
self.patches = fin_patch_dict