FreeCAD-macros

Форк
0
/
AeroFoil.FCMacro 
2741 строка · 111.9 Кб
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3

4
##########################################################################################
5
#####				L I C E N S E					     #####
6
##########################################################################################
7
#
8
#  GNU LESSER GENERAL PUBLIC LICENSE
9
#  Version 2.1, February 1999
10
#
11
#  Copyright (C) 1991, 1999 Free Software Foundation, Inc.
12
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
13
#  Everyone is permitted to copy and distribute verbatim copies
14
#  of this license document, but changing it is not allowed.
15
#
16
#  [This is the first released version of the Lesser GPL. It also counts
17
#  as the successor of the GNU Library Public License, version 2, hence
18
#  the version number 2.1.]
19
#
20
#  'AeroFoil' is a FreeCAD macro. AeroFoil creates airfoil curves and faces
21
#  using pre-defined models, algebraic functions, and DAT or CSV Files.
22
#
23
#  Copyright (C) 2021  Melwyn Francis Carlo
24
#
25
#  This library is free software; you can redistribute it and/or
26
#  modify it under the terms of the GNU Lesser General Public
27
#  License as published by the Free Software Foundation; either
28
#  version 2.1 of the License, or (at your option) any later version.
29
#
30
#  This library is distributed in the hope that it will be useful,
31
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
32
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
33
#  Lesser General Public License for more details.
34
#
35
#  You should have received a copy of the GNU Lesser General Public License
36
#  along with this library; if not, write to the Free Software Foundation, Inc.,
37
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
38
#
39
#  Contact Information :-
40
#  Email :  carlo.melwyn@outlook.com
41
#  FreeCAD UserTalk :  http://www.freecadweb.org/wiki/index.php?title=User:Melwyncarlo
42
#
43
##########################################################################################
44
#####				L I C E N S E					     #####
45
##########################################################################################
46
#
47
#
48
#
49
#  The AeroFoil macro was developed and tested on a platform containing the
50
#  following system and FreeCAD software specifications :
51
#
52
#  - OS			: Ubuntu 18.04.5 LTS (LXDE/Lubuntu)
53
#  - Word size of OS	: 64-bit
54
#  - Word size of FreeCAD: 64-bit
55
#  - Version		: 0.19
56
#  - Build type		: Release
57
#  - Branch		: unknown
58
#  - Hash		: 32200b604d421c4dad527fe587a7d047cf953b4f
59
#  - Python version	: 3.6.9
60
#  - Qt version		: 5.9.5
61
#  - Coin version	: 4.0.0a
62
#  - OCC version	: 7.3.0
63
#  - Locale		: English/UnitedKingdom (en_GB)
64

65

66
"""
67
  To use this macro, the steps to be followed are simple and straightforward :
68
  follow the instructions in the respective dialog boxes, fill in the relevant inputs,
69
  and navigate accordingly. In case of error or warning, you will automatically be
70
  notified the same. In case you are notified to report an unexpected error,
71
  communicate the error by mentioning the FreeCAD version, tracing the steps
72
  taken, and mentioning whether (and how much) or not any ouput was generated.
73

74
  Note (1)	Performing the macro operation with custom points and refinement
75
		produces no visible changes.
76
  Note (2)	The AeroFoil object properties are only visible on the FreeCAD
77
		software version 0.19. On older versions, you will be shown
78
		a warning on the console.
79
  Note (3)	The single underscore prefix (e.g. _name) denotes a private
80
		function or a private variable.
81
  Note (4)	Some of the short forms used in this script are as follows:
82
		  'af' stands for AeroFoil
83
  		  'mfb' stands for Math Functions Box
84
  		  'd' stands for Dialog (e.g. d2c, d3, etc.)
85
  Note (5)	The dialog boxes and their corresponding UI elements may be
86
		labelled differently. Here is a map:
87
		  d1	= AeroFoil_Initial_Dialog
88
		  d2a	= AeroFoil_NACA4Digit_Dialog
89
		  d2b	= AeroFoil_NACA5Digit_Dialog
90
		  d2c	= AeroFoil_CurvesInput_Dialog
91
		  d2d	= AeroFoil_PointsInput_Dialog
92
		  d2d1a	= AeroFoil_DATInput_Dialog
93
		  d2d1b	= AeroFoil_CSVInput_Dialog
94
		  dcd2	= AeroFoil_FileLoad_Dialog
95
		  d3	= AeroFoil_Final_Dialog
96
		  mfb	= AeroFoil_Math_Functions_Box
97
		Read the DocString of the '_setDialogIndex' method in the
98
		'AeroFoilDialog' class to know more about its implementation.
99
  Note (6)	The global variable 'dialogIndex' is listed in the exact order
100
		as the above list of dialog boxes.
101
"""
102

103
__Title__         = "AeroFoil"
104
__Author__        = "Melwyncarlo"
105
__Version__       = "2.0.3"
106
__Date__          = "2021-03-10"
107
__Comment__       = "AeroFoil creates airfoil curves and faces using pre-defined models, algebraic functions, and DAT or CSV Files"
108
__Web__           = "https://github.com/melwyncarlo/AeroFoil"
109
__Wiki__          = "http://www.freecadweb.org/wiki/index.php?title=Macro_AeroFoil"
110
__Icon__          = "AeroFoil.svg"
111
__Help__          = "Click on the AeroFoil button/macro, and follow the instructions in the subsequent dialog boxes."
112
__Status__        = "stable"
113
__Requires__      = "Freecad >= v0.17"
114
__License__       = "LGPL-2.1-or-later"
115
__Communication__ = "https://github.com/melwyncarlo/AeroFoil/issues"
116
__Files__         = "AeroFoil_UI_Files/AeroFoil_Initial_Dialog.ui,AeroFoil_UI_Files/AeroFoil_NACA4Digit_Dialog.ui,AeroFoil_UI_Files/AeroFoil_NACA5Digit_Dialog.ui,AeroFoil_UI_Files/AeroFoil_CurvesInput_Dialog.ui,AeroFoil_UI_Files/AeroFoil_PointsInput_Dialog.ui,AeroFoil_UI_Files/AeroFoil_DATInput_Dialog.ui,AeroFoil_UI_Files/AeroFoil_CSVInput_Dialog.ui,AeroFoil_UI_Files/AeroFoil_FileLoad_Dialog.ui,AeroFoil_UI_Files/AeroFoil_Final_Dialog.ui,AeroFoil_UI_Files/AeroFoil_Math_Functions_Box.ui,AeroFoil_UI_Files/AeroFoil_mfb_img.gif,AeroFoil.svg"
117

118
# Library Imports
119
# ------------------------------------------------------------------------------------------------
120

121
import FreeCAD as app
122
import FreeCADGui as gui
123
import Sketcher, Part, Draft
124
from pathlib import Path
125
import PySide
126
from PySide import QtGui, QtCore
127
from PySide.QtGui import *
128
from PySide.QtCore import *
129
import time, math, csv, re
130

131
###########################################################################
132
###---------------------------------------------------------------------###
133
### 		AEROFOIL MACRO CALLS - Top (check Bottom)		###
134
###---------------------------------------------------------------------###
135
                                                                        ###
136
def AeroFoil_generate(obj):						###
137
    """
138
    The 'AeroFoil_generate' function generates the 2D curves
139
    and shapes, assigns them properties, and creates
140
    multiples of created curves and shapes.
141

142
    Arguments
143
    ----------
144
    obj: An instance of the 'AeroFoilDialog' object.
145

146
    Return
147
    ----------
148
    True:   The airfoil curves/shapes were generated
149
            successfully.
150
    False:  The airfoil curves/shapes were NOT generated
151
            successfully.
152
    """									###
153
    n_ = (obj.multi * (obj.multiParam - 1)) + 1				###
154
    AeroFoil_object = AeroFoil(obj)					###
155
    if AeroFoil_object.create():					###
156
        AeroFoil_object.characterize()					###
157
    else:								###
158
        return False							###
159
    AeroFoil_object.copy(n_)						###
160
    return True								###
161
                                                                        ###
162
###---------------------------------------------------------------------###
163
### 		AEROFOIL MACRO CALLS - Top (check Bottom)		###
164
###---------------------------------------------------------------------###
165
###########################################################################
166

167

168

169
# Constant Variables
170
# ------------------------------------------------------------------------------------------------
171

172
MAX_REFINE_PARAM = 2
173
# Pertaining to the 'Refine Parameter' input from AeroFoil_Final_Dialog
174
# It is the maximum airfoil points refinement coefficient above which
175
# a warning is diplayed notifying the user of increased computation.
176
MAX_QUANTITY_PARAM = 5
177
# Pertaining to the 'Quantity Parameter' input from AeroFoil_Final_Dialog
178
# It is the maximum number of Airfoil curves to be generated above which
179
# a warning is diplayed notifying the user of increased computation.
180
MIN_DATA_POINTS = 10
181
# Pertaining to the 'Quantity Parameter' input from AeroFoil_FileLoad_Dialog
182
# It is the minimum pair of airfoil data points to be present in the file
183
# below which an error is shown.
184
NACA_NUMBER_OF_POINTS = 50
185
# It is the number of airfoil data points pair used while generating the curves
186
# It is the value set at REFINE_PARAM = 1
187
NACA_5_2ND_3RD_DIGITS = ["10", "20", "30", "40", "50", "21", "31", "41", "51"]
188
# The NACA 5 Digit airfoils are limited. These are the 2nd and 3rd digit combinations
189
# of the available NACA 5 Digit airfoils
190
FUNCTIONSARRAY = [
191
    "+",
192
    "-",
193
    "*",
194
    "/",
195
    "^",
196
    "e",
197
    "pi",
198
    "ln(",
199
    "log(",
200
    "sqrt(",
201
    "sin(",
202
    "cos(",
203
    "tan(",
204
    "asin(",
205
    "acos(",
206
    "atan(",
207
    "x",
208
    "(",
209
    ")",
210
    ".",
211
]
212
# A list of functions and mathematics-based characters that are allowed for input.
213
UNITSCONVERSION = [0, 1, 10, 1000, 25.4, 304.8, 914.4]
214
# This is the conversion coefficients list to convert different units into millimetres (mm).
215
# From element 2 to 7 :  millimetre (mm), centimetre (cm), metre (m), inch (in), feet (ft),
216
# and yards.
217
# Ignore the 1st element; it's superfluous.
218
MIN_CHORD_LENGTH_MM = 1
219
# It is the minimum airfoil chord length input below which a warning is displayed.
220
MACRO_DIR = app.getUserMacroDir(True) + "/AeroFoil_UI_Files/AeroFoil_"
221
# It is the user's macro directory
222

223

224

225
# Global Variables
226
# ------------------------------------------------------------------------------------------------
227

