FreeCAD-macros
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"""
67To use this macro, the steps to be followed are simple and straightforward :
68follow the instructions in the respective dialog boxes, fill in the relevant inputs,
69and navigate accordingly. In case of error or warning, you will automatically be
70notified the same. In case you are notified to report an unexpected error,
71communicate the error by mentioning the FreeCAD version, tracing the steps
72taken, and mentioning whether (and how much) or not any ouput was generated.
73
74Note (1) Performing the macro operation with custom points and refinement
75produces no visible changes.
76Note (2) The AeroFoil object properties are only visible on the FreeCAD
77software version 0.19. On older versions, you will be shown
78a warning on the console.
79Note (3) The single underscore prefix (e.g. _name) denotes a private
80function or a private variable.
81Note (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.)
85Note (5) The dialog boxes and their corresponding UI elements may be
86labelled differently. Here is a map:
87d1 = AeroFoil_Initial_Dialog
88d2a = AeroFoil_NACA4Digit_Dialog
89d2b = AeroFoil_NACA5Digit_Dialog
90d2c = AeroFoil_CurvesInput_Dialog
91d2d = AeroFoil_PointsInput_Dialog
92d2d1a = AeroFoil_DATInput_Dialog
93d2d1b = AeroFoil_CSVInput_Dialog
94dcd2 = AeroFoil_FileLoad_Dialog
95d3 = AeroFoil_Final_Dialog
96mfb = AeroFoil_Math_Functions_Box
97Read the DocString of the '_setDialogIndex' method in the
98'AeroFoilDialog' class to know more about its implementation.
99Note (6) The global variable 'dialogIndex' is listed in the exact order
100as 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
121import FreeCAD as app
122import FreeCADGui as gui
123import Sketcher, Part, Draft
124from pathlib import Path
125import PySide
126from PySide import QtGui, QtCore
127from PySide.QtGui import *
128from PySide.QtCore import *
129import time, math, csv, re
130
131###########################################################################
132###---------------------------------------------------------------------###
133### AEROFOIL MACRO CALLS - Top (check Bottom) ###
134###---------------------------------------------------------------------###
135###
136def AeroFoil_generate(obj): ###
137"""
138The 'AeroFoil_generate' function generates the 2D curves
139and shapes, assigns them properties, and creates
140multiples of created curves and shapes.
141
142Arguments
143----------
144obj: An instance of the 'AeroFoilDialog' object.
145
146Return
147----------
148True: The airfoil curves/shapes were generated
149successfully.
150False: The airfoil curves/shapes were NOT generated
151successfully.
152""" ###
153n_ = (obj.multi * (obj.multiParam - 1)) + 1 ###
154AeroFoil_object = AeroFoil(obj) ###
155if AeroFoil_object.create(): ###
156AeroFoil_object.characterize() ###
157else: ###
158return False ###
159AeroFoil_object.copy(n_) ###
160return True ###
161###
162###---------------------------------------------------------------------###
163### AEROFOIL MACRO CALLS - Top (check Bottom) ###
164###---------------------------------------------------------------------###
165###########################################################################
166
167
168
169# Constant Variables
170# ------------------------------------------------------------------------------------------------
171
172MAX_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.
176MAX_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.
180MIN_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.
184NACA_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
187NACA_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
190FUNCTIONSARRAY = [
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.
213UNITSCONVERSION = [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.
218MIN_CHORD_LENGTH_MM = 1
219# It is the minimum airfoil chord length input below which a warning is displayed.
220MACRO_DIR = app.getUserMacroDir(True) + "/AeroFoil_UI_Files/AeroFoil_"
221# It is the user's macro directory
222
223
224
225# Global Variables
226# ------------------------------------------------------------------------------------------------
227
228dialogIndex = [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.
237doc = app.activeDocument()
238
239
240
241# Main Function Class
242# ------------------------------------------------------------------------------------------------
243
244class AeroFoil:
245
246"""
247The 'AeroFoil' class is the AeroFoil object itself. It is a non-interactive
248console-based class that is responsible for physical creation of the custom
2492D airfoil curves/shapes. The gathered user input data is used for the process.
250
251Functions include:
252__init__
253copy
254characterize
255create
256_create_tempX_var
257_createSketcherPolyLine
258_createSketcherBSpline
259"""
260
261def __init__(self, obj):
262"""
263This function initializes of the 'AeroFoil' class.
264This function transfers the user input data through the 'obj' variable.
265
266Arguments
267----------
268obj: An instance of the 'AeroFoilDialog' object.
269"""
270global UNITSCONVERSION
271self.profileName = generateName("AeroFoil")
272self.chord = obj.chordLength * UNITSCONVERSION[obj.chordUnits]
273# 'designtype' is used as a container for a set of relevant variables
274self.designtype = [obj.workbench, obj.curveType]
275self.designclosed = obj.closed_
276self.designsplit = obj.splitCurve
277self.designsplitmode = obj.splitCurveMode
278self.defaultEndPoints = obj.defaultEndPoints
279# 'midIndices' is used as a container for a set of relevant variables
280self.midIndices = [obj.midIndex1, obj.midIndex2]
281self.points = []
282self.points.append(obj.pointsX)
283self.points.append(obj.pointsY)
284self.n_units = len(self.points[0])
285self.designprogressbar = obj.progressBar_
286# 'objRefData' is used as a container for a set of relevant variables
287self.objRefData = [
288obj.airfoilType,
289obj.airfoil4DNumber,
290obj.airfoil5DNumber,
291obj.airfoilProfileType,
292obj.importFrom,
293]
294
295def copy(self, quantity_):
296"""
297This function creates multiple copies of the created airfoil curves/shapes.
298
299Arguments
300----------
301quantity_: (Integer) Number of copies required.
302"""
303if quantity_ != 1:
304refNum = int(re.findall(r"\d+", self.profileName)[0])
305gui.Selection.clearSelection()
306if self.designsplit:
307gui.Selection.addSelection(
308doc.getObjectsByLabel(self.profileName + "_Upper")[0]
309)
310gui.Selection.addSelection(
311doc.getObjectsByLabel(self.profileName + "_Lower")[0]
312)
313else:
314gui.Selection.addSelection(doc.getObjectsByLabel(self.profileName)[0])
315gui.runCommand("Std_Copy")
316for i in range(quantity_ - 1):
317gui.runCommand("Std_Paste")
318if self.designsplit:
319doc.getObjectsByLabel(
320"AeroFoil_" + str(refNum) + "_Upper" + str(i + 1).zfill(3)
321)[0].Label = ("AeroFoil_" + str(refNum + i + 1) + "_Upper")
322doc.getObjectsByLabel(
323"AeroFoil_" + str(refNum) + "_Lower" + str(i + 1).zfill(3)
324)[0].Label = ("AeroFoil_" + str(refNum + i + 1) + "_Lower")
325else:
326doc.getObjectsByLabel("AeroFoil_" + str(refNum + i + 1).zfill(3))[
3270
328].Label = "AeroFoil_" + str(refNum + i + 1)
329if self.designprogressbar.value() < 99:
330self.designprogressbar.setValue(
331round(75 + (((i + 1) / (4 * quantity_)) * 100))
332)
333self.designprogressbar.setValue(100)
334time.sleep(1)
335
336def characterize(self):
337"""
338This function adds external (read-only) properties to an instance of the AeroFoil object.
339The user input data has been obtained from the '__init__' module of this class.
340
341Properties
342---------------
343Airfoil Type: Options - NACA 4 Digit, NACA 5 Digit, Custom Curves (Symmetric or
344Asymmetric), or Custom Points (DAT or CSV)
345Design Curve Type: Options - Unsplit or Open Split or Open Split (End Points Fixed) or
346Closed Split or Closed Split (End Points Fixed) |
347Polygon or BSpline | Draft or Sketch or Surface
348Airfoil Chord Length: Length in millimetres (mm)
349Number of Points : Number of airfoil points
350"""
351try:
352objRef = doc.getObjectsByLabel(self.profileName)[0]
353objRef.addProperty("App::PropertyString", "AirfoilType", "", "", 1)
354objRef.addProperty("App::PropertyLength", "AirfoilChordLength", "", "", 1)
355objRef.addProperty("App::PropertyString", "DesignCurveType", "", "", 1)
356objRef.addProperty("App::PropertyInteger", "NumberOfPoints", "", "", 1)
357tempStr = ""
358# Refer to the '__init__' method of this class for more info. on 'objRefData'
359if self.objRefData[0] == 1:
360tempStr = "NACA - " + self.objRefData[1]
361elif self.objRefData[0] == 2:
362tempStr = "NACA - " + self.objRefData[2]
363elif self.objRefData[0] == 3 and self.objRefData[3] == 1:
364tempStr = "Custom Curves (Symmetric)"
365elif self.objRefData[0] == 3 and self.objRefData[3] == 2:
366tempStr = "Custom Curves (Asymmetric)"
367elif self.objRefData[0] == 4 and self.objRefData[4] == 1:
368tempStr = "Custom Points (DAT)"
369elif self.objRefData[0] == 4 and self.objRefData[4] == 2:
370tempStr = "Custom Points (CSV)"
371objRef.AirfoilType = tempStr
372objRef.AirfoilChordLength = str(self.chord) + "mm"
373tempStr = ""
374if self.designsplit:
375if self.designsplitmode == 1:
376tempStr += "Open Split "
377else:
378tempStr += "Closed Split "
379tempStr += "(End Points Fixed) " if self.defaultEndPoints else ""
380else:
381tempStr += "Unsplit "
382tempStr += "Polygon " if self.designtype[1] == 1 else "BSpline "
383if self.designclosed:
384tempStr += "Surface"
385elif self.designtype[0] == 1:
386tempStr += "Sketch"
387elif self.designtype[0] == 2:
388tempStr += "Draft"
389objRef.DesignCurveType = tempStr
390objRef.NumberOfPoints = self.n_units - 1
391except Exception:
392print(
393"\nCannot generate AeroFoil properties in FreeCAD version <0.19 !\n"
394)
395app.Console.PrintWarning(
396"\nCannot generate AeroFoil properties in FreeCAD version <0.19 !\n"
397)
398
399def create(self):
400"""
401This function is responsible for physical creation of the custom 2D airfoil curves/shapes.
402The user input data has been obtained from the '__init__' module of this class.
403This function utilizes the 'try ... except' method to avoid any complications
404on the user's end. A simple error message is displayed instead.
405"""
406# Generating Polygons and BSplines
407try:
408if self.designtype[0] == 2:
409if self.designtype[1] == 1:
410if self.designsplit:
411self._createSketcherPolyLine(
412self.profileName + "_Upper", 0, self.midIndices[0]
413)
414self._createSketcherPolyLine(
415self.profileName + "_Lower",
416self.midIndices[1],
417len(self.points[0]) - 1,
418)
419else:
420self._createSketcherPolyLine(
421self.profileName, 0, len(self.points[0]) - 1
422)
423elif self.designtype[1] == 2:
424if self.designsplit:
425self._createSketcherBSpline(
426self.profileName + "_Upper", 0, self.midIndices[0]
427)
428self._createSketcherBSpline(
429self.profileName + "_Lower",
430self.midIndices[1],
431len(self.points[0]) - 1,
432)
433else:
434self._createSketcherBSpline(
435self.profileName, 0, len(self.points[0]) - 1
436)
437elif self.designtype[0] == 1:
438tempXif, tempXm = 0, 0
439i_count, iteration = 0, 1
440if self.designsplit:
441iteration = 2
442while i_count < iteration:
443DWireOrBSpline_, pointsVector, draftCurveClosed = 0, [], True
444startIndex, endIndex = 0, len(self.points[0]) - 1
445draftOutputName = self.profileName
446n_ = len(self.points[0])
447if self.designsplit:
448draftCurveClosed = False
449startIndex, endIndex = (
450(0, self.midIndices[0])
451if i_count == 0
452else (self.midIndices[1], len(self.points[0]) - 1)
453)
454tempXif, tempXm, startIndex, endIndex = self._create_tempX_var(startIndex, endIndex)
455draftOutputName = (
456self.profileName + "_Upper"
457if i_count == 0
458else self.profileName + "_Lower"
459)
460n_ = endIndex - startIndex + 1
461if self.designsplit and (
462tempXif != self.points[0][startIndex]
463or self.points[1][startIndex] != 0
464):
465pointsVector.append(app.Vector(tempXif, 0, 0))
466for i in range(startIndex, endIndex + 1):
467pointsVector.append(
468app.Vector(self.points[0][i], 0, self.points[1][i])
469)
470if not self.designsplit:
471if self.designprogressbar.value() < 75:
472self.designprogressbar.setValue(
473round(50 + ((i / (4 * n_)) * 100))
474)
475elif self.designsplit and i_count == 0:
476if self.designprogressbar.value() < 62.5:
477self.designprogressbar.setValue(
478round(50 + ((i / (8 * n_)) * 100))
479)
480else:
481if self.designprogressbar.value() < 75:
482self.designprogressbar.setValue(
483round(62.5 + ((i / (8 * n_)) * 100))
484)
485if self.designsplit and (
486tempXm != self.points[0][endIndex]
487or self.points[1][endIndex] != 0
488):
489pointsVector.append(app.Vector(tempXm, 0, 0))
490if self.designtype[1] == 1:
491# if self.designclosed is determined by face=self.designclosed
492DWireOrBSpline_ = Draft.makeWire(
493pointsVector,
494closed=draftCurveClosed,
495placement=None,
496face=self.designclosed,
497support=None,
498)
499elif self.designtype[1] == 2:
500# if self.designclosed is determined by face=self.designclosed
501DWireOrBSpline_ = Draft.makeBSpline(
502pointsVector,
503closed=draftCurveClosed,
504placement=None,
505face=self.designclosed,
506support=None,
507)
508if self.designsplit and self.designsplitmode == 2:
509pointsVector_aux = [pointsVector[0], pointsVector[-1]]
510DWire_aux = Draft.makeWire(
511pointsVector_aux,
512closed=False,
513placement=None,
514face=False,
515support=None,
516)
517addList, deleteList = Draft.upgrade(
518[DWireOrBSpline_, DWire_aux], delete=True, force=None
519)
520doc.recompute()
521if self.designsplit and self.designsplitmode == 2:
522doc.getObject(addList[0].Name).Label = draftOutputName
523else:
524shape_ = doc.addObject("Part::Feature", draftOutputName)
525shape_.Shape = DWireOrBSpline_.Shape
526doc.removeObject(DWireOrBSpline_.Label)
527i_count += 1
528except Exception:
529# Unexpected Error Occurred while Processing
530try:
531doc.removeObject(self.profileName)
532doc.removeObject(self.profileName + "_Upper")
533doc.removeObject(self.profileName + "_Lower")
534except Exception:
535pass
536doc.recompute()
537return False
538doc.recompute()
539gui.activeDocument().activeView().viewFront()
540gui.SendMsgToActiveView("ViewFit")
541return True
542
543def _create_tempX_var(self, startIndex, endIndex):
544"""
545This function determines the start and end points of a 'split' curve.
546This function generates additional start and end points if
547their existing counterparts do not have a Y-axis value of zero (0).
548This function calls the linear interpolation method to achieve the same.
549
550Arguments
551----------
552startIndex: (Integer) Zero-based index of the data points list to begin from.
553endIndex: (Integer) Zero-based index of the data points list to end to.
554"""
555posVar, negVar, startIndex_new, endIndex_new = 0, 0, startIndex, endIndex
556for i1 in range(startIndex, startIndex + int(0.1 * (endIndex - startIndex))):
557if self.points[1][i1] >= 0:
558posVar += 1
559else:
560negVar += 1
561for j1 in range(startIndex, startIndex + int(0.1 * (endIndex - startIndex))):
562if posVar >= negVar:
563if self.points[1][j1] >= 0:
564startIndex_new = j1
565break
566else:
567if self.points[1][j1] < 0:
568startIndex_new = j1
569break
570for i2 in range(endIndex, endIndex - int(0.1 * (endIndex - startIndex)), -1):
571if self.points[1][i2] >= 0:
572posVar += 1
573else:
574negVar += 1
575for j2 in range(endIndex, endIndex - int(0.1 * (endIndex - startIndex)), -1):
576if posVar >= negVar:
577if self.points[1][j2] >= 0:
578endIndex_new = j2
579break
580else:
581if self.points[1][j2] < 0:
582endIndex_new = j2
583break
584if self.defaultEndPoints:
585tempXif = round(self.points[0][startIndex_new], 2)
586tempXm = round(self.points[0][endIndex_new], 2)
587self.points[0][startIndex_new] = round(self.points[0][startIndex_new], 2)
588self.points[0][endIndex_new] = round(self.points[0][endIndex_new], 2)
589else:
590if startIndex_new == 0:
591xi_, yi_ = self.points[0][0], self.points[1][0]
592xf_, yf_ = (
593(self.points[0][-1], self.points[1][-1])
594if (
595self.points[0][0] != self.points[0][-1]
596or self.points[1][0] != self.points[1][-1]
597)
598else (self.points[0][-2], self.points[1][-2])
599)
600x1m_, y1m_ = self.points[0][endIndex_new], self.points[1][endIndex_new]
601x2m_, y2m_ = (
602(self.points[0][endIndex_new + 1], self.points[1][endIndex_new + 1])
603if self.points[0][endIndex_new] != self.points[0][endIndex_new + 1]
604or self.points[1][endIndex_new] != self.points[1][endIndex_new + 1]
605else (self.points[0][endIndex_new + 2], self.points[1][endIndex_new + 2])
606)
607else:
608xi_, yi_ = self.points[0][startIndex_new], self.points[1][startIndex_new]
609xf_, yf_ = (
610(self.points[0][startIndex_new - 1], self.points[1][startIndex_new - 1])
611if (
612self.points[0][startIndex_new] != self.points[0][startIndex_new - 1]
613or self.points[1][startIndex_new] != self.points[1][startIndex_new - 1]
614)
615else (
616self.points[0][startIndex_new - 2],
617self.points[1][startIndex_new - 2],
618)
619)
620x1m_, y1m_ = self.points[0][-1], self.points[1][-1]
621x2m_, y2m_ = (
622(self.points[0][0], self.points[1][0])
623if self.points[0][-1] != self.points[0][0]
624or self.points[1][-1] != self.points[1][0]
625else (self.points[0][1], self.points[1][1])
626)
627tempXif = interpolateNum(yi_, xi_, yf_, xf_, 0)
628tempXm = interpolateNum(y1m_, x1m_, y2m_, x2m_, 0)
629return tempXif, tempXm, startIndex_new, endIndex_new
630
631def _createSketcherPolyLine(self, sketchName, startIndex, endIndex):
632"""
633This function creates the Sketcher Workbench based PolyLine curve.
634
635Arguments
636----------
637sketchName: (String) The name/label of the generated sketch.
638startIndex: (Integer) Zero-based index of the data points list to begin from.
639endIndex: (Integer) Zero-based index of the data points list to end to.
640"""
641# Sketch Preparation and Naming
642sketchObj = doc.addObject("Sketcher::SketchObject", sketchName)
643sketchObj.Placement = app.Placement(
644app.Vector(0.000000, 0.000000, 0.000000),
645app.Rotation(-0.707107, 0.000000, 0.000000, -0.707107),
646)
647sketchObj.MapMode = "Deactivated"
648# Sketch PolyLine Procedure
649count, tempBool = 0, False
650tempXif, tempXm = 0, 0
651if self.designsplit:
652tempXif, tempXm, startIndex, endIndex = self._create_tempX_var(startIndex, endIndex)
653if (
654tempXif == self.points[0][startIndex]
655and self.points[1][startIndex] == 0
656):
657tempBool = False
658else:
659tempBool = True
660sketchObj.addGeometry(
661Part.LineSegment(
662app.Vector(tempXif, 0, 0),
663app.Vector(
664self.points[0][startIndex], self.points[1][startIndex], 0
665),
666),
667False,
668)
669constraintIndex = sketchObj.addConstraint(
670Sketcher.Constraint("DistanceX", 0, 1, tempXif)
671)
672sketchObj.setDatum(
673constraintIndex, app.Units.Quantity(str(tempXif) + "mm")
674)
675constraintIndex = sketchObj.addConstraint(
676Sketcher.Constraint("DistanceY", 0, 1, 0)
677)
678sketchObj.setDatum(constraintIndex, app.Units.Quantity(str(0) + "mm"))
679constraintIndex = sketchObj.addConstraint(
680Sketcher.Constraint("DistanceX", 0, 2, self.points[0][startIndex])
681)
682sketchObj.setDatum(
683constraintIndex,
684app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
685)
686constraintIndex = sketchObj.addConstraint(
687Sketcher.Constraint("DistanceY", 0, 2, self.points[1][startIndex])
688)
689sketchObj.setDatum(
690constraintIndex,
691app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
692)
693sketchObj.addGeometry(
694Part.LineSegment(
695app.Vector(self.points[0][startIndex], self.points[1][startIndex], 0),
696app.Vector(
697self.points[0][startIndex + 1], self.points[1][startIndex + 1], 0
698),
699),
700False,
701)
702if tempBool:
703sketchObj.addConstraint(Sketcher.Constraint("Coincident", 0, 2, 1, 1))
704count = 2
705else:
706count = 1
707n_ = endIndex - startIndex + 1
708for i in range(startIndex + 1, endIndex):
709sketchObj.addGeometry(
710Part.LineSegment(
711app.Vector(self.points[0][i], self.points[1][i], 0),
712app.Vector(self.points[0][i + 1], self.points[1][i + 1], 0),
713),
714False,
715)
716sketchObj.addConstraint(
717Sketcher.Constraint("Coincident", count - 1, 2, count, 1)
718)
719if self.designprogressbar.value() < 62.5:
720self.designprogressbar.setValue(round(50 + ((count / (8 * n_)) * 100)))
721count += 1
722tempVar1, tempVar2 = self.points[0][endIndex], self.points[1][endIndex]
723if self.designsplit and (
724tempXm != self.points[0][endIndex] or self.points[1][endIndex] != 0
725):
726sketchObj.addGeometry(
727Part.LineSegment(
728app.Vector(self.points[0][endIndex], self.points[1][endIndex], 0),
729app.Vector(tempXm, 0, 0),
730),
731False,
732)
733sketchObj.addConstraint(
734Sketcher.Constraint("Coincident", count - 1, 2, count, 1)
735)
736tempVar1, tempVar2 = tempXm, 0
737count += 1
738if self.designsplitmode == 2:
739sketchObj.addGeometry(
740Part.LineSegment(
741app.Vector(tempVar1, tempVar2, 0),
742app.Vector(
743self.points[0][startIndex], self.points[1][startIndex], 0
744),
745),
746False,
747)
748sketchObj.addConstraint(
749Sketcher.Constraint("Coincident", count - 1, 2, count, 1)
750)
751sketchObj.addConstraint(Sketcher.Constraint("Coincident", count, 2, 0, 1))
752if self.designsplit:
753# Here, 'count; denotes the sketcher line number
754count = 1 if tempBool else 0
755else:
756sketchObj.addConstraint(
757Sketcher.Constraint("Coincident", count - 1, 2, 0, 1)
758)
759count = 0
760for j in range(startIndex, endIndex - 1):
761constraintIndex = sketchObj.addConstraint(
762Sketcher.Constraint("DistanceX", count, 2, self.points[0][j + 1])
763)
764sketchObj.setDatum(
765constraintIndex, app.Units.Quantity(str(self.points[0][j + 1]) + "mm")
766)
767constraintIndex = sketchObj.addConstraint(
768Sketcher.Constraint("DistanceY", count, 2, self.points[1][j + 1])
769)
770sketchObj.setDatum(
771constraintIndex, app.Units.Quantity(str(self.points[1][j + 1]) + "mm")
772)
773if self.designprogressbar.value() < 75:
774self.designprogressbar.setValue(
775round(62.5 + ((count / (8 * n_)) * 100))
776)
777count += 1
778if self.designsplit:
779constraintIndex = sketchObj.addConstraint(
780Sketcher.Constraint("DistanceX", count, 2, self.points[0][endIndex])
781)
782sketchObj.setDatum(
783constraintIndex,
784app.Units.Quantity(str(self.points[0][endIndex]) + "mm"),
785)
786constraintIndex = sketchObj.addConstraint(
787Sketcher.Constraint("DistanceY", count, 2, self.points[1][endIndex])
788)
789sketchObj.setDatum(
790constraintIndex,
791app.Units.Quantity(str(self.points[1][endIndex]) + "mm"),
792)
793count += 1
794if tempXm != self.points[0][endIndex] or self.points[1][endIndex] != 0:
795constraintIndex = sketchObj.addConstraint(
796Sketcher.Constraint("DistanceX", count, 2, tempXm)
797)
798sketchObj.setDatum(
799constraintIndex, app.Units.Quantity(str(tempXm) + "mm")
800)
801constraintIndex = sketchObj.addConstraint(
802Sketcher.Constraint("DistanceY", count, 2, 0)
803)
804sketchObj.setDatum(constraintIndex, app.Units.Quantity(str(0) + "mm"))
805count += 1
806if not tempBool and self.designsplitmode == 2:
807constraintIndex = sketchObj.addConstraint(
808Sketcher.Constraint(
809"DistanceX", count, 2, self.points[0][startIndex]
810)
811)
812sketchObj.setDatum(
813constraintIndex,
814app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
815)
816constraintIndex = sketchObj.addConstraint(
817Sketcher.Constraint(
818"DistanceY", count, 2, self.points[1][startIndex]
819)
820)
821sketchObj.setDatum(
822constraintIndex,
823app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
824)
825count += 1
826if not tempBool and self.designsplitmode != 2:
827constraintIndex = sketchObj.addConstraint(
828Sketcher.Constraint("DistanceX", 0, 1, self.points[0][startIndex])
829)
830sketchObj.setDatum(
831constraintIndex,
832app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
833)
834constraintIndex = sketchObj.addConstraint(
835Sketcher.Constraint("DistanceY", 0, 1, self.points[1][startIndex])
836)
837sketchObj.setDatum(
838constraintIndex,
839app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
840)
841
842def _createSketcherBSpline(self, sketchName, startIndex, endIndex):
843"""
844This function creates the Sketcher Workbench based BSpline curve.
845
846Arguments
847----------
848sketchName: (String) The name/label of the generated sketch.
849startIndex: (Integer) Zero-based index of the data points list to begin from.
850endIndex: (Integer) Zero-based index of the data points list to end to.
851"""
852# Sketch Preparation and Naming
853sketchObj = doc.addObject("Sketcher::SketchObject", sketchName)
854sketchObj.Placement = app.Placement(
855app.Vector(0.000000, 0.000000, 0.000000),
856app.Rotation(-0.707107, 0.000000, 0.000000, -0.707107),
857)
858sketchObj.MapMode = "Deactivated"
859# Sketch BSpline Procedure
860count, points, conList = 0, [], []
861n_ = endIndex - startIndex + 1
862isXif, isXm = False, False
863tempXif, tempXm = 0, 0
864if self.designsplit:
865tempXif, tempXm, startIndex, endIndex = self._create_tempX_var(startIndex, endIndex)
866if self.designsplit and (
867tempXif != self.points[0][startIndex] or self.points[1][startIndex] != 0
868):
869sketchObj.addGeometry(
870Part.Circle(app.Vector(tempXif, 0, 0), app.Vector(0, 0, 1), 10), True
871)
872sketchObj.addGeometry(
873Part.Circle(
874app.Vector(
875self.points[0][startIndex], self.points[1][startIndex], 0
876),
877app.Vector(0, 0, 1),
87810,
879),
880True,
881)
882sketchObj.addConstraint(Sketcher.Constraint("Radius", 0, 1.000000))
883sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, 1))
884sketchObj.addGeometry(
885Part.Circle(
886app.Vector(
887self.points[0][startIndex + 1],
888self.points[1][startIndex + 1],
8890,
890),
891app.Vector(0, 0, 1),
89210,
893),
894True,
895)
896sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, 2))
897isXif = True
898count = 3
899else:
900sketchObj.addGeometry(
901Part.Circle(
902app.Vector(
903self.points[0][startIndex], self.points[1][startIndex], 0
904),
905app.Vector(0, 0, 1),
90610,
907),
908True,
909)
910sketchObj.addGeometry(
911Part.Circle(
912app.Vector(
913self.points[0][startIndex + 1],
914self.points[1][startIndex + 1],
9150,
916),
917app.Vector(0, 0, 1),
91810,
919),
920True,
921)
922sketchObj.addConstraint(Sketcher.Constraint("Radius", 0, 1.000000))
923sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, 1))
924count = 2
925for h in range(startIndex + 2, endIndex + 1):
926sketchObj.addGeometry(
927Part.Circle(
928app.Vector(self.points[0][h], self.points[1][h], 0),
929app.Vector(0, 0, 1),
93010,
931),
932True,
933)
934sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, count))
935if self.designprogressbar.value() < 60:
936self.designprogressbar.setValue(round(50 + ((h / (10 * n_)) * 100)))
937count += 1
938if self.designsplit and (
939tempXm != self.points[0][endIndex] or self.points[1][endIndex] != 0
940):
941sketchObj.addGeometry(
942Part.Circle(app.Vector(tempXm, 0, 0), app.Vector(0, 0, 1), 10), True
943)
944sketchObj.addConstraint(Sketcher.Constraint("Equal", 0, count))
945isXm = True
946count += 1
947tempVal = count
948if isXif:
949points.append(app.Vector(tempXif, 0))
950conList.append(
951Sketcher.Constraint(
952"InternalAlignment:Sketcher::BSplineControlPoint", 0, 3, tempVal, 0
953)
954)
955count = 0 + isXif
956for i in range(startIndex, endIndex + 1):
957points.append(app.Vector(self.points[0][i], self.points[1][i]))
958conList.append(
959Sketcher.Constraint(
960"InternalAlignment:Sketcher::BSplineControlPoint",
961count,
9623,
963tempVal,
964count,
965)
966)
967if self.designprogressbar.value() < 70:
968self.designprogressbar.setValue(round(60 + ((i / (10 * n_)) * 100)))
969count += 1
970if isXm:
971points.append(app.Vector(tempXm, 0))
972conList.append(
973Sketcher.Constraint(
974"InternalAlignment:Sketcher::BSplineControlPoint",
975count,
9763,
977tempVal,
978count,
979)
980)
981count += 1
982if self.designsplit:
983sketchObj.addGeometry(
984Part.BSplineCurve(points, None, None, False, 3, None, False), False
985)
986else:
987sketchObj.addGeometry(
988Part.BSplineCurve(points, None, None, True, 3, None, False), False
989)
990sketchObj.addConstraint(conList)
991sketchObj.exposeInternalGeometry(tempVal)
992tempVar1x = tempXif if isXif else self.points[0][startIndex]
993tempVar1y = 0 if isXif else self.points[1][startIndex]
994tempVar1bx = (
995self.points[0][startIndex] if isXif else self.points[0][startIndex + 1]
996)
997tempVar1by = (
998self.points[1][startIndex] if isXif else self.points[1][startIndex + 1]
999)
1000tempVar2x = tempXm if isXm else self.points[0][endIndex]
1001tempVar2y = 0 if isXm else self.points[1][endIndex]
1002count, startFrom = 0, 0
1003if isXif:
1004constraintIndex = sketchObj.addConstraint(
1005Sketcher.Constraint("DistanceX", -1, 1, tempVal, 1, tempVar1x)
1006)
1007sketchObj.setDatum(
1008constraintIndex, app.Units.Quantity(str(tempVar1x) + "mm")
1009)
1010constraintIndex = sketchObj.addConstraint(
1011Sketcher.Constraint("DistanceY", -1, 1, tempVal, 1, tempVar1y)
1012)
1013sketchObj.setDatum(
1014constraintIndex, app.Units.Quantity(str(tempVar1y) + "mm")
1015)
1016constraintIndex = sketchObj.addConstraint(
1017Sketcher.Constraint("DistanceX", -1, 1, count + 1, 3, tempVar1bx)
1018)
1019sketchObj.setDatum(
1020constraintIndex, app.Units.Quantity(str(tempVar1bx) + "mm")
1021)
1022constraintIndex = sketchObj.addConstraint(
1023Sketcher.Constraint("DistanceY", -1, 1, count + 1, 3, tempVar1by)
1024)
1025sketchObj.setDatum(
1026constraintIndex, app.Units.Quantity(str(tempVar1by) + "mm")
1027)
1028startFrom = startIndex + 1
1029count += 2
1030else:
1031startFrom = startIndex
1032if self.designsplit:
1033endTo = endIndex
1034if not isXif:
1035constraintIndex = sketchObj.addConstraint(
1036Sketcher.Constraint(
1037"DistanceX", -1, 1, tempVal, 1, self.points[0][startIndex]
1038)
1039)
1040sketchObj.setDatum(
1041constraintIndex,
1042app.Units.Quantity(str(self.points[0][startIndex]) + "mm"),
1043)
1044constraintIndex = sketchObj.addConstraint(
1045Sketcher.Constraint(
1046"DistanceY", -1, 1, tempVal, 1, self.points[1][startIndex]
1047)
1048)
1049sketchObj.setDatum(
1050constraintIndex,
1051app.Units.Quantity(str(self.points[1][startIndex]) + "mm"),
1052)
1053startFrom = startIndex + 1
1054count += 1
1055else:
1056endTo = endIndex + 1
1057for j in range(startFrom, endTo):
1058constraintIndex = sketchObj.addConstraint(
1059Sketcher.Constraint("DistanceX", -1, 1, count, 3, self.points[0][j])
1060)
1061sketchObj.setDatum(
1062constraintIndex, app.Units.Quantity(str(self.points[0][j]) + "mm")
1063)
1064constraintIndex = sketchObj.addConstraint(
1065Sketcher.Constraint("DistanceY", -1, 1, count, 3, self.points[1][j])
1066)
1067sketchObj.setDatum(
1068constraintIndex, app.Units.Quantity(str(self.points[1][j]) + "mm")
1069)
1070if self.designprogressbar.value() < 75:
1071self.designprogressbar.setValue(round(70 + ((j / (20 * n_)) * 100)))
1072count += 1
1073if self.designsplit:
1074tempVar1, tempVar2 = 0, 0
1075if isXm:
1076tempVar1, tempVar2 = count, 3
1077else:
1078tempVar1, tempVar2 = tempVal, 2
1079constraintIndex = sketchObj.addConstraint(
1080Sketcher.Constraint(
1081"DistanceX", -1, 1, tempVar1, tempVar2, self.points[0][endIndex]
1082)
1083)
1084sketchObj.setDatum(
1085constraintIndex,
1086app.Units.Quantity(str(self.points[0][endIndex]) + "mm"),
1087)
1088constraintIndex = sketchObj.addConstraint(
1089Sketcher.Constraint(
1090"DistanceY", -1, 1, tempVar1, tempVar2, self.points[1][endIndex]
1091)
1092)
1093sketchObj.setDatum(
1094constraintIndex,
1095app.Units.Quantity(str(self.points[1][endIndex]) + "mm"),
1096)
1097count += 1
1098if isXm:
1099constraintIndex = sketchObj.addConstraint(
1100Sketcher.Constraint("DistanceX", -1, 1, tempVal, 2, tempVar2x)
1101)
1102sketchObj.setDatum(
1103constraintIndex, app.Units.Quantity(str(tempVar2x) + "mm")
1104)
1105constraintIndex = sketchObj.addConstraint(
1106Sketcher.Constraint("DistanceY", -1, 1, tempVal, 2, tempVar2y)
1107)
1108sketchObj.setDatum(
1109constraintIndex, app.Units.Quantity(str(tempVar2y) + "mm")
1110)
1111if self.designsplitmode == 2:
1112sketchObj.addGeometry(
1113Part.LineSegment(
1114app.Vector(tempVar1x, tempVar1y, 0),
1115app.Vector(tempVar2x, tempVar2y, 0),
1116),
1117False,
1118)
1119sketchObj.addConstraint(
1120Sketcher.Constraint("Coincident", (tempVal * 2) - 1, 2, tempVal, 1)
1121)
1122sketchObj.addConstraint(
1123Sketcher.Constraint("Coincident", (tempVal * 2) - 1, 1, tempVal, 2)
1124)
1125
1126
1127
1128# Sub-main Function Class
1129# ------------------------------------------------------------------------------------------------
1130
1131class AeroFoilDialog:
1132
1133"""
1134The 'AeroFoilDialog' class itself is the AeroFoil GUI interface.
1135It contains dialog boxes that go to and fro, from the initial input dialog box
1136to the final creation dialog box. This class accumulates the user's inputs on
1137how the airfoils are to be created: their dimensions, their design type,
1138their resolution, their multiplicity, etc.
1139
1140This class is also responsible for validating the input choices and values,
1141and notifying or warning the user accordingly.
1142
1143Functions 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
1164def __init__(self):
1165"""
1166This function initializes of the 'AeroFoilDialog' class.
1167"""
1168# Variables Pertaining to the Entire Program
1169(
1170self.canMirror,
1171self.functionElements,
1172self.functionElements_Pos,
1173self.pointsX,
1174self.pointsY,
1175) = (True, [], [], [], [])
1176self.tempStart, self.tempEnd = 0, 0
1177self.midIndex1, self.midIndex2 = 0, 0
1178# Variables Pertaining to AeroFoil_Initial_Dialog
1179self.airfoilType = 1
1180# Variables Pertaining to AeroFoil_NACA4Digit_Dialog
1181self.airfoil4DNumber = ""
1182# Variables Pertaining to AeroFoil_NACA5Digit_Dialog
1183self.airfoil5DNumber = ""
1184# Variables Pertaining to AeroFoil_CurvesInput_Dialog
1185self.airfoilProfileType, self.topCurveFunction, self.bottomCurveFunction = (
11861,
1187"",
1188"",
1189)
1190# Variables Pertaining to AeroFoil_PointsInput_Dialog
1191self.importFrom, self.mirroring = 1, False
1192# Variables Pertaining to AeroFoil_DATInput_Dialog
1193self.lineStart, self.lineEnd, self.decimalType = 0, 0, 1
1194# Variables Pertaining to AeroFoil_CSVInput_Dialog
1195self.col1, self.col2, self.row1, self.row2 = 1, 2, 0, 0
1196# Variables Pertaining to AeroFoil_FileLoad_Dialog
1197(
1198self.loadSuccess,
1199self.loadedFilePath,
1200self.loadedFile,
1201self.loadedFileContents,
1202) = (False, [""], "", [])
1203# Variables Pertaining to AeroFoil_Final_Dialog
1204(
1205self.refine,
1206self.refineParam,
1207self.multi,
1208self.multiParam,
1209self.closed_,
1210self.workbench,
1211self.curveType,
1212self.maxRefineParamWarned,
1213self.maxQuantityParamWarned,
1214self.chordLength,
1215self.chordUnits,
1216self.progressBar_,
1217self.splitCurve,
1218self.splitCurveMode,
1219self.defaultEndPoints,
1220) = (False, 2, False, 2, False, 1, 1, False, False, 0, 1, 0, False, 1, False)
1221# Actual Functions begin here
1222dialogIndex[0] = 1
1223self._createDialogs()
1224
1225def _d(self, qcode, objStr):
1226"""
1227This function is a shortcut for obtaining a dialog's UI element.
1228
1229Arguments
1230----------
1231qcode: A distinct code for each UI element as listed below.
1232objStr: The UI element's name/label.
1233"""
1234qkey = [
12350,
1236QPushButton,
1237QLineEdit,
1238QComboBox,
1239QRadioButton,
1240QCheckBox,
1241QSpinBox,
1242QDoubleSpinBox,
1243QLabel,
1244QProgressBar,
1245]
1246# Elements 2 to 10 are the various UI elements employed in this macro.
1247# Ignore the 1st element; it's superfluous.
1248return self.dialog.findChild(qkey[qcode], objStr)
1249
1250def _createDialogs(self):
1251"""
1252This function generates and displays the AeroFoil GUI interface, that is,
1253it creates all the dialog boxes for user interaction and input.
1254"""
1255global MACRO_DIR
1256global dialogIndex
1257if dialogIndex[0] == 1:
1258self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "Initial_Dialog.ui")
1259self._d(1, "af_d1_close_button").clicked.connect(lambda: self._close())
1260self._d(1, "af_d1_next_button").clicked.connect(lambda: self._next(1))
1261self._d(3, "af_d1_combo").setCurrentIndex(self.airfoilType - 1)
1262elif dialogIndex[0] == 2:
1263self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "NACA4Digit_Dialog.ui")
1264self._d(1, "af_d2a_next_button").clicked.connect(lambda: self._next(1))
1265self._d(1, "af_d2a_back_button").clicked.connect(lambda: self._next(-1))
1266self._d(2, "af_d2a_textbox").setText(self.airfoil4DNumber)
1267elif dialogIndex[0] == 3:
1268self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "NACA5Digit_Dialog.ui")
1269self._d(1, "af_d2b_next_button").clicked.connect(lambda: self._next(1))
1270self._d(1, "af_d2b_back_button").clicked.connect(lambda: self._next(-1))
1271self._d(2, "af_d2b_textbox").setText(self.airfoil5DNumber)
1272elif dialogIndex[0] == 4:
1273self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "CurvesInput_Dialog.ui")
1274self._d(2, "af_d2c_textbox_1").setText(self.topCurveFunction)
1275self._d(2, "af_d2c_textbox_2").setText(self.bottomCurveFunction)
1276self._d(4, "af_d2c_radio_1").toggled.connect(
1277lambda: self._af_d2c_radio_toggled()
1278)
1279if self.airfoilProfileType == 1:
1280self._d(4, "af_d2c_radio_1").setChecked(True)
1281else:
1282self._d(4, "af_d2c_radio_2").setChecked(True)
1283self._d(1, "af_d2c_list_button").clicked.connect(
1284lambda: self._functionsList()
1285)
1286self._d(1, "af_d2c_next_button").clicked.connect(lambda: self._next(1))
1287self._d(1, "af_d2c_back_button").clicked.connect(lambda: self._next(-1))
1288elif dialogIndex[0] == 5:
1289self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "PointsInput_Dialog.ui")
1290if self.importFrom == 1:
1291self._d(4, "af_d2d_radio_1").setChecked(True)
1292else:
1293self._d(4, "af_d2d_radio_2").setChecked(True)
1294if self.mirroring:
1295self._d(5, "af_d2d_checkbox").setChecked(True)
1296else:
1297self._d(5, "af_d2d_checkbox").setChecked(False)
1298self._d(1, "af_d2d_next_button").clicked.connect(lambda: self._next(1))
1299self._d(1, "af_d2d_back_button").clicked.connect(lambda: self._next(-1))
1300elif dialogIndex[0] == 6:
1301self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "DATInput_Dialog.ui")
1302self._d(6, "af_d2d1a_spinBox_1").setValue(self.tempStart)
1303self._d(6, "af_d2d1a_spinBox_2").setValue(self.tempEnd)
1304if self.decimalType == 1:
1305self._d(4, "af_d2d1a_radio_1").setChecked(True)
1306else:
1307self._d(4, "af_d2d1a_radio_2").setChecked(True)
1308self._d(1, "af_d2d1a_next_button").clicked.connect(lambda: self._next(1))
1309self._d(1, "af_d2d1a_back_button").clicked.connect(lambda: self._next(-1))
1310elif dialogIndex[0] == 7:
1311self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "CSVInput_Dialog.ui")
1312self._d(6, "af_d2d1b_spinBox_1").setValue(self.col1)
1313self._d(6, "af_d2d1b_spinBox_2").setValue(self.col2)
1314self._d(6, "af_d2d1b_spinBox_3").setValue(self.tempStart)
1315self._d(6, "af_d2d1b_spinBox_4").setValue(self.tempEnd)
1316if self.decimalType == 1:
1317self._d(4, "af_d2d1b_radio_1").setChecked(True)
1318else:
1319self._d(4, "af_d2d1b_radio_2").setChecked(True)
1320self._d(1, "af_d2d1b_next_button").clicked.connect(lambda: self._next(1))
1321self._d(1, "af_d2d1b_back_button").clicked.connect(lambda: self._next(-1))
1322elif dialogIndex[0] == 8:
1323self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "FileLoad_Dialog.ui")
1324self._d(2, "af_d2cd2_textbox").setText(self.loadedFilePath[0])
1325if self.loadedFilePath[0] == "":
1326self.loadSuccess = False
1327self._d(8, "af_d2cd2_label_2").setText("File Not Loaded")
1328self._d(8, "af_d2cd2_label_2").setStyleSheet("color: black;")
1329else:
1330if self.loadSuccess:
1331self._d(8, "af_d2cd2_label_2").setText("File Loaded Successfully")
1332self._d(8, "af_d2cd2_label_2").setStyleSheet("color: darkgreen;")
1333else:
1334self._d(8, "af_d2cd2_label_2").setText(
1335"File Loaded Un-successfully"
1336)
1337self._d(8, "af_d2cd2_label_2").setStyleSheet("color: crimson;")
1338self._d(1, "af_d2cd2_load_button").clicked.connect(lambda: self._loadFile())
1339self._d(1, "af_d2cd2_next_button").clicked.connect(lambda: self._next(1))
1340self._d(1, "af_d2cd2_back_button").clicked.connect(lambda: self._next(-1))
1341elif dialogIndex[0] == 9:
1342self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "Final_Dialog.ui")
1343if self.refine:
1344self._d(6, "af_d3_spinbox_1").setValue(self.refineParam)
1345self._d(6, "af_d3_spinbox_1").setEnabled(True)
1346self._d(5, "af_d3_checkbox_1").setChecked(True)
1347else:
1348self.refineParam = 2
1349self._d(6, "af_d3_spinbox_1").setValue(2)
1350self._d(6, "af_d3_spinbox_1").setEnabled(False)
1351self._d(5, "af_d3_checkbox_1").setChecked(False)
1352if self.multi:
1353self._d(6, "af_d3_spinbox_2").setValue(self.multiParam)
1354self._d(6, "af_d3_spinbox_2").setEnabled(True)
1355self._d(5, "af_d3_checkbox_2").setChecked(True)
1356else:
1357self.multiParam = 2
1358self._d(6, "af_d3_spinbox_2").setValue(2)
1359self._d(6, "af_d3_spinbox_2").setEnabled(False)
1360self._d(5, "af_d3_checkbox_2").setChecked(False)
1361if self.splitCurve:
1362self._d(5, "af_d3_checkbox_5").setChecked(False)
1363self._d(5, "af_d3_checkbox_5").setEnabled(False)
1364self._d(5, "af_d3_checkbox_6").setChecked(False)
1365self._d(5, "af_d3_checkbox_6").setEnabled(True)
1366if self.splitCurveMode == 1:
1367self._d(5, "af_d3_checkbox_3").setChecked(True)
1368self._d(5, "af_d3_checkbox_4").setChecked(False)
1369else:
1370self._d(5, "af_d3_checkbox_3").setChecked(False)
1371self._d(5, "af_d3_checkbox_4").setChecked(True)
1372self._af_d3_checkbox_5_toggled()
1373else:
1374self._d(5, "af_d3_checkbox_3").setChecked(False)
1375self._d(5, "af_d3_checkbox_4").setChecked(False)
1376self._d(5, "af_d3_checkbox_6").setChecked(False)
1377self._d(5, "af_d3_checkbox_6").setEnabled(False)
1378self._d(5, "af_d3_checkbox_5").setChecked(False)
1379self._d(5, "af_d3_checkbox_5").setEnabled(True)
1380if self.closed_:
1381self._d(5, "af_d3_checkbox_3").setEnabled(False)
1382self._d(5, "af_d3_checkbox_4").setEnabled(False)
1383self._d(5, "af_d3_checkbox_5").setChecked(True)
1384self._af_d3_checkbox_5_toggled()
1385else:
1386self._d(5, "af_d3_checkbox_5").setChecked(False)
1387self._d(4, "af_d3_radio_1").setEnabled(True)
1388self._d(4, "af_d3_radio_2").setEnabled(True)
1389if self.workbench == 1:
1390self._d(4, "af_d3_radio_1").setChecked(True)
1391self._d(4, "af_d3_radio_1a").setEnabled(True)
1392self._d(4, "af_d3_radio_1b").setEnabled(True)
1393self._d(4, "af_d3_radio_2a").setEnabled(False)
1394self._d(4, "af_d3_radio_2b").setEnabled(False)
1395if self.curveType == 1:
1396self._d(4, "af_d3_radio_1a").setChecked(True)
1397else:
1398self._d(4, "af_d3_radio_1b").setChecked(True)
1399else:
1400self._d(4, "af_d3_radio_2").setChecked(True)
1401self._d(4, "af_d3_radio_1a").setEnabled(False)
1402self._d(4, "af_d3_radio_1b").setEnabled(False)
1403self._d(4, "af_d3_radio_2a").setEnabled(True)
1404self._d(4, "af_d3_radio_2b").setEnabled(True)
1405if self.curveType == 1:
1406self._d(4, "af_d3_radio_2a").setChecked(True)
1407else:
1408self._d(4, "af_d3_radio_2b").setChecked(True)
1409self.progressBar_ = self._d(9, "af_d3_progressbar")
1410self.progressBar_.setEnabled(False)
1411self.progressBar_.setValue(0)
1412self._d(7, "af_d3_spinbox_3").setValue(self.chordLength)
1413self._d(3, "af_d3_combobox").setCurrentIndex(self.chordUnits - 1)
1414self._d(4, "af_d3_radio_1").toggled.connect(
1415lambda: self._af_d3_radio_toggled()
1416)
1417self._d(5, "af_d3_checkbox_1").toggled.connect(
1418lambda: self._af_d3_checkbox_1_toggled()
1419)
1420self._d(5, "af_d3_checkbox_2").toggled.connect(
1421lambda: self._af_d3_checkbox_2_toggled()
1422)
1423self._d(5, "af_d3_checkbox_5").toggled.connect(
1424lambda: self._af_d3_checkbox_5_toggled()
1425)
1426self._d(5, "af_d3_checkbox_3").clicked.connect(
1427lambda: self._af_d3_checkbox_3_toggled()
1428)
1429self._d(5, "af_d3_checkbox_4").clicked.connect(
1430lambda: self._af_d3_checkbox_4_toggled()
1431)
1432self._d(6, "af_d3_spinbox_1").valueChanged.connect(
1433lambda: self._af_d3_spinbox_1_toggled()
1434)
1435self._d(6, "af_d3_spinbox_2").valueChanged.connect(
1436lambda: self._af_d3_spinbox_2_toggled()
1437)
1438self._d(1, "af_d3_create_button").clicked.connect(
1439lambda: self._startCreating()
1440)
1441self._d(1, "af_d3_close_button").clicked.connect(lambda: self._close())
1442self._d(1, "af_d3_back_button").clicked.connect(lambda: self._next(-1))
1443elif dialogIndex[0] == 10:
1444self.dialog = gui.PySideUic.loadUi(MACRO_DIR + "Math_Functions_Box.ui")
1445self._d(8, "af_mfb_label_6").setText(
1446"<img src=" + MACRO_DIR + "mfb_img.gif>"
1447)
1448self._d(1, "af_mfb_okay_button").clicked.connect(lambda: self._okay())
1449self.dialog.setWindowIcon(
1450QtGui.QIcon(app.getUserMacroDir(True) + "/AeroFoil_UI_Files/AeroFoil.svg")
1451)
1452self.dialog.exec_()
1453
1454# Button Functions
1455# ----------------------------------------------------------------
1456
1457def _close(self):
1458"""
1459This function closes an open dialog box.
1460This function is called when the 'Close' button is clicked.
1461"""
1462self.dialog.done(1)
1463
1464def _next(self, direction):
1465"""
1466This function takes the user from one dialog box to another.
1467This function is called when the 'Next' or 'Back' buttons are clicked.
1468
1469Arguments
1470----------
1471direction: '1' denotes a 'Go Next'; and '-1' denotes a 'Go Back'
1472"""
1473global dialogIndex
1474global MIN_DATA_POINTS
1475if direction == -1:
1476if dialogIndex[0] == 2:
1477self.airfoil4DNumber = ""
1478elif dialogIndex[0] == 3:
1479self.airfoil5DNumber = ""
1480elif dialogIndex[0] == 4:
1481(
1482self.airfoilProfileType,
1483self.topCurveFunction,
1484self.bottomCurveFunction,
1485) = (1, "", "")
1486elif dialogIndex[0] == 5:
1487self.importFrom, self.mirroring = 1, False
1488elif dialogIndex[0] == 6:
1489self.lineStart, self.lineEnd, self.decimalType = 0, 0, 1
1490self.tempStart, self.tempEnd, = (
14910,
14920,
1493)
1494self.midIndex1, self.midIndex2 = 0, 0
1495elif dialogIndex[0] == 7:
1496self.col1, self.col2, self.row1, self.row2 = 1, 2, 0, 0
1497self.tempStart, self.tempEnd, = (
14980,
14990,
1500)
1501self.midIndex1, self.midIndex2 = 0, 0
1502elif dialogIndex[0] == 8:
1503(
1504self.loadedFilePath,
1505self.loadSuccess,
1506self.loadedFile,
1507self.loadedFileContents,
1508) = ([""], False, "", [])
1509elif dialogIndex[0] == 9:
1510(
1511self.refine,
1512self.refineParam,
1513self.multi,
1514self.multiParam,
1515self.closed_,
1516self.workbench,
1517self.curveType,
1518self.maxRefineParamWarned,
1519self.maxQuantityParamWarned,
1520self.chordLength,
1521self.chordUnits,
1522self.splitCurve,
1523self.splitCurveMode,
1524self.defaultEndPoints,
1525) = (
1526False,
15272,
1528False,
15292,
1530False,
15311,
15321,
1533False,
1534False,
15350,
15361,
1537False,
15381,
1539False,
1540)
1541dialogIndex[0] = dialogIndex[dialogIndex[0]]
1542else:
1543if dialogIndex[0] == 1:
1544self.airfoilType = self._d(3, "af_d1_combo").currentIndex() + 1
1545if self.airfoilType == 1:
1546_setDialogIndex(2)
1547elif self.airfoilType == 2:
1548_setDialogIndex(3)
1549elif self.airfoilType == 3:
1550_setDialogIndex(4)
1551elif self.airfoilType == 4:
1552_setDialogIndex(5)
1553elif dialogIndex[0] == 2:
1554self.airfoil4DNumber = self._d(2, "af_d2a_textbox").text()
1555if _validateAirfoilNumber(self, 4):
1556_setDialogIndex(9)
1557else:
1558return
1559elif dialogIndex[0] == 3:
1560self.airfoil5DNumber = self._d(2, "af_d2b_textbox").text()
1561if _validateAirfoilNumber(self, 5):
1562_setDialogIndex(9)
1563else:
1564return
1565elif dialogIndex[0] == 4:
1566self.topCurveFunction = self._d(2, "af_d2c_textbox_1").text()
1567self.bottomCurveFunction = self._d(2, "af_d2c_textbox_2").text()
1568if not self._d(4, "af_d2c_radio_2").isChecked():
1569self.airfoilProfileType = 1
1570if _validateFunction(self, 1):
1571_setDialogIndex(9)
1572else:
1573return
1574else:
1575self.airfoilProfileType = 2
1576if _validateFunction(self, 2):
1577_setDialogIndex(9)
1578else:
1579return
1580elif dialogIndex[0] == 5:
1581if self._d(4, "af_d2d_radio_1").isChecked():
1582self.importFrom = 1
1583_setDialogIndex(6)
1584else:
1585self.importFrom = 2
1586_setDialogIndex(7)
1587if self._d(5, "af_d2d_checkbox").isChecked():
1588self.mirroring = True
1589else:
1590self.mirroring = False
1591elif dialogIndex[0] == 6:
1592tempVar1 = self._d(6, "af_d2d1a_spinBox_1").value()
1593tempVar2 = self._d(6, "af_d2d1a_spinBox_2").value()
1594tempVar3 = (
15950 if tempVar1 == 0 or tempVar2 == 0 else tempVar2 - tempVar1 + 1
1596)
1597if tempVar3 == 0 or tempVar3 >= MIN_DATA_POINTS:
1598self.tempStart = self.lineStart = self._d(
15996, "af_d2d1a_spinBox_1"
1600).value()
1601self.tempEnd = self.lineEnd = self._d(
16026, "af_d2d1a_spinBox_2"
1603).value()
1604if self._d(4, "af_d2d1a_radio_1").isChecked():
1605self.decimalType = 1
1606else:
1607self.decimalType = 2
1608_setDialogIndex(8)
1609else:
1610setAlertBox(
1611"There must be a minimum of "
1612+ str(MIN_DATA_POINTS)
1613+ " selected file lines,\nthat is, pairs of data points !",
1614True,
1615)
1616return
1617elif dialogIndex[0] == 7:
1618tempVar = (
1619self._d(6, "af_d2d1b_spinBox_4").value()
1620- self._d(6, "af_d2d1b_spinBox_3").value()
1621+ 1
1622)
1623if tempVar >= MIN_DATA_POINTS:
1624if (
1625self._d(6, "af_d2d1b_spinBox_1").value()
1626== self._d(6, "af_d2d1b_spinBox_2").value()
1627):
1628self.col1 = self._d(6, "af_d2d1b_spinBox_1").value()
1629self.col2 = self._d(6, "af_d2d1b_spinBox_2").value()
1630self.tempStart = self.row1 = self._d(
16316, "af_d2d1b_spinBox_3"
1632).value()
1633self.tempEnd = self.row2 = self._d(
16346, "af_d2d1b_spinBox_4"
1635).value()
1636if self._d(4, "af_d2d1b_radio_1").isChecked():
1637self.decimalType = 1
1638else:
1639self.decimalType = 2
1640_setDialogIndex(8)
1641else:
1642setAlertBox("The two file columns cannot be the same !", True)
1643return
1644else:
1645setAlertBox(
1646"There must be a minimum of "
1647+ str(MIN_DATA_POINTS)
1648+ " selected file rows,\nthat is, pairs of data points !",
1649True,
1650)
1651return
1652elif dialogIndex[0] == 8:
1653if self.loadSuccess:
1654_setDialogIndex(9)
1655else:
1656setAlertBox(
1657"Cannot proceed further with an invalid/null file !", True
1658)
1659return
1660self._close()
1661self._createDialogs()
1662
1663def _okay(self):
1664"""
1665This function closes an open dialog box, and
1666reverts back to the previous dialog box.
1667This function is called when the 'Okay' button is clicked.
1668"""
1669global dialogIndex
1670if dialogIndex[0] == 10:
1671dialogIndex[0] = dialogIndex[10]
1672self._close()
1673self._createDialogs()
1674
1675def _loadFile(self):
1676"""
1677This function open up the file loader with custom settings.
1678This function is called when the 'Load File' button from the.
1679'AeroFoil_FileLoad_Dialog' is clicked.
1680This function calls the '_validateFile' function/method
1681post-loading to verify the file's contents, and then
1682notifies the user whether or not the file load was successful.
1683"""
1684fileType = ""
1685homeDirPath = str(Path.home())
1686if self.importFrom == 1:
1687fileType = "Text Files (*.dat)"
1688elif self.importFrom == 2:
1689fileType = "CSV Files (*.dat)"
1690self.loadedFilePath = PySide.QtGui.QFileDialog.getOpenFileName(
1691None, "Load 'Airfoil Points' Data File", homeDirPath, fileType
1692)
1693try:
1694self.loadedFile.close()
1695except Exception:
1696pass
1697try:
1698self.loadedFile = open(self.loadedFilePath[0], "r")
1699except Exception:
1700self.loadedFile = ""
1701if self.loadedFile == "":
1702setAlertBox("No file has been selected!", True)
1703else:
1704if _validateFile(self):
1705self.loadSuccess = True
1706if self.mirroring and not self.canMirror:
1707setAlertBox(
1708"Airfoil profile is complete and cannot be mirrored.", False
1709)
1710else:
1711self.loadSuccess = False
1712self._close()
1713self._createDialogs()
1714
1715def _startCreating(self):
1716"""
1717This function begins the airfoil creation process.
1718This function accumulates and stores the final dialog's input data.
1719This function also notifies the user whether or not the entire
1720'AeroFoil' procedure has been successfully completed.
1721This function is called when the 'Create AeroFoil' button from the
1722'AeroFoil_Final_Dialog' is clicked.
1723This function is responsible for launching the second (Top) macro call.
1724"""
1725global UNITSCONVERSION
1726global MIN_CHORD_LENGTH_MM
1727if self._d(5, "af_d3_checkbox_1").isChecked():
1728self.refine = True
1729self.refineParam = self._d(6, "af_d3_spinbox_1").value()
1730if self._d(5, "af_d3_checkbox_2").isChecked():
1731self.multi = True
1732self.multiParam = self._d(6, "af_d3_spinbox_2").value()
1733if self._d(5, "af_d3_checkbox_5").isChecked():
1734self.closed_ = True
1735if self._d(5, "af_d3_checkbox_3").isChecked():
1736self.splitCurve = True
1737self.splitCurveMode = 1
1738if self._d(5, "af_d3_checkbox_4").isChecked():
1739self.splitCurve = True
1740self.splitCurveMode = 2
1741if self._d(5, "af_d3_checkbox_6").isChecked():
1742self.defaultEndPoints = True
1743if self._d(4, "af_d3_radio_1").isChecked():
1744self.workbench = 1
1745if self._d(4, "af_d3_radio_1a").isChecked():
1746self.curveType = 1
1747else:
1748self.curveType = 2
1749else:
1750self.workbench = 2
1751if self._d(4, "af_d3_radio_2a").isChecked():
1752self.curveType = 1
1753else:
1754self.curveType = 2
1755self.chordLength = self._d(7, "af_d3_spinbox_3").value()
1756self.chordUnits = self._d(3, "af_d3_combobox").currentIndex() + 1
1757chordLength_conv = self.chordLength * UNITSCONVERSION[self.chordUnits]
1758if chordLength_conv >= MIN_CHORD_LENGTH_MM:
1759if _prepareAeroFoil(self):
1760if AeroFoil_generate(self):
1761self._close()
1762alertBox = QtGui.QMessageBox(
1763QtGui.QMessageBox.Warning,
1764"AeroFoil",
1765"The process has been completed successfully !",
1766)
1767alertBox.setWindowModality(QtCore.Qt.ApplicationModal)
1768alertBox.exec_()
1769app.Console.PrintMessage(
1770"\nThe AeroFoil process has been completed successfully !\n"
1771)
1772return
1773setAlertBox(
1774"Unexpected error has occurred while processing !\nPlease report.", True
1775)
1776self.progressBar_.setEnabled(False)
1777self.progressBar_.setValue(0)
1778app.Console.PrintError(
1779"\nUnexpected error has occurred while processing! Please report.\n"
1780)
1781else:
1782setAlertBox(
1783"Airfoil chord length cannot be less than "
1784+ str(MIN_CHORD_LENGTH_MM)
1785+ "mm !",
1786True,
1787)
1788
1789# Other Design Functions
1790# ------------------------------------------------------------------------------------------
1791
1792def _af_d2c_radio_toggled(self):
1793if self._d(4, "af_d2c_radio_1").isChecked():
1794self.airfoilProfileType = 1
1795self._d(2, "af_d2c_textbox_2").setEnabled(False)
1796else:
1797self.airfoilProfileType = 2
1798self._d(2, "af_d2c_textbox_2").setEnabled(True)
1799
1800def _af_d3_spinbox_1_toggled(self):
1801global MAX_REFINE_PARAM
1802if not self.maxRefineParamWarned:
1803if self._d(6, "af_d3_spinbox_1").value() > MAX_REFINE_PARAM:
1804setAlertBox(
1805"Increase in refinement parameter leads to increase\nin time and memory usage.",
1806False,
1807)
1808self.maxRefineParamWarned = True
1809
1810def _af_d3_spinbox_2_toggled(self):
1811global MAX_QUANTITY_PARAM
1812if not self.maxQuantityParamWarned:
1813if self._d(6, "af_d3_spinbox_2").value() > MAX_QUANTITY_PARAM:
1814setAlertBox(
1815"Increase in quantity parameter leads to increase\nin time and memory usage.",
1816False,
1817)
1818self.maxQuantityParamWarned = True
1819
1820def _af_d3_checkbox_1_toggled(self):
1821self._d(6, "af_d3_spinbox_1").setValue(2)
1822if self._d(5, "af_d3_checkbox_1").isChecked():
1823self._d(6, "af_d3_spinbox_1").setEnabled(True)
1824else:
1825self._d(6, "af_d3_spinbox_1").setEnabled(False)
1826
1827def _af_d3_checkbox_2_toggled(self):
1828self._d(6, "af_d3_spinbox_2").setValue(2)
1829if self._d(5, "af_d3_checkbox_2").isChecked():
1830self._d(6, "af_d3_spinbox_2").setEnabled(True)
1831else:
1832self._d(6, "af_d3_spinbox_2").setEnabled(False)
1833
1834def _af_d3_checkbox_5_toggled(self):
1835self._d(4, "af_d3_radio_1").setChecked(True)
1836self._d(4, "af_d3_radio_1a").setChecked(True)
1837self._d(4, "af_d3_radio_2a").setChecked(True)
1838self._d(4, "af_d3_radio_1a").setEnabled(True)
1839self._d(4, "af_d3_radio_1b").setEnabled(True)
1840self._d(4, "af_d3_radio_2a").setEnabled(False)
1841self._d(4, "af_d3_radio_2b").setEnabled(False)
1842if self._d(5, "af_d3_checkbox_5").isChecked():
1843self.closed_ = True
1844self._d(4, "af_d3_radio_1").setEnabled(False)
1845self._d(4, "af_d3_radio_2").setEnabled(False)
1846self._d(5, "af_d3_checkbox_3").setEnabled(False)
1847self._d(5, "af_d3_checkbox_4").setEnabled(False)
1848self._d(5, "af_d3_checkbox_6").setEnabled(False)
1849else:
1850if self.closed_:
1851self._d(5, "af_d3_checkbox_3").setEnabled(True)
1852self._d(5, "af_d3_checkbox_4").setEnabled(True)
1853self._d(5, "af_d3_checkbox_6").setEnabled(True)
1854self.closed_ = False
1855self._d(4, "af_d3_radio_1").setEnabled(True)
1856self._d(4, "af_d3_radio_2").setEnabled(True)
1857
1858def _af_d3_checkbox_3_toggled(self):
1859if self._d(5, "af_d3_checkbox_3").isChecked():
1860self._d(5, "af_d3_checkbox_5").setEnabled(False)
1861self._d(5, "af_d3_checkbox_6").setEnabled(True)
1862self._d(5, "af_d3_checkbox_4").setChecked(False)
1863else:
1864self._d(5, "af_d3_checkbox_5").setEnabled(True)
1865self._d(5, "af_d3_checkbox_6").setEnabled(False)
1866self._d(5, "af_d3_checkbox_6").setChecked(False)
1867self._d(5, "af_d3_checkbox_5").setChecked(False)
1868self._af_d3_checkbox_5_toggled()
1869
1870def _af_d3_checkbox_4_toggled(self):
1871if self._d(5, "af_d3_checkbox_4").isChecked():
1872self._d(5, "af_d3_checkbox_5").setEnabled(False)
1873self._d(5, "af_d3_checkbox_6").setEnabled(True)
1874self._d(5, "af_d3_checkbox_3").setChecked(False)
1875else:
1876self._d(5, "af_d3_checkbox_5").setEnabled(True)
1877self._d(5, "af_d3_checkbox_6").setEnabled(False)
1878self._d(5, "af_d3_checkbox_6").setChecked(False)
1879self._d(5, "af_d3_checkbox_5").setChecked(False)
1880self._af_d3_checkbox_5_toggled()
1881
1882def _af_d3_radio_toggled(self):
1883self._d(4, "af_d3_radio_1a").setChecked(True)
1884self._d(4, "af_d3_radio_2a").setChecked(True)
1885if self._d(4, "af_d3_radio_1").isChecked():
1886self._d(4, "af_d3_radio_1a").setEnabled(True)
1887self._d(4, "af_d3_radio_1b").setEnabled(True)
1888self._d(4, "af_d3_radio_2a").setEnabled(False)
1889self._d(4, "af_d3_radio_2b").setEnabled(False)
1890else:
1891self._d(4, "af_d3_radio_1a").setEnabled(False)
1892self._d(4, "af_d3_radio_1b").setEnabled(False)
1893self._d(4, "af_d3_radio_2a").setEnabled(True)
1894self._d(4, "af_d3_radio_2b").setEnabled(True)
1895
1896def _functionsList(self):
1897self.topCurveFunction = self._d(2, "af_d2c_textbox_1").text()
1898self.bottomCurveFunction = self._d(2, "af_d2c_textbox_2").text()
1899_setDialogIndex(10)
1900self._close()
1901self._createDialogs()
1902
1903
1904
1905def _setDialogIndex(index):
1906"""
1907This function sets the appropriate dialog box, when the
1908'Next', 'Back', and 'Okay' buttons are clicked.
1909This function is NOT responsible for loading any dialog boxes.
1910
1911The 'index' argument denotes the next or previous dialog box to go to.
1912The 'index' number corresponds to the global variable list 'dialogIndex'
1913as well as the dialog names table mentioned in the macro's main DocString.
1914
1915The first element in the global variable 'dialogIndex' is the index number
1916of the currently open dialog box. The other elements too are eventually
1917replaced with the index numbers of the dialog box that was opened prior
1918to the current one. This allows the macro to remember its backward
1919journey through the various dialog boxes.
1920
1921Arguments
1922----------
1923index: A dialog box code (Integer) as listed in the global variable
1924'dialogIndex'.
1925"""
1926global dialogIndex
1927dialogIndex[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.
1934dialogIndex[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
1940def setAlertBox(message, error):
1941"""
1942This function sets the error and warning pop-up messages.
1943
1944Arguments
1945----------
1946message: The string-based message to be displayed.
1947error: 'True' opens up the 'Critical' message box, and
1948'False' opens up the 'Warning' message box.
1949"""
1950msgbox_ = 0
1951if error:
1952msgbox_ = QtGui.QMessageBox(
1953QtGui.QMessageBox.Critical, "AeroFoil - Error Message", message
1954)
1955else:
1956msgbox_ = QtGui.QMessageBox(
1957QtGui.QMessageBox.Warning, "AeroFoil - Warning Message", message
1958)
1959msgbox_.setWindowModality(QtCore.Qt.ApplicationModal)
1960msgbox_.exec_()
1961
1962
1963
1964# Operating and Math Functions
1965# ------------------------------------------------------------------------------------------
1966
1967def _prepareAeroFoil(objRef):
1968"""
1969This function utilizes the various user input data as a basis
1970for creating a list of refined, verified, and finalized
1971airfoil data points to be used later for physical creation.
1972This function is called from the '_startCreating' method
1973of the 'AeroFoilDialog' class.
1974
1975Arguments
1976----------
1977objRef: An instance of the 'AeroFoilDialog' object.
1978"""
1979global UNITSCONVERSION
1980global NACA_NUMBER_OF_POINTS
1981chordLength_conv = objRef.chordLength * UNITSCONVERSION[objRef.chordUnits]
1982# Enable Progress Bar
1983objRef.progressBar_.setEnabled(True)
1984objRef.progressBar_.setValue(0)
1985# Generating and Furnishing Points
1986if objRef.airfoilType == 1:
1987_naca4digit(objRef)
1988elif objRef.airfoilType == 2:
1989_naca5digit(objRef)
1990elif objRef.airfoilType == 3:
1991xu_, yu_, xl_, yl_ = [], [], [], []
1992n_ = (
1993round(NACA_NUMBER_OF_POINTS / 2)
1994* ((objRef.refine * (objRef.refineParam - 1)) + 1)
1995) + 1
1996for points_i in range(n_):
1997xu_.append((points_i / (n_ - 1)) * chordLength_conv)
1998xl_.append((points_i / (n_ - 1)) * chordLength_conv)
1999try:
2000yu_.append(solveFx(objRef.topCurveFunction, xu_[-1]) * chordLength_conv)
2001if objRef.airfoilProfileType == 1:
2002yl_.append(
2003-1
2004* solveFx(objRef.topCurveFunction, xu_[-1])
2005* chordLength_conv
2006)
2007elif objRef.airfoilProfileType == 2:
2008yl_.append(
2009solveFx(objRef.bottomCurveFunction, xu_[-1]) * chordLength_conv
2010)
2011except Exception:
2012# Unexpected Error Occurred while Processing
2013return False
2014if objRef.progressBar_.value() < 50:
2015objRef.progressBar_.setValue(round((points_i / (2 * n_)) * 100))
2016xl_.reverse()
2017yl_.reverse()
2018objRef.midIndex1, objRef.midIndex2 = len(xu_) - 1, len(xu_)
2019objRef.pointsX, objRef.pointsY = xu_ + xl_, yu_ + yl_
2020elif objRef.airfoilType == 4:
2021count, tempVar, pointsInLine, pointsInLine_prev = (
20221,
2023"",
2024[],
2025[],
2026)
2027n_ = len(objRef.loadedFileContents)
2028for lineOrRow in objRef.loadedFileContents:
2029if objRef.decimalType == 1:
2030tempVar = lineOrRow
2031elif objRef.decimalType == 2:
2032tempVar = (
2033lineOrRow.replace(",", ".")
2034if objRef.importFrom == 1
2035else [element_.replace(",", ".") for element_ in lineOrRow]
2036)
2037tempVar = tempVar.replace("-.", "-0.")
2038tempVar = tempVar.replace(" .", "0.")
2039tempVar = "0" + tempVar if tempVar[0] == "." else tempVar
2040pointsInLine = (
2041re.findall(r"[+-]?\d+(?:\.\d+)?", tempVar)
2042if objRef.importFrom == 1
2043else re.findall(
2044r"[+-]?\d+(?:\.\d+)?", tempVar[col1 - 1] + " " + tempVar[col2 - 1]
2045)
2046)
2047if objRef.refine and count > 1:
2048n_ = (
2049float(pointsInLine[0]) - float(pointsInLine_prev[0])
2050) / objRef.refineParam
2051for i in range(objRef.refineParam - 1):
2052tempVar = float(pointsInLine_prev[0]) + n_
2053objRef.pointsX.append(tempVar * chordLength_conv)
2054objRef.pointsY.append(
2055interpolateNum(
2056float(pointsInLine_prev[0]),
2057float(pointsInLine_prev[1]),
2058float(pointsInLine[0]),
2059float(pointsInLine[1]),
2060tempVar,
2061)
2062* chordLength_conv
2063)
2064pointsInLine_prev = pointsInLine
2065objRef.pointsX.append(float(pointsInLine[0]) * chordLength_conv)
2066objRef.pointsY.append(float(pointsInLine[1]) * chordLength_conv)
2067if objRef.progressBar_.value() < 50:
2068objRef.progressBar_.setValue(round((count / (2 * n_)) * 100))
2069count += 1
2070if (
2071objRef.pointsX[len(objRef.pointsX) - 1] != objRef.pointsX[0]
2072or objRef.pointsY[len(objRef.pointsY) - 1] != objRef.pointsY[0]
2073) and not objRef.splitCurve:
2074objRef.pointsX.append(objRef.pointsX[0])
2075objRef.pointsY.append(objRef.pointsY[0])
2076return True
2077
2078
2079
2080def interpolateNum(x1_, y1_, x2_, y2_, x_):
2081"""
2082This function performs a linear interpolation of any given number.
2083
2084Arguments
2085----------
2086x1_, y1_, x2_, y2_: X and Y-axis values of a pair of data points.
2087x: Input value of one of the axes of the data point sought.
2088"""
2089return y1_ + (((x_ - x1_) * (y2_ - y1_)) / (x2_ - x1_))
2090
2091
2092
2093def generateName(inputName):
2094"""
2095This function correctly provides a given name/label with
2096appropriate and non-dulplicate numbering.
2097e.g. 'AeroFoil' results in 'AeroFoil_1' if such a labelled
2098object does not exist; else, it becomes 'AeroFoil_2'.
2099
2100Arguments
2101----------
2102inputName: A non-numbered (simple) name/label
2103"""
2104nameIndex = 1
2105while True:
2106if (
2107not doc.getObjectsByLabel(inputName + "_" + str(nameIndex))
2108and not doc.getObjectsByLabel(inputName + "_" + str(nameIndex) + "_Upper")
2109and not doc.getObjectsByLabel(inputName + "_" + str(nameIndex) + "_Lower")
2110):
2111return inputName + "_" + str(nameIndex)
2112nameIndex += 1
2113
2114
2115
2116def _naca4digit(objRef):
2117"""
2118This function generates a list of airfoil data points
2119for a typical NACA 4 Digit airfoil model.
2120This function is called from the '_startCreating' method
2121of the 'AeroFoilDialog' class.
2122
2123Arguments
2124----------
2125objRef: An instance of the 'AeroFoilDialog' object.
2126"""
2127global UNITSCONVERSION
2128global NACA_NUMBER_OF_POINTS
2129chordLength_conv = objRef.chordLength * UNITSCONVERSION[objRef.chordUnits]
2130xu_, yu_, xl_, yl_ = [], [], [], []
2131yc_ = yt_ = theta_ = 0
2132a0_, a1_, a2_, a3_, a4_ = 0.2969, -0.126, -0.3516, 0.2843, -0.1015
2133m_, p_, t_ = (
2134int(objRef.airfoil4DNumber[0]) / 100,
2135int(objRef.airfoil4DNumber[1]) / 10,
2136int(objRef.airfoil4DNumber[2] + objRef.airfoil4DNumber[3]) / 100,
2137)
2138n_ = (
2139round(NACA_NUMBER_OF_POINTS / 2)
2140* ((objRef.refine * (objRef.refineParam - 1)) + 1)
2141) + 1
2142for i in range(n_):
2143x_ = i / (n_ - 1)
2144yt_ = (
21455
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)
2155if x_ < p_:
2156yc_ = (m_ * ((2 * p_ * x_) - (x_ * x_))) / (p_ * p_)
2157theta_ = math.degrees(math.atan((2 * m_ * (p_ - x_)) / (p_ * p_)))
2158else:
2159yc_ = (m_ * (1 - (2 * p_) + (2 * p_ * x_) - (x_ * x_))) / pow(1 - p_, 2)
2160theta_ = math.degrees(math.atan((2 * m_ * (p_ - x_)) / pow(1 - p_, 2)))
2161xu_.append((x_ - (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2162yu_.append((yc_ + (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2163xl_.append((x_ + (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2164yl_.append((yc_ - (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2165if objRef.progressBar_.value() < 50:
2166objRef.progressBar_.setValue(round((i / (2 * n_)) * 100))
2167xl_.reverse()
2168yl_.reverse()
2169objRef.midIndex1, objRef.midIndex2 = len(xu_) - 1, len(xu_)
2170objRef.pointsX, objRef.pointsY = xu_ + xl_, yu_ + yl_
2171
2172
2173
2174def _naca5digit(objRef):
2175"""
2176This function generates a list of airfoil data points
2177for a typical NACA 5 Digit airfoil model.
2178This function is called from the '_startCreating' method
2179of the 'AeroFoilDialog' class.
2180
2181Arguments
2182----------
2183objRef: An instance of the 'AeroFoilDialog' object.
2184"""
2185global UNITSCONVERSION
2186global NACA_NUMBER_OF_POINTS
2187chordLength_conv = objRef.chordLength * UNITSCONVERSION[objRef.chordUnits]
2188xu_, yu_, xl_, yl_ = [], [], [], []
2189yc_ = yt_ = theta_ = 0
2190a0_, a1_, a2_, a3_, a4_ = 0.2969, -0.126, -0.3516, 0.2843, -0.1015
2191R_ = [0.0580, 0.1260, 0.2025, 0.2900, 0.3910, 0.1300, 0.2170, 0.3180, 0.4410]
2192K1_ = [361.400, 51.640, 15.957, 6.643, 3.230, 51.990, 15.793, 6.520, 3.191]
2193K2K1_ = [0, 0, 0, 0, 0, 0.000764, 0.00677, 0.0303, 0.1355]
2194index = int(objRef.airfoil5DNumber[1]) - 1 + (4 * int(objRef.airfoil5DNumber[2]))
2195r_, k1_, k2k1_, t_ = (
2196R_[index],
2197K1_[index],
2198K2K1_[index],
2199int(objRef.airfoil5DNumber[3] + objRef.airfoil5DNumber[4]) / 100,
2200)
2201n_ = (
2202round(NACA_NUMBER_OF_POINTS / 2)
2203* ((objRef.refine * (objRef.refineParam - 1)) + 1)
2204) + 1
2205for i in range(n_):
2206x_ = i / (n_ - 1)
2207yt_ = (
22085
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)
2218if int(objRef.airfoil5DNumber[2]) == 0:
2219if x_ < r_:
2220yc_ = (
2221k1_ * (pow(x_, 3) - (3 * r_ * x_ * x_) + (x_ * r_ * r_ * (3 - r_)))
2222) / 6
2223theta_ = math.degrees(
2224math.atan(
2225(k1_ * ((3 * x_ * x_) - (6 * r_ * x_) + (r_ * r_ * (3 - r_))))
2226/ 6
2227)
2228)
2229else:
2230yc_ = (k1_ * pow(r_, 3) * (1 - x_)) / 6
2231theta_ = math.degrees(math.atan(-(k1_ * pow(r_, 3)) / 6))
2232elif int(objRef.airfoil5DNumber[2]) == 1:
2233if x_ < r_[int(objRef.airfoil5DNumber[1] + objRef.airfoil5DNumber[2])]:
2234yc_ = (
2235k1_
2236* (
2237pow(x_ - r_, 3)
2238- (k2k1_ * x_ * pow(1 - r_, 3))
2239- (x_ * pow(r_, 3))
2240+ pow(r_, 3)
2241)
2242) / 6
2243theta_ = math.degrees(
2244math.atan(
2245(
2246k1_
2247* (
2248(3 * pow(x_ - r_, 2))
2249- (k2k1_ * pow(1 - r_, 3))
2250- (pow(r_, 3))
2251)
2252)
2253/ 6
2254)
2255)
2256else:
2257yc_ = (
2258k1_
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
2266theta_ = math.degrees(
2267math.atan(
2268(
2269k1_
2270* (
2271(3 * k2k1_ * pow(x_ - r_, 2))
2272- (k2k1_ * pow(1 - r_, 3))
2273- (pow(r_, 3))
2274)
2275)
2276/ 6
2277)
2278)
2279xu_.append((x_ - (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2280yu_.append((yc_ + (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2281xl_.append((x_ + (yt_ * math.sin(math.radians(theta_)))) * chordLength_conv)
2282yl_.append((yc_ - (yt_ * math.cos(math.radians(theta_)))) * chordLength_conv)
2283if objRef.progressBar_.value() < 50:
2284objRef.progressBar_.setValue(round((i / (2 * n_)) * 100))
2285xl_.reverse()
2286yl_.reverse()
2287objRef.midIndex1, objRef.midIndex2 = len(xu_) - 1, len(xu_)
2288objRef.pointsX, objRef.pointsY = xu_ + xl_, yu_ + yl_
2289
2290
2291
2292def convTrig(mode, inputVal):
2293"""
2294This function converts specific, unconventional trigonometric code
2295into a Python-based (Math module) trigonometric code.
2296This function is called from the 'solveFx' method.
2297
2298Arguments
2299----------
2300mode: A code number denoting a specific trignometric function.
2301inputVal: An unconventional trigonometric code string.
2302"""
2303if mode == 1:
2304return math.sin(math.radians(inputVal))
2305if mode == 2:
2306return math.cos(math.radians(inputVal))
2307if mode == 3:
2308return math.tan(math.radians(inputVal))
2309if mode == 4:
2310return math.degrees(math.asin(inputVal))
2311if mode == 5:
2312return math.degrees(math.acos(inputVal))
2313if mode == 6:
2314return math.degrees(math.atan(inputVal))
2315
2316
2317
2318def solveFx(f_, x_):
2319"""
2320This function generates a list of airfoil data points
2321for a typical NACA 4 Digit airfoil model.
2322This function is called from the '_validateFunction'
2323and the '_startCreating' methods of the
2324'AeroFoilDialog' class.
2325
2326Arguments
2327----------
2328objRef: An instance of the 'AeroFoilDialog' object.
2329"""
2330f_ = f_.replace("x", "x_")
2331f_ = f_.replace("^", "**")
2332f_ = f_.replace("e", "math.e")
2333f_ = f_.replace("pi", "math.pi")
2334f_ = f_.replace("ln", "math.log")
2335f_ = f_.replace("log", "math.log10")
2336f_ = f_.replace("sqrt", "math.sqrt")
2337f_ = f_.replace("sin(", "convTrig(1,")
2338f_ = f_.replace("cos(", "convTrig(2,")
2339f_ = f_.replace("tan(", "convTrig(3,")
2340f_ = f_.replace("asin(", "convTrig(4,")
2341f_ = f_.replace("acos(", "convTrig(5,")
2342f_ = f_.replace("atan(", "convTrig(6,")
2343try:
2344return eval(f_)
2345except Exception:
2346return
2347
2348
2349
2350def _validateAirfoilNumber(objRef, digit):
2351"""
2352This function validates whether or not the inputted NACA airfoil model code
2353is valid and existing.
2354This function is called from the '_next' method of the 'AeroFoilDialog' class.
2355This function is called when the 'Next' button from the 'AeroFoil_NACA4Digit_Dialog'
2356or the 'AeroFoil_NACA5Digit_Dialog' is clicked.
2357
2358Arguments
2359----------
2360objRef: An instance of the 'AeroFoilDialog' object.
2361digit: An integer number denoting the NACA airfoil type.
2362Options: 4 or 5.
2363"""
2364global NACA_5_2ND_3RD_DIGITS
2365airfoilNumber = ""
2366airfoilNumber = objRef.airfoil4DNumber if digit == 4 else objRef.airfoil5DNumber
2367if airfoilNumber.isnumeric():
2368if len(airfoilNumber) == digit:
2369if int(airfoilNumber[-2:]) == 0:
2370setAlertBox(
2371"The last two digits of the airfoil code cannot be a\nzero value !",
2372True,
2373)
2374else:
2375if digit == 4:
2376return True
2377elif digit == 5:
2378if int(airfoilNumber[0]) == 2:
2379if int(airfoilNumber[2]) <= 1:
2380for numRef in NACA_5_2ND_3RD_DIGITS:
2381if airfoilNumber[1] + airfoilNumber[2] == numRef:
2382return True
2383if airfoilNumber[2] == 0:
2384setAlertBox(
2385"The second digit of the airfoil code must be\nbetween one and five, both inclusive !",
2386True,
2387)
2388elif airfoilNumber[2] == 1:
2389setAlertBox(
2390"The second digit of the airfoil code must be\nbetween two and five, both inclusive !",
2391True,
2392)
2393else:
2394setAlertBox(
2395"The third digit of the airfoil code must be either\na zero or a one !",
2396True,
2397)
2398else:
2399setAlertBox(
2400"The first digit of the airfoil code must be a two !", True
2401)
2402else:
2403setAlertBox(
2404"Airfoil code must contain exactly " + str(digit) + " digits !", True
2405)
2406else:
2407setAlertBox("Airfoil code must be numeric !", True)
2408objRef.airfoil4DNumber, objRef.airfoil5DNumber = "", ""
2409return False
2410
2411
2412
2413def _validateFunction(objRef, mode):
2414"""
2415This function parses the inputted curve functions, and validates them,
2416resulting in whether or not the inputted functions are valid from points
24170 to 1, both inclusive, in the X-axis of a typical graph.
2418This function is called from the '_next' method of the 'AeroFoilDialog' class.
2419This function is called when the 'Next' button from the 'AeroFoil_CurvesInput_Dialog'
2420is clicked.
2421
2422Arguments
2423----------
2424objRef: An instance of the 'AeroFoilDialog' object.
2425mode: An integer number specifying the function input mode.
2426Options: '1' denotes that one function has been inputted,
2427and the function is to be symmetrically mirrored
2428to produce the lower airfoil points.
2429'2' denotes that two functions have been inputted,
2430one for the upper, and one for the lower airfoil points.
2431"""
2432global FUNCTIONSARRAY
2433function = [
2434objRef.topCurveFunction.replace(" ", ""),
2435objRef.bottomCurveFunction.replace(" ", ""),
2436]
2437badFunctionCombinations = [
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]
2484modeName = ["Top", "Bottom"]
2485for i in range(mode):
2486if len(function[i]) > 0:
2487if function[i].count("(") == function[i].count(")"):
2488tempVar = function[i].replace(",", ".")
2489for stringRef in FUNCTIONSARRAY:
2490tempVar = tempVar.replace(stringRef, "")
2491if len(tempVar) == 0 or tempVar.isnumeric():
2492badFunction, badFunctionFound = "", False
2493for stringRef in badFunctionCombinations:
2494if function[i].count(stringRef) != 0:
2495badFunction, badFunctionFound = stringRef, True
2496break
2497if not badFunctionFound:
2498try:
2499if mode == 1:
2500if (
2501solveFx(function[i], 0) >= 0
2502and solveFx(function[i], 1) >= 0
2503):
2504return True
2505else:
2506setAlertBox(
2507"Top curve function 'range' should not be less than zero !",
2508True,
2509)
2510elif mode == 2 and i == 1:
2511if solveFx(function[i], 0) <= solveFx(
2512function[i - 1], 0
2513) and solveFx(function[i], 1) <= solveFx(
2514function[i - 1], 1
2515):
2516return True
2517else:
2518setAlertBox(
2519"Top and Bottom curve function 'ranges' should not intersect!",
2520True,
2521)
2522except Exception:
2523setAlertBox(
2524modeName[i]
2525+ " curve function contains unrecognizable,\ninvalid components !",
2526True,
2527)
2528else:
2529setAlertBox(
2530modeName[i]
2531+ " curve function contains this invalidly\nwritten component :: "
2532+ badFunction,
2533True,
2534)
2535else:
2536setAlertBox(
2537modeName[i]
2538+ " curve function contains one or more\ninvalid, extraneous components !",
2539True,
2540)
2541else:
2542setAlertBox(
2543modeName[i] + " curve function contains unequal parantheses !", True
2544)
2545else:
2546setAlertBox(modeName[i] + " curve function cannot be empty !", True)
2547objRef.topCurveFunction, objRef.bottomCurveFunction = "", ""
2548return False
2549
2550
2551
2552def _validateFile(objRef):
2553"""
2554This function parses the file contents of the inputted file path, and validates them,
2555resulting in whether or not the file is valid (that is, contains minimum number of points,
2556valid data points structure, etc.)
2557This function is called from the '_loadFile' method of the 'AeroFoilDialog' class.
2558
2559Arguments
2560----------
2561objRef: An instance of the 'AeroFoilDialog' object.
2562"""
2563global MIN_DATA_POINTS
2564objRef.midIndex1, objRef.midIndex2 = 0, 0
2565i_start, i_end, tempVar, tempStr, count, objRef.canMirror, x_prev, dir_ = (
25660,
25670,
2568"",
2569"",
25701,
2571True,
25720,
2573[1, 1],
2574)
2575i_start, i_end, fileReader_ = (
2576(objRef.tempStart, objRef.tempEnd, objRef.loadedFile)
2577if objRef.importFrom == 1
2578else (objRef.row1, objRef.row2, csv.reader(objRef.loadedFile))
2579)
2580objRef.loadedFileContents = []
2581objRef.loadedFile.seek(0)
2582if i_start == 0:
2583i_start = i_end = 0
2584for lineOrRow in fileReader_:
2585if objRef.decimalType == 1:
2586tempVar = lineOrRow
2587elif objRef.decimalType == 2:
2588tempVar = (
2589lineOrRow.replace(",", ".")
2590if objRef.importFrom == 1
2591else [element_.replace(",", ".") for element_ in lineOrRow]
2592)
2593pointsInLine = (
2594re.findall(r"[+-]?\d+(?:\.\d+)?", tempVar)
2595if objRef.importFrom == 1
2596else re.findall(
2597r"[+-]?\d+(?:\.\d+)?",
2598tempVar[objRef.col1 - 1] + " " + tempVar[objRef.col2 - 1],
2599)
2600)
2601if len(pointsInLine) == 2:
2602i_start = count if i_start == 0 else i_start
2603objRef.loadedFileContents.append(lineOrRow)
2604else:
2605if i_start != 0:
2606i_end = count - 1
2607break
2608if i_start != 0:
2609if tempStr == "":
2610tempStr = "1"
2611elif tempStr == "1":
2612dir_[0] = dir_[1]
2613dir_[1] = (float(pointsInLine[0]) - x_prev) / abs(
2614float(pointsInLine[0]) - x_prev
2615)
2616if dir_[0] != dir_[1] and (count - i_start) > 2:
2617if objRef.canMirror:
2618objRef.midIndex1, objRef.midIndex2 = (
2619count - i_start - 1,
2620count - i_start,
2621)
2622swapVar = objRef.loadedFileContents[-1]
2623objRef.loadedFileContents[-1] = objRef.loadedFileContents[
2624-2
2625]
2626objRef.loadedFileContents.append(swapVar)
2627objRef.canMirror = False
2628tempStr = "0"
2629x_prev = float(pointsInLine[0])
2630count += 1
2631if i_start == 0:
2632tempStr = (
2633"File Line :: " + str(count)
2634if objRef.importFrom == 1
2635else "File Row :: " + str(count)
2636)
2637setAlertBox(
2638"File is invalid, or file is of incorrect format !\n" + tempStr, True
2639)
2640return False
2641else:
2642for lineOrRow in fileReader_:
2643if count >= i_start:
2644if objRef.decimalType == 1:
2645tempVar = lineOrRow
2646elif objRef.decimalType == 2:
2647tempVar = (
2648lineOrRow.replace(",", ".")
2649if objRef.importFrom == 1
2650else [element_.replace(",", ".") for element_ in lineOrRow]
2651)
2652pointsInLine = (
2653re.findall(r"[+-]?\d+(?:\.\d+)?", tempVar)
2654if objRef.importFrom == 1
2655else re.findall(
2656r"[+-]?\d+(?:\.\d+)?",
2657tempVar[objRef.col1 - 1] + " " + tempVar[objRef.col2 - 1],
2658)
2659)
2660if len(pointsInLine) == 2:
2661objRef.loadedFileContents.append(lineOrRow)
2662else:
2663if i_end == 0 and count != 1:
2664count += 1
2665break
2666else:
2667tempStr = (
2668"File Line :: " + str(count)
2669if objRef.importFrom == 1
2670else "File Row :: " + str(count)
2671)
2672setAlertBox(
2673"File is invalid, or file is of incorrect format !\n"
2674+ tempStr,
2675True,
2676)
2677return False
2678if tempStr == "":
2679tempStr = "1"
2680elif tempStr == "1":
2681dir_[0] = dir_[1]
2682dir_[1] = (float(pointsInLine[0]) - x_prev) / abs(
2683float(pointsInLine[0]) - x_prev
2684)
2685if dir_[0] != dir_[1] and (count - i_start) > 2:
2686if objRef.canMirror:
2687objRef.midIndex1, objRef.midIndex2 = (
2688count - i_start - 1,
2689count - i_start,
2690)
2691swapVar = objRef.loadedFileContents[-1]
2692objRef.loadedFileContents[-1] = objRef.loadedFileContents[
2693-2
2694]
2695objRef.loadedFileContents.append(swapVar)
2696objRef.canMirror = False
2697tempStr = "0"
2698x_prev = float(pointsInLine[0])
2699if i_end != 0 and count == i_end:
2700count += 1
2701break
2702count += 1
2703i_end = count - 1
2704# if i_end - i_start + 1 >= MIN_DATA_POINTS:
2705# return True
2706if i_end - i_start + 1 < MIN_DATA_POINTS:
2707setAlertBox(
2708"There must be a minimum of "
2709+ str(MIN_DATA_POINTS)
2710+ " selected file rows,\nthat is, pairs of data points !",
2711True,
2712)
2713return False
2714objRef.lineStart, objRef.row1, objRef.lineEnd, objRef.row2 = (
2715(i_start, i_start, i_end, i_end)
2716if objRef.importFrom == 1
2717else (objRef.lineStart, objRef.row1, objRef.lineEnd, objRef.row2)
2718)
2719objRef.loadedFile.close()
2720return 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###
2735if __name__ == "__main__": ###
2736AeroFoilDialog() ###
2737###
2738###----------------------------------------------------------------###
2739### AEROFOIL MACRO CALLS - Bottom (check Top) ###
2740###----------------------------------------------------------------###
2741######################################################################
2742