planformcreator2 4.3.0__tar.gz → 4.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/PKG-INFO +3 -3
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/README.md +1 -1
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/doc/README_PyPi.md +1 -1
- planformcreator2-4.3.2/icons/AE.ico +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/app.py +6 -1
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/app_model.py +44 -36
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/app_modes.py +14 -15
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/model/VLM_wing.py +92 -41
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/model/wing.py +123 -55
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/ui/pc2_artists.py +83 -28
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/ui/pc2_diagrams.py +67 -58
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/ui/pc2_dialogs.py +11 -3
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/ui/pc2_panels.py +17 -2
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/pyproject.toml +1 -1
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/.gitignore +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/LICENSE +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/icons/PC2.ico +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/icons/PC2_ico.pdn +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/__init__.py +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/__main__.py +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/model/VLM.py +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/model/__init__.py +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/model/wing_exports.py +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/resources.py +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/planformcreator2/ui/__init__.py +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/ASW_17.png +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/Amokka.pc2 +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/Bow.pc2 +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/Elliptical.pc2 +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/F3B_F3F.pc2 +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/Scale_Glider.pc2 +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/Spitfire.png +0 -0
- {planformcreator2-4.3.0 → planformcreator2-4.3.2}/templates/Trapezoid.pc2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: planformcreator2
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.2
|
|
4
4
|
Summary: Design a wing planform
|
|
5
5
|
Project-URL: Homepage, https://github.com/jxjo/PlanformCreator2
|
|
6
6
|
Project-URL: Issues, https://github.com/jxjo/PlanformCreator2/issues
|
|
@@ -12,12 +12,12 @@ Classifier: Operating System :: OS Independent
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Python: >=3.12
|
|
15
|
-
Requires-Dist: airfoileditor~=4.3.
|
|
15
|
+
Requires-Dist: airfoileditor~=4.3.2
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|