228
dialogIndex = [1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
229
# These are the code numbers pertaining to the entire program's dialogs
230
# Each dialog box is assigned an initial code number.
231
# Each of the numbers given in the list from element 2 t0 11
232
# correspond to a dialog box as mentioned in the macro's
233
# main DocString above.
234
# The 1st element contains the current dialog box's index number
235
# Read the DocString of the '_setDialogIndex' method in the
236
# 'AeroFoilDialog' class to know more about its implementation.
237
doc = app.activeDocument()
238

239

240

241
# Main Function Class
242
# ------------------------------------------------------------------------------------------------
243

244
class AeroFoil:
245

246
    """
247
    The 'AeroFoil' class is the AeroFoil object itself. It is a non-interactive
248
    console-based class that is responsible for physical creation of the custom
249
    2D airfoil curves/shapes. The gathered user input data is used for the process.
250

251
    Functions include:
252
    __init__
253
    copy
254
    characterize
255
    create
256
    _create_tempX_var
257
    _createSketcherPolyLine
258
    _createSketcherBSpline
259
    """
260

261
    def __init__(self, obj):
262
        """
263
        This function initializes of the 'AeroFoil' class.
264
        This function transfers the user input data through the 'obj' variable.
265

266
        Arguments
267
        ----------
268
        obj: An instance of the 'AeroFoilDialog' object.
269
        """
270
        global UNITSCONVERSION
271
        self.profileName = generateName("AeroFoil")
272
        self.chord = obj.chordLength * UNITSCONVERSION[obj.chordUnits]
273
        # 'designtype' is used as a container for a set of relevant variables
274
        self.designtype = [obj.workbench, obj.curveType]
275
        self.designclosed = obj.closed_
276
        self.designsplit = obj.splitCurve
277
        self.designsplitmode = obj.splitCurveMode
278
        self.defaultEndPoints = obj.defaultEndPoints
279
        # 'midIndices' is used as a container for a set of relevant variables
280
        self.midIndices = [obj.midIndex1, obj.midIndex2]
281
        self.points = []
282
        self.points.append(obj.pointsX)
283
        self.points.append(obj.pointsY)
284
        self.n_units = len(self.points[0])
285
        self.designprogressbar = obj.progressBar_
286
        # 'objRefData' is used as a container for a set of relevant variables
287
        self.objRefData = [
288
            obj.airfoilType,
289
            obj.airfoil4DNumber,
290
            obj.airfoil5DNumber,
291
            obj.airfoilProfileType,
292
            obj.importFrom,
293
        ]
294

295
    def copy(self, quantity_):
296
        """
297
        This function creates multiple copies of the created airfoil curves/shapes.
298

299
        Arguments
300
        ----------
301
        quantity_: (Integer) Number of copies required.
302
        """
303
        if quantity_ != 1:
304
            refNum = int(re.findall(r"\d+", self.profileName)[0])
305
            gui.Selection.clearSelection()
306
            if self.designsplit:
307
                gui.Selection.addSelection(
308
                    doc.getObjectsByLabel(self.profileName + "_Upper")[0]
309
                )
310
                gui.Selection.addSelection(
311
                    doc.getObjectsByLabel(self.profileName + "_Lower")[0]
312
                )
313
            else:
314
                gui.Selection.addSelection(doc.getObjectsByLabel(self.profileName)[0])
315
            gui.runCommand("Std_Copy")
316
            for i in range(quantity_ - 1):
317
                gui.runCommand("Std_Paste")
318
                if self.designsplit:
319
                    doc.getObjectsByLabel(
320
                        "AeroFoil_" + str(refNum) + "_Upper" + str(i + 1).zfill(3)
321
                    )[0].Label = ("AeroFoil_" + str(refNum + i + 1) + "_Upper")
322
                    doc.getObjectsByLabel(
323
                        "AeroFoil_" + str(refNum) + "_Lower" + str(i + 1).zfill(3)
324
                    )[0].Label = ("AeroFoil_" + str(refNum + i + 1) + "_Lower")
325
                else:
326
                    doc.getObjectsByLabel("AeroFoil_" + str(refNum + i + 1).zfill(3))[
327
                        0
328
                    ].Label = "AeroFoil_" + str(refNum + i + 1)
329
                if self.designprogressbar.value() < 99:
330
                    self.designprogressbar.setValue(
331
                        round(75 + (((i + 1) / (4 * quantity_)) * 100))
332
                    )
333
        self.designprogressbar.setValue(100)
334
        time.sleep(1)
335

336
    def characterize(self):
337
        """
338
        This function adds external (read-only) properties to an instance of the AeroFoil object.
339
        The user input data has been obtained from the '__init__' module of this class.
340

341
        Properties
342
        ---------------
343
        Airfoil Type:         Options - NACA 4 Digit, NACA 5 Digit, Custom Curves (Symmetric or
344
                              Asymmetric), or Custom Points (DAT or CSV)
345
        Design Curve Type:    Options - Unsplit or Open Split or Open Split (End Points Fixed) or
346
                              Closed Split or Closed Split (End Points Fixed) |
347
                              Polygon or BSpline | Draft or Sketch or Surface
348
        Airfoil Chord Length: Length in millimetres (mm)
349
        Number of Points :    Number of airfoil points
350
        """
351
        try:
352
            objRef = doc.getObjectsByLabel(self.profileName)[0]
353
            objRef.addProperty("App::PropertyString", "AirfoilType", "", "", 1)
354
            objRef.addProperty("App::PropertyLength", "AirfoilChordLength", "", "", 1)
355
            objRef.addProperty("App::PropertyString", "DesignCurveType", "", "", 1)
356
            objRef.addProperty("App::PropertyInteger", "NumberOfPoints", "", "", 1)
357
            tempStr = ""
358
            # Refer to the '__init__' method of this class for more info. on 'objRefData'
359
            if self.objRefData[0] == 1:
360
                tempStr = "NACA - " + self.objRefData[1]
361
            elif self.objRefData[0] == 2:
362
                tempStr = "NACA - " + self.objRefData[2]
363
            elif self.objRefData[0] == 3 and self.objRefData[3] == 1:
364
                tempStr = "Custom Curves (Symmetric)"
365
            elif self.objRefData[0] == 3 and self.objRefData[3] == 2:
366
                tempStr = "Custom Curves (Asymmetric)"
367
            elif self.objRefData[0] == 4 and self.objRefData[4] == 1:
368
                tempStr = "Custom Points (DAT)"
369
            elif self.objRefData[0] == 4 and self.objRefData[4] == 2:
370
                tempStr = "Custom Points (CSV)"
371
            objRef.AirfoilType = tempStr
372
            objRef.AirfoilChordLength = str(self.chord) + "mm"
373
            tempStr = ""
374
            if self.designsplit:
375
                if self.designsplitmode == 1:
376
                    tempStr += "Open Split "
377
                else:
378
                    tempStr += "Closed Split "
379
                tempStr += "(End Points Fixed) " if self.defaultEndPoints else ""
380
            else:
381
                tempStr += "Unsplit "
382
            tempStr += "Polygon " if self.designtype[1] == 1 else "BSpline "
383
            if self.designclosed:
384
                tempStr += "Surface"
385
            elif self.designtype[0] == 1:
386
                tempStr += "Sketch"
387
            elif self.designtype[0] == 2:
388
                tempStr += "Draft"
389
            objRef.DesignCurveType = tempStr
390
            objRef.NumberOfPoints = self.n_units - 1
391
        except Exception:
392
            print(
393
                "\nCannot generate AeroFoil properties in FreeCAD version &#60;0.19 !\n"
394
            )
395
            app.Console.PrintWarning(
396
                "\nCannot generate AeroFoil properties in FreeCAD version &#60;0.19 !\n"
397
            )
398

399
    def create(self):
400
        """
401
        This function is responsible for physical creation of the custom 2D airfoil curves/shapes.
402
        The user input data has been obtained from the '__init__' module of this class.
403
        This function utilizes the 'try ... except' method to avoid any complications
404
        on the user's end. A simple error message is displayed instead.
405
        """
406
        # Generating Polygons and BSplines
407
        try:
408
            if self.designtype[0] == 2:
409
                if self.designtype[1] == 1:
410
                    if self.designsplit:
411
                        self._createSketcherPolyLine(
412
                            self.profileName + "_Upper", 0, self.midIndices[0]
413
                        )
414
                        self._createSketcherPolyLine(
415
                            self.profileName + "_Lower",
416
                            self.midIndices[1],
417
                            len(self.points[0]) - 1,
418
                        )
419
                    else:
420
                        self._createSketcherPolyLine(
421
                            self.profileName, 0, len(self.points[0]) - 1
422
                        )
423
                elif self.designtype[1] == 2:
424
                    if self.designsplit:
425
                        self._createSketcherBSpline(
426
                            self.profileName + "_Upper", 0, self.midIndices[0]
427
                        )
428
                        self._createSketcherBSpline(
429
                            self.profileName + "_Lower",
430
                            self.midIndices[1],
431
                            len(self.points[0]) - 1,
432
                        )
433
                    else:
434
                        self._createSketcherBSpline(
435
                            self.profileName, 0, len(self.points[0]) - 1
436
                        )
437
            elif self.designtype[0] == 1:
438
                tempXif, tempXm = 0, 0
439
                i_count, iteration = 0, 1
440
                if self.designsplit:
441
                    iteration = 2
442
                while i_count < iteration:
443
                    DWireOrBSpline_, pointsVector, draftCurveClosed = 0, [], True
444
                    startIndex, endIndex = 0, len(self.points[0]) - 1
445
                    draftOutputName = self.profileName
446
                    n_ = len(self.points[0])
447
                    if self.designsplit:
448
                        draftCurveClosed = False
449
                        startIndex, endIndex = (
450
                            (0, self.midIndices[0])
451
                            if i_count == 0
452
                            else (self.midIndices[1], len(self.points[0]) - 1)
453
                        )
454
                        tempXif, tempXm, startIndex, endIndex = self._create_tempX_var(startIndex, endIndex)
455
                        draftOutputName = (
456
                            self.profileName + "_Upper"
457
                            if i_count == 0
458
                            else self.profileName + "_Lower"
459
                        )
460
                        n_ = endIndex - startIndex + 1
461
                    if self.designsplit and (
462
                        tempXif != self.points[0][startIndex]
463
                        or self.points[1][startIndex] != 0
464
                    ):
465
                        pointsVector.append(app.Vector(tempXif, 0, 0))
466
                    for i in range(startIndex, endIndex + 1):
467
                        pointsVector.append(
468
                            app.Vector(self.points[0][i], 0, self.points[1][i])
469
                        )
470
                        if not self.designsplit:
471
                            if self.designprogressbar.value() < 75:
472
                                self.designprogressbar.setValue(
473
                                    round(50 + ((i / (4 * n_)) * 100))
474
                                )
475
                        elif self.designsplit and i_count == 0:
476
                            if self.designprogressbar.value() < 62.5:
477
                                self.designprogressbar.setValue(
478
                                    round(50 + ((i / (8 * n_)) * 100))
479
                                )
480
                        else:
481
                            if self.designprogressbar.value() < 75:
482
                                self.designprogressbar.setValue(
483
                                    round(62.5 + ((i / (8 * n_)) * 100))
484
                                )
485
                    if self.designsplit and (
486
                        tempXm != self.points[0][endIndex]
487
                        or self.points[1][endIndex] != 0
488
                    ):
489
                        pointsVector.append(app.Vector(tempXm, 0, 0))
490
                    if self.designtype[1] == 1:
491
                        # if self.designclosed is determined by face=self.designclosed
492
                        DWireOrBSpline_ = Draft.makeWire(
493
                            pointsVector,
494
                            closed=draftCurveClosed,
495
                            placement=None,
496
                            face=self.designclosed,
497
                            support=None,
498
                        )
499
                    elif self.designtype[1] == 2:
500
                        # if self.designclosed is determined by face=self.designclosed
501
                        DWireOrBSpline_ = Draft.makeBSpline(
502
                            pointsVector,
503
                            closed=draftCurveClosed,
504
                            placement=None,
505
                            face=self.designclosed,
506
                            support=None,
507
                        )
508
                        if self.designsplit and self.designsplitmode == 2:
509
                            pointsVector_aux = [pointsVector[0], pointsVector[-1]]
510
                            DWire_aux = Draft.makeWire(
511
                                pointsVector_aux,
512
                                closed=False,
513
                                placement=None,
514
                                face=False,
515
                                support=None,
516
                            )
517
                            addList, deleteList = Draft.upgrade(
518
                                [DWireOrBSpline_, DWire_aux], delete=True, force=None
519
                            )
520
                    doc.recompute()
521
                    if self.designsplit and self.designsplitmode == 2:
522
                        doc.getObject(addList[0].Name).Label = draftOutputName
523
                    else:
524
                        shape_ = doc.addObject("Part::Feature", draftOutputName)
525
                        shape_.Shape = DWireOrBSpline_.Shape
526
                        doc.removeObject(DWireOrBSpline_.Label)
527
                    i_count += 1
528
        except Exception:
529
            # Unexpected Error Occurred while Processing
530
            try:
531
                doc.removeObject(self.profileName)
532
                doc.removeObject(self.profileName + "_Upper")
533
                doc.removeObject(self.profileName + "_Lower")
534
            except Exception:
535
                pass
536
            doc.recompute()
537
            return False
538
        doc.recompute()
539
        gui.activeDocument().activeView().viewFront()
540
        gui.SendMsgToActiveView("ViewFit")
541
        return True
542

543
    def _create_tempX_var(self, startIndex, endIndex):
544
        """
545
        This function determines the start and end points of a 'split' curve.
546
        This function generates additional start and end points if
547
        their existing counterparts do not have a Y-axis value of zero (0).
548
        This function calls the linear interpolation method to achieve the same.
549

550
        Arguments
551
        ----------
552
        startIndex: (Integer) Zero-based index of the data points list to begin from.
553
        endIndex:   (Integer) Zero-based index of the data points list to end to.
554
        """
555
        posVar, negVar, startIndex_new, endIndex_new = 0, 0, startIndex, endIndex
556
        for i1 in range(startIndex, startIndex + int(0.1 * (endIndex - startIndex))):
557
            if self.points[1][i1] >= 0:
558
                posVar += 1
559
            else:
560
                negVar += 1
561
        for j1 in range(startIndex, startIndex + int(0.1 * (endIndex - startIndex))):
562
            if posVar >= negVar:
563
                if self.points[1][j1] >= 0:
564
                    startIndex_new = j1
565
                    break
566
            else:
567
                if self.points[1][j1] < 0:
568
                    startIndex_new = j1
569
                    break
570
        for i2 in range(endIndex, endIndex - int(0.1 * (endIndex - startIndex)), -1):
571
            if self.points[1][i2] >= 0:
572
                posVar += 1
573
            else:
574
                negVar += 1
575
        for j2 in range(endIndex, endIndex - int(0.1 * (endIndex - startIndex)), -1):
576
            if posVar >= negVar:
577
                if self.points[1][j2] >= 0:
578
                    endIndex_new = j2
579
                    break
580
            else:
581
                if self.points[1][j2] < 0:
582
                    endIndex_new = j2
583
                    break
584
        if self.defaultEndPoints:
585
            tempXif = round(self.points[0][startIndex_new], 2)
586
            tempXm = round(self.points[0][endIndex_new], 2)
587
            self.points[0][startIndex_new] = round(self.points[0][startIndex_new], 2)
588
            self.points[0][endIndex_new] = round(self.points[0][endIndex_new], 2)
589
        else:
590
            if startIndex_new == 0:
591
                xi_, yi_ = self.points[0][0], self.points[1][0]
592
                xf_, yf_ = (
593
                    (self.points[0][-1], self.points[1][-1])
594
                    if (
595
                        self.points[0][0] != self.points[0][-1]
596
                        or self.points[1][0] != self.points[1][-1]
597
                    )
598
                    else (self.points[0][-2], self.points[1][-2])
599
                )
600
                x1m_, y1m_ = self.points[0][endIndex_new], self.points[1][endIndex_new]
601
                x2m_, y2m_ = (
602
                    (self.points[0][endIndex_new + 1], self.points[1][endIndex_new + 1])
603
                    if self.points[0][endIndex_new] != self.points[0][endIndex_new + 1]
604
                    or self.points[1][endIndex_new] != self.points[1][endIndex_new + 1]
605
                    else (self.points[0][endIndex_new + 2], self.points[1][endIndex_new + 2])
606
                )
607
            else:
608
                xi_, yi_ = self.points[0][startIndex_new], self.points[1][startIndex_new]
609
                xf_, yf_ = (
610
                    (self.points[0][startIndex_new - 1], self.points[1][startIndex_new - 1])
611
                    if (
612
                        self.points[0][startIndex_new] != self.points[0][startIndex_new - 1]
613
                        or self.points[1][startIndex_new] != self.points[1][startIndex_new - 1]
614
                    )
615
                    else (
616
                        self.points[0][startIndex_new - 2],
617
                        self.points[1][startIndex_new - 2],
618
                    )
619
                )
620
                x1m_, y1m_ = self.points[0][-1], self.points[1][-1]
621
                x2m_, y2m_ = (
622
                    (self.points[0][0], self.points[1][0])
623
                    if self.points[0][-1] != self.points[0][0]
624
                    or self.points[1][-1] != self.points[1][0]
625
                    else (self.points[0][1], self.points[1][1])
626
                )
627
            tempXif = interpolateNum(yi_, xi_, yf_, xf_, 0)
628
            tempXm = interpolateNum(y1m_, x1m_, y2m_, x2m_, 0)
629
        return tempXif, tempXm, startIndex_new, endIndex_new
630

631
    def _createSketcherPolyLine(self, sketchName, startIndex, endIndex):
632
        """
633
        This function creates the Sketcher Workbench based PolyLine curve.
634

635
        Arguments
636
        ----------
637
        sketchName: (String)  The name/label of the generated sketch.
638
        startIndex: (Integer) Zero-based index of the data points list to begin from.
639
        endIndex:   (Integer) Zero-based index of the data points list to end to.
640
        """
641
        # Sketch Preparation and Naming
642
        sketchObj = doc.addObject("Sketcher::SketchObject", sketchName)
643
        sketchObj.Placement = app.Placement(
644
            app.Vector(0.000000, 0.000000, 0.000000),
645
            app.Rotation(-0.707107, 0.000000, 0.000000, -0.707107),
646
        )
647
        sketchObj.MapMode = "Deactivated"
648
        # Sketch PolyLine Procedure
649
        count, tempBool = 0, False
650
        tempXif, tempXm = 0, 0
651
        if self.designsplit:
652
            tempXif, tempXm, startIndex, endIndex = self._create_tempX_var(startIndex, endIndex)
653
            if (
654
                tempXif == self.points[0][startIndex]
655
                and self.points[1][startIndex] == 0
656
            ):
657
                tempBool = False
658
            else:
659
                tempBool = True
660
                sketchObj.addGeometry(
661
                    Part.LineSegment(
662
                        app.Vector(tempXif, 0, 0),
663
                        app.Vector(
664
                            self.points[0][startIndex], self.points[1][startIndex], 0
665
                        ),
666
                    ),
667
                    False,
668
                )
669
                constraintIndex = sketchObj.addConstraint(
670
                    Sketcher.Constraint("DistanceX", 0, 1, tempXif)
671
                )
672
                sketchObj.setDatum(
673
                    constraintIndex, app.Units.Quantity(str(tempXif) + "mm")
674
                )
675
                constraintIndex = sketchObj.addConstraint(
676
                    Sketcher.Constraint("DistanceY", 0, 1, 0)
677
                )
678
                sketchObj.setDatum(constraintIndex, app.Units.Quantity(str(0) + "mm"))
679
                constraintIndex = sketchObj.addConstraint(
680
                    Sketcher.Constraint("DistanceX", 0, 2, self.points[0][startIndex])
681
                )
682
                sketchObj.setDatum(
683
                    constraintIndex,
684
                    app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
685
                )
686
                constraintIndex = sketchObj.addConstraint(
687
                    Sketcher.Constraint("DistanceY", 0, 2, self.points[1][startIndex])
688
                )
689
                sketchObj.setDatum(
690
                    constraintIndex,
691
                    app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
692
                )
693
        sketchObj.addGeometry(
694
            Part.LineSegment(
695
                app.Vector(self.points[0][startIndex], self.points[1][startIndex], 0),
696
                app.Vector(
697
                    self.points[0][startIndex + 1], self.points[1][startIndex + 1], 0
698
                ),
699
            ),
700
            False,
701
        )
702
        if tempBool:
703
            sketchObj.addConstraint(Sketcher.Constraint("Coincident", 0, 2, 1, 1))
704
            count = 2
705
        else:
706
            count = 1
707
        n_ = endIndex - startIndex + 1
708
        for i in range(startIndex + 1, endIndex):
709
            sketchObj.addGeometry(
710
                Part.LineSegment(
711
                    app.Vector(self.points[0][i], self.points[1][i], 0),
712
                    app.Vector(self.points[0][i + 1], self.points[1][i + 1], 0),
713
                ),
714
                False,
715
            )
716
            sketchObj.addConstraint(
717
                Sketcher.Constraint("Coincident", count - 1, 2, count, 1)
718
            )
719
            if self.designprogressbar.value() < 62.5:
720
                self.designprogressbar.setValue(round(50 + ((count / (8 * n_)) * 100)))
721
            count += 1
722
        tempVar1, tempVar2 = self.points[0][endIndex], self.points[1][endIndex]
723
        if self.designsplit and (
724
            tempXm != self.points[0][endIndex] or self.points[1][endIndex] != 0
725
        ):
726
            sketchObj.addGeometry(
727
                Part.LineSegment(
728
                    app.Vector(self.points[0][endIndex], self.points[1][endIndex], 0),
729
                    app.Vector(tempXm, 0, 0),
730
                ),
731
                False,
732
            )
733
            sketchObj.addConstraint(
734
                Sketcher.Constraint("Coincident", count - 1, 2, count, 1)
735
            )
736
            tempVar1, tempVar2 = tempXm, 0
737
            count += 1
738
        if self.designsplitmode == 2:
739
            sketchObj.addGeometry(
740
                Part.LineSegment(
741
                    app.Vector(tempVar1, tempVar2, 0),
742
                    app.Vector(
743
                        self.points[0][startIndex], self.points[1][startIndex], 0
744
                    ),
745
                ),
746
                False,
747
            )
748
            sketchObj.addConstraint(
749
                Sketcher.Constraint("Coincident", count - 1, 2, count, 1)
750
            )
751
            sketchObj.addConstraint(Sketcher.Constraint("Coincident", count, 2, 0, 1))
752
        if self.designsplit:
753
            # Here, 'count; denotes the sketcher line number
754
            count = 1 if tempBool else 0
755
        else:
756
            sketchObj.addConstraint(
757
                Sketcher.Constraint("Coincident", count - 1, 2, 0, 1)
758
            )
759
            count = 0
760
        for j in range(startIndex, endIndex - 1):
761
            constraintIndex = sketchObj.addConstraint(
762
                Sketcher.Constraint("DistanceX", count, 2, self.points[0][j + 1])
763
            )
764
            sketchObj.setDatum(
765
                constraintIndex, app.Units.Quantity(str(self.points[0][j + 1]) + "mm")
766
            )
767
            constraintIndex = sketchObj.addConstraint(
768
                Sketcher.Constraint("DistanceY", count, 2, self.points[1][j + 1])
769
            )
770
            sketchObj.setDatum(
771
                constraintIndex, app.Units.Quantity(str(self.points[1][j + 1]) + "mm")
772
            )
773
            if self.designprogressbar.value() < 75:
774
                self.designprogressbar.setValue(
775
                    round(62.5 + ((count / (8 * n_)) * 100))
776
                )
777
            count += 1
778
        if self.designsplit:
779
            constraintIndex = sketchObj.addConstraint(
780
                Sketcher.Constraint("DistanceX", count, 2, self.points[0][endIndex])
781
            )
782
            sketchObj.setDatum(
783
                constraintIndex,
784
                app.Units.Quantity(str(self.points[0][endIndex]) + "mm"),
785
            )
786
            constraintIndex = sketchObj.addConstraint(
787
                Sketcher.Constraint("DistanceY", count, 2, self.points[1][endIndex])
788
            )
789
            sketchObj.setDatum(
790
                constraintIndex,
791
                app.Units.Quantity(str(self.points[1][endIndex]) + "mm"),
792
            )
793
            count += 1
794
            if tempXm != self.points[0][endIndex] or self.points[1][endIndex] != 0:
795
                constraintIndex = sketchObj.addConstraint(
796
                    Sketcher.Constraint("DistanceX", count, 2, tempXm)
797
                )
798
                sketchObj.setDatum(
799
                    constraintIndex, app.Units.Quantity(str(tempXm) + "mm")
800
                )
801
                constraintIndex = sketchObj.addConstraint(
802
                    Sketcher.Constraint("DistanceY", count, 2, 0)
803
                )
804
                sketchObj.setDatum(constraintIndex, app.Units.Quantity(str(0) + "mm"))
805
                count += 1
806
            if not tempBool and self.designsplitmode == 2:
807
                constraintIndex = sketchObj.addConstraint(
808
                    Sketcher.Constraint(
809
                        "DistanceX", count, 2, self.points[0][startIndex]
810
                    )
811
                )
812
                sketchObj.setDatum(
813
                    constraintIndex,
814
                    app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
815
                )
816
                constraintIndex = sketchObj.addConstraint(
817
                    Sketcher.Constraint(
818
                        "DistanceY", count, 2, self.points[1][startIndex]
819
                    )
820
                )
821
                sketchObj.setDatum(
822
                    constraintIndex,
823
                    app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
824
                )
825
                count += 1
826
        if not tempBool and self.designsplitmode != 2:
827
            constraintIndex = sketchObj.addConstraint(
828
                Sketcher.Constraint("DistanceX", 0, 1, self.points[0][startIndex])
829
            )
830
            sketchObj.setDatum(
831
                constraintIndex,
832
                app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
833
            )
834
            constraintIndex = sketchObj.addConstraint(
835
                Sketcher.Constraint("DistanceY", 0, 1, self.points[1][startIndex])
836
            )
837
            sketchObj.setDatum(
838
                constraintIndex,
839
                app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
840
            )
841

842
    def _createSketcherBSpline(self, sketchName, startIndex, endIndex):
843
        """
844
        This function creates the Sketcher Workbench based BSpline curve.
845

846
        Arguments
847
        ----------
848
        sketchName: (String)  The name/label of the generated sketch.
849
        startIndex: (Integer) Zero-based index of the data points list to begin from.
850
        endIndex:   (Integer) Zero-based index of the data points list to end to.
851
        """
852
        # Sketch Preparation and Naming
853
        sketchObj = doc.addObject("Sketcher::SketchObject", sketchName)
854
        sketchObj.Placement = app.Placement(
855
            app.Vector(0.000000, 0.000000, 0.000000),
856
            app.Rotation(-0.707107, 0.000000, 0.000000, -0.707107),
857
        )
858
        sketchObj.MapMode = "Deactivated"
859
        # Sketch BSpline Procedure
860
        count, points, conList = 0, [], []
861
        n_ = endIndex - startIndex + 1
862
        isXif, isXm = False, False
863
        tempXif, tempXm = 0, 0
864
        if self.designsplit:
865
            tempXif, tempXm, startIndex, endIndex = self._create_tempX_var(startIndex, endIndex)
866
        if self.designsplit and (
867
            tempXif != self.points[0][startIndex] or self.points[1][startIndex] != 0
868
        ):
869
            sketchObj.addGeometry(
870
                Part.Circle(app.Vector(tempXif, 0, 0), app.Vector(0, 0, 1), 10), True
871
            )
872
            sketchObj.addGeometry(
873
                Part.Circle(
874
                    app.Vector(
875
                        self.points[0][startIndex], self.points[1][startIndex], 0
876
                    ),
877
                    app.Vector(0, 0, 1),
878
                    10,
879
                ),
880
                True,
881
            )
882
            sketchObj.addConstraint(Sketcher.Constraint("Radius", 0, 1.000000))
883
            sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, 1))
