Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/hypervehicle/components/fin.py: 96%

145 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-29 02:51 +0000

1from __future__ import annotations 

2import numpy as np 

3from scipy.optimize import bisect 

4from hypervehicle.geometry.vector import Vector3 

5from hypervehicle.geometry.surface import CoonsPatch 

6from hypervehicle.geometry.path import Line, Polyline 

7from typing import Callable, Optional 

8from hypervehicle.components.component import Component 

9from hypervehicle.components.constants import FIN_COMPONENT 

10from hypervehicle.geometry.geometry import ( 

11 OffsetPatchFunction, 

12 SubRangedPath, 

13 ElipsePath, 

14 ArcLengthParameterizedPath, 

15 TrailingEdgePatch, 

16 TrailingEdgePath, 

17 RotatedPatch, 

18 MeanLeadingEdgePatchFunction, 

19 OffsetPathFunction, 

20 GeometricMeanPathFunction, 

21 ReversedPath, 

22) 

23 

24 

25class Fin(Component): 

26 componenttype = FIN_COMPONENT 

27 

28 def __init__( 

29 self, 

30 p0: Vector3, 

31 p1: Vector3, 

32 p2: Vector3, 

33 p3: Vector3, 

34 fin_thickness: float, 

35 fin_angle: float, 

36 top_thickness_function: Callable, 

37 bot_thickness_function: Callable, 

38 LE_wf: Optional[Callable] = None, 

39 mirror: Optional[bool] = False, 

40 rudder_type: Optional[str] = "flat", 

41 rudder_length: Optional[float] = 0, 

42 rudder_angle: Optional[float] = 0, 

43 pivot_angle: Optional[float] = 0, 

44 pivot_point: Optional[Vector3] = Vector3(x=0, y=0), 

45 offset_func: Optional[Callable] = None, 

46 stl_resolution: Optional[int] = 2, 

47 verbosity: Optional[int] = 1, 

48 name: Optional[str] = None, 

49 ) -> None: 

50 """Creates a new fin component. 

51 

52 Fin geometry defined by 4 points and straight edges 

53 between the points that define the fin planform. 

54 Leading Edge runs p3->p2->p1. 

55 

56 p1--N--p2 

57 | \ 

58 w e <---- FLOW 

59 | \ 

60 p0-----S------p3 

61 

62 Parameters 

63 ---------- 

64 p0 : Vector3 

65 Point p0 of the fin geometry. 

66 

67 p1 : Vector3 

68 Point p1 of the fin geometry. 

69 

70 p2 : Vector3 

71 Point p2 of the fin geometry. 

72 

73 p3 : Vector3 

74 Point p3 of the fin geometry. 

75 

76 fin_thickness : float 

77 The thickness of the fin. 

78 

79 fin_angle : float 

80 The axial position angle of the placement of the fin. 

81 

82 top_thickness_function : Callable 

83 The thickness function for the top surface of the fin. 

84 

85 bot_thickness_function : Callable 

86 The thickness function for the top surface of the fin. 

87 

88 LE_wf : Callable, optional 

89 The thickness function for the leading edge of the fin. 

90 

91 mirror : bool, optional 

92 Mirror the fin. The default is False. 

93 

94 rudder_type : str, optional 

95 The type of rudder to use, either "flat" or "sharp". The 

96 default is "flat". 

97 

98 rudder_length : float, optional 

99 The length of the rudder. The default is 0. 

100 

101 pivot_angle : float, optional 

102 The pivot angle of the entire fin, about its central axis. 

103 The default is 0. 

104 

105 pivot_point : Vector3, optional 

106 The point about which to apply the pivot_angle. The default 

107 is Vector3(0,0,0). 

108 

109 offset_func : Callable, optional 

110 The function to apply when offsetting the fin position. 

111 The default is None. 

112 

113 stl_resolution : int, optional 

114 The stl resolution to use when creating the mesh for this 

115 component. The default is None. 

116 

117 verbosity : int, optional 

118 The verbosity of the component. The default is 1. 

119 

120 name : str, optional 

121 The name tag for the component. The default is None. 

122 """ 

123 

124 if LE_wf is None: 