|
|
19
19
|
|
|
20
|
-
# v4.3.
|
|
20
|
+
# v4.3.2
|
|
21
21
|
|
|
22
22
|
**PlanformCreator2** (PC2) is an app for designing wing planforms, with a focus on model planes.
|
|
23
23
|
|
|
Binary file
|
|
@@ -66,7 +66,7 @@ logger = logging.getLogger(__name__)
|
|
|
66
66
|
#-------------------------------------------------------------------------------
|
|
67
67
|
|
|
68
68
|
APP_NAME = "PlanformCreator2"
|
|
69
|
-
__version__ = "4.3.
|
|
69
|
+
__version__ = "4.3.2" # hatch "version dynamic" reads this version for build
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
class Main (QMainWindow):
|
|
@@ -220,6 +220,11 @@ class Main (QMainWindow):
|
|
|
220
220
|
def closeEvent (self, event : QCloseEvent):
|
|
221
221
|
""" main window is closed """
|
|
222
222
|
|
|
223
|
+
current_mode = self._modes_manager.current_mode if self._modes_manager else None
|
|
224
|
+
if current_mode is not None and not current_mode.confirm_close():
|
|
225
|
+
event.ignore()
|
|
226
|
+
return
|
|
227
|
+
|
|
223
228
|
# terminate polar watchdog thread, clean up working dir
|
|
224
229
|
self._app_model.close()
|
|
225
230
|
|
|
@@ -70,9 +70,7 @@ class App_Model (QObject):
|
|
|
70
70
|
sig_new_polars = pyqtSignal() # new polars generated
|
|
71
71
|
sig_paneling_changed = pyqtSignal() # paneling definition changed
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
sig_vlm_polar_changed = pyqtSignal() # current VLM polar changed
|
|
75
|
-
sig_vlm_opPoint_changed = pyqtSignal() # current VLM operating point changed
|
|
73
|
+
sig_vlm_changed = pyqtSignal() # current VLM polar/op-point changed
|
|
76
74
|
|
|
77
75
|
|
|
78
76
|
def __init__(self, workingDir_default: str = None):
|
|
@@ -113,6 +111,23 @@ class App_Model (QObject):
|
|
|
113
111
|
return f"<App_Model {self.wing}>"
|
|
114
112
|
|
|
115
113
|
|
|
114
|
+
def _set_cur_polar_def_default(self, silent: bool = False):
|
|
115
|
+
"""Initialize or repair the current polar definition explicitly."""
|
|
116
|
+
|
|
117
|
+
new_polar_def = self.cur_polar_def
|
|
118
|
+
if new_polar_def == self._cur_polar_def:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
self._cur_polar_def = new_polar_def
|
|
122
|
+
logger.debug (f"{self} set cur_polar_def to {new_polar_def}")
|
|
123
|
+
|
|
124
|
+
if self.vlm_alpha_fixed_to_max:
|
|
125
|
+
self._cur_vlm_alpha = None
|
|
126
|
+
|
|
127
|
+
if not silent:
|
|
128
|
+
self.sig_vlm_changed.emit()
|
|
129
|
+
|
|
130
|
+
|
|
116
131
|
def _init_watchdog (self):
|
|
117
132
|
""" initialize watchdog thread to check for new polars and xo2 state changes """
|
|
118
133
|
|
|
@@ -272,35 +287,29 @@ class App_Model (QObject):
|
|
|
272
287
|
def cur_polar_def (self) -> Polar_Definition:
|
|
273
288
|
""" selected T1 polar definition of wingSection at root"""
|
|
274
289
|
|
|
275
|
-
|
|
290
|
+
if self._cur_polar_def in self.polar_definitions_T1:
|
|
291
|
+
return self._cur_polar_def
|
|
276
292
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
new_polar_def = None
|
|
293
|
+
if self.polar_definitions_T1:
|
|
294
|
+
return self.polar_definitions_T1[0]
|
|
280
295
|
|
|
281
|
-
|
|
282
|
-
new_polar_def = self.polar_definitions_T1[0]
|
|
283
|
-
|
|
284
|
-
if new_polar_def != self._cur_polar_def:
|
|
285
|
-
self._cur_polar_def = new_polar_def
|
|
286
|
-
logger.debug (f"{self} set cur_polar_def to {new_polar_def}")
|
|
287
|
-
|
|
288
|
-
# reset current VLM opPoint
|
|
289
|
-
self._cur_vlm_alpha = None if self.vlm_alpha_fixed_to_max else 0.0
|
|
290
|
-
self.sig_vlm_opPoint_changed.emit()
|
|
291
|
-
|
|
292
|
-
return self._cur_polar_def
|
|
296
|
+
return None
|
|
293
297
|
|
|
294
298
|
|
|
295
299
|
def set_cur_polar_def (self, polar_def : Polar_Definition):
|
|
296
300
|
""" set selected polar definition of wingSection at root"""
|
|
297
301
|
|
|
298
|
-
if polar_def
|
|
299
|
-
self._cur_polar_def
|
|
300
|
-
|
|
302
|
+
if polar_def in self.wing.polar_definitions:
|
|
303
|
+
if polar_def != self._cur_polar_def:
|
|
304
|
+
self._cur_polar_def = polar_def
|
|
305
|
+
logger.debug (f"{self} set cur_polar_def to {polar_def}")
|
|
301
306
|
|
|
302
|
-
|
|
303
|
-
|
|
307
|
+
if self.vlm_alpha_fixed_to_max:
|
|
308
|
+
self._cur_vlm_alpha = None
|
|
309
|
+
|
|
310
|
+
self.sig_vlm_changed.emit()
|
|
311
|
+
else:
|
|
312
|
+
self.sig_vlm_changed.emit()
|
|
304
313
|
|
|
305
314
|
|
|
306
315
|
@property
|
|
@@ -311,16 +320,15 @@ class App_Model (QObject):
|
|
|
311
320
|
|
|
312
321
|
@property
|
|
313
322
|
def cur_vlm_polar (self) -> VLM_Polar:
|
|
314
|
-
""" current VLM polar based on
|
|
323
|
+
""" current VLM polar based on the selected root polar definition """
|
|
315
324
|
|
|
316
325
|
if self.wing.vlm_wing and self.cur_polar_def:
|
|
317
|
-
|
|
318
|
-
new_polar = self.wing.vlm_wing.polar_at (v)
|
|
326
|
+
new_polar = self.wing.vlm_wing.polar_at (self.cur_polar_def)
|
|
319
327
|
# do change detection for signal
|
|
320
328
|
if new_polar != self._cur_vlm_polar:
|
|
321
329
|
self._cur_vlm_polar = new_polar
|
|
322
330
|
logger.debug (f"{self} set cur_vlm_polar to {new_polar}")
|
|
323
|
-
self.
|
|
331
|
+
self.sig_vlm_changed.emit()
|
|
324
332
|
return new_polar
|
|
325
333
|
else:
|
|
326
334
|
return None
|
|
@@ -338,7 +346,7 @@ class App_Model (QObject):
|
|
|
338
346
|
aVal = clip (aVal, -20, 20)
|
|
339
347
|
if self._cur_vlm_alpha != aVal:
|
|
340
348
|
self._cur_vlm_alpha = aVal
|
|
341
|
-
self.
|
|
349
|
+
self.sig_vlm_changed.emit()
|
|
342
350
|
|
|
343
351
|
@property
|
|
344
352
|
def vlm_alpha_fixed_to_max (self) -> bool:
|
|
@@ -352,7 +360,7 @@ class App_Model (QObject):
|
|
|
352
360
|
if aBool: # try to set to max now
|
|
353
361
|
self._cur_vlm_alpha = self._get_vlm_alpha_max ()
|
|
354
362
|
|
|
355
|
-
self.
|
|
363
|
+
self.sig_vlm_changed.emit()
|
|
356
364
|
|
|
357
365
|
@property
|
|
358
366
|
def cur_vlm_opPoint (self) -> VLM_OpPoint:
|
|
@@ -380,6 +388,7 @@ class App_Model (QObject):
|
|
|
380
388
|
self._cur_wingSection = None
|
|
381
389
|
self._cur_vlm_alpha = None
|
|
382
390
|
self._cur_polar_def = None
|
|
391
|
+
self._set_cur_polar_def_default(silent=True)
|
|
383
392
|
|
|
384
393
|
self.sig_new_wing.emit()
|
|
385
394
|
|
|
@@ -404,13 +413,12 @@ class App_Model (QObject):
|
|
|
404
413
|
""" notify self that polar definitions have changed """
|
|
405
414
|
|
|
406
415
|
# as polar definitions could have changed, ensure a new initialized polarSet
|
|
407
|
-
self.wing.planform.wingSections.refresh_polar_sets (
|
|
416
|
+
self.wing.planform.wingSections.refresh_polar_sets (reset=True)
|
|
417
|
+
self._set_cur_polar_def_default(silent=True)
|
|
408
418
|
self.sig_polar_set_changed.emit()
|
|
409
419
|
|
|
410
|
-
|
|
411
|
-
self.
|
|
412
|
-
|
|
413
|
-
QTimer.singleShot(0, self.sig_vlm_polar_reset.emit) # notify after current events processed
|
|
420
|
+
|
|
421
|
+
QTimer.singleShot(0, self.sig_vlm_changed.emit) # notify after current events processed
|
|
414
422
|
|
|
415
423
|
|
|
416
424
|
def notify_planform_changed (self):
|
|
@@ -446,7 +454,7 @@ class App_Model (QObject):
|
|
|
446
454
|
if self.vlm_alpha_fixed_to_max:
|
|
447
455
|
self._cur_vlm_alpha = None
|
|
448
456
|
|
|
449
|
-
QTimer.singleShot(0, self.
|
|
457
|
+
QTimer.singleShot(0, self.sig_vlm_changed.emit) # notify after current events processed
|
|
450
458
|
|
|
451
459
|
|
|
452
460
|
def notify_fileName_changed (self):
|
|
@@ -163,6 +163,12 @@ class Mode_Abstract (QObject):
|
|
|
163
163
|
logger.debug(f"Exiting {self.__class__.__name__}")
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
def confirm_close(self) -> bool:
|
|
167
|
+
""" Default close confirmation for modes that do not need special handling. """
|
|
168
|
+
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
|
|
166
172
|
def prepare_check_enter(self, on_arg=None) -> object:
|
|
167
173
|
""" Check if the mode can be entered. Prepare and Return initial object. """
|
|
168
174
|
# to be overridden in subclasses if needed.
|
|
@@ -252,10 +258,11 @@ class Mode_Modify (Mode_Abstract):
|
|
|
252
258
|
super().on_leave()
|
|
253
259
|
|
|
254
260
|
|
|
255
|
-
|
|
261
|
+
@override
|
|
262
|
+
def confirm_close (self) -> bool:
|
|
256
263
|
"""
|
|
257
|
-
Do checks and handle if user wants to leave current planform.
|
|
258
|
-
Returns True if leaving ok, False if cancelled
|
|
264
|
+
Do checks and handle if user wants to close or leave the current planform.
|
|
265
|
+
Returns True if leaving ok, False if cancelled.
|
|
259
266
|
"""
|
|
260
267
|
|
|
261
268
|
leave = True
|
|
@@ -280,7 +287,7 @@ class Mode_Modify (Mode_Abstract):
|
|
|
280
287
|
leave = False
|
|
281
288
|
|
|
282
289
|
if leave:
|
|
283
|
-
self.app_model.cleanup_wing (all = clean_up_all)
|
|
290
|
+
self.app_model.cleanup_wing (all = clean_up_all or self.wing.is_new_wing)
|
|
284
291
|
|
|
285
292
|
return leave
|
|
286
293
|
|
|
@@ -288,7 +295,7 @@ class Mode_Modify (Mode_Abstract):
|
|
|
288
295
|
def open (self):
|
|
289
296
|
""" open a new wing definition and load it"""
|
|
290
297
|
|
|
291
|
-
if not self.
|
|
298
|
+
if not self.confirm_close (): # changes made? user cancelled?
|
|
292
299
|
return
|
|
293
300
|
|
|
294
301
|
filters = "PlanformCreator2 files (*.pc2)"
|
|
@@ -341,7 +348,7 @@ class Mode_Modify (Mode_Abstract):
|
|
|
341
348
|
def new (self):
|
|
342
349
|
""" reset - and start with example definition"""
|
|
343
350
|
|
|
344
|
-
if not self.
|
|
351
|
+
if not self.confirm_close (): # changes made? user cancelled?
|
|
345
352
|
return
|
|
346
353
|
|
|
347
354
|
# select a new template
|
|
@@ -399,14 +406,7 @@ class Mode_Modify (Mode_Abstract):
|
|
|
399
406
|
def exit (self):
|
|
400
407
|
""" User action: leave current mode and app. """
|
|
401
408
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if leave:
|
|
405
|
-
# still new wing? - remove temp dir of airfoil strak etc
|
|
406
|
-
if self.wing.is_new_wing:
|
|
407
|
-
self.app_model.cleanup_wing (all = True)
|
|
408
|
-
|
|
409
|
-
self.sig_exit_requested.emit ()
|
|
409
|
+
self.sig_exit_requested.emit ()
|
|
410
410
|
|
|
411
411
|
|
|
412
412
|
@property
|
|
@@ -631,7 +631,6 @@ class Modes_Manager (QObject):
|
|
|
631
631
|
|
|
632
632
|
def exit(self):
|
|
633
633
|
""" exit mode and close app if in view mode """
|
|
634
|
-
|
|
635
634
|
if self.current_mode is not None:
|
|
636
635
|
self.current_mode.on_leave()
|
|
637
636
|
|
|
@@ -19,18 +19,23 @@ Wing build with VLM_Panels based on Planform_Paneled
|
|
|
19
19
|
|-- VLM_OpPoint - angle of attack
|
|
20
20
|
!-- aerogrid - input data structure of PanelAero
|
|
21
21
|
"""
|
|
22
|
-
|
|
22
|
+
from __future__ import annotations # for forward type references in annotations (wingSection, Planform_Paneled)
|
|
23
|
+
|
|
23
24
|
import numpy as np
|
|
25
|
+
from copy import copy
|
|
24
26
|
from enum import StrEnum
|
|
25
27
|
from typing import NamedTuple, TYPE_CHECKING
|
|
26
28
|
from math import isclose, degrees, radians
|
|
27
29
|
|
|
28
30
|
from .VLM import calc_Qjj
|
|
29
|
-
from airfoileditor.model.polar_set import Polar_Set, RE_SCALE_ROUND_TO,
|
|
31
|
+
from airfoileditor.model.polar_set import Polar_Set, RE_SCALE_ROUND_TO, polarType, Polar, Polar_Definition
|
|
30
32
|
|
|
31
33
|
if TYPE_CHECKING:
|
|
32
34
|
from .wing import Planform_Paneled, WingSection # avoid circular import
|
|
33
35
|
|
|
36
|
+
|
|
37
|
+
VLM_Polar_CacheEntry = tuple[Polar_Definition, 'VLM_Polar']
|
|
38
|
+
|
|
34
39
|
import logging
|
|
35
40
|
logger = logging.getLogger(__name__)
|
|
36
41
|
# logger.setLevel(logging.DEBUG)
|
|
@@ -227,7 +232,7 @@ class VLM_Wing:
|
|
|
227
232
|
self._b_stripes = None # width of stripes
|
|
228
233
|
|
|
229
234
|
self._y_sections = None # y position of wing sections in m
|
|
230
|
-
self._polars
|
|
235
|
+
self._polars: list[VLM_Polar_CacheEntry] = [] # list of (root polar def, VLM polar)
|
|
231
236
|
|
|
232
237
|
|
|
233
238
|
def __repr__(self) -> str:
|
|
@@ -235,7 +240,7 @@ class VLM_Wing:
|
|
|
235
240
|
return f"<{type(self).__name__}>"
|
|
236
241
|
|
|
237
242
|
@property
|
|
238
|
-
def wingSections(self) -> list :
|
|
243
|
+
def wingSections(self) -> list [WingSection]:
|
|
239
244
|
""" wing sections of self._planform_paneled"""
|
|
240
245
|
|
|
241
246
|
planform_paneled : Planform_Paneled = self._planform_paneled
|
|
@@ -350,34 +355,46 @@ class VLM_Wing:
|
|
|
350
355
|
|
|
351
356
|
|
|
352
357
|
|
|
353
|
-
def
|
|
354
|
-
"""
|
|
358
|
+
def _get_cached_polar (self, root_polar_def: Polar_Definition) -> 'VLM_Polar | None':
|
|
359
|
+
""" return cached VLM polar for a matching root polar definition """
|
|
355
360
|
|
|
356
|
-
|
|
357
|
-
|
|
361
|
+
for cached_root_polar_def, polar in self._polars:
|
|
362
|
+
if cached_root_polar_def.is_equal_to (root_polar_def):
|
|
363
|
+
return polar
|
|
364
|
+
return None
|
|
358
365
|
|
|
359
|
-
v = round (vtas, 1) # ensure clean key for dict
|
|
360
366
|
|
|
361
|
-
|
|
367
|
+
def polar_at (self, root_polar_def: Polar_Definition) -> 'VLM_Polar':
|
|
368
|
+
""" returns polar for a root airfoil polar definition """
|
|
369
|
+
|
|
370
|
+
if not isinstance (root_polar_def, Polar_Definition):
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
polar = self._get_cached_polar (root_polar_def) # already exisiting
|
|
362
374
|
|
|
363
375
|
if not polar:
|
|
364
|
-
|
|
365
|
-
|
|
376
|
+
polar_def_copy = copy (root_polar_def) # polar_def in cache must be inmutable
|
|
377
|
+
polar = VLM_Polar (self, polar_def_copy) # calculate new Polar and opPoints
|
|
378
|
+
self._polars.append ((polar_def_copy, polar))
|
|
366
379
|
|
|
367
380
|
return polar
|
|
368
381
|
|
|
382
|
+
|
|
369
383
|
def has_polars (self) -> bool:
|
|
370
384
|
""" True if at least one polar is existing"""
|
|
371
385
|
return bool (self._polars)
|
|
372
386
|
|
|
373
387
|
|
|
374
|
-
def remove_polar_at (self,
|
|
375
|
-
""" removes polar for
|
|
376
|
-
|
|
388
|
+
def remove_polar_at (self, root_polar_def: Polar_Definition):
|
|
389
|
+
""" removes polar for a root airfoil polar definition - will be calculated new on next request"""
|
|
390
|
+
|
|
391
|
+
if not isinstance (root_polar_def, Polar_Definition):
|
|
377
392
|
return
|
|
378
393
|
|
|
379
|
-
|
|
380
|
-
|
|
394
|
+
for i, (cached_root_polar_def, _) in enumerate (self._polars):
|
|
395
|
+
if cached_root_polar_def.is_equal_to (root_polar_def):
|
|
396
|
+
self._polars.pop (i)
|
|
397
|
+
break
|
|
381
398
|
|
|
382
399
|
|
|
383
400
|
|
|
@@ -544,13 +561,19 @@ class VLM_Polar:
|
|
|
544
561
|
VLM solution for certain velocity
|
|
545
562
|
"""
|
|
546
563
|
|
|
547
|
-
def __init__ (self, wing: VLM_Wing,
|
|
564
|
+
def __init__ (self, wing: VLM_Wing, root_polar_def: Polar_Definition):
|
|
565
|
+
|
|
566
|
+
self.vlm_wing = wing # my parent wing
|
|
567
|
+
|
|
568
|
+
# sanity - only T1 polars allowed for VLM calculation
|
|
569
|
+
if not isinstance (root_polar_def, Polar_Definition) or not root_polar_def.type == polarType.T1:
|
|
570
|
+
raise ValueError ("root polar definition must be a T1 polar definition")
|
|
548
571
|
|
|
549
|
-
self.
|
|
550
|
-
self.vtas = vtas # true air speed
|
|
572
|
+
self._root_polar_def = root_polar_def # polar definition of root airfoil (wingSection 0)
|
|
551
573
|
|
|
552
|
-
self.
|
|
553
|
-
self.
|
|
574
|
+
self._vtas = None # true air speed (derived from root polar def)
|
|
575
|
+
self._opPoints = {} # dict of operating points
|
|
576
|
+
self._error_reason = [] # list of error messages eg polar couldn't be loaded
|
|
554
577
|
self._generating_airfoil_polars = False # airfoil polars are currently generated
|
|
555
578
|
self._use_viscous_loop = True # in opPoint calculation
|
|
556
579
|
|
|
@@ -572,6 +595,20 @@ class VLM_Polar:
|
|
|
572
595
|
# overwrite to get a nice print string
|
|
573
596
|
return f"<{type(self).__name__} {self.vtas:.1f}m/s >"
|
|
574
597
|
|
|
598
|
+
|
|
599
|
+
@property
|
|
600
|
+
def vtas (self) -> float:
|
|
601
|
+
""" true air speed of self"""
|
|
602
|
+
|
|
603
|
+
if isinstance (self._root_polar_def, Polar_Definition) and self._vtas is None:
|
|
604
|
+
|
|
605
|
+
# calc v from airfoil polar and chord of root section
|
|
606
|
+
root_chord = self.vlm_wing.wingSections[0].c
|
|
607
|
+
self._vtas = self._root_polar_def.calc_v_for_chord(root_chord)
|
|
608
|
+
|
|
609
|
+
return self._vtas
|
|
610
|
+
|
|
611
|
+
|
|
575
612
|
@property
|
|
576
613
|
def name (self) -> str:
|
|
577
614
|
""" name of polar like T1-22.3-VLM"""
|
|
@@ -774,6 +811,25 @@ class VLM_Polar:
|
|
|
774
811
|
# ---- private ----
|
|
775
812
|
|
|
776
813
|
|
|
814
|
+
def _get_matching_polar (self, airfoil_polarSet: Polar_Set, section_re: float) -> Polar | None:
|
|
815
|
+
""" return the VLM polar that best matches root polar definition and section Re """
|
|
816
|
+
|
|
817
|
+
polar_def_target = copy (self._root_polar_def)
|
|
818
|
+
polar_def_target.set_re (section_re)
|
|
819
|
+
|
|
820
|
+
matching_polar = None
|
|
821
|
+
best_re_delta = float("inf")
|
|
822
|
+
|
|
823
|
+
for polar in airfoil_polarSet.polars_VLM:
|
|
824
|
+
if polar.is_equal_to (polar_def_target, ignore_active=True, ignore_xtrip=True, re_abs_tolerance=RE_SCALE_ROUND_TO):
|
|
825
|
+
re_delta = abs (polar.re - section_re)
|
|
826
|
+
if re_delta < best_re_delta:
|
|
827
|
+
matching_polar = polar
|
|
828
|
+
best_re_delta = re_delta
|
|
829
|
+
|
|
830
|
+
return matching_polar
|
|
831
|
+
|
|
832
|
+
|
|
777
833
|
def _get_airfoil_polars (self) -> list [Polar] | None:
|
|
778
834
|
""" loads for all wingSections polar of airfoil"""
|
|
779
835
|
|
|
@@ -782,7 +838,7 @@ class VLM_Polar:
|
|
|
782
838
|
|
|
783
839
|
# get airfoil polars for all wingSections
|
|
784
840
|
airfoil_polars = []
|
|
785
|
-
|
|
841
|
+
|
|
786
842
|
for section in self.vlm_wing.wingSections:
|
|
787
843
|
|
|
788
844
|
if not section.airfoil.isLoaded:
|
|
@@ -806,26 +862,21 @@ class VLM_Polar:
|
|
|
806
862
|
|
|
807
863
|
# find polar with matching Re of this wing section
|
|
808
864
|
|
|
809
|
-
section_re =
|
|
810
|
-
|
|
811
|
-
for polar in airfoil_polarSet.polars_VLM:
|
|
812
|
-
|
|
813
|
-
if isclose (polar.re, section_re, abs_tol=RE_SCALE_ROUND_TO):
|
|
814
|
-
if polar.isLoaded:
|
|
815
|
-
|
|
816
|
-
# there is a polar that fits to Re of wingSection
|
|
817
|
-
airfoil_polars.append(polar)
|
|
865
|
+
section_re = self._root_polar_def.re * section.cn
|
|
866
|
+
matching_polar = self._get_matching_polar (airfoil_polarSet, section_re)
|
|
818
867
|
|
|
868
|
+
if matching_polar is not None:
|
|
869
|
+
if matching_polar.isLoaded:
|
|
870
|
+
# there is a polar that fits to Re of wingSection
|
|
871
|
+
airfoil_polars.append(matching_polar)
|
|
872
|
+
else:
|
|
873
|
+
if matching_polar.error_occurred:
|
|
874
|
+
self._error_reason.append (matching_polar.error_reason)
|
|
819
875
|
else:
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
break
|
|
825
|
-
|
|
826
|
-
if not isclose (polar.re, section_re, abs_tol=RE_SCALE_ROUND_TO):
|
|
827
|
-
polars_re_list = [f"{p.re:.0f}" for p in airfoil_polarSet.polars_VLM]
|
|
828
|
-
msg = f"No polar with Re = {section_re:.0f} in Polarset of {section} with polars: {polars_re_list}"
|
|
876
|
+
self._generating_airfoil_polars = True
|
|
877
|
+
else:
|
|
878
|
+
msg = (f"No VLM polar matching root definition within ±{RE_SCALE_ROUND_TO:.0f} Re "
|
|
879
|
+
f"for section {section.id} (target Re={section_re:.0f}) " )
|
|
829
880
|
logger.error (msg)
|
|
830
881
|
self._error_reason.append (msg)
|
|
831
882
|
|