884
            sketchObj.addGeometry(
885
                Part.Circle(
886
                    app.Vector(
887
                        self.points[0][startIndex + 1],
888
                        self.points[1][startIndex + 1],
889
                        0,
890
                    ),
891
                    app.Vector(0, 0, 1),
892
                    10,
893
                ),
894
                True,
895
            )
896
            sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, 2))
897
            isXif = True
898
            count = 3
899
        else:
900
            sketchObj.addGeometry(
901
                Part.Circle(
902
                    app.Vector(
903
                        self.points[0][startIndex], self.points[1][startIndex], 0
904
                    ),
905
                    app.Vector(0, 0, 1),
906
                    10,
907
                ),
908
                True,
909
            )
910
            sketchObj.addGeometry(
911
                Part.Circle(
912
                    app.Vector(
913
                        self.points[0][startIndex + 1],
914
                        self.points[1][startIndex + 1],
915
                        0,
916
                    ),
917
                    app.Vector(0, 0, 1),
918
                    10,
919
                ),
920
                True,
921
            )
922
            sketchObj.addConstraint(Sketcher.Constraint("Radius", 0, 1.000000))
923
            sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, 1))
924
            count = 2
925
        for h in range(startIndex + 2, endIndex + 1):
926
            sketchObj.addGeometry(
927
                Part.Circle(
928
                    app.Vector(self.points[0][h], self.points[1][h], 0),
929
                    app.Vector(0, 0, 1),
930
                    10,
931
                ),
932
                True,
933
            )
934
            sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, count))
935
            if self.designprogressbar.value() < 60:
936
                self.designprogressbar.setValue(round(50 + ((h / (10 * n_)) * 100)))
937
            count += 1
938
        if self.designsplit and (
939
            tempXm != self.points[0][endIndex] or self.points[1][endIndex] != 0
940
        ):
941
            sketchObj.addGeometry(
942
                Part.Circle(app.Vector(tempXm, 0, 0), app.Vector(0, 0, 1), 10), True
943
            )
944
            sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, count))
945
            isXm = True
946
            count += 1
947
        tempVal = count
948
        if isXif:
949
            points.append(app.Vector(tempXif, 0))
950
            conList.append(
951
                Sketcher.Constraint(
952
                    "InternalAlignment:Sketcher::BSplineControlPoint", 0, 3, tempVal, 0
953
                )
954
            )
955
        count = 0 + isXif
956
        for i in range(startIndex, endIndex + 1):
957
            points.append(app.Vector(self.points[0][i], self.points[1][i]))
958
            conList.append(
959
                Sketcher.Constraint(
960
                    "InternalAlignment:Sketcher::BSplineControlPoint",
961
                    count,
962
                    3,
963
                    tempVal,
964
                    count,
965
                )
966
            )
967
            if self.designprogressbar.value() < 70:
968
                self.designprogressbar.setValue(round(60 + ((i / (10 * n_)) * 100)))
969
            count += 1
970
        if isXm:
971
            points.append(app.Vector(tempXm, 0))
972
            conList.append(
973
                Sketcher.Constraint(
974
                    "InternalAlignment:Sketcher::BSplineControlPoint",
975
                    count,
976
                    3,
977
                    tempVal,
978
                    count,
979
                )
980
            )
981
            count += 1
982
        if self.designsplit:
983
            sketchObj.addGeometry(
984
                Part.BSplineCurve(points, None, None, False, 3, None, False), False
985
            )
986
        else:
987
            sketchObj.addGeometry(
988
                Part.BSplineCurve(points, None, None, True, 3, None, False), False
989
            )
990
        sketchObj.addConstraint(conList)
991
        sketchObj.exposeInternalGeometry(tempVal)
992
        tempVar1x = tempXif if isXif else self.points[0][startIndex]
993
        tempVar1y = 0 if isXif else self.points[1][startIndex]
994
        tempVar1bx = (
995
            self.points[0][startIndex] if isXif else self.points[0][startIndex + 1]
996
        )
997
        tempVar1by = (
998
            self.points[1][startIndex] if isXif else self.points[1][startIndex + 1]
999
        )
1000
        tempVar2x = tempXm if isXm else self.points[0][endIndex]
1001
        tempVar2y = 0 if isXm else self.points[1][endIndex]
1002
        count, startFrom = 0, 0
1003
        if isXif:
1004
            constraintIndex = sketchObj.addConstraint(
1005
                Sketcher.Constraint("DistanceX", -1, 1, tempVal, 1, tempVar1x)
1006
            )
1007
            sketchObj.setDatum(
1008
                constraintIndex, app.Units.Quantity(str(tempVar1x) + "mm")
1009
            )
1010
            constraintIndex = sketchObj.addConstraint(
1011
                Sketcher.Constraint("DistanceY", -1, 1, tempVal, 1, tempVar1y)
1012
            )
1013
            sketchObj.setDatum(
1014
                constraintIndex, app.Units.Quantity(str(tempVar1y) + "mm")
1015
            )
1016
            constraintIndex = sketchObj.addConstraint(
1017
                Sketcher.Constraint("DistanceX", -1, 1, count + 1, 3, tempVar1bx)
1018
            )
1019
            sketchObj.setDatum(
1020
                constraintIndex, app.Units.Quantity(str(tempVar1bx) + "mm")
1021
            )
1022
            constraintIndex = sketchObj.addConstraint(
1023
                Sketcher.Constraint("DistanceY", -1, 1, count + 1, 3, tempVar1by)
1024
            )
1025
            sketchObj.setDatum(
1026
                constraintIndex, app.Units.Quantity(str(tempVar1by) + "mm")
1027
            )
1028
            startFrom = startIndex + 1
1029
            count += 2
1030
        else:
1031
            startFrom = startIndex
1032
        if self.designsplit:
1033
            endTo = endIndex
1034
            if not isXif:
1035
                constraintIndex = sketchObj.addConstraint(
1036
                    Sketcher.Constraint(
1037
                        "DistanceX", -1, 1, tempVal, 1, self.points[0][startIndex]
1038
                    )
1039
                )
1040
                sketchObj.setDatum(
1041
                    constraintIndex,
1042
                    app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
1043
                )
1044
                constraintIndex = sketchObj.addConstraint(
1045
                    Sketcher.Constraint(
1046
                        "DistanceY", -1, 1, tempVal, 1, self.points[1][startIndex]
1047
                    )
1048
                )
1049
                sketchObj.setDatum(
1050
                    constraintIndex,
1051
                    app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
1052
                )
1053
                startFrom = startIndex + 1
1054
                count += 1
1055
        else:
1056
            endTo = endIndex + 1
1057
        for j in range(startFrom, endTo):