125 # Use default LE function 

126 from hypervehicle.components.common import leading_edge_width_function 

127 

128 LE_wf = leading_edge_width_function 

129 

130 params = { 

131 "p0": p0, 

132 "p1": p1, 

133 "p2": p2, 

134 "p3": p3, 

135 "FIN_THICKNESS": fin_thickness, 

136 "FIN_ANGLE": fin_angle, 

137 "FIN_TOP_THICKNESS_FUNC": top_thickness_function, 

138 "FIN_BOTTOM_THICKNESS_FUNC": bot_thickness_function, 

139 "FIN_LEADING_EDGE_FUNC": LE_wf, 

140 "MIRROR_NEW_COMPONENT": mirror, 

141 "rudder_type": rudder_type, 

142 "rudder_length": rudder_length, 

143 "rudder_angle": rudder_angle, 

144 "pivot_angle": pivot_angle, 

145 "pivot_point": pivot_point, 

146 "offset_function": offset_func, 

147 } 

148 

149 super().__init__( 

150 params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name 

151 ) 

152 

153 def generate_patches(self): 

154 # Initialise 

155 temp_fin_patch_dict = {} 

156 

157 # Extract geometric properties 

158 p0 = self.params["p0"] 

159 p1 = self.params["p1"] 

160 p2 = self.params["p2"] 

161 p3 = self.params["p3"] 

162 fin_thickness = self.params["FIN_THICKNESS"] 

163 fin_angle = self.params["FIN_ANGLE"] 

164 fin_thickness_function_top = self.params["FIN_TOP_THICKNESS_FUNC"] 

165 fin_thickness_function_bot = self.params["FIN_BOTTOM_THICKNESS_FUNC"] 

166 leading_edge_width_function = self.params["FIN_LEADING_EDGE_FUNC"] 

167 

168 p1p2 = Line(p1, p2) 

169 p2p3 = Line(p2, p3) 

170 p1p3 = Polyline(segments=[p1p2, p2p3]) 

171 

172 if self.verbosity > 1: 

173 print(" Creating fin planform.") 

174 

175 p0p1 = Line(p0, p1) 

176 fin_patch = CoonsPatch( 

177 north=Line(p1, p2), 

178 east=Line(p3, p2), 

179 south=Line(p0, p3), 

180 west=Line(p0, p1), 

181 ) 

182 flipped_fin_patch = CoonsPatch( 

183 north=Line(p3, p2), 

184 east=Line(p1, p2), 

185 south=Line(p0, p1), 

186 west=Line(p0, p3), 

187 ) 

188 

189 if self.verbosity > 1: 

190 print(" Adding thickness to fin.") 

191 top_patch = OffsetPatchFunction(flipped_fin_patch, fin_thickness_function_top) 

192 bot_patch = OffsetPatchFunction(fin_patch, fin_thickness_function_bot) 

193 temp_fin_patch_dict["top_patch"] = top_patch 

194 temp_fin_patch_dict["bot_patch"] = bot_patch 

195 

196 if self.verbosity > 1: 

197 print(" Adding Leading Edge to fin.") 

198 

199 top_edge_path = OffsetPathFunction(p1p3, fin_thickness_function_top) 

200 bot_edge_path = OffsetPathFunction(p1p3, fin_thickness_function_bot) 

201 mean_path = GeometricMeanPathFunction(top_edge_path, bot_edge_path) 

202 

203 # Find Locations 

204 fun_B1 = lambda t: p1p3(t).x - p2.x 

205 t_B1 = round(bisect(fun_B1, 0.0, 1.0), 6) 

206 t_B2 = 1 

207 

208 LE_top_patch = [np.nan, np.nan, np.nan] 

209 LE_bot_patch = [np.nan, np.nan, np.nan] 

210 

211 # Eliptical LE 

212 LE_top_patch[0] = MeanLeadingEdgePatchFunction( 

213 mean_path, 

214 top_edge_path, 

215 LE_width_function=leading_edge_width_function, 

216 t0=0.0, 

217 t1=t_B1, 

218 side="top", 

219 ) 

