Wrap a toy car for a flow analysis#

Summary: This example demonstrates how to wrap a toy car for a flow analysis.

Objective#

This example wraps a toy car and volume meshes with a tetrahedral mesh with prisms. It uses several meshing utilities available in the lucid class for convenience and ease.

Toy car wrap.

Procedure#

  1. Launch an Ansys Prime Server instance.

  2. Instantiate the meshing utilities from the lucid class.

  3. Import the geometry.

  4. Coarse wrap parts with holes to clean up.

  5. Extract the fluid region using a wrapper.

  6. Check that the wrap surface is closed and that the quality is suitable.

  7. Mesh only fluid with tetrahedral elements and boundary layer refinement.

  8. Create face zones from labels imported from the geometry.

  9. Print statistics on the generated mesh.

  10. Improve the mesh quality.

  11. Write a CAS file for use in the Fluent solver.

  12. Exit the PyPrimeMesh session.

Launch Ansys Prime Server#

Import all necessary modules and launch an instance of Ansys Prime Server. From the PyPrimeMesh client get the model. Instantiate meshing utilities from the lucid class.

import os
import tempfile

import ansys.meshing.prime as prime
from ansys.meshing.prime.graphics import PrimePlotter

prime_client = prime.launch_prime()
model = prime_client.model

mesh_util = prime.lucid.Mesh(model)
Using Ansys Prime Server from container ansys-prime-server-5

Import geometry#

Download the toy car geometry (FMD) file exported by SpaceClaim. Import the geometry and display everything except the tunnel.

# For Windows OS users, scdoc is also available:
# toy_car = prime.examples.download_toy_car_scdoc()

toy_car = prime.examples.download_toy_car_fmd()

mesh_util.read(file_name=toy_car)

scope = prime.ScopeDefinition(model, part_expression="* !*tunnel*")

pl = PrimePlotter()
pl.plot(model, scope)
pl.show()
04 lucid toy car

Close holes#

Several parts are open surfaces (with holes). Coarse wrap to close the holes and delete the originals. You could use leakage detection to close these regions. This example uses a coarse wrap and disables feature edge refinement to walk over the holes. As this is not the final wrap, this example does not remesh after the wrap. Wrapping each object in turn avoids coarse wrap bridging across narrow gaps.

coarse_wrap = {"cabin": 1.5, "exhaust": 0.6, "engine": 1.5}

for part_name in coarse_wrap:
    # Each open part before wrap
    scope = prime.ScopeDefinition(model, part_expression=part_name)
    pl = PrimePlotter()
    pl.plot(model, scope)
    pl.show()
    closed_part = mesh_util.wrap(
        input_parts=part_name,
        max_size=coarse_wrap[part_name],
        remesh_postwrap=False,
        enable_feature_octree_refinement=False,
    )
    # Closed part with no hole
    scope = prime.ScopeDefinition(model, part_expression=closed_part.name)
    pl = PrimePlotter()
    pl.plot(model, scope)
    pl.show()
04 lucid toy car
04 lucid toy car
04 lucid toy car
04 lucid toy car
04 lucid toy car
04 lucid toy car

Extract fluid using a wrapper#

Wrap the full model and extract the largest internal region as the fluid. Create edges at intersecting regions to improve the quality. Refine mesh to avoid contact between different parts. The new wrap object replaces all original geometry unless keep_input is set to True. Volumes are generated from the wrap for use later.

wrap_part = mesh_util.wrap(
    min_size=0.1,
    max_size=2.0,
    region_extract=prime.WrapRegion.LARGESTINTERNAL,
    create_intersection_loops=True,
    contact_prevention_size=0.1,
)

print(model)
Part Summary:

Part Name: __wrap__.3
Part ID: 23
    1 Edge Zonelets
    93 Face Zonelets
    0 Cell Zonelets

    0 Edge Zones
        Edge Zone Name(s) : []
    5 Face Zones
        Face Zone Name(s) : [surface, surface.1, cabin, exhaust, engine]
    6 Volume Zones
        Volume Zone Name(s) : [__wrap__.2, __wrap__.1, __wrap__, component25, component24, tunnel.1]

    24 Label(s)
        Names: [___geom_features___, __extracted__features__, __intersect_loops__, __wrap__, __wrap__.1, __wrap__.2, cabin, component21, component22, component24, component25, engine, exhaust, ground, inlet, outer, outlet, overlap_floor, tunnel, unreferenced, wheel_1, wheel_2, wheel_3, wheel_4]

    Bounding box (-31.1366 -59.1336 1.49055)
                 (29.3366 40.1337 52.0558)