1058
            constraintIndex = sketchObj.addConstraint(
1059
                Sketcher.Constraint("DistanceX", -1, 1, count, 3, self.points[0][j])
1060
            )
1061
            sketchObj.setDatum(
1062
                constraintIndex, app.Units.Quantity(str(self.points[0][j]) + "mm")
1063
            )
1064
            constraintIndex = sketchObj.addConstraint(
1065
                Sketcher.Constraint("DistanceY", -1, 1, count, 3, self.points[1][j])
1066
            )
1067
            sketchObj.setDatum(
1068
                constraintIndex, app.Units.Quantity(str(self.points[1][j]) + "mm")
1069
            )
1070
            if self.designprogressbar.value() < 75:
1071
                self.designprogressbar.setValue(round(70 + ((j / (20 * n_)) * 100)))
1072
            count += 1
1073
        if self.designsplit:
1074
            tempVar1, tempVar2 = 0, 0
1075
            if isXm:
1076
                tempVar1, tempVar2 = count, 3
1077
            else:
1078
                tempVar1, tempVar2 = tempVal, 2
1079
            constraintIndex = sketchObj.addConstraint(
1080
                Sketcher.Constraint(
1081
                    "DistanceX", -1, 1, tempVar1, tempVar2, self.points[0][endIndex]
1082
                )
1083
            )
1084
            sketchObj.setDatum(
1085
                constraintIndex,
1086
                app.Units.Quantity(str(self.points[0][endIndex]) + "mm"),
1087
            )
1088
            constraintIndex = sketchObj.addConstraint(
1089
                Sketcher.Constraint(
1090
                    "DistanceY", -1, 1, tempVar1, tempVar2, self.points[1][endIndex]
1091
                )
1092
            )
1093
            sketchObj.setDatum(
1094
                constraintIndex,
1095
                app.Units.Quantity(str(self.points[1][endIndex]) + "mm"),
1096
            )
1097
            count += 1
1098
        if isXm:
1099
            constraintIndex = sketchObj.addConstraint(
1100
                Sketcher.Constraint("DistanceX", -1, 1, tempVal, 2, tempVar2x)
1101
            )
1102
            sketchObj.setDatum(
1103
                constraintIndex, app.Units.Quantity(str(tempVar2x) + "mm")
1104
            )
1105
            constraintIndex = sketchObj.addConstraint(
1106
                Sketcher.Constraint("DistanceY", -1, 1, tempVal, 2, tempVar2y)
1107
            )
1108
            sketchObj.setDatum(
1109
                constraintIndex, app.Units.Quantity(str(tempVar2y) + "mm")
1110
            )
1111
        if self.designsplitmode == 2:
1112
            sketchObj.addGeometry(
1113
                Part.LineSegment(
1114
                    app.Vector(tempVar1x, tempVar1y, 0),
1115
                    app.Vector(tempVar2x, tempVar2y, 0),
1116
                ),
1117
                False,
1118
            )
1119
            sketchObj.addConstraint(
1120
                Sketcher.Constraint("Coincident", (tempVal * 2) - 1, 2, tempVal, 1)
1121
            )
1122
            sketchObj.addConstraint(
1123
                Sketcher.Constraint("Coincident", (tempVal * 2) - 1, 1, tempVal, 2)
1124
            )
1125

1126

1127

1128
# Sub-main Function Class
1129
# ------------------------------------------------------------------------------------------------
1130

1131
class AeroFoilDialog:
1132

1133
    """
1134
    The 'AeroFoilDialog' class itself is the AeroFoil GUI interface.
1135
    It contains dialog boxes that go to and fro, from the initial input dialog box
1136
    to the final creation dialog box. This class accumulates the user's inputs on
1137
    how the airfoils are to be created: their dimensions, their design type,
1138
    their resolution, their multiplicity, etc.
1139

1140
    This class is also responsible for validating the input choices and values,
1141
    and notifying or warning the user accordingly.
1142

1143
    Functions include:
1144
    __init__
1145
    _d
1146
    _createDialogs
1147
    _close
1148
    _next
1149
    _okay
1150
    _loadFile
1151
    _create
1152
    _af_d2c_radio_toggled
1153
    _af_d3_spinbox_1_toggled
1154
    _af_d3_spinbox_2_toggled
1155
    _af_d3_checkbox_1_toggled
1156
    _af_d3_checkbox_2_toggled
1157
    _af_d3_checkbox_5_toggled
1158
    _af_d3_checkbox_3_toggled
1159
    _af_d3_checkbox_4_toggled
1160
    _af_d3_radio_toggled
1161
    _functionsList
1162
    """
1163

1164
    def __init__(self):
1165
        """
1166
        This function initializes of the 'AeroFoilDialog' class.
1167
        """
1168
        # Variables Pertaining to the Entire Program
1169
        (
1170
            self.canMirror,
1171
            self.functionElements,
1172
            self.functionElements_Pos,
1173
            self.pointsX,
1174
            self.pointsY,
1175
        ) = (True, [], [], [], [])
1176
        self.tempStart, self.tempEnd = 0, 0
1177
        self.midIndex1, self.midIndex2 = 0, 0
1178
        # Variables Pertaining to AeroFoil_Initial_Dialog
1179
        self.airfoilType = 1
1180
        # Variables Pertaining to AeroFoil_NACA4Digit_Dialog
1181
        self.airfoil4DNumber = ""
1182
        # Variables Pertaining to AeroFoil_NACA5Digit_Dialog
1183
        self.airfoil5DNumber = ""
1184
        # Variables Pertaining to AeroFoil_CurvesInput_Dialog
1185
        self.airfoilProfileType, self.topCurveFunction, self.bottomCurveFunction = (
1186
            1,
1187
            "",
1188
            "",
1189
        )
1190
        # Variables Pertaining to AeroFoil_PointsInput_Dialog
1191
        self.importFrom, self.mirroring = 1, False
1192
        # Variables Pertaining to AeroFoil_DATInput_Dialog
1193
        self.lineStart, self.lineEnd, self.decimalType = 0, 0, 1
1194
        # Variables Pertaining to AeroFoil_CSVInput_Dialog
1195
        self.col1, self.col2, self.row1, self.row2 = 1, 2, 0, 0
1196
        # Variables Pertaining to AeroFoil_FileLoad_Dialog
1197
        (
1198
            self.loadSuccess,
1199
            self.loadedFilePath,
1200
            self.loadedFile,
1201
            self.loadedFileContents,
1202
        ) = (False, [""], "", [])
1203
        # Variables Pertaining to AeroFoil_Final_Dialog
1204
        (
1205
            self.refine,
1206
            self.refineParam,
1207
            self.multi,
1208
            self.multiParam,
1209
            self.closed_,
1210
            self.workbench,
1211
            self.curveType,
1212
            self.maxRefineParamWarned,
1213
            self.maxQuantityParamWarned,
1214
            self.chordLength,
1215
            self.chordUnits,
1216
            self.progressBar_,
1217
            self.splitCurve,
1218
            self.splitCurveMode,
1219
            self.defaultEndPoints,
1220
        ) = (False, 2, False, 2, False, 1, 1, False, False, 0, 1, 0, False, 1, False)
1221
        # Actual Functions begin here
1222
        dialogIndex[0] = 1
1223
        self._createDialogs()
1224

1225
    def _d(self, qcode, objStr):
1226
        """
1227
        This function is a shortcut for obtaining a dialog's UI element.
1228

1229
        Arguments
1230
        ----------
1231
        qcode: A distinct code for each UI element as listed below.
1232
        objStr: The UI element's name/label.
1233
        """
1234
        qkey = [
1235
            0,
1236
            QPushButton,
1237
            QLineEdit,
1238
            QComboBox,
1239
            QRadioButton,
1240
            QCheckBox,
1241
            QSpinBox,
1242
            QDoubleSpinBox,
1243
            QLabel,
1244
            QProgressBar,
1245
        ]
1246
        # Elements 2 to 10 are the various UI elements employed in this macro.
1247
        # Ignore the 1st element; it's superfluous.
1248
        return self.dialog.findChild(qkey[qcode], objStr)
1249

1250
    def _createDialogs(self):
1251
        """
1252
        This function generates and displays the AeroFoil GUI interface, that is,
1253
        it creates all the dialog boxes for user interaction and input.
1254
        """
1255
        global MACRO_DIR
1256
        global dialogIndex
1257
        if dialogIndex[0] == 1:
1258
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "Initial_Dialog.ui")
1259
            self._d(1, "af_d1_close_button").clicked.connect(lambda: self._close())
1260
            self._d(1, "af_d1_next_button").clicked.connect(lambda: self._next(1))
1261
            self._d(3, "af_d1_combo").setCurrentIndex(self.airfoilType - 1)
1262
        elif dialogIndex[0] == 2:
1263
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "NACA4Digit_Dialog.ui")
1264
            self._d(1, "af_d2a_next_button").clicked.connect(lambda: self._next(1))
1265
            self._d(1, "af_d2a_back_button").clicked.connect(lambda: self._next(-1))
1266
            self._d(2, "af_d2a_textbox").setText(self.airfoil4DNumber)
1267
        elif dialogIndex[0] == 3:
1268
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "NACA5Digit_Dialog.ui")
1269
            self._d(1, "af_d2b_next_button").clicked.connect(lambda: self._next(1))
1270
            self._d(1, "af_d2b_back_button").clicked.connect(lambda: self._next(-1))
1271
            self._d(2, "af_d2b_textbox").setText(self.airfoil5DNumber)
1272
        elif dialogIndex[0] == 4:
1273
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "CurvesInput_Dialog.ui")
1274
            self._d(2, "af_d2c_textbox_1").setText(self.topCurveFunction)
1275
            self._d(2, "af_d2c_textbox_2").setText(self.bottomCurveFunction)
1276
            self._d(4, "af_d2c_radio_1").toggled.connect(
1277
                lambda: self._af_d2c_radio_toggled()
1278
            )
1279
            if self.airfoilProfileType == 1:
1280
                self._d(4, "af_d2c_radio_1").setChecked(True)
1281
            else:
1282
                self._d(4, "af_d2c_radio_2").setChecked(True)
1283
            self._d(1, "af_d2c_list_button").clicked.connect(
1284
                lambda: self._functionsList()
1285
            )
1286
            self._d(1, "af_d2c_next_button").clicked.connect(lambda: self._next(1))
1287
            self._d(1, "af_d2c_back_button").clicked.connect(lambda: self._next(-1))
1288
        elif dialogIndex[0] == 5:
1289
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "PointsInput_Dialog.ui")
1290
            if self.importFrom == 1:
1291
                self._d(4, "af_d2d_radio_1").setChecked(True)
1292
            else:
1293
                self._d(4, "af_d2d_radio_2").setChecked(True)
1294
            if self.mirroring:
1295
                self._d(5, "af_d2d_checkbox").setChecked(True)
1296
            else:
1297
                self._d(5, "af_d2d_checkbox").setChecked(False)
1298
            self._d(1, "af_d2d_next_button").clicked.connect(lambda: self._next(1))
1299
            self._d(1, "af_d2d_back_button").clicked.connect(lambda: self._next(-1))
1300
        elif dialogIndex[0] == 6:
1301
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "DATInput_Dialog.ui")
1302
            self._d(6, "af_d2d1a_spinBox_1").setValue(self.tempStart)
1303
            self._d(6, "af_d2d1a_spinBox_2").setValue(self.tempEnd)
1304
            if self.decimalType == 1:
1305
                self._d(4, "af_d2d1a_radio_1").setChecked(True)
1306
            else:
1307
                self._d(4, "af_d2d1a_radio_2").setChecked(True)
1308
            self._d(1, "af_d2d1a_next_button").clicked.connect(lambda: self._next(1))
1309
            self._d(1, "af_d2d1a_back_button").clicked.connect(lambda: self._next(-1))
1310
        elif dialogIndex[0] == 7:
1311
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "CSVInput_Dialog.ui")
1312
            self._d(6, "af_d2d1b_spinBox_1").setValue(self.col1)
1313
            self._d(6, "af_d2d1b_spinBox_2").setValue(self.col2)
1314
            self._d(6, "af_d2d1b_spinBox_3").setValue(self.tempStart)
1315
            self._d(6, "af_d2d1b_spinBox_4").setValue(self.tempEnd)
1316
            if self.decimalType == 1:
1317
                self._d(4, "af_d2d1b_radio_1").setChecked(True)
1318
            else:
1319
                self._d(4, "af_d2d1b_radio_2").setChecked(True)
1320
            self._d(1, "af_d2d1b_next_button").clicked.connect(lambda: self._next(1))
1321
            self._d(1, "af_d2d1b_back_button").clicked.connect(lambda: self._next(-1))
1322
        elif dialogIndex[0] == 8:
1323
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "FileLoad_Dialog.ui")
1324
            self._d(2, "af_d2cd2_textbox").setText(self.loadedFilePath[0])
1325
            if self.loadedFilePath[0] == "":
1326
                self.loadSuccess = False
1327
                self._d(8, "af_d2cd2_label_2").setText("File Not Loaded")
1328
                self._d(8, "af_d2cd2_label_2").setStyleSheet("color: black;")
1329
            else:
1330
                if self.loadSuccess:
1331
                    self._d(8, "af_d2cd2_label_2").setText("File Loaded Successfully")
1332
                    self._d(8, "af_d2cd2_label_2").setStyleSheet("color: darkgreen;")
1333
                else:
1334
                    self._d(8, "af_d2cd2_label_2").setText(
1335
                        "File Loaded Un-successfully"
1336
                    )
1337
                    self._d(8, "af_d2cd2_label_2").setStyleSheet("color: crimson;")
1338
            self._d(1, "af_d2cd2_load_button").clicked.connect(lambda: self._loadFile())
1339
            self._d(1, "af_d2cd2_next_button").clicked.connect(lambda: self._next(1))
1340
            self._d(1, "af_d2cd2_back_button").clicked.connect(lambda: self._next(-1))
1341
        elif dialogIndex[0] == 9:
1342
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "Final_Dialog.ui")
1343
            if self.refine:
1344
                self._d(6, "af_d3_spinbox_1").setValue(self.refineParam)
1345
                self._d(6, "af_d3_spinbox_1").setEnabled(True)
1346
                self._d(5, "af_d3_checkbox_1").setChecked(True)
1347
            else:
1348
                self.refineParam = 2
1349
                self._d(6, "af_d3_spinbox_1").setValue(2)
1350
                self._d(6, "af_d3_spinbox_1").setEnabled(False)
1351
                self._d(5, "af_d3_checkbox_1").setChecked(False)
1352
            if self.multi:
1353
                self._d(6, "af_d3_spinbox_2").setValue(self.multiParam)
1354
                self._d(6, "af_d3_spinbox_2").setEnabled(True)
1355
                self._d(5, "af_d3_checkbox_2").setChecked(True)
1356
            else:
1357
                self.multiParam = 2
1358
                self._d(6, "af_d3_spinbox_2").setValue(2)
1359
                self._d(6, "af_d3_spinbox_2").setEnabled(False)
1360
                self._d(5, "af_d3_checkbox_2").setChecked(False)
1361
            if self.splitCurve:
1362
                self._d(5, "af_d3_checkbox_5").setChecked(False)
