Automotive Report#
This script demonstrates the automated generation of a comprehensive aerodynamic report for the DrivAer automotive model using the Flow360 Python API and its reporting plugin.
It sets up and runs a series of steady-state simulations at varying yaw angles (beta = 0/5/10 degrees).
Post-simulation, it utilizes the flow360.plugins.report module to compile results, including statistical data, force coefficients, and various flow visualizations (e.g., surface pressure coefficient, wall shear stress, flow field slices, isosurfaces) into a structured PDF document.
1import flow360 as fl
2from flow360 import u
3from flow360.examples import DrivAer
4from flow360.log import log
5from flow360.plugins.report.report import ReportTemplate
6from flow360.plugins.report.report_items import (
7 BottomCamera,
8 Chart2D,
9 Chart3D,
10 FrontCamera,
11 FrontLeftBottomCamera,
12 FrontLeftTopCamera,
13 Inputs,
14 LeftCamera,
15 RearCamera,
16 RearLeftTopCamera,
17 RearRightBottomCamera,
18 Settings,
19 Summary,
20 Table,
21 TopCamera,
22)
23from flow360.plugins.report.utils import Average, DataItem, Delta, Expression, Variable
24
25DrivAer.get_files()
26
27project = fl.Project.from_volume_mesh(
28 DrivAer.mesh_filename,
29 name="Automotive DrivAer",
30)
31
32vm = project.volume_mesh
33
34log.info("Volume mesh contains the following boundaries:")
35for boundary in vm.boundary_names:
36 log.info("Boundary: " + boundary)
37
38freestream_surfaces = ["blk-1/WT_side1", "blk-1/WT_side2", "blk-1/WT_inlet", "blk-1/WT_outlet"]
39slip_wall_surfaces = ["blk-1/WT_ceiling", "blk-1/WT_ground_front", "blk-1/WT_ground"]
40wall_surfaces = list(set(vm.boundary_names) - set(freestream_surfaces) - set(slip_wall_surfaces))
41
42cases = []
43
44for beta in [0, 5, 10]:
45 with fl.SI_unit_system:
46 params = fl.SimulationParams(
47 meshing=None,
48 reference_geometry=fl.ReferenceGeometry(area=2.17, moment_length=2.7862),
49 operating_condition=fl.AerospaceCondition(velocity_magnitude=40, beta=beta * u.deg),
50 models=[
51 fl.Wall(surfaces=[vm[i] for i in wall_surfaces], use_wall_function=True),
52 fl.Freestream(
53 surfaces=[vm[i] for i in freestream_surfaces],
54 ),
55 fl.SlipWall(
56 surfaces=[vm[i] for i in slip_wall_surfaces],
57 ),
58 ],
59 user_defined_fields=[
60 fl.UserDefinedField(
61 name="Cpx",
62 expression="double prel = primitiveVars[4] - pressureFreestream;"
63 + "double PressureForce_X = prel * nodeNormals[0]; "
64 + "Cpx = PressureForce_X / (0.5 * MachRef * MachRef) / magnitude(nodeNormals);",
65 ),
66 ],
67 outputs=[
68 fl.SurfaceOutput(
69 surfaces=vm["*"],
70 output_fields=[
71 "Cp",
72 "Cf",
73 "yPlus",
74 "CfVec",
75 "primitiveVars",
76 "wall_shear_stress_magnitude",
77 "Cpx",
78 ],
79 ),
80 fl.SliceOutput(
81 entities=[
82 *[
83 fl.Slice(
84 name=f"slice_y_{name}",
85 normal=(0, 1, 0),
86 origin=(0, y, 0),
87 )
88 for name, y in zip(
89 ["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8]
90 )
91 ],
92 *[
93 fl.Slice(
94 name=f"slice_z_{name}",
95 normal=(0, 0, 1),
96 origin=(0, 0, z),
97 )
98 for name, z in zip(
99 ["neg0_2", "0", "0_2", "0_4", "0_6", "0_8"],
100 [-0.2, 0, 0.2, 0.4, 0.6, 0.8],
101 )
102 ],
103 ],
104 output_fields=["velocity", "velocity_x", "velocity_y", "velocity_z"],
105 ),
106 fl.IsosurfaceOutput(
107 output_fields=["Cp", "Mach"],
108 isosurfaces=[
109 fl.Isosurface(
110 name="isosurface-cpt",
111 iso_value=-1,
112 field="Cpt",
113 ),
114 ],
115 ),
116 fl.ProbeOutput(
117 entities=[fl.Point(name="point1", location=(10, 0, 1))],
118 output_fields=["velocity"],
119 ),
120 ],
121 )
122
123 case_new = project.run_case(params=params, name=f"DrivAer 5.7M - beta={beta}")
124
125 cases.append(case_new)
126
127# wait until all cases finish running
128for case in cases:
129 case.wait()
130
131exclude = ["blk-1/WT_ground_close", "blk-1/WT_ground_patch"]
132size = "5.7M"
133
134exclude += freestream_surfaces + slip_wall_surfaces
135
136top_camera = TopCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
137top_camera_slice = TopCamera(pan_target=(2.5, 0, 0), dimension=8, dimension_dir="width")
138side_camera = LeftCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
139side_camera_slice = LeftCamera(pan_target=(2.5, 0, 1.5), dimension=8, dimension_dir="width")
140rear_camera = RearCamera(dimension=2.5, dimension_dir="width")
141front_camera = FrontCamera(dimension=2.5, dimension_dir="width")
142bottom_camera = BottomCamera(pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width")
143front_left_bottom_camera = FrontLeftBottomCamera(
144 pan_target=(1.5, 0, 0), dimension=5, dimension_dir="width"
145)
146rear_right_bottom_camera = RearRightBottomCamera(
147 pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width"
148)
149front_left_top_camera = FrontLeftTopCamera(
150 pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width"
151)
152rear_left_top_camera = RearLeftTopCamera(pan_target=(1.5, 0, 0), dimension=6, dimension_dir="width")
153
154cameras_geo = [
155 top_camera,
156 side_camera,
157 rear_camera,
158 bottom_camera,
159 front_left_bottom_camera,
160 rear_right_bottom_camera,
161]
162
163limits_cp = [(-1, 1), (-1, 1), (-1, 1), (-0.3, 0), (-0.3, 0), (-1, 1), (-1, 1), (-1, 1)]
164cameras_cp = [
165 front_camera,
166 front_left_top_camera,
167 side_camera,
168 rear_left_top_camera,
169 rear_camera,
170 bottom_camera,
171 front_left_bottom_camera,
172 rear_right_bottom_camera,
173]
174
175avg = Average(fraction=0.1)
176CD = DataItem(data="surface_forces/totalCD", exclude=exclude, title="CD", operations=avg)
177
178CL = DataItem(data="surface_forces/totalCL", exclude=exclude, title="CL", operations=avg)
179
180CDA = DataItem(
181 data="surface_forces",
182 exclude=exclude,
183 title="CD*area",
184 variables=[Variable(name="area", data="params.reference_geometry.area")],
185 operations=[Expression(expr="totalCD * area"), avg],
186)
187
188CLf = DataItem(
189 data="surface_forces",
190 exclude=exclude,
191 title="CLf",
192 operations=[Expression(expr="1/2*totalCL + totalCMy"), avg],
193)
194
195CLr = DataItem(
196 data="surface_forces",
197 exclude=exclude,
198 title="CLr",
199 operations=[Expression(expr="1/2*totalCL - totalCMy"), avg],
200)
201
202CFy = DataItem(data="surface_forces/totalCFy", exclude=exclude, title="CS", operations=avg)
203
204statistical_data = [
205 "params/reference_geometry/area",
206 CD,
207 CDA,
208 Delta(data=CD),
209 CL,
210 CLf,
211 CLr,
212 CFy,
213 "volume_mesh/stats/n_nodes",
214 "params/time_stepping/max_steps",
215]
216statistical_table = Table(
217 data=statistical_data,
218 section_title="Statistical data",
219 formatter=[
220 (
221 None
222 if d
223 in [
224 "params/reference_geometry/area",
225 "volume_mesh/stats/n_nodes",
226 "params/time_stepping/max_steps",
227 ]
228 else ".4f"
229 )
230 for d in statistical_data
231 ],
232)
233
234geometry_screenshots = [
235 Chart3D(
236 section_title="Geometry",
237 items_in_row=2,
238 force_new_page=True,
239 show="boundaries",
240 camera=camera,
241 exclude=exclude,
242 fig_name=f"geo_{i}",
243 )
244 for i, camera in enumerate(cameras_geo)
245]
246cpt_screenshots = [
247 Chart3D(
248 section_title="Isosurface, Cpt=-1",
249 items_in_row=2,
250 force_new_page=True,
251 show="isosurface",
252 iso_field="Cpt",
253 exclude=exclude,
254 camera=camera,
255 )
256 for camera in cameras_cp
257]
258cfvec_screenshots = [
259 Chart3D(
260 section_title="CfVec",
261 items_in_row=2,
262 force_new_page=True,
263 show="boundaries",
264 field="CfVec",
265 mode="lic",
266 limits=(1e-4, 10),
267 is_log_scale=True,
268 exclude=exclude,
269 camera=camera,
270 )
271 for camera in cameras_cp
272]
273y_slices_lic_screenshots = [
274 Chart3D(
275 section_title=f"Slice velocity LIC y={y}",
276 items_in_row=2,
277 force_new_page=True,
278 show="slices",
279 include=[f"slice_y_{name}"],
280 field="velocityVec",
281 mode="lic",
282 limits=(0 * u.m / u.s, 50 * u.m / u.s),
283 camera=side_camera_slice,
284 fig_name=f"slice_y_vec_{name}",
285 )
286 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
287]
288y_slices_screenshots = [
289 Chart3D(
290 section_title=f"Slice velocity y={y}",
291 items_in_row=2,
292 force_new_page=True,
293 show="slices",
294 include=[f"slice_y_{name}"],
295 field="velocity",
296 limits=(0 * u.m / u.s, 50 * u.m / u.s),
297 camera=side_camera_slice,
298 fig_name=f"slice_y_{name}",
299 )
300 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
301]
302y_slices_lic_screenshots = [
303 Chart3D(
304 section_title=f"Slice velocity LIC y={y}",
305 items_in_row=2,
306 force_new_page=True,
307 show="slices",
308 include=[f"slice_y_{name}"],
309 field="velocityVec",
310 mode="lic",
311 limits=(0 * u.m / u.s, 50 * u.m / u.s),
312 camera=side_camera_slice,
313 fig_name=f"slice_y_vec_{name}",
314 )
315 for name, y in zip(["0", "0_2", "0_4", "0_6", "0_8"], [0, 0.2, 0.4, 0.6, 0.8])
316]
317z_slices_screenshots = [
318 Chart3D(
319 section_title=f"Slice velocity z={z}",
320 items_in_row=2,
321 force_new_page=True,
322 show="slices",
323 include=[f"slice_z_{name}"],
324 field="velocity",
325 limits=(0 * u.m / u.s, 50 * u.m / u.s),
326 camera=top_camera_slice,
327 fig_name=f"slice_z_{name}",
328 )
329 for name, z in zip(["neg0_2", "0", "0_2", "0_4", "0_6", "0_8"], [-0.2, 0, 0.2, 0.4, 0.6, 0.8])
330]
331y_plus_screenshots = [
332 Chart3D(
333 section_title="y+",
334 items_in_row=2,
335 show="boundaries",
336 field="yPlus",
337 exclude=exclude,
338 limits=(0, 5),
339 camera=camera,
340 fig_name=f"yplus_{i}",
341 )
342 for i, camera in enumerate([top_camera, bottom_camera])
343]
344cp_screenshots = [
345 Chart3D(
346 section_title="Cp",
347 items_in_row=2,
348 show="boundaries",
349 field="Cp",
350 exclude=exclude,
351 limits=limits,
352 camera=camera,
353 fig_name=f"cp_{i}",
354 )
355 for i, (limits, camera) in enumerate(zip(limits_cp, cameras_cp))
356]
357cpx_screenshots = [
358 Chart3D(
359 section_title="Cpx",
360 items_in_row=2,
361 show="boundaries",
362 field="Cpx",
363 exclude=exclude,
364 limits=(-0.3, 0.3),
365 camera=camera,
366 fig_name=f"cpx_{i}",
367 )
368 for i, camera in enumerate(cameras_cp)
369]
370wall_shear_screenshots = [
371 Chart3D(
372 section_title="Wall shear stress magnitude",
373 items_in_row=2,
374 show="boundaries",
375 field="wallShearMag",
376 exclude=exclude,
377 limits=(0 * u.Pa, 5 * u.Pa),
378 camera=camera,
379 fig_name=f"wallShearMag_{i}",
380 )
381 for i, camera in enumerate(cameras_cp)
382]
383
384report = ReportTemplate(
385 title="Aerodynamic analysis of DrivAer",
386 items=[
387 Summary(),
388 Inputs(),
389 statistical_table,
390 Chart2D(
391 x="x_slicing_force_distribution/X",
392 y="x_slicing_force_distribution/totalCumulative_CD_Curve",
393 fig_name="totalCumulative_CD_Curve",
394 background="geometry",
395 exclude=exclude,
396 ),
397 Chart2D(
398 x="surface_forces/pseudo_step",
399 y="surface_forces/totalCD",
400 section_title="Drag Coefficient",
401 fig_name="cd_fig",
402 exclude=exclude,
403 focus_x=(1 / 3, 1),
404 ),
405 *geometry_screenshots,
406 *cp_screenshots,
407 *cpx_screenshots,
408 *cpt_screenshots,
409 *y_slices_screenshots,
410 *y_slices_lic_screenshots,
411 *z_slices_screenshots,
412 *y_plus_screenshots,
413 *wall_shear_screenshots,
414 ],
415 settings=Settings(dpi=150),
416)
417
418report = report.create_in_cloud(
419 f"{size}-{len(cases)}cases-slices-using-groups-Cpt, Cpx, wallShear, dpi=default",
420 cases,
421)
422
423report.wait()
424report.download("report.pdf")
Notes#
Demonstrates the use of the report module, including
ReportTemplateand various report items (e.g.,Chart3D,Table,DataItem), to automatically generate a detailed PDF report from simulation data.Illustrates a workflow for conducting a parametric study by programmatically submitting multiple simulation cases with varying operating conditions (yaw angle
beta) using theProjectinterface.Shows the configuration of diverse output types (
SurfaceOutput,SliceOutput,IsosurfaceOutput) to capture data for detailed flow analysis and visualization within the report.