Check wrap#

Check that the wrap surface is closed and that the quality is suitable to use as surface mesh.

scope = prime.ScopeDefinition(model=model, part_expression=wrap_part.name)
diag = prime.SurfaceSearch(model)

diag_params = prime.SurfaceDiagnosticSummaryParams(
    model,
    scope=scope,
    compute_free_edges=True,
    compute_multi_edges=True,
    compute_self_intersections=True,
)

diag_res = diag.get_surface_diagnostic_summary(diag_params)

print('Number of free edges', diag_res.n_free_edges)
print('Number of multi edges', diag_res.n_multi_edges)
print('Number of self intersections', diag_res.n_self_intersections)

face_quality_measures = [prime.FaceQualityMeasure.SKEWNESS, prime.FaceQualityMeasure.ASPECTRATIO]
quality_params = prime.SurfaceQualitySummaryParams(
    model=model, scope=scope, face_quality_measures=face_quality_measures, quality_limit=[0.9, 20]
)

quality = prime.SurfaceSearch(model)
qual_summary_res = quality.get_surface_quality_summary(quality_params)

for summary_res in qual_summary_res.quality_results:
    print("\nMax value of ", summary_res.measure_name, ": ", summary_res.max_quality)
    print("Faces above limit: ", summary_res.n_found)
Number of free edges 0
Number of multi edges 0
Number of self intersections 0

Max value of  Skewness :  0.888453
Faces above limit:  0

Max value of  AspectRatio :  11.18
Faces above limit:  0

Create zones#

Create face zones from labels imported from the geometry that can be used in the solver to define boundary conditions. If specifying individual labels to create zones, the order is important. The last label in the list wins. Providing no label_expression flattens all labels into zones. For example, if LabelA and LabelB are overlapping, three zones are created: LabelA, LabelB, and LabelA_LabelB.

mesh_util.create_zones_from_labels()

print(model)
Part Summary:

Part Name: __wrap__.3
Part ID: 23
    1 Edge Zonelets
    93 Face Zonelets
    0 Cell Zonelets

    0 Edge Zones
        Edge Zone Name(s) : []
    21 Face Zones
        Face Zone Name(s) : [surface, surface.1, cabin, exhaust, engine, __wrap__.2_engine_unreferenced, __wrap__.1_exhaust_unreferenced, __wrap___cabin_unreferenced, unreferenced_wheel_2, unreferenced_wheel_3, unreferenced_wheel_4, unreferenced_wheel_1, outer_unreferenced, component24_unreferenced, component21_unreferenced, tunnel_unreferenced, ground_overlap_floor_tunnel, outlet_tunnel, component25_unreferenced, inlet_tunnel, component22_unreferenced]
    6 Volume Zones
        Volume Zone Name(s) : [__wrap__.2, __wrap__.1, __wrap__, component25, component24, tunnel.1]

    24 Label(s)
        Names: [___geom_features___, __extracted__features__, __intersect_loops__, __wrap__, __wrap__.1, __wrap__.2, cabin, component21, component22, component24, component25, engine, exhaust, ground, inlet, outer, outlet, overlap_floor, tunnel, unreferenced, wheel_1, wheel_2, wheel_3, wheel_4]

    Bounding box (-31.1366 -59.1336 1.49055)
                 (29.3366 40.1337 52.0558)

Volume mesh#

Mesh only fluid volume with tetrahedral elements and boundary layer refinement. This example does not mesh other volumetric regions. Volume zones exist already for volume meshing and passing to the solver. The largest face zonelet is used by default to define volume zone names at creation. After volume meshing, you can see that you have a cell zonelet in the part summary.

volume = prime.lucid.VolumeScope(
    part_expression=wrap_part.name,
    entity_expression="tunnel*",
    scope_evaluation_type=prime.ScopeEvaluationType.ZONES,
)