1363
                self._d(5, "af_d3_checkbox_5").setEnabled(False)
1364
                self._d(5, "af_d3_checkbox_6").setChecked(False)
1365
                self._d(5, "af_d3_checkbox_6").setEnabled(True)
1366
                if self.splitCurveMode == 1:
1367
                    self._d(5, "af_d3_checkbox_3").setChecked(True)
1368
                    self._d(5, "af_d3_checkbox_4").setChecked(False)
1369
                else:
1370
                    self._d(5, "af_d3_checkbox_3").setChecked(False)
1371
                    self._d(5, "af_d3_checkbox_4").setChecked(True)
1372
                self._af_d3_checkbox_5_toggled()
1373
            else:
1374
                self._d(5, "af_d3_checkbox_3").setChecked(False)
1375
                self._d(5, "af_d3_checkbox_4").setChecked(False)
1376
                self._d(5, "af_d3_checkbox_6").setChecked(False)
1377
                self._d(5, "af_d3_checkbox_6").setEnabled(False)
1378
                self._d(5, "af_d3_checkbox_5").setChecked(False)
1379
                self._d(5, "af_d3_checkbox_5").setEnabled(True)
1380
                if self.closed_:
1381
                    self._d(5, "af_d3_checkbox_3").setEnabled(False)
1382
                    self._d(5, "af_d3_checkbox_4").setEnabled(False)
1383
                    self._d(5, "af_d3_checkbox_5").setChecked(True)
1384
                    self._af_d3_checkbox_5_toggled()
1385
                else:
1386
                    self._d(5, "af_d3_checkbox_5").setChecked(False)
1387
                    self._d(4, "af_d3_radio_1").setEnabled(True)
1388
                    self._d(4, "af_d3_radio_2").setEnabled(True)
1389
                    if self.workbench == 1:
1390
                        self._d(4, "af_d3_radio_1").setChecked(True)
1391
                        self._d(4, "af_d3_radio_1a").setEnabled(True)
1392
                        self._d(4, "af_d3_radio_1b").setEnabled(True)
1393
                        self._d(4, "af_d3_radio_2a").setEnabled(False)
1394
                        self._d(4, "af_d3_radio_2b").setEnabled(False)
1395
                        if self.curveType == 1:
1396
                            self._d(4, "af_d3_radio_1a").setChecked(True)
1397
                        else:
1398
                            self._d(4, "af_d3_radio_1b").setChecked(True)
1399
                    else:
1400
                        self._d(4, "af_d3_radio_2").setChecked(True)
1401
                        self._d(4, "af_d3_radio_1a").setEnabled(False)
1402
                        self._d(4, "af_d3_radio_1b").setEnabled(False)
1403
                        self._d(4, "af_d3_radio_2a").setEnabled(True)
1404
                        self._d(4, "af_d3_radio_2b").setEnabled(True)
1405
                        if self.curveType == 1:
1406
                            self._d(4, "af_d3_radio_2a").setChecked(True)
1407
                        else:
1408
                            self._d(4, "af_d3_radio_2b").setChecked(True)
1409
            self.progressBar_ = self._d(9, "af_d3_progressbar")
1410
            self.progressBar_.setEnabled(False)
1411
            self.progressBar_.setValue(0)
1412
            self._d(7, "af_d3_spinbox_3").setValue(self.chordLength)
1413
            self._d(3, "af_d3_combobox").setCurrentIndex(self.chordUnits - 1)
1414
            self._d(4, "af_d3_radio_1").toggled.connect(
1415
                lambda: self._af_d3_radio_toggled()
1416
            )
1417
            self._d(5, "af_d3_checkbox_1").toggled.connect(
1418
                lambda: self._af_d3_checkbox_1_toggled()
1419
            )
1420
            self._d(5, "af_d3_checkbox_2").toggled.connect(
1421
                lambda: self._af_d3_checkbox_2_toggled()
1422
            )
1423
            self._d(5, "af_d3_checkbox_5").toggled.connect(
1424
                lambda: self._af_d3_checkbox_5_toggled()
1425
            )
1426
            self._d(5, "af_d3_checkbox_3").clicked.connect(
1427
                lambda: self._af_d3_checkbox_3_toggled()
1428
            )
1429
            self._d(5, "af_d3_checkbox_4").clicked.connect(
1430
                lambda: self._af_d3_checkbox_4_toggled()
1431
            )
1432
            self._d(6, "af_d3_spinbox_1").valueChanged.connect(
1433
                lambda: self._af_d3_spinbox_1_toggled()
1434
            )
1435
            self._d(6, "af_d3_spinbox_2").valueChanged.connect(
1436
                lambda: self._af_d3_spinbox_2_toggled()
1437
            )
1438
            self._d(1, "af_d3_create_button").clicked.connect(
1439
                lambda: self._startCreating()
1440
            )
1441
            self._d(1, "af_d3_close_button").clicked.connect(lambda: self._close())
1442
            self._d(1, "af_d3_back_button").clicked.connect(lambda: self._next(-1))
1443
        elif dialogIndex[0] == 10:
1444
            self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "Math_Functions_Box.ui")
1445
            self._d(8, "af_mfb_label_6").setText(
1446
                "<img src=" + MACRO_DIR + "mfb_img.gif>"
1447
            )
1448
            self._d(1, "af_mfb_okay_button").clicked.connect(lambda: self._okay())
1449
        self.dialog.setWindowIcon(
1450
            QtGui.QIcon(app.getUserMacroDir(True) + "/AeroFoil_UI_Files/AeroFoil.svg")
1451
        )
1452
        self.dialog.exec_()
1453

1454
# Button Functions
1455
# ----------------------------------------------------------------
1456

1457
    def _close(self):
1458
        """
1459
        This function closes an open dialog box.
1460
        This function is called when the 'Close' button is clicked.
1461
        """
1462
        self.dialog.done(1)
1463

1464
    def _next(self, direction):
1465
        """
1466
        This function takes the user from one dialog box to another.
1467
        This function is called when the 'Next' or 'Back' buttons are clicked.
1468

1469
        Arguments
1470
        ----------
1471
        direction: '1' denotes a 'Go Next'; and '-1' denotes a 'Go Back'
1472
        """
1473
        global dialogIndex
1474
        global MIN_DATA_POINTS
1475
        if direction == -1:
1476
            if dialogIndex[0] == 2:
1477
                self.airfoil4DNumber = ""
1478
            elif dialogIndex[0] == 3:
1479
                self.airfoil5DNumber = ""
1480
            elif dialogIndex[0] == 4:
1481
                (
1482
                    self.airfoilProfileType,
1483
                    self.topCurveFunction,
1484
                    self.bottomCurveFunction,
1485
                ) = (1, "", "")
1486
            elif dialogIndex[0] == 5:
1487
                self.importFrom, self.mirroring = 1, False
1488
            elif dialogIndex[0] == 6:
1489
                self.lineStart, self.lineEnd, self.decimalType = 0, 0, 1
1490
                self.tempStart, self.tempEnd, = (
1491
                    0,
1492
                    0,
1493
                )
1494
                self.midIndex1, self.midIndex2 = 0, 0
1495
            elif dialogIndex[0] == 7:
1496
                self.col1, self.col2, self.row1, self.row2 = 1, 2, 0, 0
1497
                self.tempStart, self.tempEnd, = (
1498
                    0,
1499
                    0,
1500
                )
1501
                self.midIndex1, self.midIndex2 = 0, 0
1502
            elif dialogIndex[0] == 8:
1503
                (
1504
                    self.loadedFilePath,
1505
                    self.loadSuccess,
1506
                    self.loadedFile,
1507
                    self.loadedFileContents,
1508
                ) = ([""], False, "", [])
1509
            elif dialogIndex[0] == 9:
1510
                (
1511
                    self.refine,
1512
                    self.refineParam,
1513
                    self.multi,
1514
                    self.multiParam,
1515
                    self.closed_,
1516
                    self.workbench,
1517
                    self.curveType,
1518
                    self.maxRefineParamWarned,
1519
                    self.maxQuantityParamWarned,
1520
                    self.chordLength,
1521
                    self.chordUnits,
1522
                    self.splitCurve,
1523
                    self.splitCurveMode,
1524
                    self.defaultEndPoints,
1525
                ) = (
1526
                    False,
1527
                    2,
1528
                    False,
1529
                    2,
1530
                    False,
1531
                    1,
1532
                    1,
1533
                    False,
1534
                    False,
1535
                    0,
1536
                    1,
1537
                    False,
1538
                    1,
1539
                    False,
1540
                )
1541
            dialogIndex[0] = dialogIndex[dialogIndex[0]]
1542
        else:
1543
            if dialogIndex[0] == 1:
1544
                self.airfoilType = self._d(3, "af_d1_combo").currentIndex() + 1
1545
                if self.airfoilType == 1:
1546
                    _setDialogIndex(2)
1547
                elif self.airfoilType == 2:
1548
                    _setDialogIndex(3)
1549
                elif self.airfoilType == 3:
1550
                    _setDialogIndex(4)
1551
                elif self.airfoilType == 4:
1552
                    _setDialogIndex(5)
1553
            elif dialogIndex[0] == 2:
1554
                self.airfoil4DNumber = self._d(2, "af_d2a_textbox").text()
1555
                if _validateAirfoilNumber(self, 4):
1556
                    _setDialogIndex(9)
1557
                else:
1558
                    return
1559
            elif dialogIndex[0] == 3:
1560
                self.airfoil5DNumber = self._d(2, "af_d2b_textbox").text()
1561
                if _validateAirfoilNumber(self, 5):
1562
                    _setDialogIndex(9)
1563
                else:
1564
                    return
1565
            elif dialogIndex[0] == 4:
1566
                self.topCurveFunction = self._d(2, "af_d2c_textbox_1").text()
1567
                self.bottomCurveFunction = self._d(2, "af_d2c_textbox_2").text()
1568
                if not self._d(4, "af_d2c_radio_2").isChecked():
1569
                    self.airfoilProfileType = 1
1570
                    if _validateFunction(self, 1):
1571
                        _setDialogIndex(9)
1572
                    else:
1573
                        return
1574
                else:
1575
                    self.airfoilProfileType = 2
1576
                    if _validateFunction(self, 2):
1577
                        _setDialogIndex(9)
1578
                    else:
1579
                        return
1580
            elif dialogIndex[0] == 5:
1581
                if self._d(4, "af_d2d_radio_1").isChecked():
1582
                    self.importFrom = 1
1583
                    _setDialogIndex(6)
1584
                else:
1585
                    self.importFrom = 2
1586
                    _setDialogIndex(7)
1587
                if self._d(5, "af_d2d_checkbox").isChecked():
1588
                    self.mirroring = True
1589
                else:
1590
                    self.mirroring = False
1591
            elif dialogIndex[0] == 6:
1592
                tempVar1 = self._d(6, "af_d2d1a_spinBox_1").value()
1593
                tempVar2 = self._d(6, "af_d2d1a_spinBox_2").value()
1594
                tempVar3 = (
1595
                    0 if tempVar1 == 0 or tempVar2 == 0 else tempVar2 - tempVar1 + 1
1596
                )
1597
                if tempVar3 == 0 or tempVar3 >= MIN_DATA_POINTS:
1598
                    self.tempStart = self.lineStart = self._d(
1599
                        6, "af_d2d1a_spinBox_1"
1600
                    ).value()
1601
                    self.tempEnd = self.lineEnd = self._d(
1602
                        6, "af_d2d1a_spinBox_2"
1603
                    ).value()
1604
                    if self._d(4, "af_d2d1a_radio_1").isChecked():
1605
                        self.decimalType = 1
1606
                    else:
1607
                        self.decimalType = 2
1608
                    _setDialogIndex(8)
1609
                else:
1610
                    setAlertBox(
1611
                        "There must be a minimum of "
1612
                        + str(MIN_DATA_POINTS)
1613
                        + " selected file lines,\nthat is, pairs of data points !",
1614
                        True,
1615
                    )
1616
                    return
1617
            elif dialogIndex[0] == 7:
1618
                tempVar = (
1619
                    self._d(6, "af_d2d1b_spinBox_4").value()
1620
                    - self._d(6, "af_d2d1b_spinBox_3").value()
1621
                    + 1
1622
                )
1623
                if tempVar >= MIN_DATA_POINTS:
1624
                    if (
1625
                        self._d(6, "af_d2d1b_spinBox_1").value()
1626
                        == self._d(6, "af_d2d1b_spinBox_2").value()
1627
                    ):
1628
                        self.col1 = self._d(6, "af_d2d1b_spinBox_1").value()
1629
                        self.col2 = self._d(6, "af_d2d1b_spinBox_2").value()
1630
                        self.tempStart = self.row1 = self._d(
1631
                            6, "af_d2d1b_spinBox_3"
1632
                        ).value()
1633
                        self.tempEnd = self.row2 = self._d(
1634
                            6, "af_d2d1b_spinBox_4"
1635
                        ).value()
1636
                        if self._d(4, "af_d2d1b_radio_1").isChecked():
1637
                            self.decimalType = 1
1638
                        else:
1639
                            self.decimalType = 2
1640
                        _setDialogIndex(8)
1641
                    else:
1642
                        setAlertBox("The two file columns cannot be the same !", True)
1643
                        return
1644
                else:
1645
                    setAlertBox(
1646
                        "There must be a minimum of "
1647
                        + str(MIN_DATA_POINTS)
1648
                        + " selected file rows,\nthat is, pairs of data points !",
1649
                        True,
1650
                    )
1651
                    return
1652
            elif dialogIndex[0] == 8:
1653
                if self.loadSuccess:
1654
                    _setDialogIndex(9)
1655
                else:
1656
                    setAlertBox(
1657
                        "Cannot proceed further with an invalid/null file !", True
1658
                    )
1659
                    return
1660
        self._close()
1661
        self._createDialogs()
1662

1663
    def _okay(self):
1664
        """
1665
        This function closes an open dialog box, and
1666
        reverts back to the previous dialog box.
1667
        This function is called when the 'Okay' button is clicked.
1668
        """
1669
        global dialogIndex
1670
        if dialogIndex[0] == 10:
1671
            dialogIndex[0] = dialogIndex[10]
1672
        self._close()
1673
        self._createDialogs()
1674

1675
    def _loadFile(self):
1676
        """
1677
        This function open up the file loader with custom settings.
1678
        This function is called when the 'Load File' button from the.
1679
        'AeroFoil_FileLoad_Dialog' is clicked.
1680
        This function calls the '_validateFile' function/method
1681
        post-loading to verify the file's contents, and then
1682
        notifies the user whether or not the file load was successful.
1683
        """
1684
        fileType = ""
1685
        homeDirPath = str(Path.home())
1686
        if self.importFrom == 1:
1687
            fileType = "Text Files (*.dat)"
1688
        elif self.importFrom == 2:
1689
            fileType = "CSV Files (*.dat)"
1690
        self.loadedFilePath = PySide.QtGui.QFileDialog.getOpenFileName(
1691
            None, "Load 'Airfoil Points' Data File", homeDirPath, fileType
1692
        )
1693
        try:
1694
            self.loadedFile.close()
1695
        except Exception:
1696
            pass
