Skip to content

Commit 757cd1e

Browse files
agvarghepyansys-ci-botpre-commit-ci[bot]RobPasMuePipKat
authored
docs: Ahmed body example for external aero simulation (#1886)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Roberto Pastor Muela <[email protected]> Co-authored-by: Kathy Pippert <[email protected]>
1 parent d3d6c77 commit 757cd1e

File tree

9 files changed

+360
-0
lines changed

9 files changed

+360
-0
lines changed

doc/changelog.d/1886.documentation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ahmed body example for external aero simulation
Loading
46.2 KB
Loading
Loading
51.4 KB
Loading
48.2 KB
Loading

doc/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ def intersphinx_pyansys_geometry(switcher_version: str):
313313
"examples/03_modeling/chamfer": "_static/thumbnails/chamfer.png",
314314
"examples/04_applied/01_naca_airfoils": "_static/thumbnails/naca_airfoils.png",
315315
"examples/04_applied/02_naca_fluent": "_static/thumbnails/naca_fluent.png",
316+
"examples/04_applied/03_ahmed_body_fluent": "_static/thumbnails/ahmed_body.png",
316317
}
317318
nbsphinx_epilog = """
318319
----

doc/source/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ applications.
6363

6464
examples/04_applied/01_naca_airfoils.mystnb
6565
examples/04_applied/02_naca_fluent.mystnb
66+
examples/04_applied/03_ahmed_body_fluent.mystnb
Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
---
2+
jupytext:
3+
text_representation:
4+
extension: .mystnb
5+
format_name: myst
6+
format_version: 0.13
7+
jupytext_version: 1.14.1
8+
kernelspec:
9+
display_name: Python 3 (ipykernel)
10+
language: python
11+
name: python3
12+
---
13+
# Applied: Develop an external aerodynamic simulation model for Fluent analysis
14+
15+
The Ahmed body is a simplified car model used to study airflow around vehicles. The wake (turbulent flow behind the body)
16+
depends on the slant angle:
17+
18+
- Less than 12 degrees: The airflow stays attached to the slant, creating low drag and a mostly two-dimensional flow.
19+
- 12 to 30 degrees: The flow becomes three-dimensional with strong c-pillar vortices, peaking at 30 degrees. This increases drag due to low-pressure areas on the rear surfaces.
20+
- More than 30 degrees: The flow fully separates from the slant, reducing drag and weakening the c-pillar vortices.
21+
22+
This example creates an Ahmed body with a slant angle of 20 degrees. It consists of these steps:
23+
1. Launch PyAnsys Geometry and define the default units.
24+
2. Create sketches for the Ahmed body, enclosure, and BOI (Body of Influence).
25+
3. Generate solid bodies from the sketches.
26+
4. Perform Boolean operations for region extraction.
27+
5. Group faces and define a named selection.
28+
6. Export model as a CAD file.
29+
7. Close session.
30+
31+
### Define function for sorting planar face pairs along any axis
32+
33+
This function is used to sort the planar faces along any of the coordinate axis. This is used primarily for sorting the faces to define
34+
the named selections.
35+
36+
```{code-cell} ipython3
37+
def face_identifier(faces, axis):
38+
"""
39+
Sort a pair of planar faces based on their positions along the specified coordinate axis.
40+
41+
Args:
42+
faces : List[IFace, IFace]
43+
List of planar face pairs.
44+
45+
axis : (string)
46+
Axis to sort the face pair on. Options are "x", "y", or "z".
47+
48+
Returns:
49+
IFace, IFace
50+
- IFace: Face with the centroid positioned behind the other face along the specified axis.
51+
- IFace: Face with the centroid positioned ahead of the other face along the specified axis.
52+
53+
"""
54+
min_face = ""
55+
max_face = ""
56+
if axis == "x":
57+
position = 0
58+
elif axis == "y":
59+
position = 1
60+
else:
61+
position = 2
62+
min_face_cor_val = faces[0].point(0.5, 0.5)[position]
63+
min_face = faces[0]
64+
max_face_cor_val = faces[0].point(0.5, 0.5)[position]
65+
max_face = faces[0]
66+
for face in faces[1:]:
67+
if face.point(0.5, 0.5)[position] < min_face_cor_val:
68+
min_face_cor_val = face.point(0.5, 0.5)[position]
69+
min_face = face
70+
continue
71+
elif face.point(0.5, 0.5)[position] > max_face_cor_val:
72+
max_face_cor_val = face.point(0.5, 0.5)[position]
73+
max_face = face
74+
return min_face, max_face
75+
76+
```
77+
78+
### Define function for calculating the vertical and horizontal distances based on the slant angle
79+
80+
81+
```{code-cell} ipython3
82+
def distance_calculator(hypo, slant_angle):
83+
"""
84+
Calculate the horizontal and vertical distances based on the hypotenuse and slant angle.
85+
86+
Args:
87+
hypo : int
88+
Length of the hypotenuse in millimeters.
89+
90+
slant_angle : int
91+
Slant angle in degrees.
92+
93+
Returns:
94+
slant_x (float): Horizontal distance calculated using the sine of the slant angle.
95+
slant_y (float): Vertical distance calculated using the cosine of the slant angle.
96+
97+
"""
98+
slant_x = hypo * math.cos(math.radians(slant_angle))
99+
slant_y = hypo * math.sin(math.radians(slant_angle))
100+
return slant_y, slant_x
101+
102+
```
103+
104+
### Launch PyAnsys geometry and define the default units
105+
Before you start creating the Ahmed body, you must import the necessary modules to create the model using PyAnsys Geometry.
106+
It's also a good practice to define the units before initiating the development of the sketch.
107+
108+
```{code-cell} ipython3
109+
from ansys.geometry.core import launch_modeler
110+
from ansys.geometry.core.sketch import Sketch
111+
from ansys.geometry.core.math import (
112+
Point2D,
113+
Plane,
114+
Point3D,
115+
UNITVECTOR3D_X,
116+
UNITVECTOR3D_Y,
117+
UNITVECTOR3D_Z,
118+
)
119+
from ansys.geometry.core.misc import UNITS, DEFAULT_UNITS
120+
121+
from ansys.geometry.core.plotting import GeometryPlotter
122+
import math
123+
import os
124+
125+
modeler = launch_modeler()
126+
DEFAULT_UNITS.LENGTH = UNITS.mm
127+
DEFAULT_UNITS.angle = UNITS.degrees
128+
129+
```
130+
131+
### Create sketches for the Ahmed body, enclosure, and BOI
132+
133+
Define the appropriate sketch planes parallel to the y-z and x-z planes, passing through the origin (namely `sketch_plane` and `sketch_plane_2` respectively).
134+
135+
#### Define the sketch planes
136+
137+
![Sketch Planes](../../_static/images/sketch_planes.jpg){width=500px, align=center}
138+
139+
```{code-cell} ipython3
140+
141+
# Define sketch plane on the y-z plane passing through the origin
142+
sketch_plane = Plane(
143+
origin=Point3D([0, 0, 0]),
144+
direction_x=UNITVECTOR3D_Y,
145+
direction_y=UNITVECTOR3D_Z,
146+
)
147+
148+
# Define sketch plane on the x-z plane passing through the origin
149+
sketch_plane_2 = Plane(
150+
origin=Point3D([0, 0, 0]),
151+
direction_x=UNITVECTOR3D_X,
152+
direction_y=UNITVECTOR3D_Z,
153+
)
154+
```
155+
#### Define the Ahmed body
156+
157+
![Ahmed body schematic image](../../_static/images/ahmed_body_schematic.jpg){width=500px, align=center}
158+
159+
```{code-cell} ipython3
160+
# Calculate the horizontal and vertical distance based on slant angle of 20 degrees
161+
slant_y, slant_x = distance_calculator(hypo=222, slant_angle=20)
162+
163+
# Define sketch for the Ahmed body
164+
ahmed_body_sketch = Sketch(sketch_plane)
165+
ahmed_body_sketch.segment(
166+
start=Point2D([50, 0]), end=Point2D([338 - slant_y, 0])
167+
).segment_to_point(Point2D([338, slant_x])).segment_to_point(
168+
Point2D([338, 944])
169+
).arc_to_point(
170+
end=Point2D([238, 1044]), center=Point2D([238, 944]), clockwise=False
171+
).segment_to_point(
172+
Point2D([150, 1044])
173+
).arc_to_point(
174+
end=Point2D([50, 944]), center=Point2D([150, 944]), clockwise=False
175+
).segment_to_point(
176+
end=Point2D([50, 0])
177+
)
178+
ahmed_body_sketch.plot()
179+
```
180+
181+
#### Define the enclosure
182+
183+
![Enclosure schematic image](../../_static/images/enclosure_schematic.jpg){width=500px, align=center}
184+
185+
186+
```{code-cell} ipython3
187+
# Define sketch for enclosure
188+
enclosure_sketch = Sketch(plane=sketch_plane)
189+
enclosure_sketch.box(center=Point2D([1014 / 2, 0]), height=4176, width=1014)
190+
enclosure_sketch.plot()
191+
192+
# Define sketch for mounting 1
193+
mount_sketch_1 = Sketch(sketch_plane_2)
194+
mount_sketch_1.circle(center=Point2D([163.5, 792]), radius=15)
195+
196+
# Define sketch for mounting 2
197+
mount_sketch_2 = Sketch(sketch_plane_2)
198+
mount_sketch_2.circle(center=Point2D([163.5, 322]), radius=15)
199+
200+
# Define sketch for the fillet
201+
ahmed_body_fillet_sketch = Sketch(sketch_plane_2)
202+
ahmed_body_fillet_sketch.segment(
203+
start=Point2D([194.5, 944]), end=Point2D([194.5, 1044])
204+
).segment_to_point(Point2D([94.5, 1044])).arc_to_point(
205+
Point2D([194.5, 944]), center=Point2D([94.5, 944]), clockwise=True
206+
)
207+
```
208+
209+
#### Define the BOI
210+
211+
![BOI schematic image](../../_static/images/boi_schematic.jpg){width=500px, align=center}
212+
213+
```{code-cell} ipython3
214+
# Define sketch for BOI
215+
boi_sketch = Sketch(sketch_plane_2)
216+
boi_sketch.box(center=Point2D([0, -325]), width=1000, height=1450)
217+
boi_sketch.plot()
218+
```
219+
220+
### Generate solid bodies from the sketches
221+
From the 2D sketches, generate 3D models by extruding the sketch. First create the design body (namely `ahmed_model`, which is the root part. A component named `Component1` is
222+
created under the root part. All the bodies generated as a part of sketch extrusion would be placed within `Component1`.
223+
224+
```{code-cell} ipython3
225+
226+
# Create design object
227+
design = modeler.create_design("ahmed_model")
228+
229+
# Create component
230+
component_1 = design.add_component("Component1")
231+
232+
# Create body `ahmed_body` by extrusion
233+
ahmed_body = component_1.extrude_sketch(
234+
"ahmed_body", sketch=ahmed_body_sketch, distance=194.5
235+
)
236+
237+
# Create body `ahmed_body_fillet` by cut extrusion
238+
ahmed_body_fillet = component_1.extrude_sketch(
239+
"ahmed_body_fillet", sketch=ahmed_body_fillet_sketch, distance=-500, cut=True
240+
)
241+
242+
# Create body `enclosure` by extrusion
243+
enclosure = component_1.extrude_sketch(
244+
"Solid1", sketch=enclosure_sketch, distance=1167
245+
)
246+
247+
# Create body `mounting_1` by extrusion
248+
mount_1 = component_1.extrude_sketch(
249+
"mount_1", sketch=mount_sketch_1, distance=-100
250+
)
251+
252+
# Create body `mounting_2` by extrusion
253+
mount_2 = component_1.extrude_sketch(
254+
"mount_2", sketch=mount_sketch_2, distance=-100
255+
)
256+
257+
# Create body `boi` by extrusion
258+
# The direction is negative since the sketch is created in X-Z plane, resulting in the direction of normal to be parallel to the -Y axis.
259+
boi_body = design.extrude_sketch(
260+
"boi", sketch=boi_sketch, distance=500, direction="-"
261+
)
262+
```
263+
264+
### Perform Boolean operations for region extraction
265+
266+
```{code-cell} ipython3
267+
enclosure.subtract([ahmed_body, mount_1, mount_2])
268+
enclosure.plot()
269+
```
270+
271+
### Group faces and define named selection
272+
273+
274+
```{code-cell} ipython3
275+
plane_surface = []
276+
cylindrical_surface = []
277+
278+
# Group faces of enclosure based on topology
279+
for face in enclosure.faces:
280+
if face.surface_type.name == "SURFACETYPE_PLANE":
281+
plane_surface.append(face)
282+
elif face.surface_type.name == "SURFACETYPE_CYLINDER":
283+
cylindrical_surface.append(face)
284+
285+
wall_mount = []
286+
287+
# Identify faces associated with mounting
288+
for cyl_face in cylindrical_surface:
289+
if cyl_face.point(0, 0.5)[1] < 0.050:
290+
wall_mount.append(cyl_face)
291+
292+
# Identify faces associated with enclosure extremes
293+
outlet_face, inlet_face = face_identifier(faces=plane_surface, axis="z")
294+
symmetry_face, symmetry_x_pos = face_identifier(faces=plane_surface, axis="x")
295+
ground, top = face_identifier(faces=plane_surface, axis="y")
296+
297+
298+
# Create named selection
299+
design.create_named_selection("wall_mount", faces=wall_mount)
300+
design.create_named_selection("inlet", faces=[inlet_face])
301+
design.create_named_selection("outlet", faces=[outlet_face])
302+
design.create_named_selection("wall_ground", faces=[ground])
303+
design.create_named_selection("symmetry_top", faces=[top])
304+
design.create_named_selection("symmetry_center_plane", faces=[symmetry_face])
305+
design.create_named_selection("symmetry_x_pos", faces=[symmetry_x_pos])
306+
design.create_named_selection(
307+
"ahmed_body_20_0degree_boi_half-boi", faces=boi_body.faces
308+
)
309+
310+
body_planar_surface = list(
311+
set(plane_surface)
312+
- set([outlet_face, inlet_face, symmetry_face, symmetry_x_pos, ground, top])
313+
)
314+
body_circular_surface = list(set(cylindrical_surface) - set(wall_mount))
315+
316+
rear_surface, front_surface = face_identifier(faces=body_planar_surface, axis="z")
317+
bottom_surface, top_surface = face_identifier(faces=body_planar_surface, axis="y")
318+
side_suface, symmetry_surface = face_identifier(faces=body_planar_surface, axis="x")
319+
320+
body_circular_surface.append(front_surface)
321+
322+
design.create_named_selection("wall_ahmed_body_front", faces=body_circular_surface)
323+
design.create_named_selection(
324+
"wall_ahmed_body_main",
325+
faces=[symmetry_surface, bottom_surface, top_surface],
326+
)
327+
328+
# Identify the face that forms a 20-degree angle with the y-axis.
329+
for face in body_planar_surface:
330+
if round(math.degrees(math.acos(abs(face.normal().y)))) == 20:
331+
hypo_face = face
332+
design.create_named_selection(
333+
"wall_ahmed_body_rear", faces=[hypo_face, rear_surface]
334+
)
335+
```
336+
337+
### Export model as a PMDB file
338+
339+
Export the geometry into a Fluent-compatible format. The following code exports the geometry into a PMDB file, which retains the named selections.
340+
341+
```{code-cell} ipython3
342+
343+
# Save design
344+
file = design.export_to_pmdb()
345+
print(f"Design saved to {file}")
346+
```
347+
You can import the exported PMDB file into Fluent to set up the mesh and perform the simulation.
348+
For an example of how to set up the mesh and boundary conditions in Fluent, see the [Ahmed Body External Aerodynamics Simulation](https://examples.fluent.docs.pyansys.com/version/dev/examples/00-released_examples/00-ahmed_body_workflow.html) example in the Fluent documentation.
349+
350+
### Close session
351+
352+
```{code-cell} ipython3
353+
modeler.close()
354+
```
355+
356+
### References
357+
[1] S.R. Ahmed, G. Ramm, Some Salient Features of the Time-Averaged Ground Vehicle Wake,SAE-Paper 840300,1984

0 commit comments

Comments
 (0)