# Use expressions to define which surfaces to grow inflation layers from
mesh_util.volume_mesh(
    scope=volume,
    prism_layers=3,
    prism_surface_expression="*cabin*,*component*,*engine*,*exhaust*,*ground*,*outer*,*wheel*",
    prism_volume_expression="tunnel*",
)

scope = prime.ScopeDefinition(
    model,
    label_expression="*cabin*,*component*,*engine*,*exhaust*,*ground*,*outer*,*wheel*,*outlet*",
)

pl = PrimePlotter()
pl.plot(model, scope)
pl.show()
print(model)
04 lucid toy car
Part Summary:

Part Name: __wrap__.3
Part ID: 23
    1 Edge Zonelets
    93 Face Zonelets
    1 Cell Zonelets

    0 Edge Zones
        Edge Zone Name(s) : []
    21 Face Zones
        Face Zone Name(s) : [surface, surface.1, cabin, exhaust, engine, __wrap__.2_engine_unreferenced, __wrap__.1_exhaust_unreferenced, __wrap___cabin_unreferenced, unreferenced_wheel_2, unreferenced_wheel_3, unreferenced_wheel_4, unreferenced_wheel_1, outer_unreferenced, component24_unreferenced, component21_unreferenced, tunnel_unreferenced, ground_overlap_floor_tunnel, outlet_tunnel, component25_unreferenced, inlet_tunnel, component22_unreferenced]
    6 Volume Zones
        Volume Zone Name(s) : [__wrap__.2, __wrap__.1, __wrap__, component25, component24, tunnel.1]

    24 Label(s)
        Names: [___geom_features___, __extracted__features__, __intersect_loops__, __wrap__, __wrap__.1, __wrap__.2, cabin, component21, component22, component24, component25, engine, exhaust, ground, inlet, outer, outlet, overlap_floor, tunnel, unreferenced, wheel_1, wheel_2, wheel_3, wheel_4]

    Bounding box (-31.1366 -59.1336 1.49055)
                 (29.3366 40.1337 52.0558)

Improve quality#

Because the mesh quality is poor, use the improve_by_auto_node_move method to improve the mesh.

improve = prime.VolumeMeshTool(model=model)
params = prime.AutoNodeMoveParams(
    model=model,
    quality_measure=prime.CellQualityMeasure.SKEWNESS,
    target_quality=0.95,
    dihedral_angle=90,
    n_iterations_per_node=50,
    restrict_boundary_nodes_along_surface=True,
    n_attempts=10,
)

improve.improve_by_auto_node_move(
    part_id=wrap_part.id,
    cell_zonelets=wrap_part.get_cell_zonelets(),
    boundary_zonelets=wrap_part.get_face_zonelets(),
    params=params,
)

result = vtool.check_mesh(part_id=wrap_part.id, params=prime.CheckMeshParams(model=model))

print("Non positive volumes:", result.has_non_positive_volumes)
print("Non positive areas:", result.has_non_positive_areas)
print("Invalid shape:", result.has_invalid_shape)
print("Left handed faces:", result.has_left_handed_faces)

qual_summary_res = quality.get_volume_quality_summary(
    prime.VolumeQualitySummaryParams(
        model=model,
        scope=scope,
        cell_quality_measures=[prime.CellQualityMeasure.SKEWNESS],
        quality_limit=[0.95],
    )
)

for summary_res in qual_summary_res.quality_results_part:
    print("\nMax value of ", summary_res.measure_name, ": ", summary_res.max_quality)
    print("Cells above limit: ", summary_res.n_found)
Non positive volumes: False
Non positive areas: False
Invalid shape: False
Left handed faces: False

Max value of  Skewness :  0.949307
Cells above limit:  0

Write mesh#

Write a CAS file for use in the Fluent solver.

with tempfile.TemporaryDirectory() as temp_folder:
    mesh_file = os.path.join(temp_folder, "toy_car_lucid.cas")
    mesh_util.write(mesh_file)
    assert os.path.exists(mesh_file)
    print("\nExported file:\n", mesh_file)
Exported file:
 /tmp/tmpyon8jrre/toy_car_lucid.cas

Exit PyPrimeMesh#

prime_client.exit()

Total running time of the script: (3 minutes 46.131 seconds)

Gallery generated by Sphinx-Gallery