1697
        try:
1698
            self.loadedFile = open(self.loadedFilePath[0], "r")
1699
        except Exception:
1700
            self.loadedFile = ""
1701
        if self.loadedFile == "":
1702
            setAlertBox("No file has been selected!", True)
1703
        else:
1704
            if _validateFile(self):
1705
                self.loadSuccess = True
1706
                if self.mirroring and not self.canMirror:
1707
                    setAlertBox(
1708
                        "Airfoil profile is complete and cannot be mirrored.", False
1709
                    )
1710
            else:
1711
                self.loadSuccess = False
1712
        self._close()
1713
        self._createDialogs()
1714

1715
    def _startCreating(self):
1716
        """
1717
        This function begins the airfoil creation process.
1718
        This function accumulates and stores the final dialog's input data.
1719
        This function also notifies the user whether or not the entire
1720
        'AeroFoil' procedure has been successfully completed.
1721
        This function is called when the 'Create AeroFoil' button from the
1722
        'AeroFoil_Final_Dialog' is clicked.
1723
        This function is responsible for launching the second (Top) macro call.
1724
        """
1725
        global UNITSCONVERSION
1726
        global MIN_CHORD_LENGTH_MM
1727
        if self._d(5, "af_d3_checkbox_1").isChecked():
1728
            self.refine = True
1729
            self.refineParam = self._d(6, "af_d3_spinbox_1").value()
1730
        if self._d(5, "af_d3_checkbox_2").isChecked():
1731
            self.multi = True
1732
            self.multiParam = self._d(6, "af_d3_spinbox_2").value()
1733
        if self._d(5, "af_d3_checkbox_5").isChecked():
1734
            self.closed_ = True
1735
        if self._d(5, "af_d3_checkbox_3").isChecked():
1736
            self.splitCurve = True
1737
            self.splitCurveMode = 1
1738
        if self._d(5, "af_d3_checkbox_4").isChecked():
1739
            self.splitCurve = True
1740
            self.splitCurveMode = 2
1741
        if self._d(5, "af_d3_checkbox_6").isChecked():
1742
            self.defaultEndPoints = True
1743
        if self._d(4, "af_d3_radio_1").isChecked():
1744
            self.workbench = 1
1745
            if self._d(4, "af_d3_radio_1a").isChecked():
1746
                self.curveType = 1
1747
            else:
1748
                self.curveType = 2
1749
        else:
1750
            self.workbench = 2
1751
            if self._d(4, "af_d3_radio_2a").isChecked():
1752
                self.curveType = 1
1753
            else:
1754
                self.curveType = 2
1755
        self.chordLength = self._d(7, "af_d3_spinbox_3").value()
1756
        self.chordUnits = self._d(3, "af_d3_combobox").currentIndex() + 1
1757
        chordLength_conv = self.chordLength * UNITSCONVERSION[self.chordUnits]
1758
        if chordLength_conv >= MIN_CHORD_LENGTH_MM:
1759
            if _prepareAeroFoil(self):
1760
                if AeroFoil_generate(self):
1761
                    self._close()
1762
                    alertBox = QtGui.QMessageBox(
1763
                        QtGui.QMessageBox.Warning,
1764
                        "AeroFoil",
1765
                        "The process has been completed successfully !",
1766
                    )
1767
                    alertBox.setWindowModality(QtCore.Qt.ApplicationModal)
1768
                    alertBox.exec_()
1769
                    app.Console.PrintMessage(
1770
                        "\nThe AeroFoil process has been completed successfully !\n"
1771
                    )
1772
                    return
1773
            setAlertBox(
1774
                "Unexpected error has occurred while processing !\nPlease report.", True
1775
            )
1776
            self.progressBar_.setEnabled(False)
1777
            self.progressBar_.setValue(0)
1778
            app.Console.PrintError(
1779
                "\nUnexpected error has occurred while processing! Please report.\n"
1780
            )
1781
        else:
1782
            setAlertBox(
1783
                "Airfoil chord length cannot be less than "
1784
                + str(MIN_CHORD_LENGTH_MM)
1785
                + "mm !",
1786
                True,
1787
            )
1788

1789
# Other Design Functions
1790
# ------------------------------------------------------------------------------------------
1791

1792
    def _af_d2c_radio_toggled(self):
1793
        if self._d(4, "af_d2c_radio_1").isChecked():
1794
            self.airfoilProfileType = 1
1795
            self._d(2, "af_d2c_textbox_2").setEnabled(False)
1796
        else:
1797
            self.airfoilProfileType = 2
1798
            self._d(2, "af_d2c_textbox_2").setEnabled(True)
1799

1800
    def _af_d3_spinbox_1_toggled(self):
1801
        global MAX_REFINE_PARAM
1802
        if not self.maxRefineParamWarned:
1803
            if self._d(6, "af_d3_spinbox_1").value() > MAX_REFINE_PARAM:
1804
                setAlertBox(
1805
                    "Increase in refinement parameter leads to increase\nin time and memory usage.",
1806
                    False,
1807
                )
1808
                self.maxRefineParamWarned = True
1809

1810
    def _af_d3_spinbox_2_toggled(self):
1811
        global MAX_QUANTITY_PARAM
1812
        if not self.maxQuantityParamWarned:
1813
            if self._d(6, "af_d3_spinbox_2").value() > MAX_QUANTITY_PARAM:
1814
                setAlertBox(
1815
                    "Increase in quantity parameter leads to increase\nin time and memory usage.",
1816
                    False,
1817
                )
1818
                self.maxQuantityParamWarned = True
1819

1820
    def _af_d3_checkbox_1_toggled(self):
1821
        self._d(6, "af_d3_spinbox_1").setValue(2)
1822
        if self._d(5, "af_d3_checkbox_1").isChecked():
1823
            self._d(6, "af_d3_spinbox_1").setEnabled(True)
1824
        else:
1825
            self._d(6, "af_d3_spinbox_1").setEnabled(False)
1826

1827
    def _af_d3_checkbox_2_toggled(self):
1828
        self._d(6, "af_d3_spinbox_2").setValue(2)
1829
        if self._d(5, "af_d3_checkbox_2").isChecked():
1830
            self._d(6, "af_d3_spinbox_2").setEnabled(True)
1831
        else:
1832
            self._d(6, "af_d3_spinbox_2").setEnabled(False)
1833

1834
    def _af_d3_checkbox_5_toggled(self):
1835
        self._d(4, "af_d3_radio_1").setChecked(True)
1836
        self._d(4, "af_d3_radio_1a").setChecked(True)
1837
        self._d(4, "af_d3_radio_2a").setChecked(True)
1838
        self._d(4, "af_d3_radio_1a").setEnabled(True)
1839
        self._d(4, "af_d3_radio_1b").setEnabled(True)
1840
        self._d(4, "af_d3_radio_2a").setEnabled(False)
1841
        self._d(4, "af_d3_radio_2b").setEnabled(False)
1842
        if self._d(5, "af_d3_checkbox_5").isChecked():
1843
            self.closed_ = True
1844
            self._d(4, "af_d3_radio_1").setEnabled(False)
1845
            self._d(4, "af_d3_radio_2").setEnabled(False)
1846
            self._d(5, "af_d3_checkbox_3").setEnabled(False)
1847
            self._d(5, "af_d3_checkbox_4").setEnabled(False)
1848
            self._d(5, "af_d3_checkbox_6").setEnabled(False)
1849
        else:
1850
            if self.closed_:
1851
                self._d(5, "af_d3_checkbox_3").setEnabled(True)
1852
                self._d(5, "af_d3_checkbox_4").setEnabled(True)
1853
                self._d(5, "af_d3_checkbox_6").setEnabled(True)
1854
            self.closed_ = False
1855
            self._d(4, "af_d3_radio_1").setEnabled(True)
1856
            self._d(4, "af_d3_radio_2").setEnabled(True)
1857

1858
    def _af_d3_checkbox_3_toggled(self):
1859
        if self._d(5, "af_d3_checkbox_3").isChecked():
1860
            self._d(5, "af_d3_checkbox_5").setEnabled(False)
1861
            self._d(5, "af_d3_checkbox_6").setEnabled(True)
1862
            self._d(5, "af_d3_checkbox_4").setChecked(False)
1863
        else:
1864
            self._d(5, "af_d3_checkbox_5").setEnabled(True)
1865
            self._d(5, "af_d3_checkbox_6").setEnabled(False)
1866
            self._d(5, "af_d3_checkbox_6").setChecked(False)
1867
        self._d(5, "af_d3_checkbox_5").setChecked(False)
1868
        self._af_d3_checkbox_5_toggled()
1869

1870
    def _af_d3_checkbox_4_toggled(self):
1871
        if self._d(5, "af_d3_checkbox_4").isChecked():
1872
            self._d(5, "af_d3_checkbox_5").setEnabled(False)
1873
            self._d(5, "af_d3_checkbox_6").setEnabled(True)
1874
            self._d(5, "af_d3_checkbox_3").setChecked(False)
1875
        else:
1876
            self._d(5, "af_d3_checkbox_5").setEnabled(True)
1877
            self._d(5, "af_d3_checkbox_6").setEnabled(False)
1878
            self._d(5, "af_d3_checkbox_6").setChecked(False)
1879
        self._d(5, "af_d3_checkbox_5").setChecked(False)
1880
        self._af_d3_checkbox_5_toggled()
1881

1882
    def _af_d3_radio_toggled(self):
1883
        self._d(4, "af_d3_radio_1a").setChecked(True)
1884
        self._d(4, "af_d3_radio_2a").setChecked(True)
1885
        if self._d(4, "af_d3_radio_1").isChecked():
1886
            self._d(4, "af_d3_radio_1a").setEnabled(True)
1887
            self._d(4, "af_d3_radio_1b").setEnabled(True)
1888
            self._d(4, "af_d3_radio_2a").setEnabled(False)
1889
            self._d(4, "af_d3_radio_2b").setEnabled(False)
1890
        else:
1891
            self._d(4, "af_d3_radio_1a").setEnabled(False)
1892
            self._d(4, "af_d3_radio_1b").setEnabled(False)
1893
            self._d(4, "af_d3_radio_2a").setEnabled(True)
1894
            self._d(4, "af_d3_radio_2b").setEnabled(True)
1895

1896
    def _functionsList(self):
1897
        self.topCurveFunction = self._d(2, "af_d2c_textbox_1").text()
1898
        self.bottomCurveFunction = self._d(2, "af_d2c_textbox_2").text()
1899
        _setDialogIndex(10)
1900
        self._close()
1901
        self._createDialogs()
1902

1903

1904

1905
def _setDialogIndex(index):
1906
    """
1907
    This function sets the appropriate dialog box, when the
1908
    'Next', 'Back', and 'Okay' buttons are clicked.
1909
    This function is NOT responsible for loading any dialog boxes.
1910

1911
    The 'index' argument denotes the next or previous dialog box to go to.
1912
    The 'index' number corresponds to the global variable list 'dialogIndex'
1913
    as well as the dialog names table mentioned in the macro's main DocString.
1914

1915
    The first element in the global variable 'dialogIndex' is the index number
1916
    of the currently open dialog box. The other elements too are eventually
1917
    replaced with the index numbers of the dialog box that was opened prior
1918
    to the current one. This allows the macro to remember its backward
1919
    journey through the various dialog boxes.
1920

1921
    Arguments
1922
    ----------
1923
    index:  A dialog box code (Integer) as listed in the global variable
1924
            'dialogIndex'.
1925
    """
1926
    global dialogIndex
1927
    dialogIndex[index] = dialogIndex[0]
1928
    # Here, the first element which contains the current dialog box
1929
    # index number is assigned to the Nth element, where 'N' is the
1930
    # index number of the next dialog box.
1931
    # This ensures that when the 'Back' button is clicked, the macro
1932
    # can approach the previous dialog box. This way, regardless of 'N',
1933
    # the macro can approach the 1st dialog box by maximum.
1934
    dialogIndex[0] = index
1935
    # Here, the first element is set to the 'index' number argument.
1936
    # This sets the macro to open up a specific next dialog box.
1937
    # The macro then opens up the next dialog box when the
1938
    # '_createDialogs' method from the 'AeroFoilDialog' class is called.
1939

1940
def setAlertBox(message, error):
1941
    """
1942
    This function sets the error and warning pop-up messages.
1943

1944
    Arguments
1945
    ----------
1946
    message: The string-based message to be displayed.
1947
    error:   'True' opens up the 'Critical' message box, and
1948
             'False' opens up the 'Warning' message box.
1949
    """
1950
    msgbox_ = 0
1951
    if error:
1952
        msgbox_ = QtGui.QMessageBox(
1953
            QtGui.QMessageBox.Critical, "AeroFoil - Error Message", message
1954
        )
1955
    else:
1956
        msgbox_ = QtGui.QMessageBox(
1957
            QtGui.QMessageBox.Warning, "AeroFoil - Warning Message", message
1958
        )
1959
    msgbox_.setWindowModality(QtCore.Qt.ApplicationModal)
1960
    msgbox_.exec_()
1961

1962

1963

1964
# Operating and Math Functions
1965
# ------------------------------------------------------------------------------------------
1966

1967
def _prepareAeroFoil(objRef):
1968
    """
1969
    This function utilizes the various user input data as a basis
1970
    for creating a list of refined, verified, and finalized
1971
    airfoil data points to be used later for physical creation.
1972
    This function is called from the '_startCreating' method
1973
    of the 'AeroFoilDialog' class.
1974

1975
    Arguments
1976
    ----------
1977
    objRef: An instance of the 'AeroFoilDialog' object.
1978
    """
1979
    global UNITSCONVERSION
1980
    global NACA_NUMBER_OF_POINTS
1981
    chordLength_conv = objRef.chordLength * UNITSCONVERSION[objRef.chordUnits]
1982
    # Enable Progress Bar
1983
    objRef.progressBar_.setEnabled(True)
1984
    objRef.progressBar_.setValue(0)
1985
    # Generating and Furnishing Points
1986
    if objRef.airfoilType == 1:
1987
        _naca4digit(objRef)
1988
    elif objRef.airfoilType == 2:
1989
        _naca5digit(objRef)
1990
    elif objRef.airfoilType == 3:
1991
        xu_, yu_, xl_, yl_ = [], [], [], []
1992
        n_ = (
1993
            round(NACA_NUMBER_OF_POINTS / 2)
1994
            * ((objRef.refine * (objRef.refineParam - 1)) + 1)
1995
        ) + 1
1996
        for points_i in range(n_):
1997
            xu_.append((points_i / (n_ - 1)) * chordLength_conv)
1998
            xl_.append((points_i / (n_ - 1)) * chordLength_conv)
1999
            try:
2000
                yu_.append(solveFx(objRef.topCurveFunction, xu_[-1]) * chordLength_conv)
2001
                if objRef.airfoilProfileType == 1:
2002
                    yl_.append(
2003
                        -1
2004
                        * solveFx(objRef.topCurveFunction, xu_[-1])
2005
                        * chordLength_conv
2006
                    )
2007
                elif objRef.airfoilProfileType == 2:
2008
                    yl_.append(
2009
                        solveFx(objRef.bottomCurveFunction, xu_[-1]) * chordLength_conv
2010
                    )