220 LE_top_patch[1] = MeanLeadingEdgePatchFunction( 

221 mean_path, 

222 top_edge_path, 

223 LE_width_function=leading_edge_width_function, 

224 t0=t_B1, 

225 t1=t_B2, 

226 side="top", 

227 ) 

228 

229 LE_bot_patch[0] = MeanLeadingEdgePatchFunction( 

230 mean_path, 

231 bot_edge_path, 

232 LE_width_function=leading_edge_width_function, 

233 t0=0.0, 

234 t1=t_B1, 

235 side="bot", 

236 ) 

237 LE_bot_patch[1] = MeanLeadingEdgePatchFunction( 

238 mean_path, 

239 bot_edge_path, 

240 LE_width_function=leading_edge_width_function, 

241 t0=t_B1, 

242 t1=t_B2, 

243 side="bot", 

244 ) 

245 

246 temp_fin_patch_dict["LE_top_patch_0"] = LE_top_patch[0] 

247 temp_fin_patch_dict["LE_top_patch_1"] = LE_top_patch[1] 

248 temp_fin_patch_dict["LE_bot_patch_0"] = LE_bot_patch[0] 

249 temp_fin_patch_dict["LE_bot_patch_1"] = LE_bot_patch[1] 

250 

251 if self.verbosity > 1: 

252 print(" Adding bottom face.") 

253 

254 thickness_top = fin_thickness_function_top(x=p3.x, y=p3.y).z 

255 thickness_bot = fin_thickness_function_bot(x=p3.x, y=p3.y).z 

256 elipse_top = ElipsePath( 

257 centre=p3, 

258 thickness=thickness_top, 

259 LE_width=leading_edge_width_function(1), 

260 side="top", 

261 ) 

262 elipse_bot = ElipsePath( 

263 centre=p3, 

264 thickness=thickness_bot, 

265 LE_width=leading_edge_width_function(1), 

266 side="bot", 

267 ) 

268 elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top) 

269 elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot) 

270 

271 p3p3_top = Line(p0=p3 - Vector3(0, 0, fin_thickness / 2), p1=p3) 

272 p3p3_bot = Line(p0=p3, p1=p3 + Vector3(0, 0, fin_thickness / 2)) 

273 

274 temp_bottom_ellipse_patch = CoonsPatch( 

275 north=p3p3_bot, south=elipse_top, east=elipse_bot, west=p3p3_top 

276 ) 

277 

278 # Rotate patch into x-z plane about p3 

279 bottom_ellipse_patch = RotatedPatch( 

280 temp_bottom_ellipse_patch, np.deg2rad(-90), axis="z", point=p3 

281 ) 

282 

283 # Create rectangular patches for rest of fin bottom 

284 p3p0 = Line(p3, p0) 

285 p3p0_bot = Line( 

286 p0=p3 + Vector3(0, 0, fin_thickness / 2), 

287 p1=p0 + Vector3(0, 0, fin_thickness / 2), 

288 ) 

289 p3p0_top = Line( 

290 p0=p3 - Vector3(0, 0, fin_thickness / 2), 

291 p1=p0 - Vector3(0, 0, fin_thickness / 2), 

292 ) 

293 

294 p0p0_top = Line(p0=p0, p1=p0 - Vector3(0, 0, fin_thickness / 2)) 

295 p0_botp0 = Line(p0=p0 + Vector3(0, 0, fin_thickness / 2), p1=p0) 

296 # p3p3_top_reversed = SubRangedPath(p3p3_top, 1, 0) 

297 # p3p3_bot_reversed = SubRangedPath(p3p3_bot, 1, 0) 

298 p3p3_top_reversed = ReversedPath(p3p3_top) 

299 p3p3_bot_reversed = ReversedPath(p3p3_bot) 

300 

301 bot_1 = CoonsPatch( 

302 north=p3p0_top, south=p3p0, east=p0p0_top, west=p3p3_top_reversed 

303 ) 

304 bot_2 = CoonsPatch( 

305 north=p3p0, south=p3p0_bot, east=p0_botp0, west=p3p3_bot_reversed 

306 ) 

307 

308 temp_fin_patch_dict["bot_ellip"] = bottom_ellipse_patch 

