[Python] Design & build airplanes from your specifications.
1
"""
2
The wing. module contains class definitions for and various components
3
we add to an (,,and).
4
5
Classes:
6
Airfoil: instantiated with class method to provide coordinates to heirs.
7
Spar: inherits from Airfoil.
8
Stringer: also inherits from Airfoil.
9
10
Functions:
11
(): generates a 2D plot of the airfoil & any components.
12
"""
13
14
import logging
15
import numpy as np
16
from math import sin, cos, atan
17
import bisect as bi
18
import matplotlib. as plt
19
20
from aircraftstudio. import base
21
import resources. as mt
22
23
24
class (.):
25
"""This class represents a single NACA airfoil.
26
27
The coordinates are saved as two np.
28
for the x- and z-coordinates. coordinates start at
29
the leading edge, travel over the airfoil's upper edge,
30
then loop back to the leading edge via the lower edge.
31
32
This method was chosen for easier future exports
33
to 3D CAD packages like SolidWorks, which can import such
34
geometry as coordinates written in a CSV file.
35
"""
36
def __init__(self,
37
,
38
,
39
chord=68,
40
semi_span=150,
41
material=.):
42
super().__init__(,)
43
parent. = self
44
if chord > 20:
45
self. = chord
46
else:
47
self. = 20
48
logging.('Chord too small, using minimum value of 20.')
49
parent
50
self. = semi_span
51
self. = material
52
self. = []
53
self. = []
54
55
def (self,naca_num=2412):
56
"""Generate surface geometry for a NACA airfoil.
57
58
The nested functions perform the required steps to generate geometry,
59
and can be called to solve the geometry y-coordinate for any 'x' input.
60
Equation coefficients were retrieved from Wikipedia..
61
62
Parameters:
63
naca_num: 4-digit NACA wing
64
65
Return:
66
None
67
"""
68
self. = naca_num
69
# Variables extracted from naca_num argument passed to the function
70
m = int(str()[0]) / 100
71
p = int(str()[1]) / 10
72
t = int(str()[2:]) / 100
73
# x-coordinate of maximum camber
74
p_c = p * self.
75
76
def ():
77
"""
78
Returns camber z-coordinate from 1 'x' along the airfoil chord.
79
"""
80
z_c = float()
81
if 0 <= x < p_c:
82
z_c = (m / (p**2)) * (2 * p * (x / self.) -
83
(x / self.)**2)
84
elif p_c <= x <= self.:
85
z_c = (m /
86
((1 - p)**2)) * ((1 - 2 * p) + 2 * p *
87
(x / self.) - (x / self.)**2)
88
return(*self.)
89
90
def ():
91
"""Return thickness from 1 'x' along the airfoil chord."""
92
x = 0 if x < 0 else x
93
z_t = 5 * t * self. * (+0.2969 *
94
(x / self.)**0.5 - 0.1260 *
95
(x / self.)**1 - 0.3516 *
96
(x / self.)**2 + 0.2843 *
97
(x / self.)**3 - 0.1015 *
98
(x / self.)**4)
99
return z_t
100
101
def ():
102
dz_c = float()
103
if 0 <= x < p_c:
104
dz_c = ((2 * m) / p**2) * (p - x / self.)
105
elif p_c <= x <= self.:
106
dz_c = (2 * m) / ((1 - p)**2) * (p - x / self.)
107
108
theta = ()
109
return theta
110
111
def ():
112
x = x - () * (())
113
z = () + () * (())
114
return(,)
115
116
def ():
117
x = x + () * (())
118
z = () - () * (())
119
return(,)
120
121
# Densify x-coordinates 10 times for first 1/4 chord length
122
x_chord_25_percent = round(self./4)
123
x_chord = [i / 10 for i in range(*10)]
124
x_chord.(forinrange(,self.+1))
125
# Generate our airfoil skin geometry from previous sub-functions
126
self. = np.([])
127
self. = np.([])
128
# Upper surface and camber line
129
for x in x_chord:
130
self. = np.(self.,)
131
self. = np.(self.,())
132
self. = np.(self.,()[0])
133
self. = np.(self.,()[1])
134
# Lower surface
135
for x in [::-1]:
136
self. = np.(self.,()[0])
137
self. = np.(self.,()[1])
138
return None
139
140
141
class (.):
142
"""Contains a single spar's data."""
143
def __init__(self,,,loc_percent=0.30,material=.):
144
"""Set spar location as percent of chord length."""
145
super().__init__(,)
146
parent..(self)
147
self. = material
148
self. = float()
149
# bi.bisect_left: returns index of first value in parent.x > loc
150
# This ensures that spar geom intersects with airfoil geom.
151
loc = loc_percent * parent.
152
# Spar upper coordinates
153
spar_u = bi.(.,) - 1
154
self. = np.(self.,.[])
155
self. = np.(self.,.[])
156
# Spar lower coordinates
157
spar_l = bi.(.[::-1],)
158
self. = np.(self.,.[-])
159
self. = np.(self.,.[-])
160
return None
161
162
def (self,):
163
self. = cap_area
164
return None
165
166
def (self,):
167
self. = mass
168
return None
169
170
171
class (.):
172
"""Contains the coordinates of all stringers."""
173
def __init__(self,
174
,
175
,
176
den_u_1=4,
177
den_u_2=4,
178
den_l_1=4,
179
den_l_2=4):
180
"""Add equally distributed stringers to four airfoil locations
181
(upper nose, lower nose, upper surface, lower surface).
182
183
den_u_1: upper nose number of stringers
184
den_u_2: upper surface number of stringers
185
den_l_1: lower nose number of stringers
186
den_l_2: lower surface number of stringers
187
"""
188
super().__init__(,)
189
parent. = self
190
self. = []
191
self. = []
192
self. = []
193
self. = []
194
self. = float()
195
self. = float()
196
197
# Find distance between leading edge and first upper stringer
198
# interval = self.parent.spars[0].x[0] / (den_u_1 + 1)
199
interval = 2
200
# initialise first self.stringer_x at first interval
201
x = interval
202
# Add upper stringers from leading edge until first spar.
203
for _ in range(0,):
204
# Index of the first value of airfoil.x > x
205
i = bi.(self..,)
206
self. = np.(self.,self..[])
207
self. = np.(self.,self..[])
208
x += interval
209
# Add upper stringers from first spar until last spar
210
interval = (self..[-1].[0] -
211
self..[0].[0]) / (den_u_2 + 1)
212
x = interval + self..[0].[0]
213
for _ in range(0,):
214
i = bi.(self..,)
215
self. = np.(self.,self..[])
216
self. = np.(self.,self..[])
217
x += interval
218
219
# Find distance between leading edge and first lower stringer
220
interval = self..[0].[1] / (den_l_1 + 1)
221
x = interval
222
# Add lower stringers from leading edge until first spar.
223
for _ in range(0,):
224
i = bi.(self..[::-1],)
225
self. = np.(self.,self..[-])
226
self. = np.(self.,self..[-])
227
x += interval
228
# Add lower stringers from first spar until last spar
229
interval = (self..[-1].[1] -
230
self..[0].[1]) / (den_l_2 + 1)
231
x = interval + self..[0].[1]
232
for _ in range(0,):
233
i = bi.(self..[::-1],)
234
self. = np.(self.,self..[-])
235
self. = np.(self.,self..[-])
236
x += interval
237
return None
238
239
def (self,):
240
self. = area
241
return None
242
243
def (self,):
244
self. = len(self.) * mass + len(self.) * mass
245
return None
246
247
def (self,):
248
"""Add webs to stringers."""
249
for _ in range(len(self.)//2):
250
self..(self.[])
251
self..(self.[+1])
252
self..(self.[])
253
self..(self.[+1])
254
self. = thickness
255
return None
256
257
def (self,round=2):
258
super().(round)
259
print('Stringer Area:\n',.(self.,round))
260
return None
261
262
263
def ():
264
"""This function plots the airfoil's + sub-components' geometry."""
265
fig, ax = plt.()
266
267
# Plot chord
268
x = [0, airfoil.]
269
y = [0, 0]
270
ax.(,,linewidth='1')
271
# Plot quarter chord
272
ax.(./4,
273
0,
274
'.',
275
color='g',
276
markersize=24,
277
label='Quarter-chord')
278
# Plot mean camber line
279
ax.(.,
280
.,
281
'-.',
282
color='r',
283
linewidth='2',
284
label='Mean camber line')
285
# Plot airfoil surfaces
286
ax.(.,.,color='b',linewidth='1')
287
288
try: # Plot spars
289
for spar in airfoil.:
290
x = (spar.)
291
y = (spar.)
292
ax.(,,'-',color='y',linewidth='4')
293
except AttributeError:
294
print('No spars to plot.')
295
try: # Plot stringers
296
for i in range(len(..)):
297
x = airfoil..[]
298
y = airfoil..[]
299
ax.(,,'.',color='y',markersize=12)
300
except AttributeError:
301
print('No stringers to plot.')
302
303
ax.(title='NACA '+str(.)+' airfoil',
304
xlabel='X axis',
305
ylabel='Z axis')
306
307
plt.(axis='both',linestyle=':',linewidth=1)
308
plt.().('equal',adjustable='box')
309
plt.().(bbox_to_anchor=(1,1),
310
bbox_transform=.().)
311
plt.()
312
return fig, ax
313