Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/hypervehicle/hangar/waverider.py: 91%
143 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
1import numpy as np
2from copy import deepcopy
3from hypervehicle import Vehicle
4from scipy.optimize import bisect
5from hypervehicle.generator import Generator
6from hypervehicle.transformations import CART3D
7from hypervehicle.components import Wing, RevolvedComponent, Fin
8from hypervehicle.geometry import Vector3, Bezier, RobertsFunction
9from hypervehicle.components.common import uniform_thickness_function
12class ParametricWaverider(Generator):
13 """Parameterised hypersonic waverider."""
15 zero_offset = 1
16 curve_zero = 5
18 def __init__(self, **kwargs) -> None:
19 """Instantiate the parametric waverider.
21 Note that the fuselage is a 'ghost' component, meaning that it will not
22 be written to STL, but will be included in any analyses and studies.
24 Parameters
25 ----------
26 length : float, optional
27 The vehicle length. The default is 1.
29 width : float, optional
30 The vehicle width. The default is 0.35.
32 aoa : float, optional
33 The vehicle angle of attack, specified in degrees. The default is
34 3.0.
36 fuse_stl_res : float, optional
37 The STL resolution of the fuselage. The default is 30.
39 wing_stl_res : float, optional
40 The STL resolution of the wings. The default is 5.
42 fin_stl_res : float, optional
43 The STL resolution of the fins. The default is 4.
45 f1x : float, optional
46 Fuselage control point.
48 f1y : float, optional
49 Fuselage control point.
51 f2y : float, optional
52 Fuselage control point.
54 p2x : float, optional
55 Planform control point.
57 p2y : float, optional
58 Planform control point.
60 p3x : float, optional
61 Planform control point.
63 p3y : float, optional
64 Planform control point.
66 u1y : float, optional
67 Upper surface control point.
69 u2x : float, optional
70 Upper surface control point.
72 u3x : float, optional
73 Upper surface control point.
75 u3y : float, optional
76 Upper surface control point.
78 flap_angle : float, optional
79 The angle of the wing flap, specified in radians. The default
80 is 0.
82 flap_height : float, optional
83 The height (length) of the flaps.
85 a0_t : float, optional
86 Wing start control point.
88 tt_t : float, optional
89 Wing end control point.
91 w1x : float, optional
92 Wing control point.
94 w1y : float, optional
95 Wing control point.
97 w2x : float, optional
98 Wing control point.
100 w2y : float, optional
101 Wing control point.
103 x_curve_mult : float, optional
104 Curvature parameter.
106 y_curve_mult : float, optional
107 Curvature parameter.
109 densities : dict, optional
110 A dictionary containing the density of each component ('fuselage',
111 'wings', 'right_flap', 'left_flap', and 'fin'). If None provided,
112 the inertial analysis will not be conducted.
113 """
114 # Global parameters
115 self.length = 1
116 self.width = 0.35
117 self.le_width = 0.003 # width of LE
118 self.LE_thickness = 0.003
119 self.aoa = 3.0
120 self.fuse_stl_res = 30
121 self.wing_stl_res = 5
122 self.fin_stl_res = 4
124 # Fuselage control points
125 body_radius = 0.075 # For manual control/development
126 self.f1x = 0.25 * self.length
127 self.f1y = body_radius
128 self.f2y = body_radius
129 self._cf = RobertsFunction(True, True, 1.025)
131 # Wing-body planform control points
132 self.p2x = 1 * self.length / 3
133 self.p2y = 2 * self.width / 2 / 3
134 self.p3x = 2.75 * self.length / 3
135 self.p3y = 1.5 * self.width / 2 / 3
137 # Upper Bezier control points
138 self.u1y = 0.1
139 self.u2x = self.width / 2 / 3
140 self.u3x = 2 * self.width / 2 / 3
141 self.u3y = self.u1y / 10
143 # Wing flaps
144 self.flap_angle = 0 # flap deflection
145 self.flap_height = 0.1 # wing span
146 self.a0_t = 0.065 # wing connection start
147 self.tt_t = 0.35 # wing connection end
148 self.w1x = 0.1
149 self.w1y = 0.0 + self.zero_offset
150 self.w2x = 0.1
151 self.w2y = 0.0 + self.zero_offset
153 # Curvature
154 self.x_curve_mult = 0.001 + self.curve_zero
155 self.y_curve_mult = 0.001 + self.curve_zero
156 self.curvatures = [
157 ("x", self._curv_x, self._curv_xd),
158 ("y", self._curv_y, self._curv_yd),
159 ]
161 # Initialise densities
162 self.densities = None
164 # Complete vehicle instantiation
165 super().__init__(**kwargs)
167 def create_instance(self) -> Vehicle:
168 # Instantiate Vehicle
169 waverider = Vehicle()
170 waverider.configure(name="waverider", verbosity=1)
172 # Create fuselage
173 self._create_fuselage(waverider)
175 # Create wings
176 self._create_wings(waverider)
178 # Create flaps
179 self._create_flaps(waverider)
181 # Create tail fin
182 self._create_fin(waverider)
184 # Transform to Cart3D coordinates
185 waverider.add_vehicle_transformations(CART3D)
187 # Also rotate vehicle to impose AoA
188 adjusted_aoa = self._adjust_aoa(self.aoa)
189 waverider.add_vehicle_transformations(("rotate", -adjusted_aoa, "z"))
191 # Also run analysis after generating patches
192 if self.densities:
193 waverider.analyse_after_generating(self.densities)
195 return waverider
197 def _create_fuselage(self, waverider: Vehicle) -> None:
198 # Create nominal revolved fuselage
199 self.fuselage_revolve_line = Bezier(
200 [
201 Vector3(x=0.0, y=0.0),
202 Vector3(x=self.f1x, y=self.f1y),
203 Vector3(x=self.length / 2, y=self.f2y),
204 Vector3(x=self.length - self.f1x, y=self.f1y),
205 Vector3(x=self.length, y=0.0),
206 ]
207 )
208 fuselage = RevolvedComponent(
209 self.fuselage_revolve_line, stl_resolution=self.fuse_stl_res
210 )
212 # Define offset function to collapse underside
213 def modify_underside(x, y, z):
214 if z > 0:
215 # This is the underside, collapse it
216 return -Vector3(0, 0, z)
217 else:
218 # This is the top side, do nothing
219 return Vector3(0, 0, 0)
221 # Add fuselage component to vehicle
222 waverider.add_component(
223 fuselage,
224 name="fuselage",
225 clustering={"i_clustering_func": self._cf},
226 curvatures=self.curvatures,
227 modifier_function=modify_underside,
228 # ghost=True,
229 )
231 def _create_wings(self, waverider: Vehicle) -> None:
232 # Create body planform line
233 A1 = Vector3(x=0.5 * self.length, y=0)
234 TT = Vector3(x=self.length, y=0)
235 B0 = Vector3(x=0, y=self.width / 2)
237 # Need to limit p2y based on fuselage width
238 p2y = max(self.p2y, self._get_local_height(self.p2x))
239 p3y = max(self.p3y, self._get_local_height(self.p2x))
240 Line_B0TT = Bezier(
241 [
242 B0,
243 Vector3(self.p2x, p2y),
244 Vector3(self.p3x, p3y),
245 TT,
246 ]
247 )
249 # Define body cross-sectional thickness line
250 self.Line_B0TT = Line_B0TT
251 self.thickness_contour = Bezier(
252 [
253 Vector3(0.0, self.u1y),
254 Vector3(self.u2x, self.u1y),
255 Vector3(self.u3x, self.u3y),
256 Vector3(self.width / 2, 0.0),
257 ]
258 )
260 def wing1_tf_bot(x, y, z=0):
261 # Get local parametric width
262 local_width = self._get_local_width(x)
263 if local_width == 0:
264 s = 0
265 else:
266 s = y / local_width
268 # Get height
269 z_val = 0
271 return Vector3(x=0, y=0, z=-z_val + self.LE_thickness / 2)
273 # Create wing component
274 wing = Wing(
275 A1=A1,
276 TT=TT,
277 B0=B0,
278 Line_B0TT=Line_B0TT,
279 top_tf=self._wing1_tf_top,
280 bot_tf=wing1_tf_bot,
281 LE_wf=self._lewf,
282 stl_resolution=self.wing_stl_res,
283 )
285 waverider.add_component(
286 wing,
287 name="wings",
288 reflection_axis="y",
289 curvatures=self.curvatures,
290 )
292 def _lewf(self, r):
293 """Constant leading edge width function."""
294 return self.le_width
296 def _get_local_width(self, x):
297 """Returns the absolute lateral vehicle width at a given x."""
298 func = lambda t: self.Line_B0TT(t).x - x
299 t = bisect(func, 0.0, 1.0)
300 return self.Line_B0TT(t).y
302 def _get_local_height(self, x):
303 func = lambda t: self.fuselage_revolve_line(t).x - x
304 t = bisect(func, 0.0, 1.0)
305 return self.fuselage_revolve_line(t).y
307 def _wing1_tf_top(self, x, y, z=0):
308 # Get local parametric width
309 local_width = self._get_local_width(x)
310 if local_width == 0:
311 s = 0
312 else:
313 s = y / local_width
315 # Get height
316 z_val = self.thickness_contour(s).y
318 # Adjust height by local fuselage height
319 z_val *= abs(self._get_local_height(x)) / self.u1y
321 return Vector3(x=0, y=0, z=-z_val - self.LE_thickness / 2)
323 # Define curvatures
324 def _curv_x(self, x, y):
325 "Curvature in x-direction (about y-axis)"
326 return 5 * (self.x_curve_mult - self.curve_zero) * 0.05 * x**2
328 def _curv_xd(self, x, y):
329 return 5 * (self.x_curve_mult - self.curve_zero) * 0.05 * 2 * x
331 def _curv_y(self, x, y):
332 "Curvature in y-direction (about x-axis)"
333 return 5 * (self.y_curve_mult - self.curve_zero) * 0.1 * y**2
335 def _curv_yd(self, x, y):
336 return 5 * (self.y_curve_mult - self.curve_zero) * 0.1 * 2 * y
338 def _adjust_aoa(self, nominal_aoa):
339 """Adjusts the nominal AoA based on the curvature function."""
340 angle = self._curv_xd(x=self.length, y=0)
341 curve_rotation = np.rad2deg(angle)
342 return nominal_aoa + curve_rotation
344 def _create_flaps(self, waverider: Vehicle):
345 # Define flap planform
346 A0 = self.Line_B0TT(self.a0_t)
347 A1 = self.Line_B0TT(self.a0_t + (self.tt_t - self.a0_t) / 2)
348 TT = self.Line_B0TT(self.tt_t)
349 B0 = A0 + Vector3(x=0.0, y=self.flap_height)
351 # Define flap leading edge profile
352 cp1 = B0 + Vector3(x=self.w1x, y=self.w1y - self.zero_offset)
353 cp2 = TT - Vector3(x=self.w2x, y=self.w2y - self.zero_offset)
354 line_B0TT = Bezier([B0, cp1, cp2, TT])
356 flap_length = A0.x
358 wing = Wing(
359 A0=A0,
360 A1=A1,
361 TT=TT,
362 B0=B0,
363 Line_B0TT=line_B0TT,
364 top_tf=uniform_thickness_function(thickness=self.LE_thickness, side="top"),
365 bot_tf=uniform_thickness_function(thickness=self.LE_thickness, side="bot"),
366 flap_length=flap_length,
367 flap_angle=self.flap_angle,
368 LE_wf=self._lewf,
369 close_wing=True,
370 stl_resolution=self.wing_stl_res,
371 )
373 waverider.add_component(
374 wing,
375 name="right_flap",
376 curvatures=self.curvatures,
377 )
378 waverider.add_component(
379 deepcopy(wing),
380 name="left_flap",
381 reflection_axis="y",
382 append_reflection=False,
383 curvatures=self.curvatures,
384 )
386 def _create_fin(self, waverider: Vehicle):
387 fin_thickness = 0.005
388 fin_height = 0.1
389 fin_length = 0.3
391 p0 = Vector3(x=0.0, y=0.0)
392 p1 = Vector3(x=0.1 * fin_length, y=0.7 * fin_height)
393 p2 = Vector3(x=0.6 * fin_length, y=fin_height)
394 p3 = Vector3(x=fin_length, y=0.0)
396 fin = Fin(
397 p0=p0,
398 p1=p1,
399 p2=p2,
400 p3=p3,
401 fin_thickness=fin_thickness,
402 fin_angle=np.deg2rad(-90),
403 top_thickness_function=uniform_thickness_function(fin_thickness, "top"),
404 bot_thickness_function=uniform_thickness_function(fin_thickness, "bot"),
405 LE_wf=lambda r: 0.01,
406 pivot_point=Vector3(x=0, y=0),
407 stl_resolution=self.fin_stl_res,
408 )
409 waverider.add_component(
410 fin,
411 name="fin",
412 curvatures=self.curvatures,
413 )
416if __name__ == "__main__":
417 # To create the nominal geometry
418 parametric_generator = ParametricWaverider()
419 vchicle = parametric_generator.create_instance()
420 vchicle.generate()
421 vchicle.to_stl(merge=True)