309 temp_fin_patch_dict["bot_1"] = bot_1 

310 temp_fin_patch_dict["bot_2"] = bot_2 

311 

312 if self.verbosity > 1: 

313 print(" Adding Trailing Edge.") 

314 

315 if self.params["rudder_type"] == "sharp": 

316 # Create sharp trailing edge 

317 

318 # First create top and bottom paths connecting fin to TE 

319 TE_top = TrailingEdgePath( 

320 p0, p1, thickness_function=fin_thickness_function_top 

321 ) 

322 TE_bot = TrailingEdgePath( 

323 p0, p1, thickness_function=fin_thickness_function_bot 

324 ) 

325 

326 # Make top and bottom of rudder 

327 TE_top_patch = TrailingEdgePatch( 

328 A0=p0, 

329 B0=p1, 

330 TE_path=TE_top, 

331 flap_length=self.params["rudder_length"], 

332 flap_angle=self.params["rudder_angle"], 

333 side="top", 

334 ) 

335 TE_bot_patch = TrailingEdgePatch( 

336 A0=p0, 

337 B0=p1, 

338 TE_path=TE_bot, 

339 flap_length=self.params["rudder_length"], 

340 flap_angle=self.params["rudder_angle"], 

341 side="bot", 

342 ) 

343 

344 # Create corner patches 

345 thickness_top = fin_thickness_function_top(x=p1.x, y=p1.y).z 

346 thickness_bot = fin_thickness_function_bot(x=p1.x, y=p1.y).z 

347 

348 elipse_top = ElipsePath( 

349 centre=p1, 

350 thickness=thickness_top, 

351 LE_width=leading_edge_width_function(0.0), 

352 side="top", 

353 ) 

354 elipse_bot = ElipsePath( 

355 centre=p1, 

356 thickness=thickness_bot, 

357 LE_width=leading_edge_width_function(0.0), 

358 side="bot", 

359 ) 

360 elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top) 

361 elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot) 

362 

363 south = Line( 

364 p0=Vector3(x=p1.x, y=p1.y, z=thickness_top), 

365 p1=Vector3( 

366 x=p1.x - self.params["rudder_length"], 

367 y=p1.y, 

368 z=self.params["rudder_length"] 

369 * np.sin(self.params["rudder_angle"]), 

370 ), 

371 ) 

372 east = Line( 

373 p0=Vector3( 

374 x=p1.x - self.params["rudder_length"], 

375 y=p1.y, 

376 z=self.params["rudder_length"] 

377 * np.sin(self.params["rudder_angle"]), 

378 ), 

379 p1=Vector3(x=p1.x, y=p1.y, z=thickness_bot), 

380 ) 

381 

382 TE_ellip_patch = CoonsPatch( 

383 north=elipse_bot, west=elipse_top, south=south, east=east 

384 ) 

385 

386 # Create bottom triangle patch 

387 p0_top = p0 - Vector3(0, 0, fin_thickness / 2) 

388 p0_bot = p0 + Vector3(0, 0, fin_thickness / 2) 

389 TE = Vector3( 

390 x=p0.x - self.params["rudder_length"], 

391 y=p0.y, 

392 z=self.params["rudder_length"] * np.sin(self.params["rudder_angle"]), 

393 ) 

394 

395 p0p0_top = Line(p0=p0 - Vector3(0, 0, fin_thickness / 2), p1=p0) 

396 p0p0_bot = Line(p0=p0, p1=p0 + Vector3(0, 0, fin_thickness / 2)) 

397 p0_top_TE = Line(p0=p0_top, p1=TE) 

398 p0_bot_TE = Line(p0=TE, p1=p0_bot) 

399 

400 TE_triangle_patch = CoonsPatch( 

401 north=p0_bot_TE, south=p0p0_top, east=p0p0_bot, west=p0_top_TE 

402 ) 

403 

404 # Add to patch dict 

405 temp_fin_patch_dict["TE_top"] = TE_top_patch 

406 temp_fin_patch_dict["TE_bot"] = TE_bot_patch 

407 temp_fin_patch_dict["TE_ellipse"] = TE_ellip_patch 

