-
Notifications
You must be signed in to change notification settings - Fork 0
/
input_avl.py
executable file
·314 lines (255 loc) · 11.5 KB
/
input_avl.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#!/usr/bin/env
import argparse
import avl_out_parse
import os
import yaml
import subprocess
import shutil
"""
Write individual airfoil section definitions to the .avl file.
Sections are defined through a 3D point in space and assigned properties such as chord, angle of incidence etc.
AVL then links them up to the other sections of a particular surface. You can define any number of sections for
a particular surface, but there always have to be at least two (a left and right edge).
Args:
plane_name (str): The name of the vehicle.
x (str): The x coordinate of the section.
y (str): The y coordinate of the section.
z (str): The z coordinate of the section.
chord (str): Chord in this section of the surface. Trailing edge is at x + chord, y, z.
ainc (str): Angle of incidence for this section. Taken as a rotation (RH rule) about the surface's
spanwise axis projected onto the Y-Z plane.
nspan (str): Number of spanwise vortices in until the next section.
sspan (str): Controls the spanwise spacing of the vortices.
naca_number (str): The chosen NACA number that will define the cambered properties of this section
of the surface. For help picking an airfoil go to: http://airfoiltools.com/airfoil/naca4digit.
ctrl_surface_type: The selected type of control surface. This should be consistent along the entirety of
the surface. (Question: Flap and Aileron along the same airfoil?)
Return:
None.
"""
def write_section(plane_name: str,x: str,y: str,z: str,chord: str,ainc: str,nspan: str,sspace: str,naca_number: str,ctrl_surf_type: str):
with open(f'{plane_name}.avl','a') as avl_file:
avl_file.write("SECTION \n")
avl_file.write("!Xle Yle Zle Chord Ainc Nspanwise Sspace \n")
avl_file.write(f'{x} {y} {z} {chord} {ainc} {nspan} {sspace} \n')
if naca_number != "0000":
avl_file.write("NACA \n")
avl_file.write(f'{naca_number} \n')
avl_file.close()
match ctrl_surf_type:
case 'aileron':
#TODO provide custom options for gain and hinge positions
with open(f'{plane_name}.avl','a') as avl_file:
avl_file.write("CONTROL \n")
avl_file.write("aileron 1.0 0.0 0.0 0.0 0.0 -1 \n")
avl_file.close()
case 'elevator':
with open(f'{plane_name}.avl','a') as avl_file:
avl_file.write("CONTROL \n")
avl_file.write("elevator 1.0 0.0 0.0 0.0 0.0 1 \n")
avl_file.close()
case 'rudder':
with open(f'{plane_name}.avl','a') as avl_file:
avl_file.write("CONTROL \n")
avl_file.write("rudder 1.0 0.0 0.0 0.0 0.0 1 \n")
avl_file.close()
"""
Read the provided yaml file and generate the corresponding .avl file that can be read into AVL.
Also calls AVL and the avl_out_parse.py file that generates the sdf plugin.
Args:
yaml_file: Path to the input yaml file
avl_path: Set the avl_path to provide a desired directory for where Avl should be located.
Return:
None
"""
def main():
user = os.environ.get('USER')
# This will find Avl on a users machine.
for root, dirs, _ in os.walk(f'/home/{user}/'):
if "Avl" in dirs:
target_directory_path = os.path.join(root, "Avl")
break
parent_directory_path = os.path.dirname(target_directory_path)
filedir = f'{parent_directory_path}/'
print(filedir)
parser = argparse.ArgumentParser()
parser.add_argument("--yaml_file", help="Path to input yaml file.")
parser.add_argument("--avl_path", default=filedir, help="Provide an absolute AVL path. If this argument is passed, AVL will be moved there and the files will adjust their paths accordingly.")
inputs = parser.parse_args()
# If the user passes the avl_path argument then move Avl to that location:
if inputs.avl_path != filedir:
#Check if the directory is already there
if os.path.exists(f'{inputs.avl_path}/Avl') and os.path.isdir(f'{inputs.avl_path}/Avl'):
print("Avl is already at desired location")
else:
shutil.move(f'{filedir}Avl',inputs.avl_path)
# Adjust paths to AVL in process.sh
print("Adjusting paths")
with open("./process.sh", "r") as file:
all_lines = file.readlines()
file.close()
it = 0
for line in all_lines:
if "cp $DIR_PATH/$CUSTOM_MODEL.avl" in line:
new_line = f'cp $DIR_PATH/$CUSTOM_MODEL.avl {inputs.avl_path}Avl/runs\n'
all_lines[it] = new_line
if "/Avl/runs/plot.ps $DIR_PATH/" in line:
new_line =f'mv {inputs.avl_path}Avl/runs/plot.ps $DIR_PATH/\n'
all_lines[it] = new_line
if "cd" in line and "/Avl/runs" in line:
new_line = f'cd {inputs.avl_path}Avl/runs\n'
all_lines[it] = new_line
it += 1
with open("./process.sh", "w") as file:
file.writelines(all_lines)
file.close()
with open(inputs.yaml_file,'r') as yaml_file:
yaml_data = yaml.safe_load(yaml_file)
airframes = ['cessna','standard_vtol','custom']
plane_name = yaml_data['vehicle_name']
frame_type = yaml_data['frame_type']
if not frame_type in airframes:
raise ValueError("\nThis is not a valid airframe, please choose a valid airframe. \n")
# Parameters that need to be provided:
# General
# - Reference Area (Sref)
# - Wing span (Bref) (wing span squared / area = aspect ratio which is a required parameter for the sdf file)
# - Reference point (X,Y,Zref) point at which moments and forces are calculated
#Control Surface specific
# - type (select from options; aileron,elevator,rudder)
# - nchord
# - cspace
# - nspanwise
# - sspace
# - x,y,z 1. (section)
# - chord 1. (section)
# - ainc 1. (section)
# - Nspan 1. (optional for section)
# - sspace 1. (optional for section)
# - x,y,z 2. (section)
# - chord 2. (section)
# - ainc 2. (section)
# - Nspan 2. (optional for section)
# - sspace 2. (optional for section)
# TODO: Find out if elevons are defined
ctrl_surface_types = ['aileron','elevator','rudder']
# - Reference Chord (Cref) (= area/wing span)
delineation = '!***************************************'
sec_demark = '#--------------------------------------------------'
num_ctrl_surfaces = 0
ctrl_surface_order = []
area = 0
span = 0
ref_pt_x = None
ref_pt_y = None
ref_pt_z = None
# Future work: Provide some pre-worked frames for a Cessna and standard VTOL if there is a need for it
match frame_type:
case "custom":
# These parameters are consistent across all models.
# At the moment we do not use any symmetry axis for mirroring.
with open(f'{plane_name}.avl','w') as avl_file:
avl_file.write(f'{delineation} \n')
avl_file.write(f'!{plane_name} input dataset \n')
avl_file.write(f'{delineation} \n')
avl_file.write(f'{plane_name} \n')
avl_file.write('!Mach \n0.0 \n')
avl_file.write('!IYsym IZsym Zsym \n')
avl_file.write('0 0 0 \n')
avl_file.close()
# First define some model-specific parameters for custom models
area = yaml_data["reference_area"]
span = yaml_data["wing_span"]
ref_pt_x = yaml_data["reference_point"]["X"]
ref_pt_y = yaml_data["reference_point"]["Y"]
ref_pt_z = yaml_data["reference_point"]["Z"]
if(span != 0 and area != 0):
ref_chord = float(area)/float(span)
else:
raise ValueError("Invalid reference chord value. Check area and wing span values.")
# Write the gathered model-wide parameters into the .avl file
with open(f'{plane_name}.avl','a') as avl_file:
avl_file.write('!Sref Cref Bref \n')
avl_file.write(f'{area} {str(ref_chord)} {span} \n')
avl_file.write('!Xref Yref Zref \n')
avl_file.write(f'{ref_pt_x} {ref_pt_y} {ref_pt_z} \n')
avl_file.close()
num_ctrl_surfaces = yaml_data["num_ctrl_surfaces"]
for i, control_surface in enumerate(yaml_data["control_surfaces"]):
# Wings always need to be defined from left to right
ctrl_surf_name = control_surface['name']
ctrl_surf_type = control_surface['type']
if ctrl_surf_type not in ctrl_surface_types:
raise ValueError(f'The selected type is invalid. Available types are: {ctrl_surface_types}')
# The order of control surfaces becomes important in the output parsing
# to correctly assign derivatives to particular surfaces.
ctrl_surface_order.append(ctrl_surf_type)
nchord = control_surface["nchord"]
cspace = control_surface["cspace"]
nspanwise = control_surface["nspan"]
sspace = control_surface["sspace"]
# TODO: Add more control surface types that also require Angles.
if ctrl_surf_type.lower() == 'aileron':
angle = control_surface["angle"]
#Translation of control surface, will move the whole surface to specified position
tx = control_surface["translation"]["X"]
ty = control_surface["translation"]["Y"]
tz = control_surface["translation"]["Z"]
# Write common part of this surface to .avl file
with open(f'{plane_name}.avl','a') as avl_file:
avl_file.write(sec_demark)
avl_file.write("\nSURFACE \n")
avl_file.write(f'{ctrl_surf_name} \n')
avl_file.write("!Nchordwise Cspace Nspanwise Sspace \n")
avl_file.write(f'{nchord} {cspace} {nspanwise} {sspace} \n')
# If we have a elevator, we can duplicate the defined control surface along the y-axis of the model
# as both sides are generally modelled and controlled as one in simulation. Adjust for split elevators if desired.
if ctrl_surf_type.lower() == 'elevator':
avl_file.write("\nYDUPLICATE\n")
avl_file.write("0.0\n\n")
# Elevators and Rudders do not require an angle of incidence.
if ctrl_surf_type.lower() == 'aileron':
avl_file.write("ANGLE \n")
avl_file.write(f'{angle} \n')
# Translate the surface to a particular position in space.
avl_file.write("TRANSLATE \n")
avl_file.write(f'{tx} {ty} {tz} \n')
avl_file.close()
# Define NACA airfoil shape.
# For help picking an airfoil go to: http://airfoiltools.com/airfoil/naca4digit
# NOTE: AVL can only use 4-digit NACA codes.
if ctrl_surf_type.lower() == "aileron":
naca_number = control_surface["naca"]
else:
# Provide a default NACA number for unused airfoils
naca_number = '0000'
# Iterating over each defined section for the control surface. There need to be at least
# two in order to define a left and right edge, but there is no upper limit.
# CRITICAL: ALWAYS DEFINE YOUR SECTION FROM LEFT TO RIGHT
for j, section in enumerate(control_surface["sections"]):
print(f'Defining {j}. section of {i+1}. control surface \n')
y = section["position"]["Y"]
z = section["position"]["Z"]
x = section["position"]["X"]
chord = section["chord"]
ainc = section["ainc"]
nspan = section["nspan"]
write_section(plane_name,x,y,z,chord,ainc,nspan,sspace,naca_number,ctrl_surf_type)
print(f'\nPARAMETER DEFINITION FOR {i+1}. CONTROL SURFACE COMPLETED \n')
# Calculation of Aspect Ratio (AR) and Mean Aerodynamic Chord (mac)
AR = str((float(span)*float(span))/float(area))
mac = str((2/3)*(float(area)/float(span)))
# Call shell script that will pass the generated .avl file to AVL
os.system(f'./process.sh {plane_name}')
# Call main function of avl parse script to parse the generated AVL files.
avl_out_parse.main(plane_name,frame_type,AR,mac,ref_pt_x,ref_pt_y,ref_pt_z,num_ctrl_surfaces,area,ctrl_surface_order,inputs.avl_path)
# Finally move all generated files to a new directory and show the generated geometry image:
result = subprocess.run(['pwd'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
# Save the output in a variable
current_path = result.stdout.strip()
# Run image plot from avl_automation directory.
os.system(f'mv ./{plane_name}.* ./{plane_name}' )
os.system(f'evince {current_path}/{plane_name}/{plane_name}.ps')
if __name__ == '__main__':
main()