2011
            except Exception:
2012
                # Unexpected Error Occurred while Processing
2013
                return False
2014
            if objRef.progressBar_.value() < 50:
2015
                objRef.progressBar_.setValue(round((points_i / (2 * n_)) * 100))
2016
        xl_.reverse()
2017
        yl_.reverse()
2018
        objRef.midIndex1, objRef.midIndex2 = len(xu_) - 1, len(xu_)
2019
        objRef.pointsX, objRef.pointsY = xu_ + xl_, yu_ + yl_
2020
    elif objRef.airfoilType == 4:
2021
        count, tempVar, pointsInLine, pointsInLine_prev = (
2022
            1,
2023
            "",
2024
            [],
2025
            [],
2026
        )
2027
        n_ = len(objRef.loadedFileContents)
2028
        for lineOrRow in objRef.loadedFileContents:
2029
            if objRef.decimalType == 1:
2030
                tempVar = lineOrRow
2031
            elif objRef.decimalType == 2:
2032
                tempVar = (
2033
                    lineOrRow.replace(",", ".")
2034
                    if objRef.importFrom == 1
2035
                    else [element_.replace(",", ".") for element_ in lineOrRow]
2036
                )
2037
            tempVar = tempVar.replace("-.", "-0.")
2038
            tempVar = tempVar.replace(" .", "0.")
2039
            tempVar = "0" + tempVar if tempVar[0] == "." else tempVar
2040
            pointsInLine = (
2041
                re.findall(r"[+-]?\d+(?:\.\d+)?", tempVar)
2042
                if objRef.importFrom == 1
2043
                else re.findall(
2044
                    r"[+-]?\d+(?:\.\d+)?", tempVar[col1 - 1] + " " + tempVar[col2 - 1]
2045
                )
2046
            )
2047
            if objRef.refine and count > 1:
2048
                n_ = (
2049
                    float(pointsInLine[0]) - float(pointsInLine_prev[0])
2050
                ) / objRef.refineParam
2051
                for i in range(objRef.refineParam - 1):
2052
                    tempVar = float(pointsInLine_prev[0]) + n_
2053
                    objRef.pointsX.append(tempVar * chordLength_conv)
2054
                    objRef.pointsY.append(
2055
                        interpolateNum(
2056
                            float(pointsInLine_prev[0]),
2057
                            float(pointsInLine_prev[1]),
2058
                            float(pointsInLine[0]),
2059
                            float(pointsInLine[1]),
2060
                            tempVar,
2061
                        )
2062
                        * chordLength_conv
2063
                    )
2064
            pointsInLine_prev = pointsInLine
2065
            objRef.pointsX.append(float(pointsInLine[0]) * chordLength_conv)
2066
            objRef.pointsY.append(float(pointsInLine[1]) * chordLength_conv)
2067
            if objRef.progressBar_.value() < 50:
2068
                objRef.progressBar_.setValue(round((count / (2 * n_)) * 100))
2069
            count += 1
2070
    if (
2071
        objRef.pointsX[len(objRef.pointsX) - 1] != objRef.pointsX[0]
2072
        or objRef.pointsY[len(objRef.pointsY) - 1] != objRef.pointsY[0]
2073
    ) and not objRef.splitCurve:
2074
        objRef.pointsX.append(objRef.pointsX[0])
2075
        objRef.pointsY.append(objRef.pointsY[0])
2076
    return True
2077

2078

2079

2080
def interpolateNum(x1_, y1_, x2_, y2_, x_):
2081
    """
2082
    This function performs a linear interpolation of any given number.
2083

2084
    Arguments
2085
    ----------
2086
    x1_, y1_, x2_, y2_: X and Y-axis values of a pair of data points.
2087
    x: Input value of one of the axes of the data point sought.
2088
    """
2089
    return y1_ + (((x_ - x1_) * (y2_ - y1_)) / (x2_ - x1_))
2090

2091

2092

2093
def generateName(inputName):
2094
    """
2095
    This function correctly provides a given name/label with
2096
    appropriate and non-dulplicate numbering.
2097
    e.g.  'AeroFoil' results in 'AeroFoil_1' if such a labelled
2098
          object does not exist; else, it becomes 'AeroFoil_2'.
2099

2100
    Arguments
2101
    ----------
2102
    inputName: A non-numbered (simple) name/label
2103
    """
2104
    nameIndex = 1
2105
    while True:
2106
        if (
2107
            not doc.getObjectsByLabel(inputName + "_" + str(nameIndex))
2108
            and not doc.getObjectsByLabel(inputName + "_" + str(nameIndex) + "_Upper")
2109
            and not doc.getObjectsByLabel(inputName + "_" + str(nameIndex) + "_Lower")
2110
        ):
2111
            return inputName + "_" + str(nameIndex)
2112
        nameIndex += 1
2113

2114

2115

2116
def _naca4digit(objRef):
2117
    """
2118
    This function generates a list of airfoil data points
2119
    for a typical NACA 4 Digit airfoil model.
2120
    This function is called from the '_startCreating' method
2121
    of the 'AeroFoilDialog' class.
2122

2123
    Arguments
2124
    ----------
2125
    objRef: An instance of the 'AeroFoilDialog' object.
2126
    """
2127
    global UNITSCONVERSION
2128
    global NACA_NUMBER_OF_POINTS
2129
    chordLength_conv = objRef.chordLength * UNITSCONVERSION[objRef.chordUnits]
2130
    xu_, yu_, xl_, yl_ = [], [], [], []
2131
    yc_ = yt_ = theta_ = 0
2132
    a0_, a1_, a2_, a3_, a4_ = 0.2969, -0.126, -0.3516, 0.2843, -0.1015
2133
    m_, p_, t_ = (
2134
        int(objRef.airfoil4DNumber[0]) / 100,
2135
        int(objRef.airfoil4DNumber[1]) / 10,
2136
        int(objRef.airfoil4DNumber[2] + objRef.airfoil4DNumber[3]) / 100,
2137
    )
2138
    n_ = (
2139
        round(NACA_NUMBER_OF_POINTS / 2)
2140
        * ((objRef.refine * (objRef.refineParam - 1)) + 1)
2141
    ) + 1
2142
    for i in range(n_):
2143
        x_ = i / (n_ - 1)
2144
        yt_ = (
2145
            5
2146
            * t_
2147
            * (
2148
                (a0_ * pow(x_, 0.5))
2149
                + (a1_ * x_)
2150
                + (a2_ * pow(x_, 2))
2151
                + (a3_ * pow(x_, 3))
2152
                + (a4_ * pow(x_, 4))
2153
            )
2154
        )
2155
        if x_ < p_:
2156
            yc_ = (m_ * ((2 * p_ * x_) - (x_ * x_))) / (p_ * p_)
2157
            theta_ = math.degrees(math.atan((2 * m_ * (p_ - x_)) / (p_ * p_)))
2158
        else:
2159
            yc_ = (m_ * (1 - (2 * p_) + (2 * p_ * x_) - (x_ * x_))) / pow(1 - p_, 2)
2160
            theta_ = math.degrees(math.atan((2 * m_ * (p_ - x_)) / pow(1 - p_, 2)))