408 temp_fin_patch_dict["TE_triangle"] = TE_triangle_patch 

409 

410 else: 

411 # Create patch for top of fin TE (elliptical section) 

412 thickness_top = fin_thickness_function_top(x=p1.x, y=p1.y).z 

413 thickness_bot = fin_thickness_function_bot(x=p1.x, y=p1.y).z 

414 elipse_top = ElipsePath( 

415 centre=p1, 

416 thickness=thickness_top, 

417 LE_width=leading_edge_width_function(0.0), 

418 side="top", 

419 ) 

420 elipse_bot = ElipsePath( 

421 centre=p1, 

422 thickness=thickness_bot, 

423 LE_width=leading_edge_width_function(0.0), 

424 side="bot", 

425 ) 

426 elipse_top = ArcLengthParameterizedPath(underlying_path=elipse_top) 

427 elipse_bot = ArcLengthParameterizedPath(underlying_path=elipse_bot) 

428 

429 # Now reverse the paths for correct orientation 

430 elipse_top = SubRangedPath(underlying_path=elipse_top, t0=1.0, t1=0.0) 

431 elipse_bot = SubRangedPath(underlying_path=elipse_bot, t0=1.0, t1=0.0) 

432 

433 p1p1_top = Line(p0=p1, p1=p1 - Vector3(0, 0, fin_thickness / 2)) 

434 p1_botp1 = Line(p0=p1 + Vector3(0, 0, fin_thickness / 2), p1=p1) 

435 

436 back_ellipse_patch = CoonsPatch( 

437 north=p1p1_top, south=elipse_bot, east=elipse_top, west=p1_botp1 

438 ) 

439 

440 # Create rectangular patches for rest of fin TE 

441 p0_botp1_bot = Line( 

442 p0=p0 + Vector3(0, 0, fin_thickness / 2), 

443 p1=p1 + Vector3(0, 0, fin_thickness / 2), 

444 ) 

445 p0_top1_top = Line( 

446 p0=p0 - Vector3(0, 0, fin_thickness / 2), 

447 p1=p1 - Vector3(0, 0, fin_thickness / 2), 

448 ) 

449 

450 p0p0_top = Line(p0=p0, p1=p0 - Vector3(0, 0, fin_thickness / 2)) 

451 p0_botp0 = Line(p0=p0 + Vector3(0, 0, fin_thickness / 2), p1=p0) 

452 

453 TE_back_1 = CoonsPatch( 

454 north=p0_top1_top, south=p0p1, east=p1p1_top, west=p0p0_top 

455 ) 

456 TE_back_2 = CoonsPatch( 

457 north=p0p1, south=p0_botp1_bot, east=p1_botp1, west=p0_botp0 

458 ) 

459 

460 # Add to patch dict 

461 temp_fin_patch_dict["TE_ellip"] = back_ellipse_patch 

462 temp_fin_patch_dict["TE_1"] = TE_back_1 

463 temp_fin_patch_dict["TE_2"] = TE_back_2 

464 

465 # Create fin patch dict 

466 fin_patch_dict = temp_fin_patch_dict.copy() 

467 

468 # Rotate patches again for rudder angle 

469 if "pivot_angle" in self.params: 

470 for key, patch in fin_patch_dict.items(): 

471 fin_patch_dict[key] = RotatedPatch( 

472 patch, 

473 self.params["pivot_angle"], 

474 axis="y", 

475 point=self.params["pivot_point"], 

476 ) 

477 

478 # Rotate patches and add to fin_patch_dict 

479 for key, patch in fin_patch_dict.items(): 

480 fin_patch_dict[key] = RotatedPatch(patch, fin_angle) 

481 

482 if ( 

483 "offset_function" in self.params 

484 and self.params["offset_function"] is not None 

485 ): 

486 if self.verbosity > 1: 

487 print(" Applying fin offset function.") 

488 for patch in fin_patch_dict: 

489 fin_patch_dict[patch] = OffsetPatchFunction( 

490 fin_patch_dict[patch], self.params["offset_function"] 

491 ) 

492 

493 # Save patches 

494 self.patches = fin_patch_dict