Coverage for /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/hypervehicle/hangar/waverider.py: 91%

143 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-25 22:58 +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 

10 

11 

12class ParametricWaverider(Generator): 

13 """Parameterised hypersonic waverider.""" 

14 

15 zero_offset = 1 

16 curve_zero = 5 

17 

18 def __init__(self, **kwargs) -> None: 

19 """Instantiate the parametric waverider. 

20 

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. 

23 

24 Parameters 

25 ---------- 

26 length : float, optional 

27 The vehicle length. The default is 1. 

28 

29 width : float, optional 

30 The vehicle width. The default is 0.35. 

31 

32 aoa : float, optional 

33 The vehicle angle of attack, specified in degrees. The default is 

34 3.0. 

35 

36 fuse_stl_res : float, optional 

37 The STL resolution of the fuselage. The default is 30. 

38 

39 wing_stl_res : float, optional 

40 The STL resolution of the wings. The default is 5. 

41 

42 fin_stl_res : float, optional 

43 The STL resolution of the fins. The default is 4. 

44 

45 f1x : float, optional 

46 Fuselage control point. 

47 

48 f1y : float, optional 

49 Fuselage control point. 

50 

51 f2y : float, optional 

52 Fuselage control point. 

53 

54 p2x : float, optional 

55 Planform control point. 

56 

57 p2y : float, optional 

58 Planform control point. 

59 

60 p3x : float, optional 

61 Planform control point. 

62 

63 p3y : float, optional 

64 Planform control point. 

65 

66 u1y : float, optional 

67 Upper surface control point. 

68 

69 u2x : float, optional 

70 Upper surface control point. 

71 

72 u3x : float, optional 

73 Upper surface control point. 

74 

75 u3y : float, optional 

76 Upper surface control point. 

77 

78 flap_angle : float, optional 

79 The angle of the wing flap, specified in radians. The default 

80 is 0. 

81 

82 flap_height : float, optional 

83 The height (length) of the flaps. 

84 

85 a0_t : float, optional 

86 Wing start control point. 

87 

88 tt_t : float, optional 

89 Wing end control point. 

90 

91 w1x : float, optional 

92 Wing control point. 

93 

94 w1y : float, optional 

95 Wing control point. 

96 

97 w2x : float, optional 

98 Wing control point. 

99 

100 w2y : float, optional 

101 Wing control point. 

102 

103 x_curve_mult : float, optional 

104 Curvature parameter. 

105 

106 y_curve_mult : float, optional 

107 Curvature parameter. 

108 

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 

123 

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) 

130 

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 

136 

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 

142 

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 

152 

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 ] 

160 

161 # Initialise densities 

162 self.densities = None 

163 

164 # Complete vehicle instantiation 

165 super().__init__(**kwargs) 

166 

167 def create_instance(self) -> Vehicle: 

168 # Instantiate Vehicle 

169 waverider = Vehicle() 

170 waverider.configure(name="waverider", verbosity=1) 

171 

172 # Create fuselage 

173 self._create_fuselage(waverider) 

174 

175 # Create wings 

176 self._create_wings(waverider) 

177 

178 # Create flaps 

179 self._create_flaps(waverider) 

180 

181 # Create tail fin 

182 self._create_fin(waverider) 

183 

184 # Transform to Cart3D coordinates 

185 waverider.add_vehicle_transformations(CART3D) 

186 

187 # Also rotate vehicle to impose AoA 

188 adjusted_aoa = self._adjust_aoa(self.aoa) 

189 waverider.add_vehicle_transformations(("rotate", -adjusted_aoa, "z")) 

190 

191 # Also run analysis after generating patches 

192 if self.densities: 

193 waverider.analyse_after_generating(self.densities) 

194 

195 return waverider 

196 

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 ) 

211 

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) 

220 

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 ) 

230 

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) 

236 

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 ) 

248 

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 ) 

259 

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 

267 

268 # Get height 

269 z_val = 0 

270 

271 return Vector3(x=0, y=0, z=-z_val + self.LE_thickness / 2) 

272 

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 ) 

284 

285 waverider.add_component( 

286 wing, 

287 name="wings", 

288 reflection_axis="y", 

289 curvatures=self.curvatures, 

290 ) 

291 

292 def _lewf(self, r): 

293 """Constant leading edge width function.""" 

294 return self.le_width 

295 

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 

301 

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 

306 

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 

314 

315 # Get height 

316 z_val = self.thickness_contour(s).y 

317 

318 # Adjust height by local fuselage height 

319 z_val *= abs(self._get_local_height(x)) / self.u1y 

320 

321 return Vector3(x=0, y=0, z=-z_val - self.LE_thickness / 2) 

322 

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 

327 

328 def _curv_xd(self, x, y): 

329 return 5 * (self.x_curve_mult - self.curve_zero) * 0.05 * 2 * x 

330 

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 

334 

335 def _curv_yd(self, x, y): 

336 return 5 * (self.y_curve_mult - self.curve_zero) * 0.1 * 2 * y 

337 

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 

343 

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) 

350 

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]) 

355 

356 flap_length = A0.x 

357 

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 ) 

372 

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 ) 

385 

386 def _create_fin(self, waverider: Vehicle): 

387 fin_thickness = 0.005 

388 fin_height = 0.1 

389 fin_length = 0.3 

390 

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) 

395 

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 ) 

414 

415 

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)