2161
        xu_.append((x_ - (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2162
        yu_.append((yc_ + (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2163
        xl_.append((x_ + (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2164
        yl_.append((yc_ - (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2165
        if objRef.progressBar_.value() < 50:
2166
            objRef.progressBar_.setValue(round((i / (2 * n_)) * 100))
2167
    xl_.reverse()
2168
    yl_.reverse()
2169
    objRef.midIndex1, objRef.midIndex2 = len(xu_) - 1, len(xu_)
2170
    objRef.pointsX, objRef.pointsY = xu_ + xl_, yu_ + yl_
2171

2172

2173

2174
def _naca5digit(objRef):
2175
    """
2176
    This function generates a list of airfoil data points
2177
    for a typical NACA 5 Digit airfoil model.
2178
    This function is called from the '_startCreating' method
2179
    of the 'AeroFoilDialog' class.
2180

2181
    Arguments
2182
    ----------
2183
    objRef: An instance of the 'AeroFoilDialog' object.
2184
    """
2185
    global UNITSCONVERSION
2186
    global NACA_NUMBER_OF_POINTS
2187
    chordLength_conv = objRef.chordLength * UNITSCONVERSION[objRef.chordUnits]
2188
    xu_, yu_, xl_, yl_ = [], [], [], []
2189
    yc_ = yt_ = theta_ = 0
2190
    a0_, a1_, a2_, a3_, a4_ = 0.2969, -0.126, -0.3516, 0.2843, -0.1015
2191
    R_ = [0.0580, 0.1260, 0.2025, 0.2900, 0.3910, 0.1300, 0.2170, 0.3180, 0.4410]
2192
    K1_ = [361.400, 51.640, 15.957, 6.643, 3.230, 51.990, 15.793, 6.520, 3.191]
2193
    K2K1_ = [0, 0, 0, 0, 0, 0.000764, 0.00677, 0.0303, 0.1355]
2194
    index = int(objRef.airfoil5DNumber[1]) - 1 + (4 * int(objRef.airfoil5DNumber[2]))
2195
    r_, k1_, k2k1_, t_ = (
2196
        R_[index],
2197
        K1_[index],
2198
        K2K1_[index],
2199
        int(objRef.airfoil5DNumber[3] + objRef.airfoil5DNumber[4]) / 100,
2200
    )
2201
    n_ = (
2202
        round(NACA_NUMBER_OF_POINTS / 2)
2203
        * ((objRef.refine * (objRef.refineParam - 1)) + 1)
2204
    ) + 1
2205
    for i in range(n_):
2206
        x_ = i / (n_ - 1)
2207
        yt_ = (
2208
            5
2209
            * t_
2210
            * (
2211
                (a0_ * pow(x_, 0.5))
2212
                + (a1_ * x_)
2213
                + (a2_ * pow(x_, 2))
2214
                + (a3_ * pow(x_, 3))
2215
                + (a4_ * pow(x_, 4))
2216
            )
2217
        )
2218
        if int(objRef.airfoil5DNumber[2]) == 0:
2219
            if x_ < r_:
2220
                yc_ = (
2221
                    k1_ * (pow(x_, 3) - (3 * r_ * x_ * x_) + (x_ * r_ * r_ * (3 - r_)))
2222
                ) / 6
2223
                theta_ = math.degrees(
2224
                    math.atan(
2225
                        (k1_ * ((3 * x_ * x_) - (6 * r_ * x_) + (r_ * r_ * (3 - r_))))
2226
                        / 6
2227
                    )
2228
                )
2229
            else:
2230
                yc_ = (k1_ * pow(r_, 3) * (1 - x_)) / 6
2231
                theta_ = math.degrees(math.atan(-(k1_ * pow(r_, 3)) / 6))
2232
        elif int(objRef.airfoil5DNumber[2]) == 1:
2233
            if x_ < r_[int(objRef.airfoil5DNumber[1] + objRef.airfoil5DNumber[2])]:
2234
                yc_ = (
2235
                    k1_
2236
                    * (
2237
                        pow(x_ - r_, 3)
2238
                        - (k2k1_ * x_ * pow(1 - r_, 3))
2239
                        - (x_ * pow(r_, 3))
2240
                        + pow(r_, 3)
2241
                    )
2242
                ) / 6
2243
                theta_ = math.degrees(
2244
                    math.atan(
2245
                        (
2246
                            k1_
2247
                            * (
2248
                                (3 * pow(x_ - r_, 2))
2249
                                - (k2k1_ * pow(1 - r_, 3))
2250
                                - (pow(r_, 3))
2251
                            )
2252
                        )
2253
                        / 6
2254
                    )
2255
                )
2256
            else:
2257
                yc_ = (
2258
                    k1_
2259
                    * (
2260
                        (k2k1_ * pow(x_ - r_, 3))
2261
                        - (k2k1_ * x_ * pow(1 - r_, 3))
2262
                        - (x_ * pow(r_, 3))
2263
                        + pow(r_, 3)
2264
                    )
2265
                ) / 6
2266
                theta_ = math.degrees(
2267
                    math.atan(
2268
                        (
2269
                            k1_
2270
                            * (
2271
                                (3 * k2k1_ * pow(x_ - r_, 2))
2272
                                - (k2k1_ * pow(1 - r_, 3))
2273
                                - (pow(r_, 3))
2274
                            )
2275
                        )
2276
                        / 6
2277
                    )
2278
                )
2279
        xu_.append((x_ - (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2280
        yu_.append((yc_ + (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2281
        xl_.append((x_ + (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2282
        yl_.append((yc_ - (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2283
        if objRef.progressBar_.value() < 50:
2284
            objRef.progressBar_.setValue(round((i / (2 * n_)) * 100))
2285
    xl_.reverse()
2286
    yl_.reverse()
2287
    objRef.midIndex1, objRef.midIndex2 = len(xu_) - 1, len(xu_)
2288
    objRef.pointsX, objRef.pointsY = xu_ + xl_, yu_ + yl_
2289

2290

2291

2292
def convTrig(mode, inputVal):
2293
    """
2294
    This function converts specific, unconventional trigonometric code
2295
    into a Python-based (Math module) trigonometric code.
2296
    This function is called from the 'solveFx' method.
2297

2298
    Arguments
2299
    ----------
2300
    mode: A code number denoting a specific trignometric function.
2301
    inputVal: An unconventional trigonometric code string.
2302
    """
2303
    if mode == 1:
2304
        return math.sin(math.radians(inputVal))
2305
    if mode == 2:
2306
        return math.cos(math.radians(inputVal))
2307
    if mode == 3:
2308
        return math.tan(math.radians(inputVal))
2309
    if mode == 4:
2310
        return math.degrees(math.asin(inputVal))
2311
    if mode == 5:
2312
        return math.degrees(math.acos(inputVal))
2313
    if mode == 6:
2314
        return math.degrees(math.atan(inputVal))
2315

2316

2317

2318
def solveFx(f_, x_):
2319
    """
2320
    This function generates a list of airfoil data points
2321
    for a typical NACA 4 Digit airfoil model.
2322
    This function is called from the '_validateFunction'
2323
    and the '_startCreating' methods of the
2324
    'AeroFoilDialog' class.
2325

2326
    Arguments
2327
    ----------
2328
    objRef: An instance of the 'AeroFoilDialog' object.
2329
    """
2330
    f_ = f_.replace("x", "x_")
2331
    f_ = f_.replace("^", "**")
2332
    f_ = f_.replace("e", "math.e")
2333
    f_ = f_.replace("pi", "math.pi")
2334
    f_ = f_.replace("ln", "math.log")
2335
    f_ = f_.replace("log", "math.log10")
2336
    f_ = f_.replace("sqrt", "math.sqrt")
2337
    f_ = f_.replace("sin(", "convTrig(1,")
2338
    f_ = f_.replace("cos(", "convTrig(2,")
2339
    f_ = f_.replace("tan(", "convTrig(3,")
2340
    f_ = f_.replace("asin(", "convTrig(4,")
2341
    f_ = f_.replace("acos(", "convTrig(5,")
2342
    f_ = f_.replace("atan(", "convTrig(6,")
2343
    try:
2344
        return eval(f_)
2345
    except Exception:
2346
        return
2347

2348

2349

2350
def _validateAirfoilNumber(objRef, digit):
2351
    """
2352
    This function validates whether or not the inputted NACA airfoil model code
2353
    is valid and existing.
2354
    This function is called from the '_next' method of the 'AeroFoilDialog' class.
2355
    This function is called when the 'Next' button from the 'AeroFoil_NACA4Digit_Dialog'
2356
    or the 'AeroFoil_NACA5Digit_Dialog' is clicked.
2357

2358
    Arguments
2359
    ----------
2360
    objRef: An instance of the 'AeroFoilDialog' object.
2361
    digit:  An integer number denoting the NACA airfoil type.
2362
            Options: 4 or 5.
2363
    """
2364
    global NACA_5_2ND_3RD_DIGITS
2365
    airfoilNumber = ""
2366
    airfoilNumber = objRef.airfoil4DNumber if digit == 4 else objRef.airfoil5DNumber
2367
    if airfoilNumber.isnumeric():
2368
        if len(airfoilNumber) == digit:
2369
            if int(airfoilNumber[-2:]) == 0:
2370
                setAlertBox(
2371
                    "The last two digits of the airfoil code cannot be a\nzero value !",
2372
                    True,
2373
                )
2374
            else:
2375
                if digit == 4:
2376
                    return True
2377
                elif digit == 5:
2378
                    if int(airfoilNumber[0]) == 2:
2379
                        if int(airfoilNumber[2]) <= 1:
2380
                            for numRef in NACA_5_2ND_3RD_DIGITS:
2381
                                if airfoilNumber[1] + airfoilNumber[2] == numRef:
2382
                                    return True
2383
                            if airfoilNumber[2] == 0:
2384
                                setAlertBox(
2385
                                    "The second digit of the airfoil code must be\nbetween one and five, both inclusive !",
2386
                                    True,
2387
                                )
2388
                            elif airfoilNumber[2] == 1:
2389
                                setAlertBox(
2390
                                    "The second digit of the airfoil code must be\nbetween two and five, both inclusive !",
2391
                                    True,
2392
                                )
2393
                        else:
2394
                            setAlertBox(
2395
                                "The third digit of the airfoil code must be either\na zero or a one !",
2396
                                True,
2397
                            )
2398
                    else:
2399
                        setAlertBox(
2400
                            "The first digit of the airfoil code must be a two !", True
2401
                        )
2402
        else:
2403
            setAlertBox(
2404
                "Airfoil code must contain exactly " + str(digit) + " digits !", True
2405
            )
2406
    else:
2407
        setAlertBox("Airfoil code must be numeric !", True)
2408
    objRef.airfoil4DNumber, objRef.airfoil5DNumber = "", ""
2409
    return False
2410

2411

2412

2413
def _validateFunction(objRef, mode):
2414
    """
2415
    This function parses the inputted curve functions, and validates them,
2416
    resulting in whether or not the inputted functions are valid from points
2417
    0 to 1, both inclusive, in the X-axis of a typical graph.
2418
    This function is called from the '_next' method of the 'AeroFoilDialog' class.
2419
    This function is called when the 'Next' button from the 'AeroFoil_CurvesInput_Dialog'
2420
    is clicked.
2421

2422
    Arguments
2423
    ----------
2424
    objRef:   An instance of the 'AeroFoilDialog' object.
2425
    mode:     An integer number specifying the function input mode.
2426
    Options:  '1' denotes that one function has been inputted,
2427
              and the function is to be symmetrically mirrored
2428
              to produce the lower airfoil points.
2429
              '2' denotes that two functions have been inputted,
2430
              one for the upper, and one for the lower airfoil points.
2431
    """
2432
    global FUNCTIONSARRAY
2433
    function = [
2434
        objRef.topCurveFunction.replace(" ", ""),
2435
        objRef.bottomCurveFunction.replace(" ", ""),
2436
    ]
2437
    badFunctionCombinations = [
2438
        "()",
2439
        "(+",
2440
        "(-",
2441
        "(*",
2442
        "(/",
2443
        "(^",
2444
        ")(",
2445
        "+)",
2446
        "-)",
2447
        "*)",
2448
        "/)",
2449
        "^)",
2450
        ".)",
2451
        ").",
2452
        ".(",
2453
        "(.",
2454
        "++",
2455
        "--",
2456
        "**",
2457
        "//",
2458
        "^^",
2459
        "xx",
2460
        "ee",
2461
        "pipi",
2462
        "xe",
2463
        "ex",
2464
        "xpi",
2465
        "pix",
2466
        "epi",
2467
        "pie",
2468
        "x(",
2469
        "e(",
2470
        "pi(",
2471
        ")x",
2472
        ")e",
2473
        ")pi",
2474
        ")ln",
2475
        ")log",
2476
        ")sqrt",
2477
        ")sin",
2478
        ")cos",
2479
        ")tan",
2480
        ")asin",
2481
        ")acos",
2482
        ")atan",
2483
    ]
2484
    modeName = ["Top", "Bottom"]
2485
    for i in range(mode):
2486
        if len(function[i]) > 0:
2487
            if function[i].count("(") == function[i].count(")"):
2488
                tempVar = function[i].replace(",", ".")
2489
                for stringRef in FUNCTIONSARRAY:
2490
                    tempVar = tempVar.replace(stringRef, "")
2491
                if len(tempVar) == 0 or tempVar.isnumeric():
2492
                    badFunction, badFunctionFound = "", False
2493
                    for stringRef in badFunctionCombinations:
2494
                        if function[i].count(stringRef) != 0:
2495
                            badFunction, badFunctionFound = stringRef, True
2496
                            break
2497
                    if not badFunctionFound:
2498
                        try:
2499
                            if mode == 1:
2500
                                if (
2501
                                    solveFx(function[i], 0) >= 0
2502
                                    and solveFx(function[i], 1) >= 0
2503
                                ):
2504
                                    return True
2505
                                else:
2506
                                    setAlertBox(
2507
                                        "Top curve function 'range' should not be less than zero !",
2508
                                        True,
2509
                                    )
2510
                            elif mode == 2 and i == 1:
2511
                                if solveFx(function[i], 0) <= solveFx(
2512
                                    function[i - 1], 0
2513
                                ) and solveFx(function[i], 1) <= solveFx(
2514
                                    function[i - 1], 1
2515
                                ):
2516
                                    return True
2517
                                else:
2518
                                    setAlertBox(
2519
                                        "Top and Bottom curve function 'ranges' should not intersect!",
2520
                                        True,
2521
                                    )
2522
                        except Exception:
2523
                            setAlertBox(
2524
                                modeName[i]
2525
                                + " curve function contains unrecognizable,\ninvalid components !",
2526
                                True,
2527
                            )
2528
                    else:
2529
                        setAlertBox(
2530
                            modeName[i]
2531
                            + " curve function contains this invalidly\nwritten component ::  "
2532
                            + badFunction,
2533
                            True,
2534
                        )
2535
                else:
2536
                    setAlertBox(
2537
                        modeName[i]
2538
                        + " curve function contains one or more\ninvalid, extraneous components !",
2539
                        True,
2540
                    )
2541
            else:
2542
                setAlertBox(
2543
                    modeName[i] + " curve function contains unequal parantheses !", True
2544
                )
2545
        else:
2546
            setAlertBox(modeName[i] + " curve function cannot be empty !", True)
2547
    objRef.topCurveFunction, objRef.bottomCurveFunction = "", ""
2548
    return False
2549

2550

2551

2552
def _validateFile(objRef):
2553
    """
2554
    This function parses the file contents of the inputted file path, and validates them,
2555
    resulting in whether or not the file is valid (that is, contains minimum number of points,
2556
    valid data points structure, etc.)
2557
    This function is called from the '_loadFile' method of the 'AeroFoilDialog' class.
2558

2559
    Arguments
2560
    ----------
2561
    objRef: An instance of the 'AeroFoilDialog' object.
2562
    """
2563
    global MIN_DATA_POINTS
2564
    objRef.midIndex1, objRef.midIndex2 = 0, 0
2565
    i_start, i_end, tempVar, tempStr, count, objRef.canMirror, x_prev, dir_ = (
2566
        0,
2567
        0,
2568
        "",
2569
        "",
2570
        1,
2571
        True,
2572
        0,
2573
        [1, 1],
2574
    )
2575
    i_start, i_end, fileReader_ = (
2576
        (objRef.tempStart, objRef.tempEnd, objRef.loadedFile)
2577
        if objRef.importFrom == 1
2578
        else (objRef.row1, objRef.row2, csv.reader(objRef.loadedFile))
2579
    )
2580
    objRef.loadedFileContents = []
2581
    objRef.loadedFile.seek(0)
2582
    if i_start == 0:
2583
        i_start = i_end = 0
2584
        for lineOrRow in fileReader_:
2585
            if objRef.decimalType == 1:
2586
                tempVar = lineOrRow
2587
            elif objRef.decimalType == 2:
2588
                tempVar = (
2589
                    lineOrRow.replace(",", ".")
2590
                    if objRef.importFrom == 1
2591
                    else [element_.replace(",", ".") for element_ in lineOrRow]
2592
                )
2593
            pointsInLine = (
2594
                re.findall(r"[+-]?\d+(?:\.\d+)?", tempVar)
2595
                if objRef.importFrom == 1
2596
                else re.findall(
2597
                    r"[+-]?\d+(?:\.\d+)?",
2598
                    tempVar[objRef.col1 - 1] + " " + tempVar[objRef.col2 - 1],
2599
                )
2600
            )
2601
            if len(pointsInLine) == 2:
2602
                i_start = count if i_start == 0 else i_start
2603
                objRef.loadedFileContents.append(lineOrRow)
2604
            else:
2605
                if i_start != 0:
2606
                    i_end = count - 1
2607
                    break
2608
            if i_start != 0:
2609
                if tempStr == "":
2610
                    tempStr = "1"
2611
                elif tempStr == "1":
2612
                    dir_[0] = dir_[1]
2613
                    dir_[1] = (float(pointsInLine[0]) - x_prev) / abs(
2614
                        float(pointsInLine[0]) - x_prev
2615
                    )
2616
                    if dir_[0] != dir_[1] and (count - i_start) > 2:
2617
                        if objRef.canMirror:
2618
                            objRef.midIndex1, objRef.midIndex2 = (
2619
                                count - i_start - 1,
2620
                                count - i_start,
2621
                            )
2622
                            swapVar = objRef.loadedFileContents[-1]
2623
                            objRef.loadedFileContents[-1] = objRef.loadedFileContents[
2624
                                -2
2625
                            ]
2626
                            objRef.loadedFileContents.append(swapVar)
2627
                        objRef.canMirror = False
2628
                        tempStr = "0"
2629
                x_prev = float(pointsInLine[0])
2630
            count += 1
2631
        if i_start == 0:
2632
            tempStr = (
2633
                "File Line :: " + str(count)
2634
                if objRef.importFrom == 1
2635
                else "File Row :: " + str(count)
2636
            )
2637
            setAlertBox(
2638
                "File is invalid, or file is of incorrect format !\n" + tempStr, True
2639
            )
2640
            return False
2641
    else:
2642
        for lineOrRow in fileReader_:
2643
            if count >= i_start:
2644
                if objRef.decimalType == 1:
2645
                    tempVar = lineOrRow
2646
                elif objRef.decimalType == 2:
2647
                    tempVar = (
2648
                        lineOrRow.replace(",", ".")
2649
                        if objRef.importFrom == 1
2650
                        else [element_.replace(",", ".") for element_ in lineOrRow]
2651
                    )
2652
                pointsInLine = (
2653
                    re.findall(r"[+-]?\d+(?:\.\d+)?", tempVar)
2654
                    if objRef.importFrom == 1
2655
                    else re.findall(
2656
                        r"[+-]?\d+(?:\.\d+)?",
2657
                        tempVar[objRef.col1 - 1] + " " + tempVar[objRef.col2 - 1],
2658
                    )
2659
                )
2660
                if len(pointsInLine) == 2:
2661
                    objRef.loadedFileContents.append(lineOrRow)
2662
                else:
2663
                    if i_end == 0 and count != 1:
2664
                        count += 1
2665
                        break
2666
                    else:
2667
                        tempStr = (
2668
                            "File Line :: " + str(count)
2669
                            if objRef.importFrom == 1
2670
                            else "File Row :: " + str(count)
2671
                        )
2672
                        setAlertBox(
2673
                            "File is invalid, or file is of incorrect format !\n"
2674
                            + tempStr,
2675
                            True,
2676
                        )
2677
                        return False
2678
                if tempStr == "":
2679
                    tempStr = "1"
2680
                elif tempStr == "1":
2681
                    dir_[0] = dir_[1]
2682
                    dir_[1] = (float(pointsInLine[0]) - x_prev) / abs(
2683
                        float(pointsInLine[0]) - x_prev
2684
                    )
2685
                    if dir_[0] != dir_[1] and (count - i_start) > 2:
2686
                        if objRef.canMirror:
2687
                            objRef.midIndex1, objRef.midIndex2 = (
2688
                                count - i_start - 1,
2689
                                count - i_start,
2690
                            )
2691
                            swapVar = objRef.loadedFileContents[-1]
2692
                            objRef.loadedFileContents[-1] = objRef.loadedFileContents[
2693
                                -2
2694
                            ]
2695
                            objRef.loadedFileContents.append(swapVar)
2696
                        objRef.canMirror = False
2697
                        tempStr = "0"
2698
                x_prev = float(pointsInLine[0])
2699
                if i_end != 0 and count == i_end:
2700
                    count += 1
2701
                    break
2702
            count += 1
2703
    i_end = count - 1
2704
    # if i_end - i_start + 1 >= MIN_DATA_POINTS:
2705
    # return True
2706
    if i_end - i_start + 1 < MIN_DATA_POINTS:
2707
        setAlertBox(
2708
            "There must be a minimum of "
2709
            + str(MIN_DATA_POINTS)
2710
            + " selected file rows,\nthat is, pairs of data points !",
2711
            True,
2712
        )
2713
        return False
2714
    objRef.lineStart, objRef.row1, objRef.lineEnd, objRef.row2 = (
2715
        (i_start, i_start, i_end, i_end)
2716
        if objRef.importFrom == 1
2717
        else (objRef.lineStart, objRef.row1, objRef.lineEnd, objRef.row2)
2718
    )
2719
    objRef.loadedFile.close()
2720
    return True
2721

2722

2723

2724
######################################################################
2725
###----------------------------------------------------------------###
2726
### 		AEROFOIL MACRO CALLS - Bottom (check Top)	   ###
2727
###----------------------------------------------------------------###
2728
###								   ###
2729
###								   ###
2730
###	This is the main macro call. The code below commences 	   ###
2731
###	the AeroFoil GUI interface. This script cannot be 	   ###
2732
###	called externally.					   ###
2733
    								   ###
2734
                                                                   ###
2735
if __name__ == "__main__":					   ###
2736
    AeroFoilDialog()						   ###
2737
                                                                   ###
2738
###----------------------------------------------------------------###
2739
### 		AEROFOIL MACRO CALLS - Bottom (check Top)	   ###
2740
###----------------------------------------------------------------###
2741
######################################################################
2742

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.