diff --git a/hypervehicle/components/__init__.py b/hypervehicle/components/__init__.py index 97e22ad..5ce712f 100644 --- a/hypervehicle/components/__init__.py +++ b/hypervehicle/components/__init__.py @@ -1,7 +1,7 @@ from .fin import Fin from .wing import Wing from .component import Component -from .swept import SweptComponent +from .swept import SweptComponent, SweptComponentMultiFace from .polygon import Cube, Sphere from .revolved import RevolvedComponent from .composite import CompositeComponent diff --git a/hypervehicle/components/component.py b/hypervehicle/components/component.py index 7997306..1985e4b 100644 --- a/hypervehicle/components/component.py +++ b/hypervehicle/components/component.py @@ -7,12 +7,12 @@ import multiprocess as mp from copy import deepcopy from abc import abstractmethod -from typing import Callable, Union from hypervehicle.geometry import Vector3 from gdtk.geom.sgrid import StructuredGrid from hypervehicle.utilities import assign_tags_to_cell from hypervehicle.utilities import parametricSurfce2stl, parametricSurfce2vtk -from typing import Callable, Union, Optional +from hypervehicle.utilities import PatchTag +from typing import Callable, Union, Optional, Dict from hypervehicle.geometry import ( CurvedPatch, RotatedPatch, @@ -92,11 +92,12 @@ def analyse(self): class Component(AbstractComponent): def __init__( self, - params: dict = None, + params: Dict = None, stl_resolution: int = 2, verbosity: int = 1, name: str = None, output_file_type: str = "stl", + patch_name_to_tags: Dict = None, ) -> None: # Set verbosity self.verbosity = verbosity @@ -106,6 +107,7 @@ def __init__( # Processed objects self.patches = {} # Parametric patches (continuous) + self._patch_name_to_tags = patch_name_to_tags or dict() # VTK Attributes self.cells = None # Mesh cells @@ -268,22 +270,27 @@ def wrapper(key: str, patch): if "swept" in key: # Swept fuselage component - res = ( - int(stl_resolution / 4) - if "end" in key - else int(stl_resolution / 4) * 4 - ) + if "end" in key: + res_r = res_s = int(stl_resolution / 4) + elif any( + direction in key for direction in ("north", "south", "east", "west") + ): + res_s = int(stl_resolution / 4) + res_r = res_s * 4 + else: + res_r = res_s = int(stl_resolution / 4) * 4 flip = True if "1" in key else False + else: + res_r = res_s = res surface = parametricSurfce2stl( - patch, res, flip_faces=flip, **self._clustering + patch, res_r, res_s, flip_faces=flip, **self._clustering ) return (key, surface) # Initialise surfaces and pool self.surfaces = {} - pool = mp.Pool() # Submit tasks single (debug mode) # for a in self.patches.items(): @@ -291,6 +298,7 @@ def wrapper(key: str, patch): # self.surfaces[result[0]] = result[1] # Submit tasks multi + pool = mp.Pool() for result in pool.starmap(wrapper, self.patches.items()): self.surfaces[result[0]] = result[1] @@ -310,15 +318,21 @@ def wrapper(key: str, patch): if "swept" in key: # Swept fuselage component - res = ( - int(stl_resolution / 4) - if "end" in key - else int(stl_resolution / 4) * 4 - ) + if "end" in key: + res_r = res_s = int(stl_resolution / 4) + elif any( + direction in key for direction in ("north", "south", "east", "west") + ): + res_s = int(stl_resolution / 4) + res_r = res_s * 4 + else: + res_r = res_s = int(stl_resolution / 4) * 4 flip = True if "1" in key else False + else: + res_r = res_s = res vertices, cell_ids = parametricSurfce2vtk( - patch, res, flip_faces=flip, **self._clustering + patch, res_r, res_s, flip_faces=flip, **self._clustering ) # Assign tags to the cells @@ -328,7 +342,6 @@ def wrapper(key: str, patch): # Initialise cells and pool self.cells = {} - pool = mp.Pool() # Submit tasks single (debug mode) # for a in self.patches.items(): @@ -336,6 +349,7 @@ def wrapper(key: str, patch): # self.cells[result[0]] = (result[1], result[2], result[3]) # Submit tasks multi + pool = mp.Pool() for result in pool.starmap(wrapper, self.patches.items()): self.cells[result[0]] = (result[1], result[2], result[3]) @@ -373,10 +387,10 @@ def to_vtk(self, outfile: str = None): tags = np.empty(0, dtype=int) # Combine all Cell data - for sss in self.cells.items(): - cell_ids = np.concatenate([cell_ids, sss[1][1] + len(vertices)]) - vertices = np.concatenate([vertices, sss[1][0]]) - tags = np.concatenate([tags, sss[1][2]]) + for cell in self.cells.items(): + cell_ids = np.concatenate([cell_ids, cell[1][1] + len(vertices)]) + vertices = np.concatenate([vertices, cell[1][0]]) + tags = np.concatenate([tags, cell[1][2]]) # Generate mesh in VTK format cell_ids = [("triangle", cell_ids)] @@ -417,3 +431,14 @@ def add_clustering_options( if j_clustering_func: self._clustering.update({"j_clustering_func": j_clustering_func}) + + def add_tag_to_patches(self): + if self.patches is None: + raise Exception("component has no patches") + + for name, patch in self.patches.items(): + # default tag is FREE_STREEM + if name not in self._patch_name_to_tags: + patch.tag = PatchTag.FREE_STREAM + continue + patch.tag = self._patch_name_to_tags[name] diff --git a/hypervehicle/components/composite.py b/hypervehicle/components/composite.py index 1b1b17f..b95593d 100644 --- a/hypervehicle/components/composite.py +++ b/hypervehicle/components/composite.py @@ -37,6 +37,7 @@ def __init__( stl_resolution: int = 2, verbosity: int = 1, name: str = None, + tags: dict = None, ) -> None: # Initialise base class super().__init__( @@ -44,6 +45,7 @@ def __init__( stl_resolution=stl_resolution, verbosity=verbosity, name=name, + patch_name_to_tags=tags, ) self.components: List[Component] = [] diff --git a/hypervehicle/components/constants.py b/hypervehicle/components/constants.py index 5152122..2be2f68 100644 --- a/hypervehicle/components/constants.py +++ b/hypervehicle/components/constants.py @@ -2,6 +2,7 @@ WING_COMPONENT = "wing" COMPOSITE_COMPONENT = "composite" SWEPT_COMPONENT = "swept" +SWEPT_COMPONENT_MULTI_FACE = "swept_multi_face" REVOLVED_COMPONENT = "revolved" CUBE = "cube" SPHERE = "sphere" diff --git a/hypervehicle/components/fin.py b/hypervehicle/components/fin.py index 3c101ae..a0bf604 100644 --- a/hypervehicle/components/fin.py +++ b/hypervehicle/components/fin.py @@ -45,6 +45,7 @@ def __init__( stl_resolution: Optional[int] = 2, verbosity: Optional[int] = 1, name: Optional[str] = None, + tags: dict = None, ) -> None: """Creates a new fin component. @@ -108,6 +109,8 @@ def __init__( name : str, optional The name tag for the component. The default is None. + tags : dict, optional + tags to be added to each patch (for generating a VTK file) """ if LE_wf is None: @@ -135,7 +138,9 @@ def __init__( "offset_function": offset_func, } - super().__init__(params, stl_resolution, verbosity, name) + super().__init__( + params, stl_resolution, verbosity, name, patch_name_to_tags=tags + ) def generate_patches(self): # Initialise @@ -473,3 +478,6 @@ def generate_patches(self): # Save patches self.patches = fin_patch_dict + + # Add tags to patches + self.add_tag_to_patches() diff --git a/hypervehicle/components/polygon.py b/hypervehicle/components/polygon.py index 98f846b..97825fa 100644 --- a/hypervehicle/components/polygon.py +++ b/hypervehicle/components/polygon.py @@ -13,6 +13,7 @@ def __init__( stl_resolution: int = 2, verbosity: int = 1, name: str = None, + tags: dict = None, ) -> None: """ Parameters @@ -25,13 +26,21 @@ def __init__( """ self.a = a self.centre = centre - super().__init__(stl_resolution=stl_resolution, verbosity=verbosity, name=name) + super().__init__( + stl_resolution=stl_resolution, + verbosity=verbosity, + name=name, + patch_name_to_tags=tags, + ) def generate_patches(self): faces = ["east", "west", "south", "north", "bottom", "top"] for face in faces: self.patches[face] = CubePatch(self.a, self.centre, face) + # Add tags to patches + self.add_tag_to_patches() + class Sphere(Component): componenttype = SPHERE @@ -43,6 +52,7 @@ def __init__( stl_resolution: int = 2, verbosity: int = 1, name: str = None, + tags: dict = None, ) -> None: """ Parameters @@ -55,10 +65,18 @@ def __init__( """ self.r = r self.centre = centre - super().__init__(stl_resolution=stl_resolution, verbosity=verbosity, name=name) + super().__init__( + stl_resolution=stl_resolution, + verbosity=verbosity, + name=name, + patch_name_to_tags=tags, + ) def generate_patches(self): faces = ["east", "west", "south", "north", "bottom", "top"] for face in faces: self.patches[face] = SpherePatch(self.r, self.centre, face) + + # Add tags to patches + self.add_tag_to_patches() diff --git a/hypervehicle/components/revolved.py b/hypervehicle/components/revolved.py index 5d4b0f1..12885d1 100644 --- a/hypervehicle/components/revolved.py +++ b/hypervehicle/components/revolved.py @@ -13,6 +13,7 @@ def __init__( stl_resolution: int = 4, verbosity: int = 1, name: str = None, + tags: dict = None, ) -> None: """Create a revolved component. @@ -22,10 +23,18 @@ def __init__( A line to be revolved about the primary axis. """ self.revolve_line = revolve_line - super().__init__(stl_resolution=stl_resolution, verbosity=verbosity, name=name) + super().__init__( + stl_resolution=stl_resolution, + verbosity=verbosity, + name=name, + patch_name_to_tags=tags, + ) def generate_patches(self): for i in range(4): self.patches[f"revolved_fuse_{i}"] = RevolvedPatch( self.revolve_line, i * np.pi / 2, (i + 1) * np.pi / 2 ) + + # Add tags to patches + self.add_tag_to_patches() diff --git a/hypervehicle/components/swept.py b/hypervehicle/components/swept.py index ef45ab1..242bb91 100644 --- a/hypervehicle/components/swept.py +++ b/hypervehicle/components/swept.py @@ -1,7 +1,10 @@ from typing import List from hypervehicle.components.component import Component -from hypervehicle.geometry import SweptPatch, CoonsPatch -from hypervehicle.components.constants import SWEPT_COMPONENT +from hypervehicle.geometry import SweptPatch, CoonsPatch, SweptPatchMultiFace +from hypervehicle.components.constants import ( + SWEPT_COMPONENT, + SWEPT_COMPONENT_MULTI_FACE, +) class SweptComponent(Component): @@ -28,8 +31,12 @@ def __init__( """ self.cross_sections = cross_sections self.sweep_axis = sweep_axis - self.patches_tags = tags - super().__init__(stl_resolution=stl_resolution, verbosity=verbosity, name=name) + super().__init__( + stl_resolution=stl_resolution, + verbosity=verbosity, + name=name, + patch_name_to_tags=tags, + ) def generate_patches(self): p = SweptPatch( @@ -38,10 +45,67 @@ def generate_patches(self): ) self.patches["swept_patch"] = p + self.patches["swept_patch_end_0"] = self.cross_sections[0] + self.patches["swept_patch_end_1"] = self.cross_sections[-1] + self.add_tag_to_patches() + + +class SweptComponentMultiFace(Component): + componenttype = SWEPT_COMPONENT_MULTI_FACE + + def __init__( + self, + cross_sections: List[CoonsPatch], + sweep_axis: str = "z", + stl_resolution: int = 2, + verbosity: int = 1, + name: str = None, + tags: dict = None, + ) -> None: + """Create a swept component. + + Parameters + ---------- + cross_sections : list, optional + A list of cross-sectional patches to sweep through. + sweep_axis : str, optional + The axis to sweep the cross sections through. The default + is z. + """ + self.cross_sections = cross_sections + self.sweep_axis = sweep_axis + self.patches_tags = tags + super().__init__( + stl_resolution=stl_resolution, + verbosity=verbosity, + name=name, + patch_name_to_tags=tags, + ) + + def generate_patches(self): + + self.patches["swept_patch_north"] = SweptPatchMultiFace( + cross_sections=self.cross_sections, + sweep_axis=self.sweep_axis, + face_direction="north", + ) + self.patches["swept_patch_east"] = SweptPatchMultiFace( + cross_sections=self.cross_sections, + sweep_axis=self.sweep_axis, + face_direction="east", + ) + self.patches["swept_patch_south"] = SweptPatchMultiFace( + cross_sections=self.cross_sections, + sweep_axis=self.sweep_axis, + face_direction="south", + ) + self.patches["swept_patch_west"] = SweptPatchMultiFace( + cross_sections=self.cross_sections, + sweep_axis=self.sweep_axis, + face_direction="west", + ) + self.patches["swept_patch_end_0"] = self.cross_sections[0] self.patches["swept_patch_end_1"] = self.cross_sections[-1] - # Assign tags - self.patches["swept_patch"].tag = self.patches_tags["swept_tag"] - self.patches["swept_patch_end_0"].tag = self.patches_tags["end_0_tag"] - self.patches["swept_patch_end_1"].tag = self.patches_tags["end_1_tag"] + self.add_tag_to_patches() diff --git a/hypervehicle/components/wing.py b/hypervehicle/components/wing.py index 4d9d709..9deb9ad 100644 --- a/hypervehicle/components/wing.py +++ b/hypervehicle/components/wing.py @@ -48,6 +48,7 @@ def __init__( stl_resolution: int = 2, verbosity: int = 1, name: str = None, + tags: dict = None, ) -> None: """Creates a new fin component. @@ -122,6 +123,8 @@ def __init__( name : str, optional The name tag for the component. The default is None. + tags : dict, optional + tags to be added to each patch (for generating a VTK file) """ # Check if a LE function was provided if LE_wf is None and LE_type == "custom": @@ -151,7 +154,9 @@ def __init__( "CLOSE_WING": close_wing, } - super().__init__(params, stl_resolution, verbosity, name) + super().__init__( + params, stl_resolution, verbosity, name, patch_name_to_tags=tags + ) # Extract construction points for planform # TODO - avoid pre-defined params dict structure for flexibility @@ -191,6 +196,9 @@ def generate_patches(self): if "CLOSE_WING" in self.params and self.params["CLOSE_WING"]: self._close_wing() + # Add tags to patches + self.add_tag_to_patches() + def _create_planform_patches(self): if self.params["Line_B0TT_TYPE"].lower() == "bezier": if self.verbosity > 1: diff --git a/hypervehicle/geometry.py b/hypervehicle/geometry.py index 8ec7f0c..b397579 100644 --- a/hypervehicle/geometry.py +++ b/hypervehicle/geometry.py @@ -149,6 +149,8 @@ class OffsetPatchFunction(ParametricSurface): def __init__(self, underlying_surf, function): self.underlying_surf = underlying_surf self.function = function + if hasattr(underlying_surf, "tag"): + self.tag = underlying_surf.tag def __repr__(self): str = " + offset function" @@ -485,6 +487,8 @@ def __init__(self, underlying_surf, direction=None, fun=None, fun_dash=None): self.direction = direction self.fun = fun self.fun_dash = fun_dash + if hasattr(underlying_surf, "tag"): + self.tag = underlying_surf.tag def __repr__(self): return self.underlying_surf.__repr__() + " with added curvature" @@ -682,6 +686,107 @@ def __call__(self, r, s) -> Vector3: return Vector3(**args) +class SweptPatchMultiFace(ParametricSurface): + """Creates a swept patch from a series of cross sections.""" + + __slots__ = ["cross_sections", "section_origins"] + + def __init__( + self, + cross_sections: list, + face_direction: str, + sweep_axis: str = "z", + ) -> None: + """Construct the SweptPatch object. + + Parameters + ----------- + cross_sections : list + A list containing the cross sections to be blended. These must + be defined in the x-y plane. + sweep_axis : str, optional + The axis to sweep the cross sections along. Note that each cross + section should vary along this axis. The default is "z". + face_direction : str + which of the four coons_path directions will be generated - 'north, 'east', 'south', 'west' + """ + self.cross_sections = cross_sections + self.section_origins = [getattr(cs(0, 0), sweep_axis) for cs in cross_sections] + self._sweep_axis = sweep_axis + self._other_axes = {"x", "y", "z"} - set(sweep_axis) + + if min(self.section_origins) == max(self.section_origins): + raise Exception( + "There is no axial variation in the cross " + "sections provided!" + ) + + self.perimeters = [SurfacePerimeter(s) for s in cross_sections] + self.min_origin = min(self.section_origins) + self.origin_dist = max(self.section_origins) - self.min_origin + + # Check if face_direction is valid + face_directions = ["north", "east", "south", "west"] + if face_direction not in face_directions: + raise Exception("Please define the swept patch corresponding direction") + method_name = f"sweep_{face_direction}" + self._method_to_call = getattr(self, method_name, None) + + def __repr__(self): + return "Swept Patch Multi Face" + + def __call__(self, r, s) -> Vector3: + + if callable(self._method_to_call): + s_dash = self._method_to_call(s) + else: + raise Exception(f"{self._method_to_call} is not callable") + + # Calculate physical axial distance + dist = self.min_origin + r * self.origin_dist + + # Find index of bounding cross sections + for i, lv in enumerate(self.section_origins): + if dist == self.section_origins[-1]: + i = len(self.section_origins) - 2 + break + elif lv <= dist < self.section_origins[i + 1]: + break + + # Get upper and lower perimeter + lps = self.perimeters[i](s_dash) + ups = self.perimeters[i + 1](s_dash) + + # Get upper and lower bounding sections + ls = self.section_origins[i] + us = self.section_origins[i + 1] + + # Linearly interpolate between cross-sectional perimeters + args = { + a: getattr(lps, a) + + (dist - ls) * (getattr(ups, a) - getattr(lps, a)) / (us - ls) + for a in self._other_axes + } + args[self._sweep_axis] = dist + + return Vector3(**args) + + def sweep_south(self, s): + s_dash = s * 0.25 + return s_dash + + def sweep_east(self, s): + s_dash = 0.25 + s * 0.25 + return s_dash + + def sweep_north(self, s): + s_dash = 0.5 + s * 0.25 + return s_dash + + def sweep_west(self, s): + s_dash = 0.75 + s * 0.25 + return s_dash + + class RotatedPatch(ParametricSurface): """ Rotates a surface about a point in an axis-specified direction. @@ -694,7 +799,8 @@ def __init__(self, underlying_surf, angle, axis="x", point=Vector3(x=0, y=0, z=0 self.angle = angle self.axis = axis.lower() self.point = point - self.tag = underlying_surf.tag + if hasattr(underlying_surf, "tag"): + self.tag = underlying_surf.tag def __repr__(self): str = f" (rotated by {np.rad2deg(self.angle)} degrees)" @@ -728,6 +834,8 @@ class MirroredPatch(ParametricSurface): def __init__(self, underlying_surf, axis="x"): self.underlying_surf = underlying_surf self.axis = axis.lower() + if hasattr(underlying_surf, "tag"): + self.tag = underlying_surf.tag def __repr__(self): return self.underlying_surf.__repr__() + f" mirrored along {self.axis}-axis" diff --git a/hypervehicle/utilities.py b/hypervehicle/utilities.py index 6effd20..0e9e443 100644 --- a/hypervehicle/utilities.py +++ b/hypervehicle/utilities.py @@ -3,6 +3,7 @@ import glob import numpy as np import pandas as pd +import enum from stl import mesh from tqdm import tqdm from art import tprint, art @@ -12,7 +13,8 @@ def create_cells( parametric_surface, - triangles_per_edge: int, + triangles_per_edge_r: int, + triangles_per_edge_s: int, si: float = 1.0, sj: float = 1.0, mirror_y=False, @@ -35,8 +37,11 @@ def create_cells( sj : float, optional The clustering in the j-direction. The default is 1.0. - triangles_per_edge : int - The resolution for the stl object. + triangles_per_edge_r : int + The resolution for the stl object in r direction. + + triangles_per_edge_s : int + The resolution for the stl object in s direction. mirror_y : bool, optional Create mirror image about x-z plane. The default is False. @@ -56,10 +61,9 @@ def create_cells( of a single cell """ - # TODO - allow different ni and nj discretisation - ni = triangles_per_edge - nj = triangles_per_edge + ni = triangles_per_edge_r + nj = triangles_per_edge_s # Create list of vertices if i_clustering_func: @@ -123,12 +127,25 @@ def create_cells( cell_ids = [] for i in range(ni): for j in range(nj): - p00 = j * (nj + 1) + i # bottom left - p10 = j * (nj + 1) + i + 1 # bottom right + + ############################################################ + ### Amir - adding ni != nj capability for tagging sub_patchs + + # p00 = j * (nj + 1) + i # bottom left + # p10 = j * (nj + 1) + i + 1 # bottom right + # p01 = (j + 1) * (ni + 1) + i # top left + # p11 = (j + 1) * (ni + 1) + i + 1 # top right + # + # pc = centre_ix + j * min(ni, nj) + i # vertex at centre of cell + + p00 = j * (ni + 1) + i # bottom left + p10 = j * (ni + 1) + i + 1 # bottom right p01 = (j + 1) * (ni + 1) + i # top left p11 = (j + 1) * (ni + 1) + i + 1 # top right - pc = centre_ix + j * min(ni, nj) + i # vertex at centre of cell + pc = centre_ix + j * ni + i # vertex at centre of cell + + ################################################################### if mirror_y or flip_faces: cell_ids.append([p00, pc, p10]) @@ -153,7 +170,8 @@ def default_vertex_func(lb, ub, steps, spacing=1.0): def parametricSurfce2stl( parametric_surface, - triangles_per_edge: int, + triangles_per_edge_r: int, + triangles_per_edge_s: int = None, si: float = 1.0, sj: float = 1.0, mirror_y=False, @@ -176,8 +194,11 @@ def parametricSurfce2stl( sj : float, optional The clustering in the j-direction. The default is 1.0. - triangles_per_edge : int - The resolution for the stl object. + triangles_per_edge_r : int + The resolution for the stl object in r direction. + + triangles_per_edge_s : int, optional + The resolution for the stl object in s direction. mirror_y : bool, optional Create mirror image about x-z plane. The default is False. @@ -204,9 +225,19 @@ def parametricSurfce2stl( The numpy-stl mesh. """ - vertices, cell_ids = create_cells(parametric_surface, triangles_per_edge, - si, sj, mirror_y, flip_faces, i_clustering_func, - j_clustering_func) + if triangles_per_edge_s == None: + triangles_per_edge_s = triangles_per_edge_r + vertices, cell_ids = create_cells( + parametric_surface, + triangles_per_edge_r, + triangles_per_edge_s, + si, + sj, + mirror_y, + flip_faces, + i_clustering_func, + j_clustering_func, + ) # Create the STL mesh object stl_mesh = mesh.Mesh(np.zeros(cell_ids.shape[0], dtype=mesh.Mesh.dtype)) @@ -221,7 +252,8 @@ def parametricSurfce2stl( def parametricSurfce2vtk( parametric_surface, - triangles_per_edge: int, + triangles_per_edge_r: int, + triangles_per_edge_s: int, si: float = 1.0, sj: float = 1.0, mirror_y=False, @@ -242,8 +274,11 @@ def parametricSurfce2vtk( sj : float, optional The clustering in the j-direction. The default is 1.0. - triangles_per_edge : int - The resolution for the stl object. + triangles_per_edge_r : int + The resolution for the stl object in r direction. + + triangles_per_edge_s : int + The resolution for the stl object in s direction. mirror_y : bool, optional Create mirror image about x-z plane. The default is False. @@ -254,7 +289,13 @@ def parametricSurfce2vtk( """ # Generate the mesh vertices and cell index list vertices, cell_ids = create_cells( - parametric_surface, triangles_per_edge, si, sj, mirror_y, flip_faces + parametric_surface, + triangles_per_edge_r, + triangles_per_edge_s, + si, + sj, + mirror_y, + flip_faces, ) # Create the vtk cells format and add patch_tag to each cell @@ -1056,6 +1097,12 @@ def print_banner(): def assign_tags_to_cell(patch, length): """Assign tags to cells.""" # Creates a tag vector for a given patch - tags_definition = {"FreeStream": 1, "Inlet": 2, "Outlet": 3, "Nozzle": 4} - tags = np.ones(length) * tags_definition[patch.tag] + tags = [patch.tag.value] * length return tags + + +class PatchTag(enum.Enum): + FREE_STREAM = 1 + INLET = 2 + OUTLET = 3 + NOZZLE = 4 diff --git a/hypervehicle/vehicle.py b/hypervehicle/vehicle.py index bb22cc8..de123f2 100644 --- a/hypervehicle/vehicle.py +++ b/hypervehicle/vehicle.py @@ -8,6 +8,7 @@ WING_COMPONENT, COMPOSITE_COMPONENT, SWEPT_COMPONENT, + SWEPT_COMPONENT_MULTI_FACE, REVOLVED_COMPONENT, ) @@ -17,6 +18,7 @@ class Vehicle: FIN_COMPONENT, WING_COMPONENT, SWEPT_COMPONENT, + SWEPT_COMPONENT_MULTI_FACE, REVOLVED_COMPONENT, COMPOSITE_COMPONENT, ]