Source code for hypervehicle.components.wing

from __future__ import annotations
import numpy as np
from typing import Callable
from scipy.optimize import bisect
from hypervehicle.geometry.vector import Vector3
from hypervehicle.geometry.surface import CoonsPatch
from hypervehicle.geometry.path import Line, Bezier, Polyline

from hypervehicle.components.component import Component
from hypervehicle.components.constants import WING_COMPONENT
from hypervehicle.geometry.geometry import (
    OffsetPatchFunction,
    SubRangedPath,
    ElipsePath,
    ArcLengthParameterizedPath,
    TrailingEdgePath,
    OffsetPathFunction,
    GeometricMeanPathFunction,
    MeanLeadingEdgePatchFunction,
    MeanTrailingEdgePatch,
    RotatedPatch,
    FlatLeadingEdgePatchFunction,
)


[docs] class Wing(Component): componenttype = WING_COMPONENT
[docs] def __init__( self, A0: Vector3 = Vector3(0, 0, 0), A1: Vector3 = Vector3(0, 0, 0), TT: Vector3 = Vector3(0, 0, 0), B0: Vector3 = Vector3(0, 0, 0), Line_B0TT: Polyline = None, Line_B0TT_TYPE: str = "Bezier", t_B1: float = None, t_B2: float = None, top_tf: Callable[[float, float, float], Vector3] = None, bot_tf: Callable[[float, float, float], Vector3] = None, LE_wf: Callable[[float], Vector3] = None, LE_type: str = "custom", tail_option: str = "FLAP", flap_length: float = 0, flap_angle: float = 0, close_wing: bool = False, stl_resolution: int = 2, verbosity: int = 1, name: str = None, ) -> None: """Creates a new fin component. Parameters ---------- A0 : Vector3 Point p0 of the fin geometry. A1 : Vector3 Point p1 of the fin geometry. TT : Vector3 Point p2 of the fin geometry. B0 : Vector3 Point p3 of the fin geometry. Line_B0TT : Polyline The thickness of the fin. Line_B0TT_TYPE : str, optional The axial position angle of the placement of the fin. t_B1 : float, optional The t value of the first discretisation point. The default is None. t_B2 : float, optional The t value of the second discretisation point. The default is None. top_tf : Callable The thickness function for the top surface of the wing. bot_tf : Callable The thickness function for the top surface of the wing. LE_wf : Callable, optional The thickness function for the leading edge of the wing. LE_type : str, optional The type of LE to create, either "FLAT" or "custom". The default is "custom". tail_option : str, optional The type of trailing edge to use, currently only "FLAP". The default is "FLAP". flap_length : float, optional The length of the trailing edge flap. The default is 0. flap_angle : float, optional The angle of the flap, specified in radians. The default is 0. close_wing : bool, optional If the wing is not being mirrored, it is useful to set this to True, to close the STL object. The default is False. 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. """ # Check if a LE function was provided if LE_wf is None and LE_type == "custom": # Assign default LE function from hypervehicle.components.common import leading_edge_width_function LE_wf = leading_edge_width_function params = { "A0": A0, "A1": A1, "TT": TT, "B0": B0, "Line_B0TT": Line_B0TT, "Line_B0TT_TYPE": Line_B0TT_TYPE, "t_B1": t_B1, "t_B2": t_B2, "FUNC_TOP_THICKNESS": top_tf, "FUNC_BOT_THICKNESS": bot_tf, "FUNC_LEADING_EDGE_WIDTH": LE_wf, "LE_TYPE": LE_type.upper(), "TAIL_OPTION": tail_option, "FLAP_LENGTH": flap_length, "FLAP_ANGLE": flap_angle, "CLOSE_WING": close_wing, } super().__init__( params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name ) # Extract construction points for planform # TODO - avoid pre-defined params dict structure for flexibility self.A0 = params["A0"] self.A1 = params["A1"] self.TT = params["TT"] self.B0 = params["B0"] self.Line_B0TT = params["Line_B0TT"] # Find Locations if self.params["t_B1"] == None: fun_B1 = lambda t: self.Line_B0TT(t).x - self.A1.x self.t_B1 = round(bisect(fun_B1, 0.0, 1.0), 6) else: self.t_B1 = self.params["t_B1"] if self.params["t_B2"] == None: self.t_B2 = 0.5 * (1 + self.t_B1) else: self.t_B2 = self.params["t_B1"] # Save other params self.TE_top = None self.TE_bot = None self.TE_mean_line = None
[docs] def generate_patches(self): # Create wing planform shape self._create_planform_patches() # Create leading edge patches self._create_leading_edge() # Create trailing edge patches self._create_trailing_edge() if "CLOSE_WING" in self.params and self.params["CLOSE_WING"]: self._close_wing()
def _create_planform_patches(self): if self.params["Line_B0TT_TYPE"].lower() == "bezier": if self.verbosity > 1: print( " Constructing planform using Bezier Curve as Leading Edge shape." ) B1 = self.Line_B0TT(self.t_B1) Line_B0B1 = SubRangedPath( underlying_path=self.Line_B0TT, t0=0.0, t1=self.t_B1 ) Line_B1B2 = SubRangedPath( underlying_path=self.Line_B0TT, t0=self.t_B1, t1=self.t_B2 ) Line_TTB2 = SubRangedPath( underlying_path=self.Line_B0TT, t0=1.0, t1=self.t_B2 ) else: raise Exception( f"Option for 'Line_B0TT'={self.params['Line_B0TT_TYPE']} " + "not supported." ) wing_patch = [np.nan, np.nan] wing_patch_flipped = [np.nan, np.nan] wing_patch[0] = CoonsPatch( south=Line(p0=self.A0, p1=self.A1), north=Line_B0B1, west=Line(p0=self.A0, p1=self.B0), east=Line(p0=self.A1, p1=B1), ) wing_patch[1] = CoonsPatch( south=Line(p0=self.A1, p1=self.TT), north=Line_B1B2, west=Line(p0=self.A1, p1=B1), east=Line_TTB2, ) # Need flipped planform for lower side to ensure vecors point in correct direction Line_B0B1_flipped = SubRangedPath(underlying_path=Line_B0B1, t0=1.0, t1=0.0) Line_B1B2_flipped = SubRangedPath(underlying_path=Line_B1B2, t0=1.0, t1=0.0) wing_patch_flipped[0] = CoonsPatch( south=Line(p0=self.A1, p1=self.A0), north=Line_B0B1_flipped, east=Line(p0=self.A0, p1=self.B0), west=Line(p0=self.A1, p1=B1), ) wing_patch_flipped[1] = CoonsPatch( south=Line(p0=self.TT, p1=self.A1), north=Line_B1B2_flipped, east=Line(p0=self.A1, p1=B1), west=Line_TTB2, ) # Create wing top & bottom surface if self.verbosity > 1: print(" Adding thickness to wing.") top_patch = [np.nan, np.nan] bot_patch = [np.nan, np.nan] for i in range(2): top_patch[i] = OffsetPatchFunction( wing_patch_flipped[i], function=self.params["FUNC_TOP_THICKNESS"] ) # flipped moves to top as z-positive points downwards bot_patch[i] = OffsetPatchFunction( wing_patch[i], function=self.params["FUNC_BOT_THICKNESS"] ) self.patches["top_patch_0"] = top_patch[0] # top B0B1A1A0 self.patches["top_patch_1"] = top_patch[1] # top B1B2TTA1 self.patches["bot_patch_0"] = bot_patch[0] # bot B0B1A1A0 self.patches["bot_patch_1"] = bot_patch[1] # bot B1B2TTA1 def _create_leading_edge(self): # Add leading edge if self.verbosity > 1: print(" Adding Leading Edge to wing.") top_edge_path = OffsetPathFunction( self.Line_B0TT, self.params["FUNC_TOP_THICKNESS"] ) bot_edge_path = OffsetPathFunction( self.Line_B0TT, self.params["FUNC_BOT_THICKNESS"] ) if "LE_TYPE" in self.params and self.params["LE_TYPE"] == "FLAT": self.patches["LE_patch0"] = FlatLeadingEdgePatchFunction( top_edge_path, bot_edge_path, 0, self.t_B1 ) self.patches["LE_patch1"] = FlatLeadingEdgePatchFunction( top_edge_path, bot_edge_path, self.t_B1, self.t_B2 ) self.patches["LE_patch2"] = FlatLeadingEdgePatchFunction( top_edge_path, bot_edge_path, self.t_B1, 1 ) else: # Get mean line between upper and lower wing patches mean_path = GeometricMeanPathFunction(top_edge_path, bot_edge_path) 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=self.params["FUNC_LEADING_EDGE_WIDTH"], t0=0.0, t1=self.t_B1, side="top", ) LE_top_patch[1] = MeanLeadingEdgePatchFunction( mean_path, top_edge_path, LE_width_function=self.params["FUNC_LEADING_EDGE_WIDTH"], t0=self.t_B1, t1=self.t_B2, side="top", ) LE_top_patch[2] = MeanLeadingEdgePatchFunction( mean_path, top_edge_path, LE_width_function=self.params["FUNC_LEADING_EDGE_WIDTH"], t0=self.t_B2, t1=1.0, side="top", ) LE_bot_patch[0] = MeanLeadingEdgePatchFunction( mean_path, bot_edge_path, LE_width_function=self.params["FUNC_LEADING_EDGE_WIDTH"], t0=0.0, t1=self.t_B1, side="bot", ) LE_bot_patch[1] = MeanLeadingEdgePatchFunction( mean_path, bot_edge_path, LE_width_function=self.params["FUNC_LEADING_EDGE_WIDTH"], t0=self.t_B1, t1=self.t_B2, side="bot", ) LE_bot_patch[2] = MeanLeadingEdgePatchFunction( mean_path, bot_edge_path, LE_width_function=self.params["FUNC_LEADING_EDGE_WIDTH"], t0=self.t_B2, t1=1.0, side="bot", ) # Append to patch_dict self.patches["LE_top_patch_0"] = LE_top_patch[0] self.patches["LE_top_patch_1"] = LE_top_patch[1] self.patches["LE_top_patch_2"] = LE_top_patch[2] self.patches["LE_bot_patch_0"] = LE_bot_patch[0] self.patches["LE_bot_patch_1"] = LE_bot_patch[1] self.patches["LE_bot_patch_2"] = LE_bot_patch[2] def _create_trailing_edge(self): # Add trailing Edge if self.verbosity > 1: print(" Adding Trailing Edge.") print(" Tail options - {}".format(self.params["TAIL_OPTION"])) if self.params["TAIL_OPTION"] == "FLAP": if self.verbosity > 1: print(" Flap length = {}".format(self.params["FLAP_LENGTH"])) print(" Flap angle = {}".format(self.params["FLAP_ANGLE"])) # Define top and bottom TE paths self.TE_top = TrailingEdgePath( self.A0, self.B0, thickness_function=self.params["FUNC_TOP_THICKNESS"] ) self.TE_bot = TrailingEdgePath( self.A0, self.B0, thickness_function=self.params["FUNC_BOT_THICKNESS"] ) self.TE_mean_line = GeometricMeanPathFunction(self.TE_top, self.TE_bot) # Make top and bottom of flap TE_top_patch = MeanTrailingEdgePatch( self.TE_mean_line, TE_path=self.TE_top, flap_length=self.params["FLAP_LENGTH"], flap_angle=self.params["FLAP_ANGLE"], side="top", ) TE_bot_patch = MeanTrailingEdgePatch( self.TE_mean_line, TE_path=self.TE_bot, flap_length=self.params["FLAP_LENGTH"], flap_angle=self.params["FLAP_ANGLE"], side="bot", ) # Append to patch_dict self.patches["TE_top_patch"] = TE_top_patch self.patches["TE_bot_patch"] = TE_bot_patch # Create edge (connecting side to TE) if "LE_TYPE" in self.params and self.params["LE_TYPE"] == "FLAT": # Flat LE west = Line(p0=self.TE_top(1), p1=self.TE_mean_line(1)) north = Line(p0=self.TE_mean_line(1), p1=self.TE_bot(1)) else: # Eiliptical LE thickness_top = self.TE_mean_line(1).z - self.TE_top(1).z thickness_bot = self.TE_mean_line(1).z - self.TE_bot(1).z if thickness_top == 0 or thickness_bot == 0: raise Exception( "Elliptical LE cannot be created when thickness converges to zero." ) elipse_top = ElipsePath( centre=self.TE_mean_line(1), thickness=thickness_top, LE_width=self.params["FUNC_LEADING_EDGE_WIDTH"](0.0), side="top", ) elipse_bot = ElipsePath( centre=self.TE_mean_line(1), thickness=thickness_bot, LE_width=self.params["FUNC_LEADING_EDGE_WIDTH"](0.0), side="bot", ) elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top) elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot) north = elipse_bot west = elipse_top south = Line( p0=Vector3( x=self.TE_mean_line(1).x, y=self.TE_mean_line(1).y, z=self.TE_top(1).z, ), p1=Vector3( x=self.TE_mean_line(1).x - self.params["FLAP_LENGTH"] * np.cos(self.params["FLAP_ANGLE"]), y=self.TE_mean_line(1).y, z=self.TE_mean_line(1).z + self.params["FLAP_LENGTH"] * np.sin(self.params["FLAP_ANGLE"]), ), ) east = Line( p0=Vector3( x=self.TE_mean_line(1).x - self.params["FLAP_LENGTH"] * np.cos(self.params["FLAP_ANGLE"]), y=self.TE_mean_line(1).y, z=self.TE_mean_line(1).z + self.params["FLAP_LENGTH"] * np.sin(self.params["FLAP_ANGLE"]), ), p1=Vector3( x=self.TE_mean_line(1).x, y=self.TE_mean_line(1).y, z=self.TE_bot(1).z, ), ) TELE_patch = CoonsPatch(north=north, west=west, south=south, east=east) # Append to patch_dict self.patches["TELE_patch"] = TELE_patch else: raise Exception( "Tail option = {} not supported.".format(self.params["TAIL_OPTION"]) ) def _close_wing(self): # Add patch to close wing volume if self.verbosity > 1: print(" Closing interior side of wing.") TT_top = self.TT + self.params["FUNC_TOP_THICKNESS"]( x=self.TT.x, y=self.TT.y, z=self.TT.z ) TT_bot = self.TT + self.params["FUNC_BOT_THICKNESS"]( x=self.TT.x, y=self.TT.y, z=self.TT.z ) TT_mid = 0.5 * (TT_top + TT_bot) interior_top_0 = Line(p0=self.TE_top(0), p1=self.patches["top_patch_0"](0, 0)) interior_top_1 = Line(p0=self.patches["top_patch_0"](0, 0), p1=TT_top) interior_bot_0 = Line(p0=self.TE_bot(0), p1=self.patches["bot_patch_0"](1, 0)) interior_bot_1 = Line(p0=self.patches["bot_patch_0"](1, 0), p1=TT_bot) mid_mid_point = Vector3( x=self.patches["bot_patch_0"](1, 0).x, y=self.patches["bot_patch_0"](1, 0).y, z=( self.patches["bot_patch_0"](1, 0).z + self.patches["top_patch_0"](0, 0).z ) / 2, ) interior_mid_0 = Line(p0=self.TE_mean_line(0), p1=mid_mid_point) interior_mid_1 = Line(p0=mid_mid_point, p1=TT_mid) # Create vertical edges mid_vert_top = Line(p1=mid_mid_point, p0=self.patches["top_patch_0"](0, 0)) mid_vert_bot = Line(p0=mid_mid_point, p1=self.patches["bot_patch_0"](1, 0)) # Vertical edges at TE ('back') back_top = Line(p0=self.TE_top(0), p1=self.TE_mean_line(0)) back_bot = Line(p0=self.TE_mean_line(0), p1=self.TE_bot(0)) # Create patches interior_top_patch0 = CoonsPatch( north=interior_mid_0, east=mid_vert_top, south=interior_top_0, west=back_top, ) interior_top_patch1 = CoonsPatch( north=interior_mid_1, east=Line(p0=TT_top, p1=TT_mid), south=interior_top_1, west=mid_vert_top, ) interior_bot_patch0 = CoonsPatch( north=interior_bot_0, east=mid_vert_bot, south=interior_mid_0, west=back_bot, ) interior_bot_patch1 = CoonsPatch( north=interior_bot_1, east=Line(p0=TT_mid, p1=TT_bot), south=interior_mid_1, west=mid_vert_bot, ) elipse_top = ElipsePath( centre=TT_mid, thickness=TT_mid.z - TT_top.z, LE_width=self.params["FUNC_LEADING_EDGE_WIDTH"](1), side="top", ) elipse_bot = ElipsePath( centre=TT_mid, thickness=TT_mid.z - TT_bot.z, LE_width=self.params["FUNC_LEADING_EDGE_WIDTH"](1), side="bot", ) elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top) elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot) # Now reverse the paths for correct orientation elipse_bot = SubRangedPath(underlying_path=elipse_bot, t0=1.0, t1=0.0) interior_ellip = CoonsPatch( north=elipse_bot, east=elipse_top, south=Line(p0=TT_mid, p1=TT_top), west=Line(p0=TT_mid, p1=TT_bot), ) # Rotate patch interior_ellip = RotatedPatch( interior_ellip, np.deg2rad(-90), axis="z", point=TT_mid ) # Append to patch_dict self.patches["wing_close_top_patch0"] = interior_top_patch0 self.patches["wing_close_top_patch1"] = interior_top_patch1 self.patches["wing_close_bot_patch0"] = interior_bot_patch0 self.patches["wing_close_bot_patch1"] = interior_bot_patch1 self.patches["interior_ellip"] = interior_ellip # Interior TE TE_point = Vector3( x=self.TE_mean_line(0).x - self.params["FLAP_LENGTH"] * np.cos(self.params["FLAP_ANGLE"]), y=self.TE_mean_line(0).y, z=self.TE_mean_line(0).z + self.params["FLAP_LENGTH"] * np.sin(self.params["FLAP_ANGLE"]), ) interior_flap_patch = CoonsPatch( north=Line(p0=self.TE_top(0), p1=TE_point), east=Line(p0=self.TE_bot(0), p1=TE_point), south=Line(p0=self.TE_mean_line(0), p1=self.TE_bot(0)), west=Line(p0=self.TE_mean_line(0), p1=self.TE_top(0)), ) self.patches["interior_flap_patch"] = interior_flap_patch