From 9a02b2e351f5d92a7c09b451b688c5667e205ffb Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 13:08:46 +0100 Subject: [PATCH 01/27] fix(temp): Serialize sphinx jobs --- .github/workflows/ci_cd.yml | 2 +- doc/Makefile | 2 +- doc/make.bat | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index fb22edab7e..78554040b6 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -108,7 +108,7 @@ jobs: with: check-links: false needs-quarto: true - sphinxopts: "-j auto --keep-going" + sphinxopts: "-j 1 --keep-going" env: PYPRIMEMESH_LAUNCH_CONTAINER: 1 PYPRIMEMESH_SPHINX_BUILD: 1 diff --git a/doc/Makefile b/doc/Makefile index 5f9d1a9451..151e09fb5a 100755 --- a/doc/Makefile +++ b/doc/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = -j auto +SPHINXOPTS = -j 1 SPHINXBUILD = sphinx-build SOURCEDIR = source BUILDDIR = _build diff --git a/doc/make.bat b/doc/make.bat index 05a0bf9dc2..69c9588fc5 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -8,7 +8,7 @@ if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) if "%SPHINXOPTS%" == "" ( - set SPHINXOPTS=-j auto + set SPHINXOPTS=-j 1 ) set SOURCEDIR=source set BUILDDIR=_build From f1008c4dc248da567f200632318f5b69440d7f22 Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 13:09:40 +0100 Subject: [PATCH 02/27] fix(temp): Remove job dependency --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 78554040b6..839cdfb93f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -87,7 +87,7 @@ jobs: docs: name: Documentation runs-on: ubuntu-latest - needs: [docs-style] + # needs: [docs-style] steps: - name: Login in Github Container registry From d51c296e37d1205eba95edbbfc3641043dd7c7e4 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:10:14 +0000 Subject: [PATCH 03/27] chore: adding changelog file 944.fixed.md [dependabot-skip] --- doc/changelog.d/944.fixed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/944.fixed.md diff --git a/doc/changelog.d/944.fixed.md b/doc/changelog.d/944.fixed.md new file mode 100644 index 0000000000..7963e531e3 --- /dev/null +++ b/doc/changelog.d/944.fixed.md @@ -0,0 +1 @@ +fix(temp): Serialize sphinx jobs \ No newline at end of file From e0ef2150d36239657780e00d6fcaca06a9f66a56 Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 14:16:26 +0100 Subject: [PATCH 04/27] update python version --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 839cdfb93f..f09ecd4b50 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -16,7 +16,7 @@ on: env: DOCKER_IMAGE_NAME: ghcr.io/ansys/prime DOCKER_IMAGE_TAG: '25.1.0.dev20' - MAIN_PYTHON_VERSION: '3.10' + MAIN_PYTHON_VERSION: '3.12' PACKAGE_NAME: 'ansys-meshing-prime' PACKAGE_NAMESPACE: 'ansys.meshing.prime' DOCUMENTATION_CNAME: 'prime.docs.pyansys.com' From f4449381df2ead83be8faf6a8dc5bf111a4b76c5 Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 14:49:36 +0100 Subject: [PATCH 05/27] test --- doc/make.bat | 2 +- doc/source/sg_execution_times.rst | 55 +++ examples/gallery/00_lucid_file_IO.py | 289 --------------- examples/gallery/01_bracket_scaffold.py | 175 --------- examples/gallery/02_lucid_mixing_elbow.py | 157 -------- examples/gallery/03_lucid_pipe_tee.py | 202 ----------- examples/gallery/04_lucid_toy_car.py | 321 ----------------- examples/gallery/05_pcb_stacker.py | 187 ---------- examples/gallery/06_blade_morph.py | 151 -------- examples/gallery/07_saddle_bracket.py | 239 ------------ .../gallery/08_lucid_generic_f1_rear_wing.py | 340 ------------------ .../gallery/09_multi_layer_quad_mesh_pcb.py | 315 ---------------- .../gallery/10_wheel_ground_contact_patch.py | 233 ------------ 13 files changed, 56 insertions(+), 2610 deletions(-) create mode 100644 doc/source/sg_execution_times.rst delete mode 100644 examples/gallery/00_lucid_file_IO.py delete mode 100644 examples/gallery/01_bracket_scaffold.py delete mode 100644 examples/gallery/02_lucid_mixing_elbow.py delete mode 100644 examples/gallery/03_lucid_pipe_tee.py delete mode 100644 examples/gallery/04_lucid_toy_car.py delete mode 100644 examples/gallery/05_pcb_stacker.py delete mode 100644 examples/gallery/06_blade_morph.py delete mode 100644 examples/gallery/07_saddle_bracket.py delete mode 100644 examples/gallery/08_lucid_generic_f1_rear_wing.py delete mode 100644 examples/gallery/09_multi_layer_quad_mesh_pcb.py delete mode 100644 examples/gallery/10_wheel_ground_contact_patch.py diff --git a/doc/make.bat b/doc/make.bat index 69c9588fc5..05a0bf9dc2 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -8,7 +8,7 @@ if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) if "%SPHINXOPTS%" == "" ( - set SPHINXOPTS=-j 1 + set SPHINXOPTS=-j auto ) set SOURCEDIR=source set BUILDDIR=_build diff --git a/doc/source/sg_execution_times.rst b/doc/source/sg_execution_times.rst new file mode 100644 index 0000000000..57e9e4e713 --- /dev/null +++ b/doc/source/sg_execution_times.rst @@ -0,0 +1,55 @@ + +:orphan: + +.. _sphx_glr_sg_execution_times: + + +Computation times +================= +**15:25.512** total execution time for 7 files **from all galleries**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable + + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_examples_gallery_examples_gallery_10_wheel_ground_contact_patch.py` (``..\..\examples\gallery\10_wheel_ground_contact_patch.py``) + - 05:39.009 + - 0.0 + * - :ref:`sphx_glr_examples_gallery_examples_gallery_11_solder_ball.py` (``..\..\examples\gallery\11_solder_ball.py``) + - 04:02.654 + - 0.0 + * - :ref:`sphx_glr_examples_gallery_examples_gallery_09_multi_layer_quad_mesh_pcb.py` (``..\..\examples\gallery\09_multi_layer_quad_mesh_pcb.py``) + - 01:42.827 + - 0.0 + * - :ref:`sphx_glr_examples_gallery_examples_gallery_06_blade_morph.py` (``..\..\examples\gallery\06_blade_morph.py``) + - 01:26.560 + - 0.0 + * - :ref:`sphx_glr_examples_gallery_examples_misc_example_template.py` (``..\..\examples\misc\example_template.py``) + - 01:03.457 + - 0.0 + * - :ref:`sphx_glr_examples_gallery_examples_gallery_07_saddle_bracket.py` (``..\..\examples\gallery\07_saddle_bracket.py``) + - 00:51.550 + - 0.0 + * - :ref:`sphx_glr_examples_gallery_examples_gallery_08_lucid_generic_f1_rear_wing.py` (``..\..\examples\gallery\08_lucid_generic_f1_rear_wing.py``) + - 00:39.456 + - 0.0 diff --git a/examples/gallery/00_lucid_file_IO.py b/examples/gallery/00_lucid_file_IO.py deleted file mode 100644 index 1b03377764..0000000000 --- a/examples/gallery/00_lucid_file_IO.py +++ /dev/null @@ -1,289 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_file_io: - -============================================================== -Convert data when importing and exporting mesh and CAD formats -============================================================== - -**Summary**: This example shows how mesh and geometry formats are converted -during import and export. - -Objective -~~~~~~~~~~ - -The objective is to illustrate how data is converted and passed during import -and export of mesh and geometry. - - -.. image:: ../../../images/part_type_new.png - :align: center - :width: 800 - :alt: Part Structure - - -Procedure -~~~~~~~~~~ -#. Launch an Ansys Prime Server instance. -#. Instantiate the meshing utilities from the ``lucid`` class. -#. Import CAD geometry and review the imported entities. -#. Generate surface mesh with a constant mesh size of 2mm. -#. Generate volume mesh using tetrahedral elements and default settings . -#. Review the entities to be exported to solvers. -#. Export the mesh file as pmdat, cdb and cas format. -#. Import the created solver files to review the entities as they are coming from the solvers. -#. Exit the PyPrimeMesh session. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules and -# launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and 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.plotter import PrimePlotter - -prime_client = prime.launch_prime() -model = prime_client.model -mesh_util = prime.lucid.Mesh(model=model) - -############################################################################### -# Import geometry -# ~~~~~~~~~~~~~~~~~~~ -# Download the CAD file “pyprime_block_import.fmd” and import its geometry -# into PyPrimeMesh. -# -# Display part details by printing the model. The TopoPart’s -# name from model details is **pyprime_block_import**. -# -# After import of CAD model, within the topopart the facets from the CAD -# exists in the form of geom data. This can be seen in the image below. -# -# .. image:: ../../../images/00_after_cad_import.png -# :align: center -# :width: 800 -# :alt: TopoPart after import of CAD file “pyprime_block_import.fmd”. -# -# The topology consists of the following TopoEntities , they are TopoEdges, TopoFaces -# and TopoVolumes. -# -# * TopoEdge represent the curves/edges present in the CAD. -# In this case there are **17 edges** present in SpaceClaim are imported -# as **17 TopoEdges**. -# -# * TopoFace represent the surfaces/faces present in the CAD. -# The **8 CAD Faces** present in SpaceClaim are imported as **8 Topofaces** in -# PyPrimeMesh. -# -# * TopoVolume represent the solid volumes present in the CAD. -# Since there is only **one solid body** in SpaceClaim, this is imported as **one Topovolume** -# in PyPrimeMesh. -# -# Named selections or groups in the CAD become labels after import. In this example , -# the Named Selection / Group named **my_group** in Spaceclaim is imported as a label -# in PyPrimeMesh. -# -# After CAD import the solid body, surface body or an edge body present in SCDM would be defined -# as Volume Zones, Face Zones and Edge Zones in PyPrimeMesh. In the CAD model , there exist a -# **single solid body named “solid”** which after import becomes as a **Volume Zone named solid**. - -mesh_util.read(file_name=prime.examples.download_block_model_fmd()) -# mesh_util.read(file_name=prime.examples.download_block_model_scdoc()) -print(model) -display = PrimePlotter() -display.plot(model) -display.show() - -############################################################################### -# Generate Mesh -# ~~~~~~~~~~~~~~~ -# The topo part currently has no mesh associated with it and contains only -# geometry. -# -# Using the Lucid API ``surface_mesh``, users can generate a conformal mesh on the topofaces. -# A conformal mesh with a constant mesh size of 2mm is generated. After mesh generation, the -# mesh data is available within the TopoPart. -# -# This can be seen in the image below -# -# .. image:: ../../../images/00_after_mesh_generation.png -# :align: center -# :width: 800 -# :alt: TopoPart after mesh generation. -# -# The mesh for a group of topo faces labeled “my_group” is displayed by defining the -# label expression in the display scope. The Volume Mesh is generated keeping the volume fill -# as the default meshing algorithms. - -mesh_util.surface_mesh(min_size=2.0) -display = PrimePlotter() -display.plot(model, update=True) -display.show() - -part = model.get_part_by_name("pyprime_block_import") - -display = PrimePlotter() -display.plot(model, prime.ScopeDefinition(model, label_expression="my_group")) -display.show() - -mesh_util.volume_mesh() - -############################################################################### -# Export mesh as PyPrimeMesh (.pmdat) native format mesh file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# PyPrimeMesh allows user to export mesh in its native format name pmdat. -# This configuration allows retaining the topology data along with mesh data. -# -temp_folder = tempfile.TemporaryDirectory() -mesh_file_pmdat = os.path.join(temp_folder.name, "pyprime_block_mesh.pmdat") -mesh_util.write(mesh_file_pmdat) -assert os.path.exists(mesh_file_pmdat) -print("\nExported file:\n", mesh_file_pmdat) - -############################################################################### -# Export mesh as Ansys MAPDL (.cdb) format mesh file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# PyPrimeMesh allows export of mesh as Ansys MAPDL (.cdb) format mesh file. While exporting -# the mesh to Ansys MAPDL, the labels present in session are converted to components -# containing nodes. -# - -mesh_file_cdb = os.path.join(temp_folder.name, "pyprime_block_mesh.cdb") -mesh_util.write(mesh_file_cdb) -assert os.path.exists(mesh_file_cdb) -print("\nExported file:\n", mesh_file_cdb) - - -############################################################################### -# Export mesh Ansys Fluent (CAS) format mesh file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Zones in PyPrimeMesh can be defined as a collection of either topo or zonelet entities that -# we can assign properties to in a solver when exported as mesh , for example "if the user -# wishes to assign a material to a region of the model they can define a volume zone for -# multiple topo volumes or cell zonelets so they can apply the property. -# -# Hence while exporting the mesh as (MSH or CAS) file to the Fluent solver, the -# boundary conditions for the zones needs to be defined. For this reason the topo -# entities / zonelets associated with a labels are converted to volume/face/edge zones -# respectively. -# -# The property of a zone is that a zonelet or TopoEntity can only be present in a single zone. -# The topo entities / zonelets that are not associated with their respective zones types are -# merged together during export to Fluent formats. The topology data present is removed -# automatically when export to Fluent(MSH or CAS) formats. -# - -mesh_util.create_zones_from_labels("my_group") -print(model) - -# Export as Fluent (*.cas) format mesh file -mesh_file_cas = os.path.join(temp_folder.name, "pyprime_block_mesh.cas") -mesh_util.write(mesh_file_cas) -assert os.path.exists(mesh_file_cas) -print("\nExported file:\n", mesh_file_cas) - -############################################################################### -# Reading Ansys PyPrimeMesh native mesh file (pmdat) -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Read the exported PyPrimeMesh(pmdat) native mesh format file, it is observed that -# part topology contains both geom data as well as mesh data. This is seen in the image below -# -# .. image:: ../../../images/00_import_pmdat.png -# :align: center -# :width: 800 -# :alt: TopoPart after reading mesh part. -# -# Meshed zonelets (that contain the mesh data) are only created once the topo part -# is converted to a mesh part by deleting the topo entities. Here , while deleting the topology -# we are deleting the geom data (face) and retaining the mesh data for solve purpose. -# When deleting the topoogy , the TopoPart is converted to MeshPart and the topo entities -# are converted to their respective zonelet type in MeshPart, this is shown as follows; -# -# * **01 TopoVolumes -> 01 Cell Zonelets** -# * **08 TopoFaces -> 08 Face Zonelets** -# * **17 TopoEdges -> 17 Edge Zonelets** -# -# The zones association with topoentities would change to their -# corresponding equivalent zonelet type in MeshParts. -# -# .. image:: ../../../images/00_after_delete_topology.png -# :align: center -# :width: 800 -# :alt: MeshPart after deleting topology. -# - -mesh_util.read(mesh_file_pmdat, append=False) - -print(model) -for part in model.parts: - if len(part.get_topo_faces()) > 0: - part.delete_topo_entities( - prime.DeleteTopoEntitiesParams( - model, delete_geom_zonelets=True, delete_mesh_zonelets=False - ) - ) - -print(model) - -############################################################################### -# Reading Ansys Fluent (.cas) format mesh file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Read the exported Fluent format mesh file. -# -# .. image:: ../../../images/00_import_cas.png -# :align: center -# :width: 800 -# :alt: After import of cas mesh file. -# -# It would be observed that -# the zone name **my_group** is retained and the remaining face zonelets that are not -# associated with a face zone(s) are merged to create a new zone named **wall**. -# There are no labels present in the mesh file. - -mesh_util.read(mesh_file_cas, append=False) -print(model) -part = model.parts[0] -for zone in part.get_face_zones(): - print(model.get_zone_name(zone)) - scope = prime.ScopeDefinition( - model, - evaluation_type=prime.ScopeEvaluationType.ZONES, - zone_expression=model.get_zone_name(zone), - ) - display = PrimePlotter() - display.plot(model, scope, update=True) - display.show() - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/01_bracket_scaffold.py b/examples/gallery/01_bracket_scaffold.py deleted file mode 100644 index e2e4ab655a..0000000000 --- a/examples/gallery/01_bracket_scaffold.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_bracket_mid_surface_mesh: - -===================================================== -Mesh a mid-surfaced bracket for a structural analysis -===================================================== - -**Summary**: This example demonstrates how to use topology-based connection -to generate conformal surface mesh. - -Objective -~~~~~~~~~ - -To create conformal surface mesh, you can scaffold topofaces, topoedges, or both to -connect all the surface bodies and mesh the bracket with quad elements. - -.. image:: ../../../images/bracket_mid_surface_scaffold_w.png - :align: center - :width: 400 - :alt: Scaffolding result in a wireframe representation. - -Procedure -~~~~~~~~~ -#. Launch Ansys Prime Server. -#. Import the CAD geometry and create the part per the CAD model. -#. Scaffold topofaces and topoedges with a tolerance parameter. -#. Surface mesh topofaces with a constant size and generate quad elements. -#. Write a CDB file for use in the APDL solver. -#. Exit the PyPrimeMesh session. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and get the model. - -import os -import tempfile - -from ansys.meshing import prime -from ansys.meshing.prime.graphics import PrimePlotter - -prime_client = prime.launch_prime() -model = prime_client.model - -############################################################################### -# Import CAD geometry -# ~~~~~~~~~~~~~~~~~~~ -# Download the bracket geometry (FMD) file exported by SpaceClaim. -# Import the CAD geometry. -# Create the part per the CAD model for the topology-based connection. - -# For Windows OS users, scdoc is also available: -# bracket_file = prime.examples.download_bracket_scdoc() - -bracket_file = prime.examples.download_bracket_fmd() - -file_io = prime.FileIO(model) -file_io.import_cad( - file_name=bracket_file, - params=prime.ImportCadParams( - model=model, - length_unit=prime.LengthUnit.MM, - part_creation_type=prime.PartCreationType.MODEL, - ), -) - -############################################################################### -# Review the part -# ~~~~~~~~~~~~~~~ -# Get the part summary. -# Display the model to show edges by connection. -# Use keyboard shortcuts to switch between -# the surface (``s``) and wireframe (``w``) representations. -# Color code for edge connectivity: -# -# - Red: free -# - Black: double -# - Purple: triple - -part = model.get_part_by_name('bracket_mid_surface-3') -part_summary_res = part.get_summary(prime.PartSummaryParams(model, print_mesh=False)) -print(part_summary_res) - -display = PrimePlotter() -display.add_model(model) -display.show() - -############################################################################### -# Connection -# ~~~~~~~~~~ -# Initialize the connection tolerance and other parameters. (The connection -# tolerance is smaller than the target element size.) -# Scaffold the topofaces, topoedges, or both with connection parameters. - -# Target element size -element_size = 0.5 - -params = prime.ScaffolderParams( - model, - absolute_dist_tol=0.1 * element_size, - intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, - constant_mesh_size=element_size, -) - -# Get existing topoface or topoedge IDs -faces = part.get_topo_faces() -beams = [] - -scaffold_res = prime.Scaffolder(model, part.id).scaffold_topo_faces_and_beams( - topo_faces=faces, topo_beams=beams, params=params -) -print(scaffold_res) - -############################################################################### -# Surface mesh -# ~~~~~~~~~~~~ -# Initialize surface meshing parameters. -# Mesh topofaces with the constant size and generate quad elements. - -surfer_params = prime.SurferParams( - model=model, - size_field_type=prime.SizeFieldType.CONSTANT, - constant_size=element_size, - generate_quads=True, -) - -surfer_result = prime.Surfer(model).mesh_topo_faces(part.id, topo_faces=faces, params=surfer_params) - -# Display the mesh -pl = PrimePlotter() -pl.plot(model, update=True) -pl.show() - -############################################################################### -# Write mesh -# ~~~~~~~~~~ -# Write a CDB file for use in the APDL solver. - -with tempfile.TemporaryDirectory() as temp_folder: - mapdl_cdb = os.path.join(temp_folder, 'bracket_scaffold.cdb') - file_io.export_mapdl_cdb(mapdl_cdb, params=prime.ExportMapdlCdbParams(model)) - assert os.path.exists(mapdl_cdb) - print(f'MAPDL case exported at {mapdl_cdb}') - -############################################################################### -# Exit the PyPrimeMesh session -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/02_lucid_mixing_elbow.py b/examples/gallery/02_lucid_mixing_elbow.py deleted file mode 100644 index b35f1ef680..0000000000 --- a/examples/gallery/02_lucid_mixing_elbow.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_mixing_elbow_mesh: - -======================================= -Mesh a mixing elbow for a flow analysis -======================================= - -**Summary**: This example demonstrates how to mesh a mixing elbow for a flow analysis. - -Objective -~~~~~~~~~ - -This example meshes a mixing elbow with polyhedral elements and wall boundary -layer refinement. It uses several meshing utilities available in the ``lucid`` class for -convenience and ease. - -.. image:: ../../../images/elbow.png - :align: center - :width: 400 - :alt: Mixing elbow mesh. - -Procedure -~~~~~~~~~ -#. Launch Ansys Prime Server and instantiate meshing utilities from the ``lucid`` class. -#. Import the geometry and create face zones from labels imported from the geometry. -#. Surface mesh geometry with curvature sizing. -#. Volume mesh with polyhedral elements and boundary layer refinement. -#. Print statistics on the generated mesh. -#. Write a CAS file for use in the Fluent solver. -#. Exit the PyPrimeMesh session. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and get the model. -# Instantiate meshing utilities from the ``lucid`` class. - -import os -import tempfile - -from ansys.meshing import prime -from ansys.meshing.prime.graphics import PrimePlotter - -prime_client = prime.launch_prime() -model = prime_client.model -mesh_util = prime.lucid.Mesh(model=model) - -############################################################################### -# Import geometry -# ~~~~~~~~~~~~~~~ -# Download the elbow geometry (FMD) file exported by SpaceClaim. -# Import the geometry. -# Create face zones from labels imported from the geometry for use in Fluent solver. - - -# For Windows OS users, scdoc is also available: -# mixing_elbow = prime.examples.download_elbow_scdoc() - -mixing_elbow = prime.examples.download_elbow_fmd() - -mesh_util.read(file_name=mixing_elbow) -mesh_util.create_zones_from_labels("inlet,outlet") - -############################################################################### -# Surface mesh -# ~~~~~~~~~~~~ -# Surface mesh the geometry setting minimum and maximum sizing -# to use for curvature refinement. - -mesh_util.surface_mesh(min_size=5, max_size=20) - -############################################################################### -# Volume mesh -# ~~~~~~~~~~~ -# Volume mesh with polyhedral elements and boundary layer refinement. -# Fill the volume with polyhedral and prism mesh -# specifying the location and number of layers for prisms. -# Use expressions to define the surfaces to have prisms grown -# where ``* !inlet !outlet`` states ``all not inlet or outlet``. - -mesh_util.volume_mesh( - volume_fill_type=prime.VolumeFillType.POLY, - prism_surface_expression="* !inlet !outlet", - prism_layers=3, -) - -# Display the mesh -pl = PrimePlotter(allow_picking=True) -pl.plot(model) -pl.show() - -############################################################################### -# Print mesh statistics -# ~~~~~~~~~~~~~~~~~~~~~ - -# Get meshed part -part = model.get_part_by_name("flow_volume") - -# Get statistics on the mesh -part_summary_res = part.get_summary(prime.PartSummaryParams(model=model)) - -# Get element quality on all parts in the model -search = prime.VolumeSearch(model=model) -params = prime.VolumeQualitySummaryParams( - model=model, - scope=prime.ScopeDefinition(model=model, part_expression="*"), - cell_quality_measures=[prime.CellQualityMeasure.SKEWNESS], - quality_limit=[0.95], -) -results = search.get_volume_quality_summary(params=params) - -# Print statistics on meshed part -print(part_summary_res) -print("\nMaximum skewness: ", results.quality_results_part[0].max_quality) - -############################################################################### -# 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, "mixing_elbow.cas") - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/03_lucid_pipe_tee.py b/examples/gallery/03_lucid_pipe_tee.py deleted file mode 100644 index 0208bc4fad..0000000000 --- a/examples/gallery/03_lucid_pipe_tee.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_pipe_tee: - -==================================================================== -Mesh a pipe T-section for structural thermal and fluid flow analysis -==================================================================== - -**Summary**: This example demonstrates how to mesh a pipe T-section for both -structural thermal and fluid flow simulation. - - -Objective -~~~~~~~~~~ - -This example meshes the solids of a pipe T-section for a -structural thermal analysis using tetrahedral elements and uses the -wrapper to extract the fluid domain and mesh using polyhedral cells with -prismatic boundary layers. - -.. figure:: ../../../images/pipe_tee.png - :align: center - :width: 800 - - **Thermal structural and fluid flow meshes** - -Procedure -~~~~~~~~~~ -#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. -#. Read the CAD geometry. -#. Mesh for the structural thermal analysis. -#. Write the mesh for the structural thermal analysis. -#. Extract the fluid by wrapping. -#. Mesh with polyhedral and prisms. -#. Write the mesh for the fluid simulation. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch the Ansys Prime Server instance and connect the client. -# Get the client model and instantiate meshing utilities from the ``lucid`` class. - -import os -import tempfile - -from ansys.meshing import prime -from ansys.meshing.prime import lucid -from ansys.meshing.prime.graphics import PrimePlotter - -prime_client = prime.launch_prime() -model = prime_client.model -mesh_util = lucid.Mesh(model) - -############################################################################### -# Read CAD geometry -# ~~~~~~~~~~~~~~~~~ -# Download the example FMD geometry file. -# The FMD file format is exported from SpaceClaim and is compatible with Linux. -# Read and display the geometry file. -# The file contains several unmeshed parts, which is what you would get after you -# import from a CAD file. -# For Windows OS users, the SCDOC format is also available: -# ``pipe_tee = prime.examples.download_pipe_tee_scdoc()`` -pipe_tee = prime.examples.download_pipe_tee_fmd() -mesh_util.read(pipe_tee) - -display = PrimePlotter() -display.plot(model) -display.show() - -print(model) - -############################################################################### -# Mesh for structural -# ~~~~~~~~~~~~~~~~~~~ -# Surface mesh using curvature sizing. -# Volume mesh with tetrahedral elements. -# Delete unwanted capping surface geometries by deleting -# parts that do not have any volume zones. -# Display structural thermal mesh ready for export. - -mesh_util.surface_mesh(min_size=2.5, max_size=10) -mesh_util.volume_mesh() - -toDelete = [part.id for part in model.parts if not part.get_volume_zones()] - -if toDelete: - model.delete_parts(toDelete) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() - -############################################################################### -# Write structural mesh -# ~~~~~~~~~~~~~~~~~~~~~ -# Labels are exported to the CDB file as components for -# applying load boundary conditions in the solver. - -with tempfile.TemporaryDirectory() as temp_folder: - structural_mesh = os.path.join(temp_folder, "pipe_tee.cdb") - mesh_util.write(structural_mesh) - print("\nExported Structural Mesh: ", structural_mesh) - -############################################################################### -# Extract fluid by wrapping -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# You can deal with the small internal diameter change between flanges in several ways: -# -# * Connect the geometry to extract a volume and refine the -# mesh around this detail to capture. -# * Modify the geometry to remove the feature. -# * Wrap to extract the internal flow volume and walk over the feature. -# -# This example wraps and walks over these features. -# -# Read in the geometry again. -# -# Use a constant size wrap to walk over the diameter change -# feature and extract the largest internal volume as the fluid. -# -# By default, the wrap uses all parts as input and deletes the input -# geometry after wrapping unless ``keep_input`` is set as ``True``. - -mesh_util.read(pipe_tee) - -wrap = mesh_util.wrap(min_size=6, region_extract=prime.WrapRegion.LARGESTINTERNAL) - -print(model) - -display = PrimePlotter() -display.add_model(model, update=True) -display.show() - -############################################################################### -# Volume mesh fluid -# ~~~~~~~~~~~~~~~~~ -# Create zones for each label to use for boundary condition definitions. -# Volume mesh with prism polyhedral, not growing prisms from inlets and outlets. -# Visualize the generated volume mesh. -# When displaying, you can avoid displaying unnecessary edge zones. -# You can clearly see the prism layers that were specified by the Prism control. - -# set global sizing -params = prime.GlobalSizingParams(model, min=6, max=50) -model.set_global_sizing_params(params) - -mesh_util.create_zones_from_labels("outlet_main,in1_inlet,in2_inlet") - -mesh_util.volume_mesh( - prism_layers=5, - prism_surface_expression="* !*inlet* !*outlet*", - volume_fill_type=prime.VolumeFillType.POLY, -) - -print(model) -display = PrimePlotter() -display.add_scope( - model, scope=prime.ScopeDefinition(model=model, label_expression="* !*__*"), update=True -) -display.show() - -############################################################################### -# Write fluid mesh -# ~~~~~~~~~~~~~~~~ -# Write a MSH file for the Fluent solver. - -with tempfile.TemporaryDirectory() as temp_folder: - fluid_mesh = os.path.join(temp_folder, "pipe_tee.msh") - mesh_util.write(fluid_mesh) - assert os.path.exists(fluid_mesh) - print("\nExported Fluid Mesh: ", fluid_mesh) - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/04_lucid_toy_car.py b/examples/gallery/04_lucid_toy_car.py deleted file mode 100644 index 059f6b88c2..0000000000 --- a/examples/gallery/04_lucid_toy_car.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_toy_car_wrap: - -================================== -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. - -.. image:: ../../../images/toy_car.png - :align: center - :width: 400 - :alt: Toy car wrap. - -Procedure -~~~~~~~~~ -#. Launch an Ansys Prime Server instance. -#. Instantiate the meshing utilities from the ``lucid`` class. -#. Import the geometry. -#. Coarse wrap parts with holes to clean up. -#. Extract the fluid region using a wrapper. -#. Check that the wrap surface is closed and that the quality is suitable. -#. Mesh only fluid with tetrahedral elements and boundary layer refinement. -#. Create face zones from labels imported from the geometry. -#. Print statistics on the generated mesh. -#. Improve the mesh quality. -#. Write a CAS file for use in the Fluent solver. -#. 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) -############################################################################### -# 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() -############################################################################### -# 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() - - -############################################################################### -# 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) - -############################################################################### -# 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) - -############################################################################### -# 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) - -############################################################################### -# 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) - -############################################################################### -# Print mesh stats -# ~~~~~~~~~~~~~~~~ -# Print statistics on the generated mesh. - -vtool = prime.VolumeMeshTool(model=model) -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) - -quality = prime.VolumeSearch(model) -scope = prime.ScopeDefinition(model, part_expression=wrap_part.name) - -part_summary_res = wrap_part.get_summary( - prime.PartSummaryParams(model=model, print_id=False, print_mesh=True) -) - -print("\nNo. of cells : ", part_summary_res.n_cells) - -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) - -############################################################################### -# 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) - -############################################################################### -# 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) - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/05_pcb_stacker.py b/examples/gallery/05_pcb_stacker.py deleted file mode 100644 index d117bb6893..0000000000 --- a/examples/gallery/05_pcb_stacker.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_pcb: - -========================================== -Mesh a PCB for structural thermal analysis -========================================== - -**Summary**: This example demonstrates how to mesh a printed circuit board -with mainly hexahedral elements for structural thermal simulation using the volume sweeper. - -Objective -~~~~~~~~~~ -This example uses the volume sweeper to mesh the solids of a printed circuit board for a -structural thermal analysis using predominantly hexahedral elements. - -.. image:: ../../../images/pcb_stacker.png - :align: center - :width: 800 - :alt: Thermal structural mesh. - -Procedure -~~~~~~~~~~ -#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. -#. Read the CAD geometry. -#. Create a base face, projecting edge loops and imprinting to capture the geometry. -#. Surface mesh the base face with quad elements. -#. Stack the base face mesh through the volumes to create a mainly hexahedral volume mesh. -#. Write the mesh for the structural thermal analysis. -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch the Ansys Prime Server instance and connect the client. -# Get the client model and 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=model) - -############################################################################### -# Import geometry -# ~~~~~~~~~~~~~~~ -# Download the PCB geometry (PMDAT) file. -# Import the geometry. -# Display the imported geometry. Purple edges indicate that the geometry is -# connected and the topology is shared between the different volumes. -# This means that the mesh is also to be connected between volumes. - -# For Windows OS users, SCDOC files are also available. -# To read the geometry as connected with shared topology, you must use -# the Workbench ``CadReaderRoute``: - -# mesh_util.read( -# file_name=prime.examples.download_pcb_scdoc(), -# cad_reader_route=prime.CadReaderRoute.WORKBENCH, -# ) - -mesh_util.read(file_name=prime.examples.download_pcb_pmdat()) - -display = PrimePlotter() -display.plot(model) -display.show() - -sizing_params = prime.GlobalSizingParams(model=model, min=0.5, max=1.0) -model.set_global_sizing_params(params=sizing_params) - -############################################################################### -# Create base face -# ~~~~~~~~~~~~~~~~ -# Define stacker parameters: -# -# - Set the direction vector for defining stacking. -# - Set the maximum offset size for mesh layers created by the stacker method. -# - Set the base faces to delete after stacking. -# -# Create the base face from the part and volumes. -# Define a label for the generated base faces and display. -# When coloured by zonelet, the display shows the imprints -# on the base face. - -part = model.parts[0] -sweeper = prime.VolumeSweeper(model) - -stacker_params = prime.MeshStackerParams( - model=model, - direction=[0, 1, 0], - max_offset_size=1.0, - delete_base=True, -) - -createbase_results = sweeper.create_base_face( - part_id=part.id, - topo_volume_ids=part.get_topo_volumes(), - params=stacker_params, -) - -base_faces = createbase_results.base_face_ids - -part.add_labels_on_topo_entities(["base_faces"], base_faces) - -scope = prime.ScopeDefinition(model=model, label_expression="base_faces") - -display = PrimePlotter() -display.plot(model, scope=scope) -display.show() - - -############################################################################### -# Surface mesh base face -# ~~~~~~~~~~~~~~~~~~~~~~ -# Quad surface mesh the generated base faces for stacking. - -base_scope = prime.lucid.SurfaceScope( - entity_expression="base_faces", - part_expression=part.name, - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, -) - -mesh_util.surface_mesh(min_size=0.5, scope=base_scope, generate_quads=True) - -display = PrimePlotter() -display.plot(model, scope=scope, update=True) -display.show() - -############################################################################### -# Stack base face -# ~~~~~~~~~~~~~~~~~~~~~~ -# Create a mainly hexahedral volume mesh using the stacker method. -# Display the volume mesh. - -stackbase_results = sweeper.stack_base_face( - part_id=part.id, - base_face_ids=base_faces, - topo_volume_ids=part.get_topo_volumes(), - params=stacker_params, -) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() - -############################################################################### -# Write mesh -# ~~~~~~~~~~ -# Write a CDB file. - -with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, "pcb.cdb") - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/06_blade_morph.py b/examples/gallery/06_blade_morph.py deleted file mode 100644 index 0c10dda7fe..0000000000 --- a/examples/gallery/06_blade_morph.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_turbine_blade: - -========================================================= -Morph a hexahedral mesh of a turbine blade to a new shape -========================================================= - -**Summary**: This example demonstrates how to morph a structural -hexahedral mesh of a turbine blade to a new deformed shape -defined by a target geometry file. - -Objective -~~~~~~~~~~ - -This example appends a CDB mesh with a CAD geometry -and match morphs the mesh to the geometry. - -.. image:: ../../../images/turbine_blade.png - :align: center - :width: 800 - :alt: Turbine blade hexahedral mesh. - -Procedure -~~~~~~~~~~ -#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. -#. Read the mesh and append the new CAD geometry shape. -#. Define the mesh source faces and the target geometry faces to match morph. -#. Match morph the turbine blade mesh to the new CAD geometry shape. -#. Write the mesh for structural analysis. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch the Ansys Prime Server instance and connect the client. -# Get the client model and 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=model) - -############################################################################### -# Read files -# ~~~~~~~~~~ -# Download the turbine blade mesh file and CAD geometry. -# Read the mesh and append the geometry. -# Display the source and the target. - -# For Windows OS users, scdoc is also available for geometry: -# target_geometry = prime.examples.download_turbine_blade_target_scdoc() - -source_mesh = prime.examples.download_turbine_blade_cdb() -target_geometry = prime.examples.download_deformed_blade_fmd() -mesh_util.read(file_name=source_mesh) -mesh_util.read(file_name=target_geometry, append=True) - - -display = PrimePlotter() -display.plot(model) -display.show() - -print(model) - -############################################################################### -# Define source and target faces -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -source_part = model.get_part(2) -target_part = model.get_part(3) -source = source_part.get_face_zonelets() -target = target_part.get_topo_faces() - -############################################################################### -# Match morph mesh -# ~~~~~~~~~~~~~~~~ -# Set the target type to be for topoface because the target is geometry. -# Morph the source face zonelets of ``source_part`` to the -# target topofaces of the geometry. - -morpher = prime.Morpher(model) -match_pair = prime.MatchPair( - model=model, - source_surfaces=source, - target_surfaces=target, - target_type=prime.MatchPairTargetType.TOPOFACE, -) - -params = prime.MatchMorphParams(model) -bc_params = prime.MorphBCParams(model) -solver_params = prime.MorphSolveParams(model) - -morpher.match_morph( - part_id=source_part.id, - match_pairs=[match_pair], - match_morph_params=params, - bc_params=bc_params, - solve_params=solver_params, -) - -# Display the morphed mesh - -display = PrimePlotter() -display.plot(model, update=True) -display.show() -############################################################################### -# Write mesh -# ~~~~~~~~~~ -# Write the morphed CDB file. The geometry is ignored when exporting to a CDB -# file. - -with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, "morphed_turbine_blade.cdb") - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/07_saddle_bracket.py b/examples/gallery/07_saddle_bracket.py deleted file mode 100644 index a8d42d6785..0000000000 --- a/examples/gallery/07_saddle_bracket.py +++ /dev/null @@ -1,239 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_saddle_thin_hex: - -=============================================== -Mesh a saddle bracket for a structural analysis -=============================================== - -**Summary**: This example demonstrates how to mesh a thin -solid with hexahedral and prism cells. - -Objective -~~~~~~~~~ - -This example creates a mainly hexahedral mesh on a thin solid volume. - -.. image:: ../../../images/saddle_bracket.png - :align: center - :width: 400 - :alt: Thin volume hexahedral mesh. - -Procedure -~~~~~~~~~ -#. Launch Ansys Prime Server. -#. Import the CAD geometry. -#. Quad surface mesh the source face. -#. Surface mesh the remaining unmeshed TopoFaces with tri surface mesh. -#. Delete the topology. -#. Define volume meshing controls to use thin volume meshing. -#. Volume mesh with hexahedral and prism cells. -#. Write a CDB file for use in the APDL solver. -#. Exit the PyPrimeMesh session. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and get the model. - -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) - -############################################################################### -# Import geometry -# ~~~~~~~~~~~~~~~ -# Download the saddle bracket geometry (FMD) file exported by SpaceClaim. -# Import the geometry and display all. - -# For Windows OS users, scdoc is also available: -# saddle_bracket = prime.examples.download_saddle_bracket_scdoc() - -saddle_bracket = prime.examples.download_saddle_bracket_fmd() - -mesh_util.read(file_name=saddle_bracket) - -print(model) - -display = PrimePlotter() -display.plot(model) -display.show() - -############################################################################### -# Quad mesh source faces -# ~~~~~~~~~~~~~~~~~~~~~~ -# Mesh the source faces for the thin volume control with quads. - -scope = prime.lucid.SurfaceScope( - part_expression="*", - entity_expression="source_thin", - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, -) - -mesh_util.surface_mesh( - scope=scope, - min_size=2.0, - generate_quads=True, -) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() - -############################################################################### -# Surface mesh unmeshed faces -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Mesh unmeshed faces with tri surface mesh. Tri surface mesh on the target -# and side faces is used to show more clearly that the result of the thin -# volume control is a hex mesh that is imprinted up to the side faces. -# All quads could be used for the surface mesh to simplify the process. - -part = model.parts[0] - -all_faces = part.get_topo_faces() -meshed_faces = part.get_topo_faces_of_label_name_pattern( - label_name_pattern="source_thin", - name_pattern_params=prime.NamePatternParams(model), -) - -unmeshed_faces = [face for face in all_faces if face not in meshed_faces] - -part.add_labels_on_topo_entities( - labels=["unmeshed_faces"], - topo_entities=unmeshed_faces, -) - -scope = prime.lucid.SurfaceScope( - part_expression="*", - entity_expression="unmeshed_faces", - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, -) - -mesh_util.surface_mesh( - scope=scope, - min_size=2.0, -) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() - -############################################################################### -# Delete topology -# ~~~~~~~~~~~~~~~ -# Delete topology to leave only the surface mesh. This is necessary for the -# thin volume control to be used. - -part.delete_topo_entities( - prime.DeleteTopoEntitiesParams( - model=model, - delete_geom_zonelets=True, - delete_mesh_zonelets=False, - ) -) - -############################################################################### -# Define volume meshing controls -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Define volume meshing controls to use thin volume meshing. -# Specify source and target faces for the thin volume using imported labels. -# Set the number of layers of cells through the thickness of the thin solid to be 4. -# To create a fully hexahedral and prism mesh the side faces must be imprinted on -# the side faces. If needed, a buffer region at the sides of the volume can be -# defined where the volume fill type used for the volume mesh parameters is -# used to infill. This is useful on more complex geometries, where it provides -# more robustness of the method. To create a buffer region set ``imprint_sides`` -# to ``False`` and specify how many rings of cells to ignore at the sides -# using ``n_ignore_rings``. - -auto_mesh_params = prime.AutoMeshParams(model=model) -thin_vol_ctrls_ids = [] -thin_vol_ctrl = model.control_data.create_thin_volume_control() - -thin_vol_ctrl.set_source_scope( - prime.ScopeDefinition( - model=model, - label_expression="source_thin", - ) -) - -thin_vol_ctrl.set_target_scope( - prime.ScopeDefinition( - model=model, - label_expression="target_thin", - ) -) - -thin_params = prime.ThinVolumeMeshParams( - model=model, - n_layers=4, - imprint_sides=True, -) - -thin_vol_ctrl.set_thin_volume_mesh_params(thin_volume_mesh_params=thin_params) -thin_vol_ctrls_ids.append(thin_vol_ctrl.id) -auto_mesh_params.thin_volume_control_ids = thin_vol_ctrls_ids -auto_mesh_params.volume_fill_type = prime.VolumeFillType.TET - -############################################################################### -# Generate volume mesh -# ~~~~~~~~~~~~~~~~~~~~ -# Volume mesh to obtain hexahedral and prism mesh. -# Print mesh summary. -# Display volume mesh. - -volume_mesh = prime.AutoMesh(model=model) -result_vol = volume_mesh.mesh(part_id=part.id, automesh_params=auto_mesh_params) -print(part.get_summary(prime.PartSummaryParams(model))) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() -############################################################################### -# Write mesh -# ~~~~~~~~~~ -# Write a CDB file for use in the MAPDL solver. - -with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, "saddle_bracket.cdb") - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/08_lucid_generic_f1_rear_wing.py b/examples/gallery/08_lucid_generic_f1_rear_wing.py deleted file mode 100644 index e15291071a..0000000000 --- a/examples/gallery/08_lucid_generic_f1_rear_wing.py +++ /dev/null @@ -1,340 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_generic_f1_rw: - -============================================================= -Mesh a generic F1 car rear wing for external aero simulation -============================================================= - -**Summary**: This example demonstrates how to generate a mesh for a generic F1 rear wing -STL file model. - -Objective -~~~~~~~~~~ - -The example connects various parts of a rear wing from a generic F1 car -and volume meshes the resulting model using a poly-hexcore mesh containing prisms. -To simplify the process and enhance convenience, this example uses multiple -meshing utilities provided in the ``lucid`` class. - -.. image:: ../../../images/generic_rear_wing.png - :align: center - :width: 800 - :alt: Generic F1 rear wing. - -Procedure -~~~~~~~~~~ -#. Launch an Ansys Prime Server instance. -#. Instantiate the meshing utilities from the ``lucid`` class. -#. Import and append the STL geometry files for each part of the F1 rear wing. -#. Merge all imported components into a single part. -#. Use the connect operation to join the components together. -#. Define local size controls on aero surfaces. -#. Generate a surface mesh with curvature sizing. -#. Compute volume zones and define the fluid zone type. -#. Define the boundary layer. -#. Generate a volume mesh using poly-hexcore elements and apply boundary layer refinement. -#. Print statistics on the generated mesh. -#. Write a CAS file for use in the Fluent solver. -#. Exit the PyPrimeMesh session. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and 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=model) - -############################################################################### -# Import geometry -# ~~~~~~~~~~~~~~~ -# Download the generic F1 rear wing geometries (STL files). -# Import each geometry and append to the model. -# Display the imported geometry. - -f1_rw_drs = prime.examples.download_f1_rw_drs_stl() -f1_rw_enclosure = prime.examples.download_f1_rw_enclosure_stl() -f1_rw_end_plates = prime.examples.download_f1_rw_end_plates_stl() -f1_rw_main_plane = prime.examples.download_f1_rw_main_plane_stl() - -for file_name in [f1_rw_drs, f1_rw_enclosure, f1_rw_end_plates, f1_rw_main_plane]: - mesh_util.read(file_name, append=True) - -# display the rear wing geometry without the enclosure -scope = prime.ScopeDefinition(model, part_expression="* !*enclosure*") -display = PrimePlotter() -display.plot(model, scope) -display.show() - -############################################################################### -# Merge parts -# ~~~~~~~~~~~ -# Establish the global size parameter to regulate mesh refinement. -# Merge all individual parts into a unified part named ``f1_car_rear_wing``. - -# Define global sizes -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4, max=32, growth_rate=1.2)) - -# Create label per part -for part in model.parts: - part.add_labels_on_zonelets([part.name.split(".")[0]], part.get_face_zonelets()) - -# Merge parts -merge_params = prime.MergePartsParams(model, merged_part_suggested_name="f1_car_rear_wing") -merge_result = model.merge_parts([part.id for part in model.parts], merge_params) -part = model.get_part_by_name(merge_result.merged_part_assigned_name) - -############################################################################### -# Mesh connect -# ~~~~~~~~~~~~ -# To generate a volume mesh for a closed domain, it is necessary to ensure -# that the components of the rear wing are properly connected. -# To achieve this, perform a connect operation using labels to join the components of -# the rear wing. -# Afterward, inspect the mesh to detect any edges that are not connected. - -# Connect faces -mesh_util.connect_faces(part.name, face_labels="*", target_face_labels="*", tolerance=0.02) - -# Diagnostics -surf_diag = prime.SurfaceSearch(model) -surf_report = surf_diag.get_surface_diagnostic_summary( - prime.SurfaceDiagnosticSummaryParams( - model, - compute_free_edges=True, - compute_self_intersections=True, - ) -) -print(f"Total number of free edges present is {surf_report.n_free_edges}") - -############################################################################### -# Define local size control and generate size-field -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# To accurately represent the physics of the DRS wing, a limitation of 8 mm -# is imposed on the mesh size of the wing. -# This is accomplished by implementing a curvature size control, which refines the -# mesh according to the curvature of the DRS surfaces. -# Additionally, to accurately capture the curved surfaces of other sections of the -# wing, curvature control is defined with a normal angle of 18 degrees. -# These controls are used during surface mesh generation. -# A volumetric size field is then computed based on the defined size controls. -# The volumetric size field plays a crucial role in controlling -# the growth and refinement of the volume mesh. - -# Local curvature size control for DRS -curv_size_control = model.control_data.create_size_control(prime.SizingType.CURVATURE) -curv_size_params = prime.CurvatureSizingParams(model, normal_angle=18, max=4) -curv_size_control.set_curvature_sizing_params(curv_size_params) -curv_scope = prime.ScopeDefinition( - model, - entity_type=prime.ScopeEntity.FACEZONELETS, - part_expression="f1_car_rear_wing*", - label_expression="*drs*", -) -curv_size_control.set_scope(curv_scope) -curv_size_control.set_suggested_name("curvature_drs") - -# Global curvature size control on all face zones of the rear wing -curv_size_control_global = model.control_data.create_size_control(prime.SizingType.CURVATURE) -curv_size_params_global = prime.CurvatureSizingParams(model, normal_angle=18, min=8) -curv_size_control_global.set_curvature_sizing_params(curv_size_params_global) -curv_scope = prime.ScopeDefinition( - model, - entity_type=prime.ScopeEntity.FACEZONELETS, - part_expression="f1_car_rear_wing*", -) -curv_size_control_global.set_scope(curv_scope) -curv_size_control_global.set_suggested_name("curvature_global") - -# Compute volumetric sizefield -compute_size = prime.SizeField(model) -vol_sf_params = prime.VolumetricSizeFieldComputeParams(model) -compute_size.compute_volumetric( - [curv_size_control.id, curv_size_control_global.id], volumetric_sizefield_params=vol_sf_params -) - -############################################################################### -# Generate surface mesh -# ~~~~~~~~~~~~~~~~~~~~~ -# Create a surface mesh for the rear wing using the defined size controls. -# To facilitate the definition of boundary conditions on the surfaces in the solver, -# generate face zones by utilizing the existing labels found in the rear wing model. - -mesh_util.surface_mesh_with_size_controls(size_control_names="*curvature*") -scope = prime.ScopeDefinition(model, label_expression="* !*enclosure*") -display = PrimePlotter() -display.plot(model, scope) -display.show() - -# Create face zones per label -for label in part.get_labels(): - mesh_util.create_zones_from_labels(label_expression=label) - -############################################################################### -# Compute volumetric regions -# ~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Compute the volume zones. - -mesh_util.compute_volumes(part_expression=part.name, create_zones_per_volume=True) - -############################################################################### -# Define volume controls -# ~~~~~~~~~~~~~~~~~~~~~~ -# To prevent the generation of a volume mesh within the solid wing, -# the type of a volume zone within the rear wing can be defined as "dead." -# To accomplish this, Volume Control is utilized to assign the type for the -# specific volume zone. -# Expressions are employed to define the volume zones that need to be filled, with -# ``* !f1_rw_enclosure`` indicating that it applies to all volume zones except -# for ``f1_rw_enclosure``. - -volume_control = model.control_data.create_volume_control() -volume_control.set_params( - prime.VolumeControlParams( - model, - cell_zonelet_type=prime.CellZoneletType.DEAD, - ) -) -volume_control.set_scope( - prime.ScopeDefinition( - model, evaluation_type=prime.ScopeEvaluationType.ZONES, zone_expression="* !f1_rw_enclosure" - ) -) - -############################################################################### -# Define prism controls -# ~~~~~~~~~~~~~~~~~~~~~ -# A prism control can be used to define inflation layers on the external aero surfaces. -# Specify the aero surfaces using labels. Here prism scope is defined on zones associated -# with labels ``*drs*`` and ``*plane*``. -# The growth for the prism layer is controlled by defining the offset type to -# be ``uniform`` with a first height of 0.5mm . - -prism_control = model.control_data.create_prism_control() -prism_control.set_surface_scope( - prime.ScopeDefinition( - model, - evaluation_type=prime.ScopeEvaluationType.LABELS, - entity_type=prime.ScopeEntity.FACEZONELETS, - label_expression="*drs*, *plane*", - ) -) -prism_control.set_volume_scope( - prime.ScopeDefinition( - model, - evaluation_type=prime.ScopeEvaluationType.ZONES, - entity_type=prime.ScopeEntity.VOLUME, - zone_expression="*f1_rw_enclosure*", - ) -) -prism_control.set_growth_params( - prime.PrismControlGrowthParams( - model, - offset_type=prime.PrismControlOffsetType.UNIFORM, - n_layers=5, - first_height=0.5, - growth_rate=1.2, - ) -) - -############################################################################### -# Generate volume mesh -# ~~~~~~~~~~~~~~~~~~~~ -# Volume mesh with hexcore polyhedral elements and boundary layer refinement. - -volume_mesh = prime.AutoMesh(model) -auto_mesh_param = prime.AutoMeshParams( - model, - prism_control_ids=[prism_control.id], - size_field_type=prime.SizeFieldType.VOLUMETRIC, - volume_fill_type=prime.VolumeFillType.HEXCOREPOLY, - volume_control_ids=[volume_control.id], -) -volume_mesh.mesh(part.id, auto_mesh_param) - -############################################################################### -# Print mesh statistics -# ~~~~~~~~~~~~~~~~~~~~~ - -# Get meshed part -part = model.get_part_by_name("f1_car_rear_wing") - -# Get statistics on the mesh -part_summary_res = part.get_summary(prime.PartSummaryParams(model=model)) - -# Get element quality on all parts in the model -search = prime.VolumeSearch(model=model) -params = prime.VolumeQualitySummaryParams( - model=model, - scope=prime.ScopeDefinition(model=model, part_expression="*"), - cell_quality_measures=[prime.CellQualityMeasure.INVERSEORTHOGONAL], - quality_limit=[0.9], -) -results = search.get_volume_quality_summary(params=params) - -# Print statistics on meshed part -print(part_summary_res) -print( - "\nMaximum inverse-orthoginal quality of the volume mesh : ", - results.quality_results_part[0].max_quality, -) - -# Mesh check -result = prime.VolumeMeshTool(model).check_mesh(part.id, params=prime.CheckMeshParams(model)) -print("\nMesh check", result, sep="\n") - -scope = prime.ScopeDefinition(model, part_expression="*", label_expression="* !*enclosure*") -display = PrimePlotter() -display.plot(model, scope, update=True) -display.show() -############################################################################### -# Write mesh -# ~~~~~~~~~~ -# Export as CAS file for external aero simulations. -with tempfile.TemporaryDirectory() as temp_folder: - print(temp_folder) - mesh_file = os.path.join(temp_folder, "f1_rear_wing_vol_mesh.cas") - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) - - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/09_multi_layer_quad_mesh_pcb.py b/examples/gallery/09_multi_layer_quad_mesh_pcb.py deleted file mode 100644 index 888671eec0..0000000000 --- a/examples/gallery/09_multi_layer_quad_mesh_pcb.py +++ /dev/null @@ -1,315 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_multi_layer_pcb_mesh: - -===================================================== -Mesh a generic PCB geometry with multiple hexa layers -===================================================== - -**Summary**: This example demonstrates how to set the base mesh size and number of -layers for each solid in a generic PCB geometry and then generate a mesh. - -Objective -~~~~~~~~~~ - -The example uses PyPrimeMesh to discretize a PCB CAD geometry by means of the -stacker technology. You can easily set up the mesh size of the base face (xy plane -in this example) and the number of mesh layers along the sweep direction (z axis in this example). -The CAD edges along the z direction are assigned with a named selection at a CAD level in Ansys -Discovery/SpaceClaim. - -The following image provides a snapshot of the Discovery tree to help you to understand the model's -organization. Share topology in Discovery/SpaceClaim guarantees the generation of a conformal mesh -between the solids. Named selections of edges allow you to specify the number of mesh elements to -generate along the sweep direction. To simplify the process and enhance convenience, this example -uses multiple meshing utilities provided in the ``lucid`` class. - -.. image:: ../../../images/multi_layer_quad_mesh_pcb.png - :align: center - :width: 800 - :alt: Generic PCB geometry. - -The resulting mesh with three layers per solid looks like this: - -.. image:: ../../../images/multi_layer_quad_mesh_pcb_3.png - :align: center - :width: 500 - :alt: Generic PCB geometry meshed. - -.. image:: ../../../images/multi_layer_quad_mesh_pcb_2.png - :align: center - :width: 500 - :alt: Generic PCB geometry meshed, zoom in. - - -Procedure -~~~~~~~~~~ -#. Import the fundamental libraries that are necessary to run the script. -#. Launch an Ansys Prime Server instance. -#. Instantiate the meshing utilities from the ``lucid`` class. -#. Define the base mesh size and number of layers along the sweep direction. -#. Import the CAD geometry. -#. Define the edge sizing along the sweep direction using existing edge named selections. -#. Define the parameters for the volume sweeper. -#. Set up, generate, and mesh the base face. -#. Stack the base face along the sweep direction. -#. Set up the zone naming before the mesh output. -#. Write a CAS file for use in the Fluent solver. -#. Exit the PyPrimeMesh session. - -""" - -############################################################################### -# Import all necessary modules -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Notice that you must install the PyVista library to be able to run the visualization -# tools included in this script. - -import os -import tempfile - -import ansys.meshing.prime as prime -from ansys.meshing.prime.graphics import PrimePlotter - -############################################################################### -# Launch Prime server and instantiate the ``lucid`` class -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and get the model. -# Instantiate meshing utilities from the ``lucid`` class. - -prime_client = prime.launch_prime() -model = prime_client.model -mesh_util = prime.lucid.Mesh(model=model) - -############################################################################### -# Define CAD file and mesh settings -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Define the number of layers per solid. -# Define the size in mm of the quad-dominant mesh on the base size. -# Define the path to the CAD file to mesh. -# Download the example CAD file using the ``prime.examples`` function. Otherwise, -# write the path to the desired CAD file on your machine. Supported file types -# are SCDOC, DSCO, and PMDB. - -# cad_file='/path/to/any/cad/file.dsco' -cad_file = prime.examples.download_multi_layer_quad_mesh_pcb_pmdat() -layers_per_solid = 4 # number of hexa mesh layers in each solid -base_face_size = 0.5 # surface mesh size in mm on the base face -# Chose whether to display the CAD/mesh at every stage -display_intermediate_steps = True # Use True/False - - -############################################################################### -# Import geometry -# ~~~~~~~~~~~~~~~ -# Import the geometry into Prime server. -# Use the Workbench ``CadReaderRoute`` to ensure that the shared topology is kept. - -mesh_util.read(file_name=cad_file) -# Use the following command to open CAD files of these types: SCDOC, DSCO, and PMDB. -# mesh_util.read( -# file_name = cad_file, -# cad_reader_route = prime.CadReaderRoute.WORKBENCH) - -############################################################################### -# Display the imported CAD in a PyVista window -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -if display_intermediate_steps: - display = PrimePlotter() - display.plot(model) - display.show() - -############################################################################### -# Define edge sizing constraints -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Set generic global sizing from 0.002mm to 2mm. -# Extract the the edge's length from the named selections, such as "edge_1_0.50_mm" -# (extract 0.5 mm length) or "edge_23_0.27_mm" (extract 0.27mm length). -# On each edge, assign a size equal to the edge's length divided by the -# predefined number of layers per solid. - -# Set generic global sizing from 0.002mm to 2mm. -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.002, max=2.0)) -ids = [] -# collect the imported geometry -part = model.parts[0] -for label in part.get_labels(): - # Check whether the named selection's name starts with the string "edge" - if label.startswith('edge'): - # Extract the edge's length, splitting its name at every "_" string - # Collect the second to last number - length = float(label.split("_")[-2]) # get 0.27 from "edge_23_0.27_mm" - # Initialize a constant-size mesh control (SOFT) - soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) - # Assign a mesh size equal to the edge's length/number of mesh layers - soft_size_params = prime.SoftSizingParams(model=model, max=length / layers_per_solid) - # Finalize the creation of mesh sizing - soft_size_control.set_soft_sizing_params(soft_size_params) - soft_size_scope = prime.ScopeDefinition( - model, - part_expression=part.name, - entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS, - label_expression=label + "*", - ) - soft_size_control.set_scope(soft_size_scope) - soft_size_control.set_suggested_name(label) - # Append the id of the edge sizing to the edge sizings' ids' list - ids.append(soft_size_control.id) - -############################################################################### -# Define controls for volume sweeper -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Set the sweep direction vector. -# Set up the geometric tolerances for lateral and stacking defeature. -# Select the sweep direction as the z axis (0,0,1). -# Append the IDs of the soft local sizings that have been previously defined on -# the edges. - -# Instantiate the volume sweeper -sweeper = prime.VolumeSweeper(model) -# Define the parameters for stacker -stacker_params = prime.MeshStackerParams( - model=model, - direction=[0, 0, 1], # define the sweep direction for the mesh - delete_base=True, # delete the base face in the end of stacker - lateral_defeature_tolerance=0.001, - stacking_defeature_tolerance=0.001, - size_control_ids=ids, -) # list of control IDs to be respected by the stacker - -############################################################################### -# Set up, generate, and mesh the base face -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create a soft sizing control. -# Assign the previously defined ``base_face_size`` function to the soft sizing. -# Create the base face. -# Mesh the base face. -# Display the base face. - -# Set up the necessary parameters for the generation of the base face. -soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) -soft_size_params = prime.SoftSizingParams(model=model, max=base_face_size) -soft_size_control.set_soft_sizing_params(soft_size_params) -soft_size_scope = prime.ScopeDefinition( - model, part_expression=part.name, entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS -) -soft_size_control.set_scope(soft_size_scope) -soft_size_control.set_suggested_name("b_f_size") - -# Create the base face, appending the the stacker mesh parameters. -createbase_results = sweeper.create_base_face( - part_id=model.get_part_by_name(part.name).id, - topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), - params=stacker_params, -) -base_faces = createbase_results.base_face_ids -model.get_part_by_name(part.name).add_labels_on_topo_entities(["base_faces"], base_faces) -scope = prime.ScopeDefinition(model=model, label_expression="base_faces") -base_scope = prime.lucid.SurfaceScope( - entity_expression="base_faces", - part_expression=part.name, - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, -) - -# Generate the quad-dominant surface mesh on the base face. -mesh_util_controls = mesh_util.surface_mesh_with_size_controls( - size_control_names="b_f_size", scope=base_scope, generate_quads=True -) - -############################################################################### -# Display the meshed base face in a PyVista window -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -if display_intermediate_steps: - display = PrimePlotter() - display.plot(model, update=True) - display.show() - -############################################################################### -# Stack the base face using the volume sweeper -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Use the volume sweeper to stack the base face along the previously defined sweep -# direction. -# Include the previously defined stacker parameters. -# Display the final volume mesh. - -# Use the ``stack_base_face`` function to generate the volume mesh -stackbase_results = sweeper.stack_base_face( - part_id=model.get_part_by_name(part.name).id, - base_face_ids=base_faces, - topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), - params=stacker_params, -) - -############################################################################### -# Display the final PCB mesh in a PyVista window -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -if display_intermediate_steps: - display = PrimePlotter() - display.plot(model, update=True) - display.show() -############################################################################### -# Set up the zone naming before the mesh output -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Delete the unnecessary topo entities. -# Name the walls of ``solid`` as ``wall_solid``. For example, if the solid's name is ``A``, then -# name the walls surrounding the solid ``wall_A``). -# Convert the labels to mesh zones. - -# Define deletion parameters -deletion_params = prime.DeleteTopoEntitiesParams( - model, delete_geom_zonelets=True, delete_mesh_zonelets=False -) -# Delete unnecessary geometrical entities. -part.delete_topo_entities(deletion_params) -# Rename the walls surrounding any volume of the mesh by appending the string -# ``wall_`` to the solid's name. For example, if the solid is named ``my_solid``, then -# name the walls surrounding the solid ``wall_my_solid``. -for volume in part.get_volumes(): - volume_zone_name = "wall_" + model.get_zone_name(part.get_volume_zone_of_volume(volume)) - label_zonelets = part.get_face_zonelets_of_volumes([volume]) - part.add_labels_on_zonelets([volume_zone_name], label_zonelets) -# Convert labels into mesh zones to use in the solver. -mesh_util_create_zones = mesh_util.create_zones_from_labels() - -############################################################################### -# Mesh output -# ~~~~~~~~~~~ -# Create a temporary folder and use it to output the mesh to a CAS file. - -with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, 'multi_layer_quad_mesh_pcb.cas') - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) -# Otherwise, specify a path on your local machine: -# mesh_util.write('local/path/to/your/mesh_file.cas') - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py deleted file mode 100644 index 87e4627867..0000000000 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_contact_patch: - -======================================================================== -Create a contact patch for wrapping between a wheel and ground interface -======================================================================== - -**Summary**: This example demonstrates how to create a contact patch for use with wrapping -to avoid meshing into a narrow contact region between two objects. - -Objective -~~~~~~~~~ -This example uses a contact patch for wrapping to avoid the interface of a wheel with the ground -to improve mesh quality when growing prism layers in the region of the contacting faces. - -.. image:: ../../../images/contact_patch.png - :align: center - :width: 600 - -The preceding image shows the following: - - -* Top left: Wheel/ground interface -* Top right: Addition of contact patch -* Lower left: Grouping tolerance at 4 with multiple contact patches -* Lower right: Grouping tolerance at 20 with merged single contact patch - - - -Procedure -~~~~~~~~~ -#. Launch an Ansys Prime Server instance and instantiate the meshing utilities - from the ``lucid`` class. -#. Import the wheel ground geometry. -#. Convert the topo parts to mesh parts so that the contact patch can be created. -#. Create a contact patch between the wheel and the ground. -#. Extract the fluid region using wrapping. -#. Volume mesh with polyhedral and prism cells. -#. Write a CAS file for use in the Fluent solver. -#. 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 - -client = prime.launch_prime() -model = client.model - -mesh_util = prime.lucid.Mesh(model) - -############################################################################### -# Import CAD geometry -# ~~~~~~~~~~~~~~~~~~~ -# Download the wheel ground geometry (FMD) file exported by SpaceClaim. -# Import the CAD geometry. The geometry consists of two topo parts: a wheel and an enclosing box. -# Labels are defined for the ground topo face on the enclosure and for the wheel -# as all the topo faces of the wheel part. - -# For Windows OS users, SCDOC or DSCO is also available. For example: -# wheel_ground_file = prime.examples.download_wheel_ground_scdoc() - -wheel_ground_file = prime.examples.download_wheel_ground_fmd() - -mesh_util.read(wheel_ground_file) - -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) -display.show() - -print(model) - -############################################################################### -# Convert topo parts to mesh parts -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Convert the faceted geometry of the topology to mesh for all parts as the contact patch -# requires face zonelets from mesh parts as input. - -wheel_part = model.get_part_by_name("wheel_body") -enclosure_part = model.get_part_by_name("enclosure") - -[part.delete_topo_entities(prime.DeleteTopoEntitiesParams(model)) for part in model.parts] - -############################################################################### -# Create a contact patch -# ~~~~~~~~~~~~~~~~~~~~~~ -# To create a contact patch, a direction is needed to define the resulting shape of the patch. -# A new part is created containing the patch. -# A prefix can be specified for the label created for the contact patch face zonelets generated. -# The offset distance determines the thickness and extent of the patch. The source face zonelet is -# offset to intersect the planar target face and the intersection used to define the contact patch. -# Due to the depth of the treads on the wheel, 20.0 is used as the offset to reach the tire surface. -# If multiple contact regions are found, they can be merged by grouping them using the grouping -# tolerance distance. With a grouping tolerance of 4.0, separate contact regions are created for -# some of the treads of the tire, see the image at the top of the example. To merge these contact -# regions into a single patch, the grouping tolerance distance is increased to 20.0, avoiding small -# gaps between contact regions. - -# The face zonelets of the wheel are defined as the source. -# The planar surface must be specified as the target. -# In this instance, the ground provides the planar target. - -source = wheel_part.get_face_zonelets() -target = enclosure_part.get_face_zonelets_of_label_name_pattern( - "ground", prime.NamePatternParams(model) -) - -params = prime.CreateContactPatchParams( - model, - contact_patch_axis=prime.ContactPatchAxis.Z, - offset_distance=20.0, - grouping_tolerance=20.0, - suggested_label_prefix="patch", -) -result = prime.SurfaceUtilities(model).create_contact_patch( - source_zonelets=source, target_zonelets=target, params=params -) -print(result.error_code) -print(model) - -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) -display.show() - -############################################################################### -# Wrap the fluid region -# ~~~~~~~~~~~~~~~~~~~~~ -# The largest internal region in this instance is the fluid region around the wheel. -# Intersection loops are created to capture the features at the corners between the -# patch, ground, and wheel. - -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) - -# Create a size control to limit the size of mesh on the wheel. -size_control = model.control_data.create_size_control(prime.SizingType.SOFT) -size_control.set_soft_sizing_params(prime.SoftSizingParams(model=model, max=8.0)) -size_control.set_scope(prime.ScopeDefinition(model=model, label_expression="wheel")) - -wrap_part = mesh_util.wrap( - min_size=4.0, - max_size=100.0, - region_extract=prime.WrapRegion.LARGESTINTERNAL, - create_intersection_loops=True, - wrap_size_controls=[size_control], -) - -display = PrimePlotter() -display.plot( - model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True -) -display.show() - -print(model) - -############################################################################### -# Volume mesh -# ~~~~~~~~~~~ -# Apply five layers of prisms to the wheel, patch, and ground. Mesh with polyhedrals. - -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) -mesh_util.volume_mesh( - volume_fill_type=prime.VolumeFillType.POLY, - prism_layers=5.0, - prism_surface_expression="wheel, patch*, ground", - prism_volume_expression="*", - scope=prime.lucid.VolumeScope(part_expression=wrap_part.name), -) - -display = PrimePlotter() -display.plot( - model, - scope=prime.ScopeDefinition(model, label_expression="!front !side_right !top"), - update=True, -) -display.show() - -mesh_util.create_zones_from_labels() - -wrap_part._print_mesh = True -print(wrap_part) - -############################################################################### -# Write model -# ~~~~~~~~~~~ -# Write a CAS file for use in the Fluent solver. - -with tempfile.TemporaryDirectory() as temp_folder: - wheel_model = os.path.join(temp_folder, "wheel_ground_contact.cas.h5") - prime.FileIO(model).export_fluent_case( - wheel_model, - export_fluent_case_params=prime.ExportFluentCaseParams(model, cff_format=True), - ) - assert os.path.exists(wheel_model) - print(f"Fluent case exported at {wheel_model}") - -############################################################################### -# Exit the PyPrimeMesh session -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -client.exit() From 6745295a441d44a5943f71dc019f117103952ff2 Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 15:58:08 +0100 Subject: [PATCH 06/27] testing --- doc/make.bat | 2 +- doc/source/conf.py | 2 +- examples/misc/example_template.py | 151 ------------------------------ 3 files changed, 2 insertions(+), 153 deletions(-) delete mode 100644 examples/misc/example_template.py diff --git a/doc/make.bat b/doc/make.bat index 05a0bf9dc2..69c9588fc5 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -8,7 +8,7 @@ if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) if "%SPHINXOPTS%" == "" ( - set SPHINXOPTS=-j auto + set SPHINXOPTS=-j 1 ) set SOURCEDIR=source set BUILDDIR=_build diff --git a/doc/source/conf.py b/doc/source/conf.py index c0f876e9c9..b132a2c398 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -171,7 +171,7 @@ "backreferences_dir": None, # Modules for which function level galleries are created. In "doc_module": "ansys-meshing-prime", - "image_scrapers": (DynamicScraper(), "matplotlib"), + "image_scrapers": ("matplotlib"), "ignore_pattern": "flycheck*", "thumbnail_size": (350, 350), } diff --git a/examples/misc/example_template.py b/examples/misc/example_template.py deleted file mode 100644 index eadade3bed..0000000000 --- a/examples/misc/example_template.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_how_to_add_an_example_reference_key: - -Add a new example ------------------ -**Summary**: This example demonstrates how to add new examples and serves as a template -that you can use in their creation. - -A block comment must be included at the top of any new example. Each example -must have a reference tag in this format: - -``.. _ref_my_example:`` - -The ``.. _ref_`` is necessary. Everything that follows is your reference tag. -Keep all references in `snake case`. - -This section should give a brief overview of what the example is about and/or demonstrates. -The title should be changed to reflect the topic your example covers. - -New examples should be added as Python scripts to: - -``pyprimemesh/examples/gallery`` - -.. note:: - Avoid creating new folders unless absolutely necessary. If in doubt, put the example - in the folder closest to what it is doing and its precise location can be advised - on in the pull request. If you *must* create a new folder, make sure to add a - ``README.txt`` file containing a reference, a title, and a single sentence describing the folder. - Otherwise, the new folder is ignored by Sphinx. - -Example file names should be in the format: - -``example_name.py`` - -.. note:: - Supporting input files for the example, such as CAD or mesh file assets, must be either original - content or have appropriate licensing and ownership permissions from their respective owners. If - the input files are used within the example script provided they must be capable of running in - the CI pipeline. This means that only files that can be read using the native file formats and - CAD readers can be used in the scripted examples. - -The recommended data formats to be included in the example are: - -* .pmdat -* .fmd -* .scdoc or .dsco (supported on Windows OS) - -Supporting input files should be added in: - -`Github Example Data Repository `_ - -Referencing files as enum and creating download function in: - -``pyprimemesh/examples.py`` - -Also adding download function to: - -``pyprimemesh/examples/__init__.py`` - -After this preamble is the first code block: -""" - -import ansys.meshing.prime as prime -from ansys.meshing.prime.graphics import PrimePlotter - -# Start Ansys Prime Server instance and get client model -prime_client = prime.launch_prime() -model = prime_client.model - -# Your code goes here... -mesh_util = prime.lucid.Mesh(model=model) - -# For Windows OS users scdoc is also available: -# mixing_elbow = prime.examples.download_elbow_scdoc() -mixing_elbow = prime.examples.download_elbow_fmd() -mesh_util.read(mixing_elbow) -print(model) - -############################################################################### -# Create sections -# ~~~~~~~~~~~~~~~ -# You can break up code blocks in titled sections that provide descriptive text. -# When Sphinx is used to generate the documentation, this content is interpreted -# as ReStructured Text (RST). -# -# .. note:: -# You only need to create the Python (PY) files for the example. The -# ``sphinx-gallery`` extension automatically generates the Jupyter -# notebook, the HTML files for the documentation, and the demo script. -# -# Sections can contain any information that you may have regarding the example, -# such as step-by-step comments and information on motivations. In the generated -# Jupyter notebook, this text is translated into a markdown cell. -# -# As in Jupyter notebooks, if code is left unassigned at the end of a code block -# (as with ``model`` in the previous block), the output is generated and -# printed to the screen according to its ``__repr__``. Otherwise, you can use -# ``print()`` to output the ``__str__``. - -# more code... -mesh_util.surface_mesh(min_size=5, max_size=20) -mesh_util.volume_mesh( - volume_fill_type=prime.VolumeFillType.POLY, - prism_surface_expression="* !inlet !outlet", - prism_layers=3, -) - -############################################################################### -# Render graphics -# ~~~~~~~~~~~~~~~ -# If you display graphics, the result is auto-generated and -# rendered on the page: -display = PrimePlotter() -display.plot(model) -display.show() - -############################################################################### -# Make a pull request -# ~~~~~~~~~~~~~~~~~~~ -# Once your example is complete and you've verified builds locally, you can make a -# pull request (PR). Branches containing examples should be prefixed with `doc/` -# as per the branch-naming conventions found in the :ref:`ref_index_contributing` -# topic in the *PyAnsys Developer's Guide*. - -############################################################################### -# Stop Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~ -# -prime_client.exit() From 25b0d373eac6ec4029dd3e96fad8a80c8c33eb38 Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 16:56:55 +0100 Subject: [PATCH 07/27] test --- doc/make.bat | 4 +- doc/source/conf.py | 2 +- examples/gallery/00_lucid_file_IO.py | 289 +++++++++ examples/gallery/01_bracket_scaffold.py | 175 +++++ examples/gallery/02_lucid_mixing_elbow.py | 157 +++++ examples/gallery/03_lucid_pipe_tee.py | 202 ++++++ examples/gallery/04_lucid_toy_car.py | 321 +++++++++ examples/gallery/05_pcb_stacker.py | 187 ++++++ examples/gallery/06_blade_morph.py | 151 +++++ examples/gallery/07_saddle_bracket.py | 239 +++++++ .../gallery/08_lucid_generic_f1_rear_wing.py | 340 ++++++++++ .../gallery/09_multi_layer_quad_mesh_pcb.py | 315 +++++++++ .../gallery/10_wheel_ground_contact_patch.py | 233 +++++++ examples/gallery/11_solder_ball.py | 608 +++++++++--------- examples/misc/example_template.py | 151 +++++ 15 files changed, 3067 insertions(+), 307 deletions(-) create mode 100644 examples/gallery/00_lucid_file_IO.py create mode 100644 examples/gallery/01_bracket_scaffold.py create mode 100644 examples/gallery/02_lucid_mixing_elbow.py create mode 100644 examples/gallery/03_lucid_pipe_tee.py create mode 100644 examples/gallery/04_lucid_toy_car.py create mode 100644 examples/gallery/05_pcb_stacker.py create mode 100644 examples/gallery/06_blade_morph.py create mode 100644 examples/gallery/07_saddle_bracket.py create mode 100644 examples/gallery/08_lucid_generic_f1_rear_wing.py create mode 100644 examples/gallery/09_multi_layer_quad_mesh_pcb.py create mode 100644 examples/gallery/10_wheel_ground_contact_patch.py create mode 100644 examples/misc/example_template.py diff --git a/doc/make.bat b/doc/make.bat index 69c9588fc5..0048f8e4d8 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -7,9 +7,7 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) -if "%SPHINXOPTS%" == "" ( - set SPHINXOPTS=-j 1 -) + set SOURCEDIR=source set BUILDDIR=_build diff --git a/doc/source/conf.py b/doc/source/conf.py index b132a2c398..ea220d4aa7 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -131,7 +131,7 @@ # The master toctree document. master_doc = 'index' -autosummary_generate = True +autosummary_generate = False autosummary_imported_members = True autosummary_ignore_module_all = False diff --git a/examples/gallery/00_lucid_file_IO.py b/examples/gallery/00_lucid_file_IO.py new file mode 100644 index 0000000000..1b03377764 --- /dev/null +++ b/examples/gallery/00_lucid_file_IO.py @@ -0,0 +1,289 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_file_io: + +============================================================== +Convert data when importing and exporting mesh and CAD formats +============================================================== + +**Summary**: This example shows how mesh and geometry formats are converted +during import and export. + +Objective +~~~~~~~~~~ + +The objective is to illustrate how data is converted and passed during import +and export of mesh and geometry. + + +.. image:: ../../../images/part_type_new.png + :align: center + :width: 800 + :alt: Part Structure + + +Procedure +~~~~~~~~~~ +#. Launch an Ansys Prime Server instance. +#. Instantiate the meshing utilities from the ``lucid`` class. +#. Import CAD geometry and review the imported entities. +#. Generate surface mesh with a constant mesh size of 2mm. +#. Generate volume mesh using tetrahedral elements and default settings . +#. Review the entities to be exported to solvers. +#. Export the mesh file as pmdat, cdb and cas format. +#. Import the created solver files to review the entities as they are coming from the solvers. +#. Exit the PyPrimeMesh session. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules and +# launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and 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.plotter import PrimePlotter + +prime_client = prime.launch_prime() +model = prime_client.model +mesh_util = prime.lucid.Mesh(model=model) + +############################################################################### +# Import geometry +# ~~~~~~~~~~~~~~~~~~~ +# Download the CAD file “pyprime_block_import.fmd” and import its geometry +# into PyPrimeMesh. +# +# Display part details by printing the model. The TopoPart’s +# name from model details is **pyprime_block_import**. +# +# After import of CAD model, within the topopart the facets from the CAD +# exists in the form of geom data. This can be seen in the image below. +# +# .. image:: ../../../images/00_after_cad_import.png +# :align: center +# :width: 800 +# :alt: TopoPart after import of CAD file “pyprime_block_import.fmd”. +# +# The topology consists of the following TopoEntities , they are TopoEdges, TopoFaces +# and TopoVolumes. +# +# * TopoEdge represent the curves/edges present in the CAD. +# In this case there are **17 edges** present in SpaceClaim are imported +# as **17 TopoEdges**. +# +# * TopoFace represent the surfaces/faces present in the CAD. +# The **8 CAD Faces** present in SpaceClaim are imported as **8 Topofaces** in +# PyPrimeMesh. +# +# * TopoVolume represent the solid volumes present in the CAD. +# Since there is only **one solid body** in SpaceClaim, this is imported as **one Topovolume** +# in PyPrimeMesh. +# +# Named selections or groups in the CAD become labels after import. In this example , +# the Named Selection / Group named **my_group** in Spaceclaim is imported as a label +# in PyPrimeMesh. +# +# After CAD import the solid body, surface body or an edge body present in SCDM would be defined +# as Volume Zones, Face Zones and Edge Zones in PyPrimeMesh. In the CAD model , there exist a +# **single solid body named “solid”** which after import becomes as a **Volume Zone named solid**. + +mesh_util.read(file_name=prime.examples.download_block_model_fmd()) +# mesh_util.read(file_name=prime.examples.download_block_model_scdoc()) +print(model) +display = PrimePlotter() +display.plot(model) +display.show() + +############################################################################### +# Generate Mesh +# ~~~~~~~~~~~~~~~ +# The topo part currently has no mesh associated with it and contains only +# geometry. +# +# Using the Lucid API ``surface_mesh``, users can generate a conformal mesh on the topofaces. +# A conformal mesh with a constant mesh size of 2mm is generated. After mesh generation, the +# mesh data is available within the TopoPart. +# +# This can be seen in the image below +# +# .. image:: ../../../images/00_after_mesh_generation.png +# :align: center +# :width: 800 +# :alt: TopoPart after mesh generation. +# +# The mesh for a group of topo faces labeled “my_group” is displayed by defining the +# label expression in the display scope. The Volume Mesh is generated keeping the volume fill +# as the default meshing algorithms. + +mesh_util.surface_mesh(min_size=2.0) +display = PrimePlotter() +display.plot(model, update=True) +display.show() + +part = model.get_part_by_name("pyprime_block_import") + +display = PrimePlotter() +display.plot(model, prime.ScopeDefinition(model, label_expression="my_group")) +display.show() + +mesh_util.volume_mesh() + +############################################################################### +# Export mesh as PyPrimeMesh (.pmdat) native format mesh file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# PyPrimeMesh allows user to export mesh in its native format name pmdat. +# This configuration allows retaining the topology data along with mesh data. +# +temp_folder = tempfile.TemporaryDirectory() +mesh_file_pmdat = os.path.join(temp_folder.name, "pyprime_block_mesh.pmdat") +mesh_util.write(mesh_file_pmdat) +assert os.path.exists(mesh_file_pmdat) +print("\nExported file:\n", mesh_file_pmdat) + +############################################################################### +# Export mesh as Ansys MAPDL (.cdb) format mesh file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# PyPrimeMesh allows export of mesh as Ansys MAPDL (.cdb) format mesh file. While exporting +# the mesh to Ansys MAPDL, the labels present in session are converted to components +# containing nodes. +# + +mesh_file_cdb = os.path.join(temp_folder.name, "pyprime_block_mesh.cdb") +mesh_util.write(mesh_file_cdb) +assert os.path.exists(mesh_file_cdb) +print("\nExported file:\n", mesh_file_cdb) + + +############################################################################### +# Export mesh Ansys Fluent (CAS) format mesh file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Zones in PyPrimeMesh can be defined as a collection of either topo or zonelet entities that +# we can assign properties to in a solver when exported as mesh , for example "if the user +# wishes to assign a material to a region of the model they can define a volume zone for +# multiple topo volumes or cell zonelets so they can apply the property. +# +# Hence while exporting the mesh as (MSH or CAS) file to the Fluent solver, the +# boundary conditions for the zones needs to be defined. For this reason the topo +# entities / zonelets associated with a labels are converted to volume/face/edge zones +# respectively. +# +# The property of a zone is that a zonelet or TopoEntity can only be present in a single zone. +# The topo entities / zonelets that are not associated with their respective zones types are +# merged together during export to Fluent formats. The topology data present is removed +# automatically when export to Fluent(MSH or CAS) formats. +# + +mesh_util.create_zones_from_labels("my_group") +print(model) + +# Export as Fluent (*.cas) format mesh file +mesh_file_cas = os.path.join(temp_folder.name, "pyprime_block_mesh.cas") +mesh_util.write(mesh_file_cas) +assert os.path.exists(mesh_file_cas) +print("\nExported file:\n", mesh_file_cas) + +############################################################################### +# Reading Ansys PyPrimeMesh native mesh file (pmdat) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Read the exported PyPrimeMesh(pmdat) native mesh format file, it is observed that +# part topology contains both geom data as well as mesh data. This is seen in the image below +# +# .. image:: ../../../images/00_import_pmdat.png +# :align: center +# :width: 800 +# :alt: TopoPart after reading mesh part. +# +# Meshed zonelets (that contain the mesh data) are only created once the topo part +# is converted to a mesh part by deleting the topo entities. Here , while deleting the topology +# we are deleting the geom data (face) and retaining the mesh data for solve purpose. +# When deleting the topoogy , the TopoPart is converted to MeshPart and the topo entities +# are converted to their respective zonelet type in MeshPart, this is shown as follows; +# +# * **01 TopoVolumes -> 01 Cell Zonelets** +# * **08 TopoFaces -> 08 Face Zonelets** +# * **17 TopoEdges -> 17 Edge Zonelets** +# +# The zones association with topoentities would change to their +# corresponding equivalent zonelet type in MeshParts. +# +# .. image:: ../../../images/00_after_delete_topology.png +# :align: center +# :width: 800 +# :alt: MeshPart after deleting topology. +# + +mesh_util.read(mesh_file_pmdat, append=False) + +print(model) +for part in model.parts: + if len(part.get_topo_faces()) > 0: + part.delete_topo_entities( + prime.DeleteTopoEntitiesParams( + model, delete_geom_zonelets=True, delete_mesh_zonelets=False + ) + ) + +print(model) + +############################################################################### +# Reading Ansys Fluent (.cas) format mesh file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Read the exported Fluent format mesh file. +# +# .. image:: ../../../images/00_import_cas.png +# :align: center +# :width: 800 +# :alt: After import of cas mesh file. +# +# It would be observed that +# the zone name **my_group** is retained and the remaining face zonelets that are not +# associated with a face zone(s) are merged to create a new zone named **wall**. +# There are no labels present in the mesh file. + +mesh_util.read(mesh_file_cas, append=False) +print(model) +part = model.parts[0] +for zone in part.get_face_zones(): + print(model.get_zone_name(zone)) + scope = prime.ScopeDefinition( + model, + evaluation_type=prime.ScopeEvaluationType.ZONES, + zone_expression=model.get_zone_name(zone), + ) + display = PrimePlotter() + display.plot(model, scope, update=True) + display.show() + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/01_bracket_scaffold.py b/examples/gallery/01_bracket_scaffold.py new file mode 100644 index 0000000000..e2e4ab655a --- /dev/null +++ b/examples/gallery/01_bracket_scaffold.py @@ -0,0 +1,175 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_bracket_mid_surface_mesh: + +===================================================== +Mesh a mid-surfaced bracket for a structural analysis +===================================================== + +**Summary**: This example demonstrates how to use topology-based connection +to generate conformal surface mesh. + +Objective +~~~~~~~~~ + +To create conformal surface mesh, you can scaffold topofaces, topoedges, or both to +connect all the surface bodies and mesh the bracket with quad elements. + +.. image:: ../../../images/bracket_mid_surface_scaffold_w.png + :align: center + :width: 400 + :alt: Scaffolding result in a wireframe representation. + +Procedure +~~~~~~~~~ +#. Launch Ansys Prime Server. +#. Import the CAD geometry and create the part per the CAD model. +#. Scaffold topofaces and topoedges with a tolerance parameter. +#. Surface mesh topofaces with a constant size and generate quad elements. +#. Write a CDB file for use in the APDL solver. +#. Exit the PyPrimeMesh session. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and get the model. + +import os +import tempfile + +from ansys.meshing import prime +from ansys.meshing.prime.graphics import PrimePlotter + +prime_client = prime.launch_prime() +model = prime_client.model + +############################################################################### +# Import CAD geometry +# ~~~~~~~~~~~~~~~~~~~ +# Download the bracket geometry (FMD) file exported by SpaceClaim. +# Import the CAD geometry. +# Create the part per the CAD model for the topology-based connection. + +# For Windows OS users, scdoc is also available: +# bracket_file = prime.examples.download_bracket_scdoc() + +bracket_file = prime.examples.download_bracket_fmd() + +file_io = prime.FileIO(model) +file_io.import_cad( + file_name=bracket_file, + params=prime.ImportCadParams( + model=model, + length_unit=prime.LengthUnit.MM, + part_creation_type=prime.PartCreationType.MODEL, + ), +) + +############################################################################### +# Review the part +# ~~~~~~~~~~~~~~~ +# Get the part summary. +# Display the model to show edges by connection. +# Use keyboard shortcuts to switch between +# the surface (``s``) and wireframe (``w``) representations. +# Color code for edge connectivity: +# +# - Red: free +# - Black: double +# - Purple: triple + +part = model.get_part_by_name('bracket_mid_surface-3') +part_summary_res = part.get_summary(prime.PartSummaryParams(model, print_mesh=False)) +print(part_summary_res) + +display = PrimePlotter() +display.add_model(model) +display.show() + +############################################################################### +# Connection +# ~~~~~~~~~~ +# Initialize the connection tolerance and other parameters. (The connection +# tolerance is smaller than the target element size.) +# Scaffold the topofaces, topoedges, or both with connection parameters. + +# Target element size +element_size = 0.5 + +params = prime.ScaffolderParams( + model, + absolute_dist_tol=0.1 * element_size, + intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, + constant_mesh_size=element_size, +) + +# Get existing topoface or topoedge IDs +faces = part.get_topo_faces() +beams = [] + +scaffold_res = prime.Scaffolder(model, part.id).scaffold_topo_faces_and_beams( + topo_faces=faces, topo_beams=beams, params=params +) +print(scaffold_res) + +############################################################################### +# Surface mesh +# ~~~~~~~~~~~~ +# Initialize surface meshing parameters. +# Mesh topofaces with the constant size and generate quad elements. + +surfer_params = prime.SurferParams( + model=model, + size_field_type=prime.SizeFieldType.CONSTANT, + constant_size=element_size, + generate_quads=True, +) + +surfer_result = prime.Surfer(model).mesh_topo_faces(part.id, topo_faces=faces, params=surfer_params) + +# Display the mesh +pl = PrimePlotter() +pl.plot(model, update=True) +pl.show() + +############################################################################### +# Write mesh +# ~~~~~~~~~~ +# Write a CDB file for use in the APDL solver. + +with tempfile.TemporaryDirectory() as temp_folder: + mapdl_cdb = os.path.join(temp_folder, 'bracket_scaffold.cdb') + file_io.export_mapdl_cdb(mapdl_cdb, params=prime.ExportMapdlCdbParams(model)) + assert os.path.exists(mapdl_cdb) + print(f'MAPDL case exported at {mapdl_cdb}') + +############################################################################### +# Exit the PyPrimeMesh session +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/02_lucid_mixing_elbow.py b/examples/gallery/02_lucid_mixing_elbow.py new file mode 100644 index 0000000000..b35f1ef680 --- /dev/null +++ b/examples/gallery/02_lucid_mixing_elbow.py @@ -0,0 +1,157 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_mixing_elbow_mesh: + +======================================= +Mesh a mixing elbow for a flow analysis +======================================= + +**Summary**: This example demonstrates how to mesh a mixing elbow for a flow analysis. + +Objective +~~~~~~~~~ + +This example meshes a mixing elbow with polyhedral elements and wall boundary +layer refinement. It uses several meshing utilities available in the ``lucid`` class for +convenience and ease. + +.. image:: ../../../images/elbow.png + :align: center + :width: 400 + :alt: Mixing elbow mesh. + +Procedure +~~~~~~~~~ +#. Launch Ansys Prime Server and instantiate meshing utilities from the ``lucid`` class. +#. Import the geometry and create face zones from labels imported from the geometry. +#. Surface mesh geometry with curvature sizing. +#. Volume mesh with polyhedral elements and boundary layer refinement. +#. Print statistics on the generated mesh. +#. Write a CAS file for use in the Fluent solver. +#. Exit the PyPrimeMesh session. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and get the model. +# Instantiate meshing utilities from the ``lucid`` class. + +import os +import tempfile + +from ansys.meshing import prime +from ansys.meshing.prime.graphics import PrimePlotter + +prime_client = prime.launch_prime() +model = prime_client.model +mesh_util = prime.lucid.Mesh(model=model) + +############################################################################### +# Import geometry +# ~~~~~~~~~~~~~~~ +# Download the elbow geometry (FMD) file exported by SpaceClaim. +# Import the geometry. +# Create face zones from labels imported from the geometry for use in Fluent solver. + + +# For Windows OS users, scdoc is also available: +# mixing_elbow = prime.examples.download_elbow_scdoc() + +mixing_elbow = prime.examples.download_elbow_fmd() + +mesh_util.read(file_name=mixing_elbow) +mesh_util.create_zones_from_labels("inlet,outlet") + +############################################################################### +# Surface mesh +# ~~~~~~~~~~~~ +# Surface mesh the geometry setting minimum and maximum sizing +# to use for curvature refinement. + +mesh_util.surface_mesh(min_size=5, max_size=20) + +############################################################################### +# Volume mesh +# ~~~~~~~~~~~ +# Volume mesh with polyhedral elements and boundary layer refinement. +# Fill the volume with polyhedral and prism mesh +# specifying the location and number of layers for prisms. +# Use expressions to define the surfaces to have prisms grown +# where ``* !inlet !outlet`` states ``all not inlet or outlet``. + +mesh_util.volume_mesh( + volume_fill_type=prime.VolumeFillType.POLY, + prism_surface_expression="* !inlet !outlet", + prism_layers=3, +) + +# Display the mesh +pl = PrimePlotter(allow_picking=True) +pl.plot(model) +pl.show() + +############################################################################### +# Print mesh statistics +# ~~~~~~~~~~~~~~~~~~~~~ + +# Get meshed part +part = model.get_part_by_name("flow_volume") + +# Get statistics on the mesh +part_summary_res = part.get_summary(prime.PartSummaryParams(model=model)) + +# Get element quality on all parts in the model +search = prime.VolumeSearch(model=model) +params = prime.VolumeQualitySummaryParams( + model=model, + scope=prime.ScopeDefinition(model=model, part_expression="*"), + cell_quality_measures=[prime.CellQualityMeasure.SKEWNESS], + quality_limit=[0.95], +) +results = search.get_volume_quality_summary(params=params) + +# Print statistics on meshed part +print(part_summary_res) +print("\nMaximum skewness: ", results.quality_results_part[0].max_quality) + +############################################################################### +# 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, "mixing_elbow.cas") + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/03_lucid_pipe_tee.py b/examples/gallery/03_lucid_pipe_tee.py new file mode 100644 index 0000000000..0208bc4fad --- /dev/null +++ b/examples/gallery/03_lucid_pipe_tee.py @@ -0,0 +1,202 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_pipe_tee: + +==================================================================== +Mesh a pipe T-section for structural thermal and fluid flow analysis +==================================================================== + +**Summary**: This example demonstrates how to mesh a pipe T-section for both +structural thermal and fluid flow simulation. + + +Objective +~~~~~~~~~~ + +This example meshes the solids of a pipe T-section for a +structural thermal analysis using tetrahedral elements and uses the +wrapper to extract the fluid domain and mesh using polyhedral cells with +prismatic boundary layers. + +.. figure:: ../../../images/pipe_tee.png + :align: center + :width: 800 + + **Thermal structural and fluid flow meshes** + +Procedure +~~~~~~~~~~ +#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. +#. Read the CAD geometry. +#. Mesh for the structural thermal analysis. +#. Write the mesh for the structural thermal analysis. +#. Extract the fluid by wrapping. +#. Mesh with polyhedral and prisms. +#. Write the mesh for the fluid simulation. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch the Ansys Prime Server instance and connect the client. +# Get the client model and instantiate meshing utilities from the ``lucid`` class. + +import os +import tempfile + +from ansys.meshing import prime +from ansys.meshing.prime import lucid +from ansys.meshing.prime.graphics import PrimePlotter + +prime_client = prime.launch_prime() +model = prime_client.model +mesh_util = lucid.Mesh(model) + +############################################################################### +# Read CAD geometry +# ~~~~~~~~~~~~~~~~~ +# Download the example FMD geometry file. +# The FMD file format is exported from SpaceClaim and is compatible with Linux. +# Read and display the geometry file. +# The file contains several unmeshed parts, which is what you would get after you +# import from a CAD file. +# For Windows OS users, the SCDOC format is also available: +# ``pipe_tee = prime.examples.download_pipe_tee_scdoc()`` +pipe_tee = prime.examples.download_pipe_tee_fmd() +mesh_util.read(pipe_tee) + +display = PrimePlotter() +display.plot(model) +display.show() + +print(model) + +############################################################################### +# Mesh for structural +# ~~~~~~~~~~~~~~~~~~~ +# Surface mesh using curvature sizing. +# Volume mesh with tetrahedral elements. +# Delete unwanted capping surface geometries by deleting +# parts that do not have any volume zones. +# Display structural thermal mesh ready for export. + +mesh_util.surface_mesh(min_size=2.5, max_size=10) +mesh_util.volume_mesh() + +toDelete = [part.id for part in model.parts if not part.get_volume_zones()] + +if toDelete: + model.delete_parts(toDelete) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() + +############################################################################### +# Write structural mesh +# ~~~~~~~~~~~~~~~~~~~~~ +# Labels are exported to the CDB file as components for +# applying load boundary conditions in the solver. + +with tempfile.TemporaryDirectory() as temp_folder: + structural_mesh = os.path.join(temp_folder, "pipe_tee.cdb") + mesh_util.write(structural_mesh) + print("\nExported Structural Mesh: ", structural_mesh) + +############################################################################### +# Extract fluid by wrapping +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# You can deal with the small internal diameter change between flanges in several ways: +# +# * Connect the geometry to extract a volume and refine the +# mesh around this detail to capture. +# * Modify the geometry to remove the feature. +# * Wrap to extract the internal flow volume and walk over the feature. +# +# This example wraps and walks over these features. +# +# Read in the geometry again. +# +# Use a constant size wrap to walk over the diameter change +# feature and extract the largest internal volume as the fluid. +# +# By default, the wrap uses all parts as input and deletes the input +# geometry after wrapping unless ``keep_input`` is set as ``True``. + +mesh_util.read(pipe_tee) + +wrap = mesh_util.wrap(min_size=6, region_extract=prime.WrapRegion.LARGESTINTERNAL) + +print(model) + +display = PrimePlotter() +display.add_model(model, update=True) +display.show() + +############################################################################### +# Volume mesh fluid +# ~~~~~~~~~~~~~~~~~ +# Create zones for each label to use for boundary condition definitions. +# Volume mesh with prism polyhedral, not growing prisms from inlets and outlets. +# Visualize the generated volume mesh. +# When displaying, you can avoid displaying unnecessary edge zones. +# You can clearly see the prism layers that were specified by the Prism control. + +# set global sizing +params = prime.GlobalSizingParams(model, min=6, max=50) +model.set_global_sizing_params(params) + +mesh_util.create_zones_from_labels("outlet_main,in1_inlet,in2_inlet") + +mesh_util.volume_mesh( + prism_layers=5, + prism_surface_expression="* !*inlet* !*outlet*", + volume_fill_type=prime.VolumeFillType.POLY, +) + +print(model) +display = PrimePlotter() +display.add_scope( + model, scope=prime.ScopeDefinition(model=model, label_expression="* !*__*"), update=True +) +display.show() + +############################################################################### +# Write fluid mesh +# ~~~~~~~~~~~~~~~~ +# Write a MSH file for the Fluent solver. + +with tempfile.TemporaryDirectory() as temp_folder: + fluid_mesh = os.path.join(temp_folder, "pipe_tee.msh") + mesh_util.write(fluid_mesh) + assert os.path.exists(fluid_mesh) + print("\nExported Fluid Mesh: ", fluid_mesh) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/04_lucid_toy_car.py b/examples/gallery/04_lucid_toy_car.py new file mode 100644 index 0000000000..059f6b88c2 --- /dev/null +++ b/examples/gallery/04_lucid_toy_car.py @@ -0,0 +1,321 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_toy_car_wrap: + +================================== +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. + +.. image:: ../../../images/toy_car.png + :align: center + :width: 400 + :alt: Toy car wrap. + +Procedure +~~~~~~~~~ +#. Launch an Ansys Prime Server instance. +#. Instantiate the meshing utilities from the ``lucid`` class. +#. Import the geometry. +#. Coarse wrap parts with holes to clean up. +#. Extract the fluid region using a wrapper. +#. Check that the wrap surface is closed and that the quality is suitable. +#. Mesh only fluid with tetrahedral elements and boundary layer refinement. +#. Create face zones from labels imported from the geometry. +#. Print statistics on the generated mesh. +#. Improve the mesh quality. +#. Write a CAS file for use in the Fluent solver. +#. 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) +############################################################################### +# 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() +############################################################################### +# 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() + + +############################################################################### +# 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) + +############################################################################### +# 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) + +############################################################################### +# 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) + +############################################################################### +# 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) + +############################################################################### +# Print mesh stats +# ~~~~~~~~~~~~~~~~ +# Print statistics on the generated mesh. + +vtool = prime.VolumeMeshTool(model=model) +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) + +quality = prime.VolumeSearch(model) +scope = prime.ScopeDefinition(model, part_expression=wrap_part.name) + +part_summary_res = wrap_part.get_summary( + prime.PartSummaryParams(model=model, print_id=False, print_mesh=True) +) + +print("\nNo. of cells : ", part_summary_res.n_cells) + +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) + +############################################################################### +# 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) + +############################################################################### +# 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) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/05_pcb_stacker.py b/examples/gallery/05_pcb_stacker.py new file mode 100644 index 0000000000..7994e8045a --- /dev/null +++ b/examples/gallery/05_pcb_stacker.py @@ -0,0 +1,187 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_pcb: + +========================================== +Mesh a PCB for structural thermal analysis +========================================== + +**Summary**: This example demonstrates how to mesh a printed circuit board +with mainly hexahedral elements for structural thermal simulation using the volume sweeper. + +Objective +~~~~~~~~~~ +This example uses the volume sweeper to mesh the solids of a printed circuit board for a +structural thermal analysis using predominantly hexahedral elements. + +.. image:: ../../../images/pcb_stacker.png + :align: center + :width: 800 + :alt: Thermal structural mesh. + +Procedure +~~~~~~~~~~ +#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. +#. Read the CAD geometry. +#. Create a base face, projecting edge loops and imprinting to capture the geometry. +#. Surface mesh the base face with quad elements. +#. Stack the base face mesh through the volumes to create a mainly hexahedral volume mesh. +#. Write the mesh for the structural thermal analysis. +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch the Ansys Prime Server instance and connect the client. +# Get the client model and 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=model) + +############################################################################### +# Import geometry +# ~~~~~~~~~~~~~~~ +# Download the PCB geometry (PMDAT) file. +# Import the geometry. +# Display the imported geometry. Purple edges indicate that the geometry is +# connected and the topology is shared between the different volumes. +# This means that the mesh is also to be connected between volumes. + +# For Windows OS users, SCDOC files are also available. +# To read the geometry as connected with shared topology, you must use +# the Workbench ``CadReaderRoute``: + +# mesh_util.read( +# file_name=prime.examples.download_pcb_scdoc(), +# cad_reader_route=prime.CadReaderRoute.WORKBENCH, +# ) + +mesh_util.read(file_name=prime.examples.download_pcb_pmdat()) + +display = PrimePlotter() +display.plot(model) +display.show() + +sizing_params = prime.GlobalSizingParams(model=model, min=0.5, max=1.0) +model.set_global_sizing_params(params=sizing_params) + +############################################################################### +# Create base face +# ~~~~~~~~~~~~~~~~ +# Define stacker parameters: +# +# - Set the direction vector for defining stacking. +# - Set the maximum offset size for mesh layers created by the stacker method. +# - Set the base faces to delete after stacking. +# +# Create the base face from the part and volumes. +# Define a label for the generated base faces and display. +# When coloured by zonelet, the display shows the imprints +# on the base face. + +part = model.parts[0] +sweeper = prime.VolumeSweeper(model) + +stacker_params = prime.MeshStackerParams( + model=model, + direction=[0, 1, 0], + max_offset_size=1.0, + delete_base=True, +) + +createbase_results = sweeper.create_base_face( + part_id=part.id, + topo_volume_ids=part.get_topo_volumes(), + params=stacker_params, +) + +base_faces = createbase_results.base_face_ids + +part.add_labels_on_topo_entities(["base_faces"], base_faces) + +scope = prime.ScopeDefinition(model=model, label_expression="base_faces") + +display = PrimePlotter() +display.plot(model, scope=scope) +display.show() + + +############################################################################### +# Surface mesh base face +# ~~~~~~~~~~~~~~~~~~~~~~ +# Quad surface mesh the generated base faces for stacking. + +base_scope = prime.lucid.SurfaceScope( + entity_expression="base_faces", + part_expression=part.name, + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, +) + +mesh_util.surface_mesh(min_size=0.5, scope=base_scope, generate_quads=True) + +display = PrimePlotter() +display.plot(model, scope=scope, update=True) +display.show() + +############################################################################### +# Stack base face +# ~~~~~~~~~~~~~~~~~~~~~~ +# Create a mainly hexahedral volume mesh using the stacker method. +# Display the volume mesh. + +stackbase_results = sweeper.stack_base_face( + part_id=part.id, + base_face_ids=base_faces, + topo_volume_ids=part.get_topo_volumes(), + params=stacker_params, +) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() + +############################################################################### +# Write mesh +# ~~~~~~~~~~ +# Write a CDB file. + +with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, "pcb.cdb") + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/06_blade_morph.py b/examples/gallery/06_blade_morph.py new file mode 100644 index 0000000000..0c10dda7fe --- /dev/null +++ b/examples/gallery/06_blade_morph.py @@ -0,0 +1,151 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_turbine_blade: + +========================================================= +Morph a hexahedral mesh of a turbine blade to a new shape +========================================================= + +**Summary**: This example demonstrates how to morph a structural +hexahedral mesh of a turbine blade to a new deformed shape +defined by a target geometry file. + +Objective +~~~~~~~~~~ + +This example appends a CDB mesh with a CAD geometry +and match morphs the mesh to the geometry. + +.. image:: ../../../images/turbine_blade.png + :align: center + :width: 800 + :alt: Turbine blade hexahedral mesh. + +Procedure +~~~~~~~~~~ +#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. +#. Read the mesh and append the new CAD geometry shape. +#. Define the mesh source faces and the target geometry faces to match morph. +#. Match morph the turbine blade mesh to the new CAD geometry shape. +#. Write the mesh for structural analysis. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch the Ansys Prime Server instance and connect the client. +# Get the client model and 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=model) + +############################################################################### +# Read files +# ~~~~~~~~~~ +# Download the turbine blade mesh file and CAD geometry. +# Read the mesh and append the geometry. +# Display the source and the target. + +# For Windows OS users, scdoc is also available for geometry: +# target_geometry = prime.examples.download_turbine_blade_target_scdoc() + +source_mesh = prime.examples.download_turbine_blade_cdb() +target_geometry = prime.examples.download_deformed_blade_fmd() +mesh_util.read(file_name=source_mesh) +mesh_util.read(file_name=target_geometry, append=True) + + +display = PrimePlotter() +display.plot(model) +display.show() + +print(model) + +############################################################################### +# Define source and target faces +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source_part = model.get_part(2) +target_part = model.get_part(3) +source = source_part.get_face_zonelets() +target = target_part.get_topo_faces() + +############################################################################### +# Match morph mesh +# ~~~~~~~~~~~~~~~~ +# Set the target type to be for topoface because the target is geometry. +# Morph the source face zonelets of ``source_part`` to the +# target topofaces of the geometry. + +morpher = prime.Morpher(model) +match_pair = prime.MatchPair( + model=model, + source_surfaces=source, + target_surfaces=target, + target_type=prime.MatchPairTargetType.TOPOFACE, +) + +params = prime.MatchMorphParams(model) +bc_params = prime.MorphBCParams(model) +solver_params = prime.MorphSolveParams(model) + +morpher.match_morph( + part_id=source_part.id, + match_pairs=[match_pair], + match_morph_params=params, + bc_params=bc_params, + solve_params=solver_params, +) + +# Display the morphed mesh + +display = PrimePlotter() +display.plot(model, update=True) +display.show() +############################################################################### +# Write mesh +# ~~~~~~~~~~ +# Write the morphed CDB file. The geometry is ignored when exporting to a CDB +# file. + +with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, "morphed_turbine_blade.cdb") + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/07_saddle_bracket.py b/examples/gallery/07_saddle_bracket.py new file mode 100644 index 0000000000..b38df619ae --- /dev/null +++ b/examples/gallery/07_saddle_bracket.py @@ -0,0 +1,239 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_saddle_thin_hex: + +=============================================== +Mesh a saddle bracket for a structural analysis +=============================================== + +**Summary**: This example demonstrates how to mesh a thin +solid with hexahedral and prism cells. + +Objective +~~~~~~~~~ + +This example creates a mainly hexahedral mesh on a thin solid volume. + +.. image:: ../../../images/saddle_bracket.png + :align: center + :width: 400 + :alt: Thin volume hexahedral mesh. + +Procedure +~~~~~~~~~ +#. Launch Ansys Prime Server. +#. Import the CAD geometry. +#. Quad surface mesh the source face. +#. Surface mesh the remaining unmeshed TopoFaces with tri surface mesh. +#. Delete the topology. +#. Define volume meshing controls to use thin volume meshing. +#. Volume mesh with hexahedral and prism cells. +#. Write a CDB file for use in the APDL solver. +#. Exit the PyPrimeMesh session. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and get the model. + +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) + +############################################################################### +# Import geometry +# ~~~~~~~~~~~~~~~ +# Download the saddle bracket geometry (FMD) file exported by SpaceClaim. +# Import the geometry and display all. + +# For Windows OS users, scdoc is also available: +# saddle_bracket = prime.examples.download_saddle_bracket_scdoc() + +saddle_bracket = prime.examples.download_saddle_bracket_fmd() + +mesh_util.read(file_name=saddle_bracket) + +print(model) + +display = PrimePlotter() +display.plot(model) +display.show() + +############################################################################### +# Quad mesh source faces +# ~~~~~~~~~~~~~~~~~~~~~~ +# Mesh the source faces for the thin volume control with quads. + +scope = prime.lucid.SurfaceScope( + part_expression="*", + entity_expression="source_thin", + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, +) + +mesh_util.surface_mesh( + scope=scope, + min_size=2.0, + generate_quads=True, +) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() + +############################################################################### +# Surface mesh unmeshed faces +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Mesh unmeshed faces with tri surface mesh. Tri surface mesh on the target +# and side faces is used to show more clearly that the result of the thin +# volume control is a hex mesh that is imprinted up to the side faces. +# All quads could be used for the surface mesh to simplify the process. + +part = model.parts[0] + +all_faces = part.get_topo_faces() +meshed_faces = part.get_topo_faces_of_label_name_pattern( + label_name_pattern="source_thin", + name_pattern_params=prime.NamePatternParams(model), +) + +unmeshed_faces = [face for face in all_faces if face not in meshed_faces] + +part.add_labels_on_topo_entities( + labels=["unmeshed_faces"], + topo_entities=unmeshed_faces, +) + +scope = prime.lucid.SurfaceScope( + part_expression="*", + entity_expression="unmeshed_faces", + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, +) + +mesh_util.surface_mesh( + scope=scope, + min_size=2.0, +) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() + +############################################################################### +# Delete topology +# ~~~~~~~~~~~~~~~ +# Delete topology to leave only the surface mesh. This is necessary for the +# thin volume control to be used. + +part.delete_topo_entities( + prime.DeleteTopoEntitiesParams( + model=model, + delete_geom_zonelets=True, + delete_mesh_zonelets=False, + ) +) + +############################################################################### +# Define volume meshing controls +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Define volume meshing controls to use thin volume meshing. +# Specify source and target faces for the thin volume using imported labels. +# Set the number of layers of cells through the thickness of the thin solid to be 4. +# To create a fully hexahedral and prism mesh the side faces must be imprinted on +# the side faces. If needed, a buffer region at the sides of the volume can be +# defined where the volume fill type used for the volume mesh parameters is +# used to infill. This is useful on more complex geometries, where it provides +# more robustness of the method. To create a buffer region set ``imprint_sides`` +# to ``False`` and specify how many rings of cells to ignore at the sides +# using ``n_ignore_rings``. + +auto_mesh_params = prime.AutoMeshParams(model=model) +thin_vol_ctrls_ids = [] +thin_vol_ctrl = model.control_data.create_thin_volume_control() + +thin_vol_ctrl.set_source_scope( + prime.ScopeDefinition( + model=model, + label_expression="source_thin", + ) +) + +thin_vol_ctrl.set_target_scope( + prime.ScopeDefinition( + model=model, + label_expression="target_thin", + ) +) + +thin_params = prime.ThinVolumeMeshParams( + model=model, + n_layers=4, + imprint_sides=True, +) + +thin_vol_ctrl.set_thin_volume_mesh_params(thin_volume_mesh_params=thin_params) +thin_vol_ctrls_ids.append(thin_vol_ctrl.id) +auto_mesh_params.thin_volume_control_ids = thin_vol_ctrls_ids +auto_mesh_params.volume_fill_type = prime.VolumeFillType.TET + +############################################################################### +# Generate volume mesh +# ~~~~~~~~~~~~~~~~~~~~ +# Volume mesh to obtain hexahedral and prism mesh. +# Print mesh summary. +# Display volume mesh. + +volume_mesh = prime.AutoMesh(model=model) +result_vol = volume_mesh.mesh(part_id=part.id, automesh_params=auto_mesh_params) +print(part.get_summary(prime.PartSummaryParams(model))) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() +############################################################################### +# Write mesh +# ~~~~~~~~~~ +# Write a CDB file for use in the MAPDL solver. + +with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, "saddle_bracket.cdb") + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/08_lucid_generic_f1_rear_wing.py b/examples/gallery/08_lucid_generic_f1_rear_wing.py new file mode 100644 index 0000000000..e15291071a --- /dev/null +++ b/examples/gallery/08_lucid_generic_f1_rear_wing.py @@ -0,0 +1,340 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_generic_f1_rw: + +============================================================= +Mesh a generic F1 car rear wing for external aero simulation +============================================================= + +**Summary**: This example demonstrates how to generate a mesh for a generic F1 rear wing +STL file model. + +Objective +~~~~~~~~~~ + +The example connects various parts of a rear wing from a generic F1 car +and volume meshes the resulting model using a poly-hexcore mesh containing prisms. +To simplify the process and enhance convenience, this example uses multiple +meshing utilities provided in the ``lucid`` class. + +.. image:: ../../../images/generic_rear_wing.png + :align: center + :width: 800 + :alt: Generic F1 rear wing. + +Procedure +~~~~~~~~~~ +#. Launch an Ansys Prime Server instance. +#. Instantiate the meshing utilities from the ``lucid`` class. +#. Import and append the STL geometry files for each part of the F1 rear wing. +#. Merge all imported components into a single part. +#. Use the connect operation to join the components together. +#. Define local size controls on aero surfaces. +#. Generate a surface mesh with curvature sizing. +#. Compute volume zones and define the fluid zone type. +#. Define the boundary layer. +#. Generate a volume mesh using poly-hexcore elements and apply boundary layer refinement. +#. Print statistics on the generated mesh. +#. Write a CAS file for use in the Fluent solver. +#. Exit the PyPrimeMesh session. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and 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=model) + +############################################################################### +# Import geometry +# ~~~~~~~~~~~~~~~ +# Download the generic F1 rear wing geometries (STL files). +# Import each geometry and append to the model. +# Display the imported geometry. + +f1_rw_drs = prime.examples.download_f1_rw_drs_stl() +f1_rw_enclosure = prime.examples.download_f1_rw_enclosure_stl() +f1_rw_end_plates = prime.examples.download_f1_rw_end_plates_stl() +f1_rw_main_plane = prime.examples.download_f1_rw_main_plane_stl() + +for file_name in [f1_rw_drs, f1_rw_enclosure, f1_rw_end_plates, f1_rw_main_plane]: + mesh_util.read(file_name, append=True) + +# display the rear wing geometry without the enclosure +scope = prime.ScopeDefinition(model, part_expression="* !*enclosure*") +display = PrimePlotter() +display.plot(model, scope) +display.show() + +############################################################################### +# Merge parts +# ~~~~~~~~~~~ +# Establish the global size parameter to regulate mesh refinement. +# Merge all individual parts into a unified part named ``f1_car_rear_wing``. + +# Define global sizes +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4, max=32, growth_rate=1.2)) + +# Create label per part +for part in model.parts: + part.add_labels_on_zonelets([part.name.split(".")[0]], part.get_face_zonelets()) + +# Merge parts +merge_params = prime.MergePartsParams(model, merged_part_suggested_name="f1_car_rear_wing") +merge_result = model.merge_parts([part.id for part in model.parts], merge_params) +part = model.get_part_by_name(merge_result.merged_part_assigned_name) + +############################################################################### +# Mesh connect +# ~~~~~~~~~~~~ +# To generate a volume mesh for a closed domain, it is necessary to ensure +# that the components of the rear wing are properly connected. +# To achieve this, perform a connect operation using labels to join the components of +# the rear wing. +# Afterward, inspect the mesh to detect any edges that are not connected. + +# Connect faces +mesh_util.connect_faces(part.name, face_labels="*", target_face_labels="*", tolerance=0.02) + +# Diagnostics +surf_diag = prime.SurfaceSearch(model) +surf_report = surf_diag.get_surface_diagnostic_summary( + prime.SurfaceDiagnosticSummaryParams( + model, + compute_free_edges=True, + compute_self_intersections=True, + ) +) +print(f"Total number of free edges present is {surf_report.n_free_edges}") + +############################################################################### +# Define local size control and generate size-field +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# To accurately represent the physics of the DRS wing, a limitation of 8 mm +# is imposed on the mesh size of the wing. +# This is accomplished by implementing a curvature size control, which refines the +# mesh according to the curvature of the DRS surfaces. +# Additionally, to accurately capture the curved surfaces of other sections of the +# wing, curvature control is defined with a normal angle of 18 degrees. +# These controls are used during surface mesh generation. +# A volumetric size field is then computed based on the defined size controls. +# The volumetric size field plays a crucial role in controlling +# the growth and refinement of the volume mesh. + +# Local curvature size control for DRS +curv_size_control = model.control_data.create_size_control(prime.SizingType.CURVATURE) +curv_size_params = prime.CurvatureSizingParams(model, normal_angle=18, max=4) +curv_size_control.set_curvature_sizing_params(curv_size_params) +curv_scope = prime.ScopeDefinition( + model, + entity_type=prime.ScopeEntity.FACEZONELETS, + part_expression="f1_car_rear_wing*", + label_expression="*drs*", +) +curv_size_control.set_scope(curv_scope) +curv_size_control.set_suggested_name("curvature_drs") + +# Global curvature size control on all face zones of the rear wing +curv_size_control_global = model.control_data.create_size_control(prime.SizingType.CURVATURE) +curv_size_params_global = prime.CurvatureSizingParams(model, normal_angle=18, min=8) +curv_size_control_global.set_curvature_sizing_params(curv_size_params_global) +curv_scope = prime.ScopeDefinition( + model, + entity_type=prime.ScopeEntity.FACEZONELETS, + part_expression="f1_car_rear_wing*", +) +curv_size_control_global.set_scope(curv_scope) +curv_size_control_global.set_suggested_name("curvature_global") + +# Compute volumetric sizefield +compute_size = prime.SizeField(model) +vol_sf_params = prime.VolumetricSizeFieldComputeParams(model) +compute_size.compute_volumetric( + [curv_size_control.id, curv_size_control_global.id], volumetric_sizefield_params=vol_sf_params +) + +############################################################################### +# Generate surface mesh +# ~~~~~~~~~~~~~~~~~~~~~ +# Create a surface mesh for the rear wing using the defined size controls. +# To facilitate the definition of boundary conditions on the surfaces in the solver, +# generate face zones by utilizing the existing labels found in the rear wing model. + +mesh_util.surface_mesh_with_size_controls(size_control_names="*curvature*") +scope = prime.ScopeDefinition(model, label_expression="* !*enclosure*") +display = PrimePlotter() +display.plot(model, scope) +display.show() + +# Create face zones per label +for label in part.get_labels(): + mesh_util.create_zones_from_labels(label_expression=label) + +############################################################################### +# Compute volumetric regions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Compute the volume zones. + +mesh_util.compute_volumes(part_expression=part.name, create_zones_per_volume=True) + +############################################################################### +# Define volume controls +# ~~~~~~~~~~~~~~~~~~~~~~ +# To prevent the generation of a volume mesh within the solid wing, +# the type of a volume zone within the rear wing can be defined as "dead." +# To accomplish this, Volume Control is utilized to assign the type for the +# specific volume zone. +# Expressions are employed to define the volume zones that need to be filled, with +# ``* !f1_rw_enclosure`` indicating that it applies to all volume zones except +# for ``f1_rw_enclosure``. + +volume_control = model.control_data.create_volume_control() +volume_control.set_params( + prime.VolumeControlParams( + model, + cell_zonelet_type=prime.CellZoneletType.DEAD, + ) +) +volume_control.set_scope( + prime.ScopeDefinition( + model, evaluation_type=prime.ScopeEvaluationType.ZONES, zone_expression="* !f1_rw_enclosure" + ) +) + +############################################################################### +# Define prism controls +# ~~~~~~~~~~~~~~~~~~~~~ +# A prism control can be used to define inflation layers on the external aero surfaces. +# Specify the aero surfaces using labels. Here prism scope is defined on zones associated +# with labels ``*drs*`` and ``*plane*``. +# The growth for the prism layer is controlled by defining the offset type to +# be ``uniform`` with a first height of 0.5mm . + +prism_control = model.control_data.create_prism_control() +prism_control.set_surface_scope( + prime.ScopeDefinition( + model, + evaluation_type=prime.ScopeEvaluationType.LABELS, + entity_type=prime.ScopeEntity.FACEZONELETS, + label_expression="*drs*, *plane*", + ) +) +prism_control.set_volume_scope( + prime.ScopeDefinition( + model, + evaluation_type=prime.ScopeEvaluationType.ZONES, + entity_type=prime.ScopeEntity.VOLUME, + zone_expression="*f1_rw_enclosure*", + ) +) +prism_control.set_growth_params( + prime.PrismControlGrowthParams( + model, + offset_type=prime.PrismControlOffsetType.UNIFORM, + n_layers=5, + first_height=0.5, + growth_rate=1.2, + ) +) + +############################################################################### +# Generate volume mesh +# ~~~~~~~~~~~~~~~~~~~~ +# Volume mesh with hexcore polyhedral elements and boundary layer refinement. + +volume_mesh = prime.AutoMesh(model) +auto_mesh_param = prime.AutoMeshParams( + model, + prism_control_ids=[prism_control.id], + size_field_type=prime.SizeFieldType.VOLUMETRIC, + volume_fill_type=prime.VolumeFillType.HEXCOREPOLY, + volume_control_ids=[volume_control.id], +) +volume_mesh.mesh(part.id, auto_mesh_param) + +############################################################################### +# Print mesh statistics +# ~~~~~~~~~~~~~~~~~~~~~ + +# Get meshed part +part = model.get_part_by_name("f1_car_rear_wing") + +# Get statistics on the mesh +part_summary_res = part.get_summary(prime.PartSummaryParams(model=model)) + +# Get element quality on all parts in the model +search = prime.VolumeSearch(model=model) +params = prime.VolumeQualitySummaryParams( + model=model, + scope=prime.ScopeDefinition(model=model, part_expression="*"), + cell_quality_measures=[prime.CellQualityMeasure.INVERSEORTHOGONAL], + quality_limit=[0.9], +) +results = search.get_volume_quality_summary(params=params) + +# Print statistics on meshed part +print(part_summary_res) +print( + "\nMaximum inverse-orthoginal quality of the volume mesh : ", + results.quality_results_part[0].max_quality, +) + +# Mesh check +result = prime.VolumeMeshTool(model).check_mesh(part.id, params=prime.CheckMeshParams(model)) +print("\nMesh check", result, sep="\n") + +scope = prime.ScopeDefinition(model, part_expression="*", label_expression="* !*enclosure*") +display = PrimePlotter() +display.plot(model, scope, update=True) +display.show() +############################################################################### +# Write mesh +# ~~~~~~~~~~ +# Export as CAS file for external aero simulations. +with tempfile.TemporaryDirectory() as temp_folder: + print(temp_folder) + mesh_file = os.path.join(temp_folder, "f1_rear_wing_vol_mesh.cas") + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/09_multi_layer_quad_mesh_pcb.py b/examples/gallery/09_multi_layer_quad_mesh_pcb.py new file mode 100644 index 0000000000..888671eec0 --- /dev/null +++ b/examples/gallery/09_multi_layer_quad_mesh_pcb.py @@ -0,0 +1,315 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_multi_layer_pcb_mesh: + +===================================================== +Mesh a generic PCB geometry with multiple hexa layers +===================================================== + +**Summary**: This example demonstrates how to set the base mesh size and number of +layers for each solid in a generic PCB geometry and then generate a mesh. + +Objective +~~~~~~~~~~ + +The example uses PyPrimeMesh to discretize a PCB CAD geometry by means of the +stacker technology. You can easily set up the mesh size of the base face (xy plane +in this example) and the number of mesh layers along the sweep direction (z axis in this example). +The CAD edges along the z direction are assigned with a named selection at a CAD level in Ansys +Discovery/SpaceClaim. + +The following image provides a snapshot of the Discovery tree to help you to understand the model's +organization. Share topology in Discovery/SpaceClaim guarantees the generation of a conformal mesh +between the solids. Named selections of edges allow you to specify the number of mesh elements to +generate along the sweep direction. To simplify the process and enhance convenience, this example +uses multiple meshing utilities provided in the ``lucid`` class. + +.. image:: ../../../images/multi_layer_quad_mesh_pcb.png + :align: center + :width: 800 + :alt: Generic PCB geometry. + +The resulting mesh with three layers per solid looks like this: + +.. image:: ../../../images/multi_layer_quad_mesh_pcb_3.png + :align: center + :width: 500 + :alt: Generic PCB geometry meshed. + +.. image:: ../../../images/multi_layer_quad_mesh_pcb_2.png + :align: center + :width: 500 + :alt: Generic PCB geometry meshed, zoom in. + + +Procedure +~~~~~~~~~~ +#. Import the fundamental libraries that are necessary to run the script. +#. Launch an Ansys Prime Server instance. +#. Instantiate the meshing utilities from the ``lucid`` class. +#. Define the base mesh size and number of layers along the sweep direction. +#. Import the CAD geometry. +#. Define the edge sizing along the sweep direction using existing edge named selections. +#. Define the parameters for the volume sweeper. +#. Set up, generate, and mesh the base face. +#. Stack the base face along the sweep direction. +#. Set up the zone naming before the mesh output. +#. Write a CAS file for use in the Fluent solver. +#. Exit the PyPrimeMesh session. + +""" + +############################################################################### +# Import all necessary modules +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Notice that you must install the PyVista library to be able to run the visualization +# tools included in this script. + +import os +import tempfile + +import ansys.meshing.prime as prime +from ansys.meshing.prime.graphics import PrimePlotter + +############################################################################### +# Launch Prime server and instantiate the ``lucid`` class +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and get the model. +# Instantiate meshing utilities from the ``lucid`` class. + +prime_client = prime.launch_prime() +model = prime_client.model +mesh_util = prime.lucid.Mesh(model=model) + +############################################################################### +# Define CAD file and mesh settings +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Define the number of layers per solid. +# Define the size in mm of the quad-dominant mesh on the base size. +# Define the path to the CAD file to mesh. +# Download the example CAD file using the ``prime.examples`` function. Otherwise, +# write the path to the desired CAD file on your machine. Supported file types +# are SCDOC, DSCO, and PMDB. + +# cad_file='/path/to/any/cad/file.dsco' +cad_file = prime.examples.download_multi_layer_quad_mesh_pcb_pmdat() +layers_per_solid = 4 # number of hexa mesh layers in each solid +base_face_size = 0.5 # surface mesh size in mm on the base face +# Chose whether to display the CAD/mesh at every stage +display_intermediate_steps = True # Use True/False + + +############################################################################### +# Import geometry +# ~~~~~~~~~~~~~~~ +# Import the geometry into Prime server. +# Use the Workbench ``CadReaderRoute`` to ensure that the shared topology is kept. + +mesh_util.read(file_name=cad_file) +# Use the following command to open CAD files of these types: SCDOC, DSCO, and PMDB. +# mesh_util.read( +# file_name = cad_file, +# cad_reader_route = prime.CadReaderRoute.WORKBENCH) + +############################################################################### +# Display the imported CAD in a PyVista window +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +if display_intermediate_steps: + display = PrimePlotter() + display.plot(model) + display.show() + +############################################################################### +# Define edge sizing constraints +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Set generic global sizing from 0.002mm to 2mm. +# Extract the the edge's length from the named selections, such as "edge_1_0.50_mm" +# (extract 0.5 mm length) or "edge_23_0.27_mm" (extract 0.27mm length). +# On each edge, assign a size equal to the edge's length divided by the +# predefined number of layers per solid. + +# Set generic global sizing from 0.002mm to 2mm. +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.002, max=2.0)) +ids = [] +# collect the imported geometry +part = model.parts[0] +for label in part.get_labels(): + # Check whether the named selection's name starts with the string "edge" + if label.startswith('edge'): + # Extract the edge's length, splitting its name at every "_" string + # Collect the second to last number + length = float(label.split("_")[-2]) # get 0.27 from "edge_23_0.27_mm" + # Initialize a constant-size mesh control (SOFT) + soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) + # Assign a mesh size equal to the edge's length/number of mesh layers + soft_size_params = prime.SoftSizingParams(model=model, max=length / layers_per_solid) + # Finalize the creation of mesh sizing + soft_size_control.set_soft_sizing_params(soft_size_params) + soft_size_scope = prime.ScopeDefinition( + model, + part_expression=part.name, + entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS, + label_expression=label + "*", + ) + soft_size_control.set_scope(soft_size_scope) + soft_size_control.set_suggested_name(label) + # Append the id of the edge sizing to the edge sizings' ids' list + ids.append(soft_size_control.id) + +############################################################################### +# Define controls for volume sweeper +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Set the sweep direction vector. +# Set up the geometric tolerances for lateral and stacking defeature. +# Select the sweep direction as the z axis (0,0,1). +# Append the IDs of the soft local sizings that have been previously defined on +# the edges. + +# Instantiate the volume sweeper +sweeper = prime.VolumeSweeper(model) +# Define the parameters for stacker +stacker_params = prime.MeshStackerParams( + model=model, + direction=[0, 0, 1], # define the sweep direction for the mesh + delete_base=True, # delete the base face in the end of stacker + lateral_defeature_tolerance=0.001, + stacking_defeature_tolerance=0.001, + size_control_ids=ids, +) # list of control IDs to be respected by the stacker + +############################################################################### +# Set up, generate, and mesh the base face +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a soft sizing control. +# Assign the previously defined ``base_face_size`` function to the soft sizing. +# Create the base face. +# Mesh the base face. +# Display the base face. + +# Set up the necessary parameters for the generation of the base face. +soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) +soft_size_params = prime.SoftSizingParams(model=model, max=base_face_size) +soft_size_control.set_soft_sizing_params(soft_size_params) +soft_size_scope = prime.ScopeDefinition( + model, part_expression=part.name, entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS +) +soft_size_control.set_scope(soft_size_scope) +soft_size_control.set_suggested_name("b_f_size") + +# Create the base face, appending the the stacker mesh parameters. +createbase_results = sweeper.create_base_face( + part_id=model.get_part_by_name(part.name).id, + topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), + params=stacker_params, +) +base_faces = createbase_results.base_face_ids +model.get_part_by_name(part.name).add_labels_on_topo_entities(["base_faces"], base_faces) +scope = prime.ScopeDefinition(model=model, label_expression="base_faces") +base_scope = prime.lucid.SurfaceScope( + entity_expression="base_faces", + part_expression=part.name, + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, +) + +# Generate the quad-dominant surface mesh on the base face. +mesh_util_controls = mesh_util.surface_mesh_with_size_controls( + size_control_names="b_f_size", scope=base_scope, generate_quads=True +) + +############################################################################### +# Display the meshed base face in a PyVista window +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if display_intermediate_steps: + display = PrimePlotter() + display.plot(model, update=True) + display.show() + +############################################################################### +# Stack the base face using the volume sweeper +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Use the volume sweeper to stack the base face along the previously defined sweep +# direction. +# Include the previously defined stacker parameters. +# Display the final volume mesh. + +# Use the ``stack_base_face`` function to generate the volume mesh +stackbase_results = sweeper.stack_base_face( + part_id=model.get_part_by_name(part.name).id, + base_face_ids=base_faces, + topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), + params=stacker_params, +) + +############################################################################### +# Display the final PCB mesh in a PyVista window +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if display_intermediate_steps: + display = PrimePlotter() + display.plot(model, update=True) + display.show() +############################################################################### +# Set up the zone naming before the mesh output +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Delete the unnecessary topo entities. +# Name the walls of ``solid`` as ``wall_solid``. For example, if the solid's name is ``A``, then +# name the walls surrounding the solid ``wall_A``). +# Convert the labels to mesh zones. + +# Define deletion parameters +deletion_params = prime.DeleteTopoEntitiesParams( + model, delete_geom_zonelets=True, delete_mesh_zonelets=False +) +# Delete unnecessary geometrical entities. +part.delete_topo_entities(deletion_params) +# Rename the walls surrounding any volume of the mesh by appending the string +# ``wall_`` to the solid's name. For example, if the solid is named ``my_solid``, then +# name the walls surrounding the solid ``wall_my_solid``. +for volume in part.get_volumes(): + volume_zone_name = "wall_" + model.get_zone_name(part.get_volume_zone_of_volume(volume)) + label_zonelets = part.get_face_zonelets_of_volumes([volume]) + part.add_labels_on_zonelets([volume_zone_name], label_zonelets) +# Convert labels into mesh zones to use in the solver. +mesh_util_create_zones = mesh_util.create_zones_from_labels() + +############################################################################### +# Mesh output +# ~~~~~~~~~~~ +# Create a temporary folder and use it to output the mesh to a CAS file. + +with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, 'multi_layer_quad_mesh_pcb.cas') + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) +# Otherwise, specify a path on your local machine: +# mesh_util.write('local/path/to/your/mesh_file.cas') + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py new file mode 100644 index 0000000000..0baa047b31 --- /dev/null +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -0,0 +1,233 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_contact_patch: + +======================================================================== +Create a contact patch for wrapping between a wheel and ground interface +======================================================================== + +**Summary**: This example demonstrates how to create a contact patch for use with wrapping +to avoid meshing into a narrow contact region between two objects. + +Objective +~~~~~~~~~ +This example uses a contact patch for wrapping to avoid the interface of a wheel with the ground +to improve mesh quality when growing prism layers in the region of the contacting faces. + +.. image:: ../../../images/contact_patch.png + :align: center + :width: 600 + +The preceding image shows the following: + + +* Top left: Wheel/ground interface +* Top right: Addition of contact patch +* Lower left: Grouping tolerance at 4 with multiple contact patches +* Lower right: Grouping tolerance at 20 with merged single contact patch + + + +Procedure +~~~~~~~~~ +#. Launch an Ansys Prime Server instance and instantiate the meshing utilities + from the ``lucid`` class. +#. Import the wheel ground geometry. +#. Convert the topo parts to mesh parts so that the contact patch can be created. +#. Create a contact patch between the wheel and the ground. +#. Extract the fluid region using wrapping. +#. Volume mesh with polyhedral and prism cells. +#. Write a CAS file for use in the Fluent solver. +#. 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 + +client = prime.launch_prime() +model = client.model + +mesh_util = prime.lucid.Mesh(model) + +############################################################################### +# Import CAD geometry +# ~~~~~~~~~~~~~~~~~~~ +# Download the wheel ground geometry (FMD) file exported by SpaceClaim. +# Import the CAD geometry. The geometry consists of two topo parts: a wheel and an enclosing box. +# Labels are defined for the ground topo face on the enclosure and for the wheel +# as all the topo faces of the wheel part. + +# For Windows OS users, SCDOC or DSCO is also available. For example: +# wheel_ground_file = prime.examples.download_wheel_ground_scdoc() + +wheel_ground_file = prime.examples.download_wheel_ground_fmd() + +mesh_util.read(wheel_ground_file) + +display = PrimePlotter() +display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) +display.show() + +print(model) + +############################################################################### +# Convert topo parts to mesh parts +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Convert the faceted geometry of the topology to mesh for all parts as the contact patch +# requires face zonelets from mesh parts as input. + +wheel_part = model.get_part_by_name("wheel_body") +enclosure_part = model.get_part_by_name("enclosure") + +[part.delete_topo_entities(prime.DeleteTopoEntitiesParams(model)) for part in model.parts] + +############################################################################### +# Create a contact patch +# ~~~~~~~~~~~~~~~~~~~~~~ +# To create a contact patch, a direction is needed to define the resulting shape of the patch. +# A new part is created containing the patch. +# A prefix can be specified for the label created for the contact patch face zonelets generated. +# The offset distance determines the thickness and extent of the patch. The source face zonelet is +# offset to intersect the planar target face and the intersection used to define the contact patch. +# Due to the depth of the treads on the wheel, 20.0 is used as the offset to reach the tire surface. +# If multiple contact regions are found, they can be merged by grouping them using the grouping +# tolerance distance. With a grouping tolerance of 4.0, separate contact regions are created for +# some of the treads of the tire, see the image at the top of the example. To merge these contact +# regions into a single patch, the grouping tolerance distance is increased to 20.0, avoiding small +# gaps between contact regions. + +# The face zonelets of the wheel are defined as the source. +# The planar surface must be specified as the target. +# In this instance, the ground provides the planar target. + +source = wheel_part.get_face_zonelets() +target = enclosure_part.get_face_zonelets_of_label_name_pattern( + "ground", prime.NamePatternParams(model) +) + +params = prime.CreateContactPatchParams( + model, + contact_patch_axis=prime.ContactPatchAxis.Z, + offset_distance=20.0, + grouping_tolerance=20.0, + suggested_label_prefix="patch", +) +result = prime.SurfaceUtilities(model).create_contact_patch( + source_zonelets=source, target_zonelets=target, params=params +) +print(result.error_code) +print(model) + +display = PrimePlotter() +display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) +display.show() + +############################################################################### +# Wrap the fluid region +# ~~~~~~~~~~~~~~~~~~~~~ +# The largest internal region in this instance is the fluid region around the wheel. +# Intersection loops are created to capture the features at the corners between the +# patch, ground, and wheel. + +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) + +# Create a size control to limit the size of mesh on the wheel. +size_control = model.control_data.create_size_control(prime.SizingType.SOFT) +size_control.set_soft_sizing_params(prime.SoftSizingParams(model=model, max=8.0)) +size_control.set_scope(prime.ScopeDefinition(model=model, label_expression="wheel")) + +wrap_part = mesh_util.wrap( + min_size=4.0, + max_size=100.0, + region_extract=prime.WrapRegion.LARGESTINTERNAL, + create_intersection_loops=True, + wrap_size_controls=[size_control], +) + +display = PrimePlotter() +display.plot( + model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True +) +display.show() + +print(model) + +############################################################################### +# Volume mesh +# ~~~~~~~~~~~ +# Apply five layers of prisms to the wheel, patch, and ground. Mesh with polyhedrals. + +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) +mesh_util.volume_mesh( + volume_fill_type=prime.VolumeFillType.POLY, + prism_layers=5.0, + prism_surface_expression="wheel, patch*, ground", + prism_volume_expression="*", + scope=prime.lucid.VolumeScope(part_expression=wrap_part.name), +) + +display = PrimePlotter() +display.plot( + model, + scope=prime.ScopeDefinition(model, label_expression="!front !side_right !top"), + update=True, +) +display.show() + +mesh_util.create_zones_from_labels() + +wrap_part._print_mesh = True +print(wrap_part) + +############################################################################### +# Write model +# ~~~~~~~~~~~ +# Write a CAS file for use in the Fluent solver. + +with tempfile.TemporaryDirectory() as temp_folder: + wheel_model = os.path.join(temp_folder, "wheel_ground_contact.cas.h5") + prime.FileIO(model).export_fluent_case( + wheel_model, + export_fluent_case_params=prime.ExportFluentCaseParams(model, cff_format=True), + ) + assert os.path.exists(wheel_model) + print(f"Fluent case exported at {wheel_model}") + +############################################################################### +# Exit the PyPrimeMesh session +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +client.exit() diff --git a/examples/gallery/11_solder_ball.py b/examples/gallery/11_solder_ball.py index 5814f39ae4..ccf3f655f1 100644 --- a/examples/gallery/11_solder_ball.py +++ b/examples/gallery/11_solder_ball.py @@ -63,337 +63,339 @@ # Import modules # ~~~~~~~~~~~~~~ # Import libraries necessary to run the script. +def example(): + import os + import tempfile + + import ansys.meshing.prime as prime + from ansys.meshing.prime.graphics import PrimePlotter + + ############################################################################### + # Launch Ansys Prime Server + # ~~~~~~~~~~~~~~~~~~~~~~~~~ + # Launch an instance of Ansys Prime Server. + # Connect the PyPrimeMesh client and get the model. + + prime_client = prime.launch_prime() + model = prime_client.model + + ############################################################################### + # Import CAD geometry + # ~~~~~~~~~~~~~~~~~~~ + # FMD is exported from SpaceClaim for the geometry. + # Geometry consists of multiple non overlapping and disconnected volumes. + # The model has multiple layers either side of several solder balls with pads + # and contains an infill volume around the solder. During import, the part + # creation type is set to BODY so that each body in the CAD is imported as a + # separate part. Refaceting is specified for more control of the scaffolding + # operation. Consistent faceting for the curved surfaces to be joined can be + # obtained by specifying CadRefacetingMaxEdgeSizeLimit as ABSOLUTE. To avoid + # over refinement of the faceting the max_edge_size is allowed to reach a size + # of 1.0. Labels can be assigned to manage the entities of each volume. + + solder_ball = prime.examples.download_solder_ball_fmd() + + params = prime.ImportCadParams( + model, + append=True, + part_creation_type=prime.PartCreationType.BODY, + refacet=True, + cad_refaceting_params=prime.CadRefacetingParams( + model, + cad_faceter=prime.CadFaceter.PARASOLID, + max_edge_size_limit=prime.CadRefacetingMaxEdgeSizeLimit.ABSOLUTE, + max_edge_size=1.0, + ), + ) -import os -import tempfile + prime.FileIO(model).import_cad(file_name=solder_ball, params=params) -import ansys.meshing.prime as prime -from ansys.meshing.prime.graphics import PrimePlotter + for part in model.parts: + part.add_labels_on_topo_entities([part.name], part.get_topo_faces()) + part.add_labels_on_topo_entities([part.name], part.get_topo_volumes()) -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and get the model. + # Display the model without the infill so the cylindrical geometry of the solder + # is visible. -prime_client = prime.launch_prime() -model = prime_client.model + display = PrimePlotter() + display.plot( + model, scope=prime.ScopeDefinition(model=model, label_expression="solder_cyl*,pad*,layer*") + ) + display.show() + + ############################################################################### + # Connect geometry + # ~~~~~~~~~~~~~~~~ + # Merge all parts into a single part so they can be connected. + # Imprint adjacent topo faces by connecting topo faces and topo edges. + # Mesh all topo faces to allow splitting the imprinted topo faces by mesh regions. + # Delete mesh on topo faces after splitting. + # Merge newly created overlapping topo faces so only a single topo face exists + # between connected volumes. + + part_ids = [part.id for part in model.parts if part.get_topo_faces()] + merge_result = model.merge_parts(part_ids=part_ids, params=prime.MergePartsParams(model)) + print(model) + + merged_part = model.get_part(merge_result.merged_part_id) + + params = prime.ScaffolderParams( + model=model, + absolute_dist_tol=0.01, + intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, + constant_mesh_size=0.1, + ) -############################################################################### -# Import CAD geometry -# ~~~~~~~~~~~~~~~~~~~ -# FMD is exported from SpaceClaim for the geometry. -# Geometry consists of multiple non overlapping and disconnected volumes. -# The model has multiple layers either side of several solder balls with pads -# and contains an infill volume around the solder. During import, the part -# creation type is set to BODY so that each body in the CAD is imported as a -# separate part. Refaceting is specified for more control of the scaffolding -# operation. Consistent faceting for the curved surfaces to be joined can be -# obtained by specifying CadRefacetingMaxEdgeSizeLimit as ABSOLUTE. To avoid -# over refinement of the faceting the max_edge_size is allowed to reach a size -# of 1.0. Labels can be assigned to manage the entities of each volume. - -solder_ball = prime.examples.download_solder_ball_fmd() - -params = prime.ImportCadParams( - model, - append=True, - part_creation_type=prime.PartCreationType.BODY, - refacet=True, - cad_refaceting_params=prime.CadRefacetingParams( - model, - cad_faceter=prime.CadFaceter.PARASOLID, - max_edge_size_limit=prime.CadRefacetingMaxEdgeSizeLimit.ABSOLUTE, - max_edge_size=1.0, - ), -) + scaffolder = prime.Scaffolder(model, merged_part.id) + res = scaffolder.scaffold_topo_faces_and_beams( + topo_faces=merged_part.get_topo_faces(), topo_beams=[], params=params + ) -prime.FileIO(model).import_cad(file_name=solder_ball, params=params) + prime.lucid.Mesh(model).surface_mesh(min_size=0.1) + prime.Scaffolder(model, merged_part.id).split_topo_faces_by_mesh_region( + merged_part.get_topo_faces() + ) -for part in model.parts: - part.add_labels_on_topo_entities([part.name], part.get_topo_faces()) - part.add_labels_on_topo_entities([part.name], part.get_topo_volumes()) + # This is a beta API. The behavior and implementation may change in future. + result = model.topo_data.delete_mesh_on_topo_faces( + merged_part.get_topo_faces(), prime.DeleteMeshParams(model=model) + ) -# Display the model without the infill so the cylindrical geometry of the solder -# is visible. + scaffolder.merge_overlapping_topo_faces(merged_part.get_topo_faces(), params) -display = PrimePlotter() -display.plot( - model, scope=prime.ScopeDefinition(model=model, label_expression="solder_cyl*,pad*,layer*") -) -display.show() + display = PrimePlotter() + display.plot(model, update=True) + display.show() -############################################################################### -# Connect geometry -# ~~~~~~~~~~~~~~~~ -# Merge all parts into a single part so they can be connected. -# Imprint adjacent topo faces by connecting topo faces and topo edges. -# Mesh all topo faces to allow splitting the imprinted topo faces by mesh regions. -# Delete mesh on topo faces after splitting. -# Merge newly created overlapping topo faces so only a single topo face exists -# between connected volumes. - -part_ids = [part.id for part in model.parts if part.get_topo_faces()] -merge_result = model.merge_parts(part_ids=part_ids, params=prime.MergePartsParams(model)) -print(model) - -merged_part = model.get_part(merge_result.merged_part_id) - -params = prime.ScaffolderParams( - model=model, - absolute_dist_tol=0.01, - intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, - constant_mesh_size=0.1, -) - -scaffolder = prime.Scaffolder(model, merged_part.id) -res = scaffolder.scaffold_topo_faces_and_beams( - topo_faces=merged_part.get_topo_faces(), topo_beams=[], params=params -) - -prime.lucid.Mesh(model).surface_mesh(min_size=0.1) -prime.Scaffolder(model, merged_part.id).split_topo_faces_by_mesh_region( - merged_part.get_topo_faces() -) - -# This is a beta API. The behavior and implementation may change in future. -result = model.topo_data.delete_mesh_on_topo_faces( - merged_part.get_topo_faces(), prime.DeleteMeshParams(model=model) -) - -scaffolder.merge_overlapping_topo_faces(merged_part.get_topo_faces(), params) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() + ############################################################################### + # Volume sweeper + # ~~~~~~~~~~~~~~ + # Setup a size control to refine the mesh around the solder. + # Setup stacker parameters to define the volume sweep mesh. + # Create the base face to quad surface mesh and use for sweeping. + # Stack the base face to create the volume mesh. + # Delete topology on mesh part to allow use of surface utilities and feature extraction. + # A large lateral defeature tolerance of 0.1 is used to avoid additional topo nodes + # from scaffolding impacting the final mesh. -############################################################################### -# Volume sweeper -# ~~~~~~~~~~~~~~ -# Setup a size control to refine the mesh around the solder. -# Setup stacker parameters to define the volume sweep mesh. -# Create the base face to quad surface mesh and use for sweeping. -# Stack the base face to create the volume mesh. -# Delete topology on mesh part to allow use of surface utilities and feature extraction. -# A large lateral defeature tolerance of 0.1 is used to avoid additional topo nodes -# from scaffolding impacting the final mesh. - -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.1, max=0.4)) - -size_control = model.control_data.create_size_control(prime.SizingType.SOFT) -size_control.set_scope( - prime.ScopeDefinition(model, part_expression=merged_part.name, label_expression="*solder*") -) -size_control.set_soft_sizing_params(prime.SoftSizingParams(model, max=0.1)) - -stacker_params = prime.MeshStackerParams( - model=model, - direction=[0, 1, 0], - max_offset_size=0.4, - delete_base=True, - lateral_defeature_tolerance=0.1, - stacking_defeature_tolerance=0.01, - size_control_ids=[size_control.id], -) - -sweeper = prime.VolumeSweeper(model) - -createbase_results = sweeper.create_base_face( - part_id=merged_part.id, - topo_volume_ids=merged_part.get_topo_volumes(), - params=stacker_params, -) - -base_faces = createbase_results.base_face_ids -merged_part.add_labels_on_topo_entities(["base_faces"], base_faces) - -scope = prime.ScopeDefinition(model=model, label_expression="base_faces") - -base_scope = prime.lucid.SurfaceScope( - entity_expression="base_faces", - part_expression=merged_part.name, - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, -) - -prime.lucid.Mesh(model).surface_mesh( - min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True -) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() - -stackbase_results = sweeper.stack_base_face( - part_id=merged_part.id, - base_face_ids=base_faces, - topo_volume_ids=merged_part.get_topo_volumes(), - params=stacker_params, -) - -merged_part.delete_topo_entities( - prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=True, delete_mesh_zonelets=False) -) - -merged_part._print_mesh = True -print(merged_part) - -display = PrimePlotter() -display.plot(model, update=True) -display.show() + model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.1, max=0.4)) -############################################################################### -# Import sphere geometry for match morphing -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create a single part on CAD import by setting the part creation type to MODEL. -# Convert topology to mesh face zonelets to use surface utilities. + size_control = model.control_data.create_size_control(prime.SizingType.SOFT) + size_control.set_scope( + prime.ScopeDefinition(model, part_expression=merged_part.name, label_expression="*solder*") + ) + size_control.set_soft_sizing_params(prime.SoftSizingParams(model, max=0.1)) + + stacker_params = prime.MeshStackerParams( + model=model, + direction=[0, 1, 0], + max_offset_size=0.4, + delete_base=True, + lateral_defeature_tolerance=0.1, + stacking_defeature_tolerance=0.01, + size_control_ids=[size_control.id], + ) -solder_ball_target = prime.examples.download_solder_ball_target_fmd() + sweeper = prime.VolumeSweeper(model) -params = prime.ImportCadParams(model, append=True, part_creation_type=prime.PartCreationType.MODEL) + createbase_results = sweeper.create_base_face( + part_id=merged_part.id, + topo_volume_ids=merged_part.get_topo_volumes(), + params=stacker_params, + ) -prime.FileIO(model).import_cad(file_name=solder_ball_target, params=params) + base_faces = createbase_results.base_face_ids + merged_part.add_labels_on_topo_entities(["base_faces"], base_faces) -imported_cad_part_ids = [part.id for part in model.parts if part.get_topo_faces()] -target_part = model.get_part(imported_cad_part_ids[0]) + scope = prime.ScopeDefinition(model=model, label_expression="base_faces") -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, part_expression=target_part.name)) -display.show() + base_scope = prime.lucid.SurfaceScope( + entity_expression="base_faces", + part_expression=merged_part.name, + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, + ) -print(model) + prime.lucid.Mesh(model).surface_mesh( + min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True + ) -target_part.delete_topo_entities( - prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=False, delete_mesh_zonelets=False) -) + display = PrimePlotter() + display.plot(model, update=True) + display.show() -############################################################################### -# Match morph the mesh to the spherical solder -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Delete feature edge zonelets on the mesh source. -# Get lists of the source and target face zonelets for the match morph. -# Pair source and target faces based on overlapping bounding box locations. -# For each source face extract feature edges with nodes attached to faces. -# Define edge pairs for each match pair control as a morph boundary condition. -# Match morph solder faces and edges. -# Delete target sphere part and retain the morphed mesh for export. - -merged_part.delete_zonelets(merged_part.get_edge_zonelets()) -cylinder_faces = merged_part.get_face_zonelets_of_label_name_pattern( - "solder_cyl", prime.NamePatternParams(model) -) -sphere_faces = target_part.get_face_zonelets() - -match_pairs = [] - -# When the match morph operation is defined by multiple -# separate local match morph source and targets, -# individual match pairs must be specified for each -# contiguous set of face zonelets. -# Separate one to one boundary condition pairs for the -# connected edge zonelets must also be defined for -# each face match pair to ensure the edges remain rigid. - -# For each solder to be morphed we have a match pair -# for the source and target face zonelets. To ensure -# the edge zonelets remain rigid during the morph, -# boundary condition pairs for each of the two edge -# zonelets need to be created. - -# For each cylindrical face zonelet on the source -# mesh find the corresponding target sphere face zonelet -# to match morph the mesh using the position of their bounding box. -# Labels defined in the CAD model would be a more efficient way to pair. -tolerance = 0.2 -for face in cylinder_faces: - box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([face]) - for i in range(len(sphere_faces)): - sphere_box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([sphere_faces[i]]) - if ( - (abs(sphere_box.xmin - box.xmin) < tolerance) - and (abs(sphere_box.ymin - box.ymin) < tolerance) - and (abs(sphere_box.zmin - box.zmin) < tolerance) - and (abs(sphere_box.xmax - box.xmax) < tolerance) - and (abs(sphere_box.ymax - box.ymax) < tolerance) - and (abs(sphere_box.zmax - box.zmax) < tolerance) - ): - break - elif i == len(sphere_faces) - 1: - # if no target face is found by the final pass then return an error - prime.PrimeRuntimeError(message=f"Target sphere not found for face {face}.") - match_pair = prime.MatchPair( - model, - source_surfaces=[face], - target_surfaces=[sphere_faces[i]], - target_type=prime.MatchPairTargetType.FACEZONELET, + stackbase_results = sweeper.stack_base_face( + part_id=merged_part.id, + base_face_ids=base_faces, + topo_volume_ids=merged_part.get_topo_volumes(), + params=stacker_params, ) - bc_pairs = [] - - # Once each source and target face zonelet are found and paired, edge zonelets - # are created and used as boundary conditions to ensure they remain rigid. - # Creating edge zonelets not disconnected from the faces ensures that the edge - # zonelet will act as a boundary condition for the morph operation. - # Extracting the edge zonelets from the source face zonelet and using - # them to define both source and target boundary conditions ensures - # the edge zonelets of the solder remain rigid. - result = prime.FeatureExtraction(model).extract_features_on_face_zonelets( - merged_part.id, - [face], - prime.ExtractFeatureParams( - model, - feature_angle=60, - separate_features=True, - separation_angle=60, - replace=False, - disconnect_with_faces=False, - ), + + merged_part.delete_topo_entities( + prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=True, delete_mesh_zonelets=False) ) - for edge in result.new_edge_zonelets: - bc_pair = prime.BCPair( - model, - int(edge), - int(edge), - type=prime.BCPairType.EDGE, - ) - bc_pairs.append(bc_pair) - match_pair.bc_pairs = bc_pairs - match_pairs.append(match_pair) -morph = prime.Morpher(model) + merged_part._print_mesh = True + print(merged_part) -morph_params = prime.MatchMorphParams(model) -bc_params = prime.MorphBCParams(model, morphable_layers=0) -solve_params = prime.MorphSolveParams(model) + display = PrimePlotter() + display.plot(model, update=True) + display.show() -morph.match_morph( - part_id=merged_part.id, - match_pairs=match_pairs, - match_morph_params=morph_params, - bc_params=bc_params, - solve_params=solve_params, -) + ############################################################################### + # Import sphere geometry for match morphing + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Create a single part on CAD import by setting the part creation type to MODEL. + # Convert topology to mesh face zonelets to use surface utilities. -model.delete_parts([target_part.id]) + solder_ball_target = prime.examples.download_solder_ball_target_fmd() -display = PrimePlotter() -display.plot( - model, scope=prime.ScopeDefinition(model=model, label_expression="solder*"), update=True -) -display.show() + params = prime.ImportCadParams(model, append=True, part_creation_type=prime.PartCreationType.MODEL) -############################################################################### -# Export mesh -# ~~~~~~~~~~~ -# Export a CDB file. + prime.FileIO(model).import_cad(file_name=solder_ball_target, params=params) -with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, "solder_balls.cdb") - params = prime.ExportMapdlCdbParams(model=model) - prime.FileIO(model=model).export_mapdl_cdb(file_name=mesh_file, params=params) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) + imported_cad_part_ids = [part.id for part in model.parts if part.get_topo_faces()] + target_part = model.get_part(imported_cad_part_ids[0]) -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ + display = PrimePlotter() + display.plot(model, scope=prime.ScopeDefinition(model, part_expression=target_part.name)) + display.show() + + print(model) + + target_part.delete_topo_entities( + prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=False, delete_mesh_zonelets=False) + ) + + ############################################################################### + # Match morph the mesh to the spherical solder + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Delete feature edge zonelets on the mesh source. + # Get lists of the source and target face zonelets for the match morph. + # Pair source and target faces based on overlapping bounding box locations. + # For each source face extract feature edges with nodes attached to faces. + # Define edge pairs for each match pair control as a morph boundary condition. + # Match morph solder faces and edges. + # Delete target sphere part and retain the morphed mesh for export. + + merged_part.delete_zonelets(merged_part.get_edge_zonelets()) + cylinder_faces = merged_part.get_face_zonelets_of_label_name_pattern( + "solder_cyl", prime.NamePatternParams(model) + ) + sphere_faces = target_part.get_face_zonelets() + + match_pairs = [] + + # When the match morph operation is defined by multiple + # separate local match morph source and targets, + # individual match pairs must be specified for each + # contiguous set of face zonelets. + # Separate one to one boundary condition pairs for the + # connected edge zonelets must also be defined for + # each face match pair to ensure the edges remain rigid. + + # For each solder to be morphed we have a match pair + # for the source and target face zonelets. To ensure + # the edge zonelets remain rigid during the morph, + # boundary condition pairs for each of the two edge + # zonelets need to be created. + + # For each cylindrical face zonelet on the source + # mesh find the corresponding target sphere face zonelet + # to match morph the mesh using the position of their bounding box. + # Labels defined in the CAD model would be a more efficient way to pair. + tolerance = 0.2 + for face in cylinder_faces: + box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([face]) + for i in range(len(sphere_faces)): + sphere_box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([sphere_faces[i]]) + if ( + (abs(sphere_box.xmin - box.xmin) < tolerance) + and (abs(sphere_box.ymin - box.ymin) < tolerance) + and (abs(sphere_box.zmin - box.zmin) < tolerance) + and (abs(sphere_box.xmax - box.xmax) < tolerance) + and (abs(sphere_box.ymax - box.ymax) < tolerance) + and (abs(sphere_box.zmax - box.zmax) < tolerance) + ): + break + elif i == len(sphere_faces) - 1: + # if no target face is found by the final pass then return an error + prime.PrimeRuntimeError(message=f"Target sphere not found for face {face}.") + match_pair = prime.MatchPair( + model, + source_surfaces=[face], + target_surfaces=[sphere_faces[i]], + target_type=prime.MatchPairTargetType.FACEZONELET, + ) + bc_pairs = [] + + # Once each source and target face zonelet are found and paired, edge zonelets + # are created and used as boundary conditions to ensure they remain rigid. + # Creating edge zonelets not disconnected from the faces ensures that the edge + # zonelet will act as a boundary condition for the morph operation. + # Extracting the edge zonelets from the source face zonelet and using + # them to define both source and target boundary conditions ensures + # the edge zonelets of the solder remain rigid. + result = prime.FeatureExtraction(model).extract_features_on_face_zonelets( + merged_part.id, + [face], + prime.ExtractFeatureParams( + model, + feature_angle=60, + separate_features=True, + separation_angle=60, + replace=False, + disconnect_with_faces=False, + ), + ) + for edge in result.new_edge_zonelets: + bc_pair = prime.BCPair( + model, + int(edge), + int(edge), + type=prime.BCPairType.EDGE, + ) + bc_pairs.append(bc_pair) + match_pair.bc_pairs = bc_pairs + match_pairs.append(match_pair) + + morph = prime.Morpher(model) + + morph_params = prime.MatchMorphParams(model) + bc_params = prime.MorphBCParams(model, morphable_layers=0) + solve_params = prime.MorphSolveParams(model) + + morph.match_morph( + part_id=merged_part.id, + match_pairs=match_pairs, + match_morph_params=morph_params, + bc_params=bc_params, + solve_params=solve_params, + ) + + model.delete_parts([target_part.id]) + + display = PrimePlotter() + display.plot( + model, scope=prime.ScopeDefinition(model=model, label_expression="solder*"), update=True + ) + display.show() + + ############################################################################### + # Export mesh + # ~~~~~~~~~~~ + # Export a CDB file. + + with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, "solder_balls.cdb") + params = prime.ExportMapdlCdbParams(model=model) + prime.FileIO(model=model).export_mapdl_cdb(file_name=mesh_file, params=params) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + + ############################################################################### + # Exit PyPrimeMesh + # ~~~~~~~~~~~~~~~~ + + prime_client.exit() -prime_client.exit() +example() diff --git a/examples/misc/example_template.py b/examples/misc/example_template.py new file mode 100644 index 0000000000..eadade3bed --- /dev/null +++ b/examples/misc/example_template.py @@ -0,0 +1,151 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_how_to_add_an_example_reference_key: + +Add a new example +----------------- +**Summary**: This example demonstrates how to add new examples and serves as a template +that you can use in their creation. + +A block comment must be included at the top of any new example. Each example +must have a reference tag in this format: + +``.. _ref_my_example:`` + +The ``.. _ref_`` is necessary. Everything that follows is your reference tag. +Keep all references in `snake case`. + +This section should give a brief overview of what the example is about and/or demonstrates. +The title should be changed to reflect the topic your example covers. + +New examples should be added as Python scripts to: + +``pyprimemesh/examples/gallery`` + +.. note:: + Avoid creating new folders unless absolutely necessary. If in doubt, put the example + in the folder closest to what it is doing and its precise location can be advised + on in the pull request. If you *must* create a new folder, make sure to add a + ``README.txt`` file containing a reference, a title, and a single sentence describing the folder. + Otherwise, the new folder is ignored by Sphinx. + +Example file names should be in the format: + +``example_name.py`` + +.. note:: + Supporting input files for the example, such as CAD or mesh file assets, must be either original + content or have appropriate licensing and ownership permissions from their respective owners. If + the input files are used within the example script provided they must be capable of running in + the CI pipeline. This means that only files that can be read using the native file formats and + CAD readers can be used in the scripted examples. + +The recommended data formats to be included in the example are: + +* .pmdat +* .fmd +* .scdoc or .dsco (supported on Windows OS) + +Supporting input files should be added in: + +`Github Example Data Repository `_ + +Referencing files as enum and creating download function in: + +``pyprimemesh/examples.py`` + +Also adding download function to: + +``pyprimemesh/examples/__init__.py`` + +After this preamble is the first code block: +""" + +import ansys.meshing.prime as prime +from ansys.meshing.prime.graphics import PrimePlotter + +# Start Ansys Prime Server instance and get client model +prime_client = prime.launch_prime() +model = prime_client.model + +# Your code goes here... +mesh_util = prime.lucid.Mesh(model=model) + +# For Windows OS users scdoc is also available: +# mixing_elbow = prime.examples.download_elbow_scdoc() +mixing_elbow = prime.examples.download_elbow_fmd() +mesh_util.read(mixing_elbow) +print(model) + +############################################################################### +# Create sections +# ~~~~~~~~~~~~~~~ +# You can break up code blocks in titled sections that provide descriptive text. +# When Sphinx is used to generate the documentation, this content is interpreted +# as ReStructured Text (RST). +# +# .. note:: +# You only need to create the Python (PY) files for the example. The +# ``sphinx-gallery`` extension automatically generates the Jupyter +# notebook, the HTML files for the documentation, and the demo script. +# +# Sections can contain any information that you may have regarding the example, +# such as step-by-step comments and information on motivations. In the generated +# Jupyter notebook, this text is translated into a markdown cell. +# +# As in Jupyter notebooks, if code is left unassigned at the end of a code block +# (as with ``model`` in the previous block), the output is generated and +# printed to the screen according to its ``__repr__``. Otherwise, you can use +# ``print()`` to output the ``__str__``. + +# more code... +mesh_util.surface_mesh(min_size=5, max_size=20) +mesh_util.volume_mesh( + volume_fill_type=prime.VolumeFillType.POLY, + prism_surface_expression="* !inlet !outlet", + prism_layers=3, +) + +############################################################################### +# Render graphics +# ~~~~~~~~~~~~~~~ +# If you display graphics, the result is auto-generated and +# rendered on the page: +display = PrimePlotter() +display.plot(model) +display.show() + +############################################################################### +# Make a pull request +# ~~~~~~~~~~~~~~~~~~~ +# Once your example is complete and you've verified builds locally, you can make a +# pull request (PR). Branches containing examples should be prefixed with `doc/` +# as per the branch-naming conventions found in the :ref:`ref_index_contributing` +# topic in the *PyAnsys Developer's Guide*. + +############################################################################### +# Stop Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +prime_client.exit() From 2a0de00dfe6e6f69f2d5c3cb88729017d520806c Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 16:57:36 +0100 Subject: [PATCH 08/27] tets --- doc/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index ea220d4aa7..14970879a9 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -174,6 +174,7 @@ "image_scrapers": ("matplotlib"), "ignore_pattern": "flycheck*", "thumbnail_size": (350, 350), + "parallel": 1, } supress_warnings = ["docutils"] From 45693171503bbcf693ef960c941e06bab33d7bd8 Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 17:23:53 +0100 Subject: [PATCH 09/27] fix: close docker container in case of unexpected program end. --- src/ansys/meshing/prime/internals/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ansys/meshing/prime/internals/client.py b/src/ansys/meshing/prime/internals/client.py index 076ed65fb3..ea9ffa240d 100644 --- a/src/ansys/meshing/prime/internals/client.py +++ b/src/ansys/meshing/prime/internals/client.py @@ -111,6 +111,10 @@ def __init__( f'Failed to load prime_communicator with message: {err.msg}' ) + def __del__(self): + """In case unexpected termination, close the connection.""" + self.exit() + @property def model(self): """Get model associated with the client.""" From db062e4c4c7869239935b1fbf11cf38610ceb0b8 Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 17:24:54 +0100 Subject: [PATCH 10/27] fix: Proper closure on exception --- src/ansys/meshing/prime/internals/client.py | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/ansys/meshing/prime/internals/client.py b/src/ansys/meshing/prime/internals/client.py index ea9ffa240d..350f8736d6 100644 --- a/src/ansys/meshing/prime/internals/client.py +++ b/src/ansys/meshing/prime/internals/client.py @@ -158,25 +158,27 @@ def exit(self): >>> print(result) >>> prime_client.exit() # Sever connection with server and kill the server. """ - if self._comm is not None: - self._comm.close() - self._comm = None - if self._process is not None: - assert self._local == False - terminate_process(self._process) - self._process = None - - if config.using_container(): - container_name = getattr(self, 'container_name') - utils.stop_prime_github_container(container_name) - elif config.has_pim(): - self.remote_instance.delete() - self.pim_client.close() - - clear_examples = bool(int(os.environ.get('PYPRIMEMESH_CLEAR_EXAMPLES', '1'))) - if clear_examples: - download_manager = examples.DownloadManager() - download_manager.clear_download_cache() + if not self._has_exited: + if self._comm is not None: + self._comm.close() + self._comm = None + if self._process is not None: + assert self._local == False + terminate_process(self._process) + self._process = None + + if config.using_container(): + container_name = getattr(self, 'container_name') + utils.stop_prime_github_container(container_name) + elif config.has_pim(): + self.remote_instance.delete() + self.pim_client.close() + + clear_examples = bool(int(os.environ.get('PYPRIMEMESH_CLEAR_EXAMPLES', '1'))) + if clear_examples: + download_manager = examples.DownloadManager() + download_manager.clear_download_cache() + self._has_exited = True def __enter__(self): """Open client.""" From 6bf9e89f6cba7b6602cc7ff70c89bb1ff6fdf95f Mon Sep 17 00:00:00 2001 From: afernand Date: Wed, 27 Nov 2024 17:41:14 +0100 Subject: [PATCH 11/27] test --- src/ansys/meshing/prime/internals/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/meshing/prime/internals/client.py b/src/ansys/meshing/prime/internals/client.py index 350f8736d6..8f9fc69be5 100644 --- a/src/ansys/meshing/prime/internals/client.py +++ b/src/ansys/meshing/prime/internals/client.py @@ -75,6 +75,7 @@ def __init__( self._local = local self._process = server_process self._comm = None + self._has_exited = False if not local: try: from ansys.meshing.prime.internals.grpc_communicator import ( From 5089f497ffef98fa11ec040a668b66f06b355b41 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 09:47:30 +0100 Subject: [PATCH 12/27] remove bad examples --- examples/gallery/00_lucid_file_IO.py | 2 +- examples/gallery/06_blade_morph.py | 151 ------- .../gallery/09_multi_layer_quad_mesh_pcb.py | 315 -------------- .../gallery/10_wheel_ground_contact_patch.py | 233 ---------- examples/gallery/11_solder_ball.py | 401 ------------------ 5 files changed, 1 insertion(+), 1101 deletions(-) delete mode 100644 examples/gallery/06_blade_morph.py delete mode 100644 examples/gallery/09_multi_layer_quad_mesh_pcb.py delete mode 100644 examples/gallery/10_wheel_ground_contact_patch.py delete mode 100644 examples/gallery/11_solder_ball.py diff --git a/examples/gallery/00_lucid_file_IO.py b/examples/gallery/00_lucid_file_IO.py index 1b03377764..29735dc0e2 100644 --- a/examples/gallery/00_lucid_file_IO.py +++ b/examples/gallery/00_lucid_file_IO.py @@ -64,7 +64,7 @@ # launch an instance of Ansys Prime Server. # Connect the PyPrimeMesh client and get the model. # Instantiate meshing utilities from the ``lucid`` class. - +try: import os import tempfile diff --git a/examples/gallery/06_blade_morph.py b/examples/gallery/06_blade_morph.py deleted file mode 100644 index 0c10dda7fe..0000000000 --- a/examples/gallery/06_blade_morph.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_turbine_blade: - -========================================================= -Morph a hexahedral mesh of a turbine blade to a new shape -========================================================= - -**Summary**: This example demonstrates how to morph a structural -hexahedral mesh of a turbine blade to a new deformed shape -defined by a target geometry file. - -Objective -~~~~~~~~~~ - -This example appends a CDB mesh with a CAD geometry -and match morphs the mesh to the geometry. - -.. image:: ../../../images/turbine_blade.png - :align: center - :width: 800 - :alt: Turbine blade hexahedral mesh. - -Procedure -~~~~~~~~~~ -#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. -#. Read the mesh and append the new CAD geometry shape. -#. Define the mesh source faces and the target geometry faces to match morph. -#. Match morph the turbine blade mesh to the new CAD geometry shape. -#. Write the mesh for structural analysis. - -""" - -############################################################################### -# Launch Ansys Prime Server -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import all necessary modules. -# Launch the Ansys Prime Server instance and connect the client. -# Get the client model and 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=model) - -############################################################################### -# Read files -# ~~~~~~~~~~ -# Download the turbine blade mesh file and CAD geometry. -# Read the mesh and append the geometry. -# Display the source and the target. - -# For Windows OS users, scdoc is also available for geometry: -# target_geometry = prime.examples.download_turbine_blade_target_scdoc() - -source_mesh = prime.examples.download_turbine_blade_cdb() -target_geometry = prime.examples.download_deformed_blade_fmd() -mesh_util.read(file_name=source_mesh) -mesh_util.read(file_name=target_geometry, append=True) - - -display = PrimePlotter() -display.plot(model) -display.show() - -print(model) - -############################################################################### -# Define source and target faces -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -source_part = model.get_part(2) -target_part = model.get_part(3) -source = source_part.get_face_zonelets() -target = target_part.get_topo_faces() - -############################################################################### -# Match morph mesh -# ~~~~~~~~~~~~~~~~ -# Set the target type to be for topoface because the target is geometry. -# Morph the source face zonelets of ``source_part`` to the -# target topofaces of the geometry. - -morpher = prime.Morpher(model) -match_pair = prime.MatchPair( - model=model, - source_surfaces=source, - target_surfaces=target, - target_type=prime.MatchPairTargetType.TOPOFACE, -) - -params = prime.MatchMorphParams(model) -bc_params = prime.MorphBCParams(model) -solver_params = prime.MorphSolveParams(model) - -morpher.match_morph( - part_id=source_part.id, - match_pairs=[match_pair], - match_morph_params=params, - bc_params=bc_params, - solve_params=solver_params, -) - -# Display the morphed mesh - -display = PrimePlotter() -display.plot(model, update=True) -display.show() -############################################################################### -# Write mesh -# ~~~~~~~~~~ -# Write the morphed CDB file. The geometry is ignored when exporting to a CDB -# file. - -with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, "morphed_turbine_blade.cdb") - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/09_multi_layer_quad_mesh_pcb.py b/examples/gallery/09_multi_layer_quad_mesh_pcb.py deleted file mode 100644 index 888671eec0..0000000000 --- a/examples/gallery/09_multi_layer_quad_mesh_pcb.py +++ /dev/null @@ -1,315 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_multi_layer_pcb_mesh: - -===================================================== -Mesh a generic PCB geometry with multiple hexa layers -===================================================== - -**Summary**: This example demonstrates how to set the base mesh size and number of -layers for each solid in a generic PCB geometry and then generate a mesh. - -Objective -~~~~~~~~~~ - -The example uses PyPrimeMesh to discretize a PCB CAD geometry by means of the -stacker technology. You can easily set up the mesh size of the base face (xy plane -in this example) and the number of mesh layers along the sweep direction (z axis in this example). -The CAD edges along the z direction are assigned with a named selection at a CAD level in Ansys -Discovery/SpaceClaim. - -The following image provides a snapshot of the Discovery tree to help you to understand the model's -organization. Share topology in Discovery/SpaceClaim guarantees the generation of a conformal mesh -between the solids. Named selections of edges allow you to specify the number of mesh elements to -generate along the sweep direction. To simplify the process and enhance convenience, this example -uses multiple meshing utilities provided in the ``lucid`` class. - -.. image:: ../../../images/multi_layer_quad_mesh_pcb.png - :align: center - :width: 800 - :alt: Generic PCB geometry. - -The resulting mesh with three layers per solid looks like this: - -.. image:: ../../../images/multi_layer_quad_mesh_pcb_3.png - :align: center - :width: 500 - :alt: Generic PCB geometry meshed. - -.. image:: ../../../images/multi_layer_quad_mesh_pcb_2.png - :align: center - :width: 500 - :alt: Generic PCB geometry meshed, zoom in. - - -Procedure -~~~~~~~~~~ -#. Import the fundamental libraries that are necessary to run the script. -#. Launch an Ansys Prime Server instance. -#. Instantiate the meshing utilities from the ``lucid`` class. -#. Define the base mesh size and number of layers along the sweep direction. -#. Import the CAD geometry. -#. Define the edge sizing along the sweep direction using existing edge named selections. -#. Define the parameters for the volume sweeper. -#. Set up, generate, and mesh the base face. -#. Stack the base face along the sweep direction. -#. Set up the zone naming before the mesh output. -#. Write a CAS file for use in the Fluent solver. -#. Exit the PyPrimeMesh session. - -""" - -############################################################################### -# Import all necessary modules -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Notice that you must install the PyVista library to be able to run the visualization -# tools included in this script. - -import os -import tempfile - -import ansys.meshing.prime as prime -from ansys.meshing.prime.graphics import PrimePlotter - -############################################################################### -# Launch Prime server and instantiate the ``lucid`` class -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Launch an instance of Ansys Prime Server. -# Connect the PyPrimeMesh client and get the model. -# Instantiate meshing utilities from the ``lucid`` class. - -prime_client = prime.launch_prime() -model = prime_client.model -mesh_util = prime.lucid.Mesh(model=model) - -############################################################################### -# Define CAD file and mesh settings -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Define the number of layers per solid. -# Define the size in mm of the quad-dominant mesh on the base size. -# Define the path to the CAD file to mesh. -# Download the example CAD file using the ``prime.examples`` function. Otherwise, -# write the path to the desired CAD file on your machine. Supported file types -# are SCDOC, DSCO, and PMDB. - -# cad_file='/path/to/any/cad/file.dsco' -cad_file = prime.examples.download_multi_layer_quad_mesh_pcb_pmdat() -layers_per_solid = 4 # number of hexa mesh layers in each solid -base_face_size = 0.5 # surface mesh size in mm on the base face -# Chose whether to display the CAD/mesh at every stage -display_intermediate_steps = True # Use True/False - - -############################################################################### -# Import geometry -# ~~~~~~~~~~~~~~~ -# Import the geometry into Prime server. -# Use the Workbench ``CadReaderRoute`` to ensure that the shared topology is kept. - -mesh_util.read(file_name=cad_file) -# Use the following command to open CAD files of these types: SCDOC, DSCO, and PMDB. -# mesh_util.read( -# file_name = cad_file, -# cad_reader_route = prime.CadReaderRoute.WORKBENCH) - -############################################################################### -# Display the imported CAD in a PyVista window -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -if display_intermediate_steps: - display = PrimePlotter() - display.plot(model) - display.show() - -############################################################################### -# Define edge sizing constraints -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Set generic global sizing from 0.002mm to 2mm. -# Extract the the edge's length from the named selections, such as "edge_1_0.50_mm" -# (extract 0.5 mm length) or "edge_23_0.27_mm" (extract 0.27mm length). -# On each edge, assign a size equal to the edge's length divided by the -# predefined number of layers per solid. - -# Set generic global sizing from 0.002mm to 2mm. -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.002, max=2.0)) -ids = [] -# collect the imported geometry -part = model.parts[0] -for label in part.get_labels(): - # Check whether the named selection's name starts with the string "edge" - if label.startswith('edge'): - # Extract the edge's length, splitting its name at every "_" string - # Collect the second to last number - length = float(label.split("_")[-2]) # get 0.27 from "edge_23_0.27_mm" - # Initialize a constant-size mesh control (SOFT) - soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) - # Assign a mesh size equal to the edge's length/number of mesh layers - soft_size_params = prime.SoftSizingParams(model=model, max=length / layers_per_solid) - # Finalize the creation of mesh sizing - soft_size_control.set_soft_sizing_params(soft_size_params) - soft_size_scope = prime.ScopeDefinition( - model, - part_expression=part.name, - entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS, - label_expression=label + "*", - ) - soft_size_control.set_scope(soft_size_scope) - soft_size_control.set_suggested_name(label) - # Append the id of the edge sizing to the edge sizings' ids' list - ids.append(soft_size_control.id) - -############################################################################### -# Define controls for volume sweeper -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Set the sweep direction vector. -# Set up the geometric tolerances for lateral and stacking defeature. -# Select the sweep direction as the z axis (0,0,1). -# Append the IDs of the soft local sizings that have been previously defined on -# the edges. - -# Instantiate the volume sweeper -sweeper = prime.VolumeSweeper(model) -# Define the parameters for stacker -stacker_params = prime.MeshStackerParams( - model=model, - direction=[0, 0, 1], # define the sweep direction for the mesh - delete_base=True, # delete the base face in the end of stacker - lateral_defeature_tolerance=0.001, - stacking_defeature_tolerance=0.001, - size_control_ids=ids, -) # list of control IDs to be respected by the stacker - -############################################################################### -# Set up, generate, and mesh the base face -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Create a soft sizing control. -# Assign the previously defined ``base_face_size`` function to the soft sizing. -# Create the base face. -# Mesh the base face. -# Display the base face. - -# Set up the necessary parameters for the generation of the base face. -soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) -soft_size_params = prime.SoftSizingParams(model=model, max=base_face_size) -soft_size_control.set_soft_sizing_params(soft_size_params) -soft_size_scope = prime.ScopeDefinition( - model, part_expression=part.name, entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS -) -soft_size_control.set_scope(soft_size_scope) -soft_size_control.set_suggested_name("b_f_size") - -# Create the base face, appending the the stacker mesh parameters. -createbase_results = sweeper.create_base_face( - part_id=model.get_part_by_name(part.name).id, - topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), - params=stacker_params, -) -base_faces = createbase_results.base_face_ids -model.get_part_by_name(part.name).add_labels_on_topo_entities(["base_faces"], base_faces) -scope = prime.ScopeDefinition(model=model, label_expression="base_faces") -base_scope = prime.lucid.SurfaceScope( - entity_expression="base_faces", - part_expression=part.name, - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, -) - -# Generate the quad-dominant surface mesh on the base face. -mesh_util_controls = mesh_util.surface_mesh_with_size_controls( - size_control_names="b_f_size", scope=base_scope, generate_quads=True -) - -############################################################################### -# Display the meshed base face in a PyVista window -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -if display_intermediate_steps: - display = PrimePlotter() - display.plot(model, update=True) - display.show() - -############################################################################### -# Stack the base face using the volume sweeper -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Use the volume sweeper to stack the base face along the previously defined sweep -# direction. -# Include the previously defined stacker parameters. -# Display the final volume mesh. - -# Use the ``stack_base_face`` function to generate the volume mesh -stackbase_results = sweeper.stack_base_face( - part_id=model.get_part_by_name(part.name).id, - base_face_ids=base_faces, - topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), - params=stacker_params, -) - -############################################################################### -# Display the final PCB mesh in a PyVista window -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -if display_intermediate_steps: - display = PrimePlotter() - display.plot(model, update=True) - display.show() -############################################################################### -# Set up the zone naming before the mesh output -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Delete the unnecessary topo entities. -# Name the walls of ``solid`` as ``wall_solid``. For example, if the solid's name is ``A``, then -# name the walls surrounding the solid ``wall_A``). -# Convert the labels to mesh zones. - -# Define deletion parameters -deletion_params = prime.DeleteTopoEntitiesParams( - model, delete_geom_zonelets=True, delete_mesh_zonelets=False -) -# Delete unnecessary geometrical entities. -part.delete_topo_entities(deletion_params) -# Rename the walls surrounding any volume of the mesh by appending the string -# ``wall_`` to the solid's name. For example, if the solid is named ``my_solid``, then -# name the walls surrounding the solid ``wall_my_solid``. -for volume in part.get_volumes(): - volume_zone_name = "wall_" + model.get_zone_name(part.get_volume_zone_of_volume(volume)) - label_zonelets = part.get_face_zonelets_of_volumes([volume]) - part.add_labels_on_zonelets([volume_zone_name], label_zonelets) -# Convert labels into mesh zones to use in the solver. -mesh_util_create_zones = mesh_util.create_zones_from_labels() - -############################################################################### -# Mesh output -# ~~~~~~~~~~~ -# Create a temporary folder and use it to output the mesh to a CAS file. - -with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, 'multi_layer_quad_mesh_pcb.cas') - mesh_util.write(mesh_file) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) -# Otherwise, specify a path on your local machine: -# mesh_util.write('local/path/to/your/mesh_file.cas') - -############################################################################### -# Exit PyPrimeMesh -# ~~~~~~~~~~~~~~~~ - -prime_client.exit() diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py deleted file mode 100644 index 0baa047b31..0000000000 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_contact_patch: - -======================================================================== -Create a contact patch for wrapping between a wheel and ground interface -======================================================================== - -**Summary**: This example demonstrates how to create a contact patch for use with wrapping -to avoid meshing into a narrow contact region between two objects. - -Objective -~~~~~~~~~ -This example uses a contact patch for wrapping to avoid the interface of a wheel with the ground -to improve mesh quality when growing prism layers in the region of the contacting faces. - -.. image:: ../../../images/contact_patch.png - :align: center - :width: 600 - -The preceding image shows the following: - - -* Top left: Wheel/ground interface -* Top right: Addition of contact patch -* Lower left: Grouping tolerance at 4 with multiple contact patches -* Lower right: Grouping tolerance at 20 with merged single contact patch - - - -Procedure -~~~~~~~~~ -#. Launch an Ansys Prime Server instance and instantiate the meshing utilities - from the ``lucid`` class. -#. Import the wheel ground geometry. -#. Convert the topo parts to mesh parts so that the contact patch can be created. -#. Create a contact patch between the wheel and the ground. -#. Extract the fluid region using wrapping. -#. Volume mesh with polyhedral and prism cells. -#. Write a CAS file for use in the Fluent solver. -#. 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 - -client = prime.launch_prime() -model = client.model - -mesh_util = prime.lucid.Mesh(model) - -############################################################################### -# Import CAD geometry -# ~~~~~~~~~~~~~~~~~~~ -# Download the wheel ground geometry (FMD) file exported by SpaceClaim. -# Import the CAD geometry. The geometry consists of two topo parts: a wheel and an enclosing box. -# Labels are defined for the ground topo face on the enclosure and for the wheel -# as all the topo faces of the wheel part. - -# For Windows OS users, SCDOC or DSCO is also available. For example: -# wheel_ground_file = prime.examples.download_wheel_ground_scdoc() - -wheel_ground_file = prime.examples.download_wheel_ground_fmd() - -mesh_util.read(wheel_ground_file) - -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) -display.show() - -print(model) - -############################################################################### -# Convert topo parts to mesh parts -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Convert the faceted geometry of the topology to mesh for all parts as the contact patch -# requires face zonelets from mesh parts as input. - -wheel_part = model.get_part_by_name("wheel_body") -enclosure_part = model.get_part_by_name("enclosure") - -[part.delete_topo_entities(prime.DeleteTopoEntitiesParams(model)) for part in model.parts] - -############################################################################### -# Create a contact patch -# ~~~~~~~~~~~~~~~~~~~~~~ -# To create a contact patch, a direction is needed to define the resulting shape of the patch. -# A new part is created containing the patch. -# A prefix can be specified for the label created for the contact patch face zonelets generated. -# The offset distance determines the thickness and extent of the patch. The source face zonelet is -# offset to intersect the planar target face and the intersection used to define the contact patch. -# Due to the depth of the treads on the wheel, 20.0 is used as the offset to reach the tire surface. -# If multiple contact regions are found, they can be merged by grouping them using the grouping -# tolerance distance. With a grouping tolerance of 4.0, separate contact regions are created for -# some of the treads of the tire, see the image at the top of the example. To merge these contact -# regions into a single patch, the grouping tolerance distance is increased to 20.0, avoiding small -# gaps between contact regions. - -# The face zonelets of the wheel are defined as the source. -# The planar surface must be specified as the target. -# In this instance, the ground provides the planar target. - -source = wheel_part.get_face_zonelets() -target = enclosure_part.get_face_zonelets_of_label_name_pattern( - "ground", prime.NamePatternParams(model) -) - -params = prime.CreateContactPatchParams( - model, - contact_patch_axis=prime.ContactPatchAxis.Z, - offset_distance=20.0, - grouping_tolerance=20.0, - suggested_label_prefix="patch", -) -result = prime.SurfaceUtilities(model).create_contact_patch( - source_zonelets=source, target_zonelets=target, params=params -) -print(result.error_code) -print(model) - -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) -display.show() - -############################################################################### -# Wrap the fluid region -# ~~~~~~~~~~~~~~~~~~~~~ -# The largest internal region in this instance is the fluid region around the wheel. -# Intersection loops are created to capture the features at the corners between the -# patch, ground, and wheel. - -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) - -# Create a size control to limit the size of mesh on the wheel. -size_control = model.control_data.create_size_control(prime.SizingType.SOFT) -size_control.set_soft_sizing_params(prime.SoftSizingParams(model=model, max=8.0)) -size_control.set_scope(prime.ScopeDefinition(model=model, label_expression="wheel")) - -wrap_part = mesh_util.wrap( - min_size=4.0, - max_size=100.0, - region_extract=prime.WrapRegion.LARGESTINTERNAL, - create_intersection_loops=True, - wrap_size_controls=[size_control], -) - -display = PrimePlotter() -display.plot( - model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True -) -display.show() - -print(model) - -############################################################################### -# Volume mesh -# ~~~~~~~~~~~ -# Apply five layers of prisms to the wheel, patch, and ground. Mesh with polyhedrals. - -model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) -mesh_util.volume_mesh( - volume_fill_type=prime.VolumeFillType.POLY, - prism_layers=5.0, - prism_surface_expression="wheel, patch*, ground", - prism_volume_expression="*", - scope=prime.lucid.VolumeScope(part_expression=wrap_part.name), -) - -display = PrimePlotter() -display.plot( - model, - scope=prime.ScopeDefinition(model, label_expression="!front !side_right !top"), - update=True, -) -display.show() - -mesh_util.create_zones_from_labels() - -wrap_part._print_mesh = True -print(wrap_part) - -############################################################################### -# Write model -# ~~~~~~~~~~~ -# Write a CAS file for use in the Fluent solver. - -with tempfile.TemporaryDirectory() as temp_folder: - wheel_model = os.path.join(temp_folder, "wheel_ground_contact.cas.h5") - prime.FileIO(model).export_fluent_case( - wheel_model, - export_fluent_case_params=prime.ExportFluentCaseParams(model, cff_format=True), - ) - assert os.path.exists(wheel_model) - print(f"Fluent case exported at {wheel_model}") - -############################################################################### -# Exit the PyPrimeMesh session -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -client.exit() diff --git a/examples/gallery/11_solder_ball.py b/examples/gallery/11_solder_ball.py deleted file mode 100644 index ccf3f655f1..0000000000 --- a/examples/gallery/11_solder_ball.py +++ /dev/null @@ -1,401 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -.. _ref_solder_ball_mesh: - -================================= -Mesh a set of solder balls (beta) -================================= - -**Summary**: This example demonstrates how to mesh a set of solder balls with mainly -hexahedral elements. The solder is initially modelled as cylindrical to allow meshing -using stacker and then local match morph controls are applied to recover the -spherical shapes. - -.. note:: This example contains a beta API. The behavior and implementation may change in future. - -Objective -~~~~~~~~~~ -This example uses locally defined match morphing controls to morph a hexahedral mesh, -created using volume sweep, to many spherical solder balls. - -.. image:: ../../../images/solder_ball_mesh_cross_section.png - :align: center - :width: 800 - :alt: Mesh cross section of solder ball model. - -Procedure -~~~~~~~~~~ -#. Import libraries necessary to run the script. -#. Launch an Ansys Prime Server instance. -#. Import stackable simplified CAD geometry with refaceting. -#. Connect the geometry using scaffolding. -#. Mesh with hex dominant elements using the volume sweeper. -#. Import the target CAD geometry for the solders for match morphing. -#. Locally match morph the simplified mesh to the target spherical solders. -#. Delete the target and export the morphed mesh. -#. Exit the PyPrimeMesh session. - - -""" - -############################################################################### -# Import modules -# ~~~~~~~~~~~~~~ -# Import libraries necessary to run the script. -def example(): - import os - import tempfile - - import ansys.meshing.prime as prime - from ansys.meshing.prime.graphics import PrimePlotter - - ############################################################################### - # Launch Ansys Prime Server - # ~~~~~~~~~~~~~~~~~~~~~~~~~ - # Launch an instance of Ansys Prime Server. - # Connect the PyPrimeMesh client and get the model. - - prime_client = prime.launch_prime() - model = prime_client.model - - ############################################################################### - # Import CAD geometry - # ~~~~~~~~~~~~~~~~~~~ - # FMD is exported from SpaceClaim for the geometry. - # Geometry consists of multiple non overlapping and disconnected volumes. - # The model has multiple layers either side of several solder balls with pads - # and contains an infill volume around the solder. During import, the part - # creation type is set to BODY so that each body in the CAD is imported as a - # separate part. Refaceting is specified for more control of the scaffolding - # operation. Consistent faceting for the curved surfaces to be joined can be - # obtained by specifying CadRefacetingMaxEdgeSizeLimit as ABSOLUTE. To avoid - # over refinement of the faceting the max_edge_size is allowed to reach a size - # of 1.0. Labels can be assigned to manage the entities of each volume. - - solder_ball = prime.examples.download_solder_ball_fmd() - - params = prime.ImportCadParams( - model, - append=True, - part_creation_type=prime.PartCreationType.BODY, - refacet=True, - cad_refaceting_params=prime.CadRefacetingParams( - model, - cad_faceter=prime.CadFaceter.PARASOLID, - max_edge_size_limit=prime.CadRefacetingMaxEdgeSizeLimit.ABSOLUTE, - max_edge_size=1.0, - ), - ) - - prime.FileIO(model).import_cad(file_name=solder_ball, params=params) - - for part in model.parts: - part.add_labels_on_topo_entities([part.name], part.get_topo_faces()) - part.add_labels_on_topo_entities([part.name], part.get_topo_volumes()) - - # Display the model without the infill so the cylindrical geometry of the solder - # is visible. - - display = PrimePlotter() - display.plot( - model, scope=prime.ScopeDefinition(model=model, label_expression="solder_cyl*,pad*,layer*") - ) - display.show() - - ############################################################################### - # Connect geometry - # ~~~~~~~~~~~~~~~~ - # Merge all parts into a single part so they can be connected. - # Imprint adjacent topo faces by connecting topo faces and topo edges. - # Mesh all topo faces to allow splitting the imprinted topo faces by mesh regions. - # Delete mesh on topo faces after splitting. - # Merge newly created overlapping topo faces so only a single topo face exists - # between connected volumes. - - part_ids = [part.id for part in model.parts if part.get_topo_faces()] - merge_result = model.merge_parts(part_ids=part_ids, params=prime.MergePartsParams(model)) - print(model) - - merged_part = model.get_part(merge_result.merged_part_id) - - params = prime.ScaffolderParams( - model=model, - absolute_dist_tol=0.01, - intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, - constant_mesh_size=0.1, - ) - - scaffolder = prime.Scaffolder(model, merged_part.id) - res = scaffolder.scaffold_topo_faces_and_beams( - topo_faces=merged_part.get_topo_faces(), topo_beams=[], params=params - ) - - prime.lucid.Mesh(model).surface_mesh(min_size=0.1) - prime.Scaffolder(model, merged_part.id).split_topo_faces_by_mesh_region( - merged_part.get_topo_faces() - ) - - # This is a beta API. The behavior and implementation may change in future. - result = model.topo_data.delete_mesh_on_topo_faces( - merged_part.get_topo_faces(), prime.DeleteMeshParams(model=model) - ) - - scaffolder.merge_overlapping_topo_faces(merged_part.get_topo_faces(), params) - - display = PrimePlotter() - display.plot(model, update=True) - display.show() - - ############################################################################### - # Volume sweeper - # ~~~~~~~~~~~~~~ - # Setup a size control to refine the mesh around the solder. - # Setup stacker parameters to define the volume sweep mesh. - # Create the base face to quad surface mesh and use for sweeping. - # Stack the base face to create the volume mesh. - # Delete topology on mesh part to allow use of surface utilities and feature extraction. - # A large lateral defeature tolerance of 0.1 is used to avoid additional topo nodes - # from scaffolding impacting the final mesh. - - model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.1, max=0.4)) - - size_control = model.control_data.create_size_control(prime.SizingType.SOFT) - size_control.set_scope( - prime.ScopeDefinition(model, part_expression=merged_part.name, label_expression="*solder*") - ) - size_control.set_soft_sizing_params(prime.SoftSizingParams(model, max=0.1)) - - stacker_params = prime.MeshStackerParams( - model=model, - direction=[0, 1, 0], - max_offset_size=0.4, - delete_base=True, - lateral_defeature_tolerance=0.1, - stacking_defeature_tolerance=0.01, - size_control_ids=[size_control.id], - ) - - sweeper = prime.VolumeSweeper(model) - - createbase_results = sweeper.create_base_face( - part_id=merged_part.id, - topo_volume_ids=merged_part.get_topo_volumes(), - params=stacker_params, - ) - - base_faces = createbase_results.base_face_ids - merged_part.add_labels_on_topo_entities(["base_faces"], base_faces) - - scope = prime.ScopeDefinition(model=model, label_expression="base_faces") - - base_scope = prime.lucid.SurfaceScope( - entity_expression="base_faces", - part_expression=merged_part.name, - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, - ) - - prime.lucid.Mesh(model).surface_mesh( - min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True - ) - - display = PrimePlotter() - display.plot(model, update=True) - display.show() - - stackbase_results = sweeper.stack_base_face( - part_id=merged_part.id, - base_face_ids=base_faces, - topo_volume_ids=merged_part.get_topo_volumes(), - params=stacker_params, - ) - - merged_part.delete_topo_entities( - prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=True, delete_mesh_zonelets=False) - ) - - merged_part._print_mesh = True - print(merged_part) - - display = PrimePlotter() - display.plot(model, update=True) - display.show() - - ############################################################################### - # Import sphere geometry for match morphing - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Create a single part on CAD import by setting the part creation type to MODEL. - # Convert topology to mesh face zonelets to use surface utilities. - - solder_ball_target = prime.examples.download_solder_ball_target_fmd() - - params = prime.ImportCadParams(model, append=True, part_creation_type=prime.PartCreationType.MODEL) - - prime.FileIO(model).import_cad(file_name=solder_ball_target, params=params) - - imported_cad_part_ids = [part.id for part in model.parts if part.get_topo_faces()] - target_part = model.get_part(imported_cad_part_ids[0]) - - display = PrimePlotter() - display.plot(model, scope=prime.ScopeDefinition(model, part_expression=target_part.name)) - display.show() - - print(model) - - target_part.delete_topo_entities( - prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=False, delete_mesh_zonelets=False) - ) - - ############################################################################### - # Match morph the mesh to the spherical solder - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Delete feature edge zonelets on the mesh source. - # Get lists of the source and target face zonelets for the match morph. - # Pair source and target faces based on overlapping bounding box locations. - # For each source face extract feature edges with nodes attached to faces. - # Define edge pairs for each match pair control as a morph boundary condition. - # Match morph solder faces and edges. - # Delete target sphere part and retain the morphed mesh for export. - - merged_part.delete_zonelets(merged_part.get_edge_zonelets()) - cylinder_faces = merged_part.get_face_zonelets_of_label_name_pattern( - "solder_cyl", prime.NamePatternParams(model) - ) - sphere_faces = target_part.get_face_zonelets() - - match_pairs = [] - - # When the match morph operation is defined by multiple - # separate local match morph source and targets, - # individual match pairs must be specified for each - # contiguous set of face zonelets. - # Separate one to one boundary condition pairs for the - # connected edge zonelets must also be defined for - # each face match pair to ensure the edges remain rigid. - - # For each solder to be morphed we have a match pair - # for the source and target face zonelets. To ensure - # the edge zonelets remain rigid during the morph, - # boundary condition pairs for each of the two edge - # zonelets need to be created. - - # For each cylindrical face zonelet on the source - # mesh find the corresponding target sphere face zonelet - # to match morph the mesh using the position of their bounding box. - # Labels defined in the CAD model would be a more efficient way to pair. - tolerance = 0.2 - for face in cylinder_faces: - box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([face]) - for i in range(len(sphere_faces)): - sphere_box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([sphere_faces[i]]) - if ( - (abs(sphere_box.xmin - box.xmin) < tolerance) - and (abs(sphere_box.ymin - box.ymin) < tolerance) - and (abs(sphere_box.zmin - box.zmin) < tolerance) - and (abs(sphere_box.xmax - box.xmax) < tolerance) - and (abs(sphere_box.ymax - box.ymax) < tolerance) - and (abs(sphere_box.zmax - box.zmax) < tolerance) - ): - break - elif i == len(sphere_faces) - 1: - # if no target face is found by the final pass then return an error - prime.PrimeRuntimeError(message=f"Target sphere not found for face {face}.") - match_pair = prime.MatchPair( - model, - source_surfaces=[face], - target_surfaces=[sphere_faces[i]], - target_type=prime.MatchPairTargetType.FACEZONELET, - ) - bc_pairs = [] - - # Once each source and target face zonelet are found and paired, edge zonelets - # are created and used as boundary conditions to ensure they remain rigid. - # Creating edge zonelets not disconnected from the faces ensures that the edge - # zonelet will act as a boundary condition for the morph operation. - # Extracting the edge zonelets from the source face zonelet and using - # them to define both source and target boundary conditions ensures - # the edge zonelets of the solder remain rigid. - result = prime.FeatureExtraction(model).extract_features_on_face_zonelets( - merged_part.id, - [face], - prime.ExtractFeatureParams( - model, - feature_angle=60, - separate_features=True, - separation_angle=60, - replace=False, - disconnect_with_faces=False, - ), - ) - for edge in result.new_edge_zonelets: - bc_pair = prime.BCPair( - model, - int(edge), - int(edge), - type=prime.BCPairType.EDGE, - ) - bc_pairs.append(bc_pair) - match_pair.bc_pairs = bc_pairs - match_pairs.append(match_pair) - - morph = prime.Morpher(model) - - morph_params = prime.MatchMorphParams(model) - bc_params = prime.MorphBCParams(model, morphable_layers=0) - solve_params = prime.MorphSolveParams(model) - - morph.match_morph( - part_id=merged_part.id, - match_pairs=match_pairs, - match_morph_params=morph_params, - bc_params=bc_params, - solve_params=solve_params, - ) - - model.delete_parts([target_part.id]) - - display = PrimePlotter() - display.plot( - model, scope=prime.ScopeDefinition(model=model, label_expression="solder*"), update=True - ) - display.show() - - ############################################################################### - # Export mesh - # ~~~~~~~~~~~ - # Export a CDB file. - - with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, "solder_balls.cdb") - params = prime.ExportMapdlCdbParams(model=model) - prime.FileIO(model=model).export_mapdl_cdb(file_name=mesh_file, params=params) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) - - ############################################################################### - # Exit PyPrimeMesh - # ~~~~~~~~~~~~~~~~ - - prime_client.exit() - -example() From b2c0b7737f42d88f7df04f6140c4b485ca4b34c7 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 11:11:50 +0100 Subject: [PATCH 13/27] test --- examples/gallery/00_lucid_file_IO.py | 1 - examples/gallery/06_blade_morph.py | 151 +++++++ .../gallery/09_multi_layer_quad_mesh_pcb.py | 310 ++++++++++++++ .../gallery/10_wheel_ground_contact_patch.py | 229 ++++++++++ examples/gallery/11_solder_ball.py | 404 ++++++++++++++++++ 5 files changed, 1094 insertions(+), 1 deletion(-) create mode 100644 examples/gallery/06_blade_morph.py create mode 100644 examples/gallery/09_multi_layer_quad_mesh_pcb.py create mode 100644 examples/gallery/10_wheel_ground_contact_patch.py create mode 100644 examples/gallery/11_solder_ball.py diff --git a/examples/gallery/00_lucid_file_IO.py b/examples/gallery/00_lucid_file_IO.py index 29735dc0e2..bc4523c8ec 100644 --- a/examples/gallery/00_lucid_file_IO.py +++ b/examples/gallery/00_lucid_file_IO.py @@ -64,7 +64,6 @@ # launch an instance of Ansys Prime Server. # Connect the PyPrimeMesh client and get the model. # Instantiate meshing utilities from the ``lucid`` class. -try: import os import tempfile diff --git a/examples/gallery/06_blade_morph.py b/examples/gallery/06_blade_morph.py new file mode 100644 index 0000000000..0c10dda7fe --- /dev/null +++ b/examples/gallery/06_blade_morph.py @@ -0,0 +1,151 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_turbine_blade: + +========================================================= +Morph a hexahedral mesh of a turbine blade to a new shape +========================================================= + +**Summary**: This example demonstrates how to morph a structural +hexahedral mesh of a turbine blade to a new deformed shape +defined by a target geometry file. + +Objective +~~~~~~~~~~ + +This example appends a CDB mesh with a CAD geometry +and match morphs the mesh to the geometry. + +.. image:: ../../../images/turbine_blade.png + :align: center + :width: 800 + :alt: Turbine blade hexahedral mesh. + +Procedure +~~~~~~~~~~ +#. Launch an Ansys Prime Server instance and connect the PyPrimeMesh client. +#. Read the mesh and append the new CAD geometry shape. +#. Define the mesh source faces and the target geometry faces to match morph. +#. Match morph the turbine blade mesh to the new CAD geometry shape. +#. Write the mesh for structural analysis. + +""" + +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Import all necessary modules. +# Launch the Ansys Prime Server instance and connect the client. +# Get the client model and 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=model) + +############################################################################### +# Read files +# ~~~~~~~~~~ +# Download the turbine blade mesh file and CAD geometry. +# Read the mesh and append the geometry. +# Display the source and the target. + +# For Windows OS users, scdoc is also available for geometry: +# target_geometry = prime.examples.download_turbine_blade_target_scdoc() + +source_mesh = prime.examples.download_turbine_blade_cdb() +target_geometry = prime.examples.download_deformed_blade_fmd() +mesh_util.read(file_name=source_mesh) +mesh_util.read(file_name=target_geometry, append=True) + + +display = PrimePlotter() +display.plot(model) +display.show() + +print(model) + +############################################################################### +# Define source and target faces +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +source_part = model.get_part(2) +target_part = model.get_part(3) +source = source_part.get_face_zonelets() +target = target_part.get_topo_faces() + +############################################################################### +# Match morph mesh +# ~~~~~~~~~~~~~~~~ +# Set the target type to be for topoface because the target is geometry. +# Morph the source face zonelets of ``source_part`` to the +# target topofaces of the geometry. + +morpher = prime.Morpher(model) +match_pair = prime.MatchPair( + model=model, + source_surfaces=source, + target_surfaces=target, + target_type=prime.MatchPairTargetType.TOPOFACE, +) + +params = prime.MatchMorphParams(model) +bc_params = prime.MorphBCParams(model) +solver_params = prime.MorphSolveParams(model) + +morpher.match_morph( + part_id=source_part.id, + match_pairs=[match_pair], + match_morph_params=params, + bc_params=bc_params, + solve_params=solver_params, +) + +# Display the morphed mesh + +display = PrimePlotter() +display.plot(model, update=True) +display.show() +############################################################################### +# Write mesh +# ~~~~~~~~~~ +# Write the morphed CDB file. The geometry is ignored when exporting to a CDB +# file. + +with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, "morphed_turbine_blade.cdb") + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/09_multi_layer_quad_mesh_pcb.py b/examples/gallery/09_multi_layer_quad_mesh_pcb.py new file mode 100644 index 0000000000..5ab7d7296b --- /dev/null +++ b/examples/gallery/09_multi_layer_quad_mesh_pcb.py @@ -0,0 +1,310 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_multi_layer_pcb_mesh: + +===================================================== +Mesh a generic PCB geometry with multiple hexa layers +===================================================== + +**Summary**: This example demonstrates how to set the base mesh size and number of +layers for each solid in a generic PCB geometry and then generate a mesh. + +Objective +~~~~~~~~~~ + +The example uses PyPrimeMesh to discretize a PCB CAD geometry by means of the +stacker technology. You can easily set up the mesh size of the base face (xy plane +in this example) and the number of mesh layers along the sweep direction (z axis in this example). +The CAD edges along the z direction are assigned with a named selection at a CAD level in Ansys +Discovery/SpaceClaim. + +The following image provides a snapshot of the Discovery tree to help you to understand the model's +organization. Share topology in Discovery/SpaceClaim guarantees the generation of a conformal mesh +between the solids. Named selections of edges allow you to specify the number of mesh elements to +generate along the sweep direction. To simplify the process and enhance convenience, this example +uses multiple meshing utilities provided in the ``lucid`` class. + +.. image:: ../../../images/multi_layer_quad_mesh_pcb.png + :align: center + :width: 800 + :alt: Generic PCB geometry. + +The resulting mesh with three layers per solid looks like this: + +.. image:: ../../../images/multi_layer_quad_mesh_pcb_3.png + :align: center + :width: 500 + :alt: Generic PCB geometry meshed. + +.. image:: ../../../images/multi_layer_quad_mesh_pcb_2.png + :align: center + :width: 500 + :alt: Generic PCB geometry meshed, zoom in. + + +Procedure +~~~~~~~~~~ +#. Import the fundamental libraries that are necessary to run the script. +#. Launch an Ansys Prime Server instance. +#. Instantiate the meshing utilities from the ``lucid`` class. +#. Define the base mesh size and number of layers along the sweep direction. +#. Import the CAD geometry. +#. Define the edge sizing along the sweep direction using existing edge named selections. +#. Define the parameters for the volume sweeper. +#. Set up, generate, and mesh the base face. +#. Stack the base face along the sweep direction. +#. Set up the zone naming before the mesh output. +#. Write a CAS file for use in the Fluent solver. +#. Exit the PyPrimeMesh session. + +""" + +############################################################################### +# Import all necessary modules +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Notice that you must install the PyVista library to be able to run the visualization +# tools included in this script. + +import os +import tempfile + +import ansys.meshing.prime as prime +from ansys.meshing.prime.graphics import PrimePlotter + +############################################################################### +# Launch Prime server and instantiate the ``lucid`` class +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and get the model. +# Instantiate meshing utilities from the ``lucid`` class. + +prime_client = prime.launch_prime() +model = prime_client.model +mesh_util = prime.lucid.Mesh(model=model) + +############################################################################### +# Define CAD file and mesh settings +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Define the number of layers per solid. +# Define the size in mm of the quad-dominant mesh on the base size. +# Define the path to the CAD file to mesh. +# Download the example CAD file using the ``prime.examples`` function. Otherwise, +# write the path to the desired CAD file on your machine. Supported file types +# are SCDOC, DSCO, and PMDB. + +# cad_file='/path/to/any/cad/file.dsco' +cad_file = prime.examples.download_multi_layer_quad_mesh_pcb_pmdat() +layers_per_solid = 4 # number of hexa mesh layers in each solid +base_face_size = 0.5 # surface mesh size in mm on the base face +# Chose whether to display the CAD/mesh at every stage +display_intermediate_steps = True # Use True/False + + +############################################################################### +# Import geometry +# ~~~~~~~~~~~~~~~ +# Import the geometry into Prime server. +# Use the Workbench ``CadReaderRoute`` to ensure that the shared topology is kept. + +mesh_util.read(file_name=cad_file) +# Use the following command to open CAD files of these types: SCDOC, DSCO, and PMDB. +# mesh_util.read( +# file_name = cad_file, +# cad_reader_route = prime.CadReaderRoute.WORKBENCH) + +############################################################################### +# Display the imported CAD in a PyVista window +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +if display_intermediate_steps: + display = PrimePlotter() + display.plot(model) + display.show() + +############################################################################### +# Define edge sizing constraints +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Set generic global sizing from 0.002mm to 2mm. +# Extract the the edge's length from the named selections, such as "edge_1_0.50_mm" +# (extract 0.5 mm length) or "edge_23_0.27_mm" (extract 0.27mm length). +# On each edge, assign a size equal to the edge's length divided by the +# predefined number of layers per solid. + +# Set generic global sizing from 0.002mm to 2mm. +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.002, max=2.0)) +ids = [] +# collect the imported geometry +part = model.parts[0] +for label in part.get_labels(): + # Check whether the named selection's name starts with the string "edge" + if label.startswith('edge'): + # Extract the edge's length, splitting its name at every "_" string + # Collect the second to last number + length = float(label.split("_")[-2]) # get 0.27 from "edge_23_0.27_mm" + # Initialize a constant-size mesh control (SOFT) + soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) + # Assign a mesh size equal to the edge's length/number of mesh layers + soft_size_params = prime.SoftSizingParams(model=model, max=length / layers_per_solid) + # Finalize the creation of mesh sizing + soft_size_control.set_soft_sizing_params(soft_size_params) + soft_size_scope = prime.ScopeDefinition( + model, + part_expression=part.name, + entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS, + label_expression=label + "*", + ) + soft_size_control.set_scope(soft_size_scope) + soft_size_control.set_suggested_name(label) + # Append the id of the edge sizing to the edge sizings' ids' list + ids.append(soft_size_control.id) + +############################################################################### +# Define controls for volume sweeper +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Set the sweep direction vector. +# Set up the geometric tolerances for lateral and stacking defeature. +# Select the sweep direction as the z axis (0,0,1). +# Append the IDs of the soft local sizings that have been previously defined on +# the edges. + +# Instantiate the volume sweeper +sweeper = prime.VolumeSweeper(model) +# Define the parameters for stacker +stacker_params = prime.MeshStackerParams( + model=model, + direction=[0, 0, 1], # define the sweep direction for the mesh + delete_base=True, # delete the base face in the end of stacker + lateral_defeature_tolerance=0.001, + stacking_defeature_tolerance=0.001, + size_control_ids=ids, +) # list of control IDs to be respected by the stacker + +############################################################################### +# Set up, generate, and mesh the base face +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a soft sizing control. +# Assign the previously defined ``base_face_size`` function to the soft sizing. +# Create the base face. +# Mesh the base face. +# Display the base face. + +# Set up the necessary parameters for the generation of the base face. +soft_size_control = model.control_data.create_size_control(prime.SizingType.SOFT) +soft_size_params = prime.SoftSizingParams(model=model, max=base_face_size) +soft_size_control.set_soft_sizing_params(soft_size_params) +soft_size_scope = prime.ScopeDefinition( + model, part_expression=part.name, entity_type=prime.ScopeEntity.FACEANDEDGEZONELETS +) +soft_size_control.set_scope(soft_size_scope) +soft_size_control.set_suggested_name("b_f_size") + +# Create the base face, appending the the stacker mesh parameters. +createbase_results = sweeper.create_base_face( + part_id=model.get_part_by_name(part.name).id, + topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), + params=stacker_params, +) +base_faces = createbase_results.base_face_ids +model.get_part_by_name(part.name).add_labels_on_topo_entities(["base_faces"], base_faces) +scope = prime.ScopeDefinition(model=model, label_expression="base_faces") +base_scope = prime.lucid.SurfaceScope( + entity_expression="base_faces", + part_expression=part.name, + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, +) + +# Generate the quad-dominant surface mesh on the base face. +mesh_util_controls = mesh_util.surface_mesh_with_size_controls( + size_control_names="b_f_size", scope=base_scope, generate_quads=True +) + +############################################################################### +# Display the meshed base face in a PyVista window +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +############################################################################### +# Stack the base face using the volume sweeper +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Use the volume sweeper to stack the base face along the previously defined sweep +# direction. +# Include the previously defined stacker parameters. +# Display the final volume mesh. + +# Use the ``stack_base_face`` function to generate the volume mesh +stackbase_results = sweeper.stack_base_face( + part_id=model.get_part_by_name(part.name).id, + base_face_ids=base_faces, + topo_volume_ids=model.get_part_by_name(part.name).get_topo_volumes(), + params=stacker_params, +) + +############################################################################### +# Display the final PCB mesh in a PyVista window +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +if display_intermediate_steps: + display = PrimePlotter() + display.plot(model, update=True) + display.show() +############################################################################### +# Set up the zone naming before the mesh output +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Delete the unnecessary topo entities. +# Name the walls of ``solid`` as ``wall_solid``. For example, if the solid's name is ``A``, then +# name the walls surrounding the solid ``wall_A``). +# Convert the labels to mesh zones. + +# Define deletion parameters +deletion_params = prime.DeleteTopoEntitiesParams( + model, delete_geom_zonelets=True, delete_mesh_zonelets=False +) +# Delete unnecessary geometrical entities. +part.delete_topo_entities(deletion_params) +# Rename the walls surrounding any volume of the mesh by appending the string +# ``wall_`` to the solid's name. For example, if the solid is named ``my_solid``, then +# name the walls surrounding the solid ``wall_my_solid``. +for volume in part.get_volumes(): + volume_zone_name = "wall_" + model.get_zone_name(part.get_volume_zone_of_volume(volume)) + label_zonelets = part.get_face_zonelets_of_volumes([volume]) + part.add_labels_on_zonelets([volume_zone_name], label_zonelets) +# Convert labels into mesh zones to use in the solver. +mesh_util_create_zones = mesh_util.create_zones_from_labels() + +############################################################################### +# Mesh output +# ~~~~~~~~~~~ +# Create a temporary folder and use it to output the mesh to a CAS file. + +with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, 'multi_layer_quad_mesh_pcb.cas') + mesh_util.write(mesh_file) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) +# Otherwise, specify a path on your local machine: +# mesh_util.write('local/path/to/your/mesh_file.cas') + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py new file mode 100644 index 0000000000..1213f9040d --- /dev/null +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -0,0 +1,229 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_contact_patch: + +======================================================================== +Create a contact patch for wrapping between a wheel and ground interface +======================================================================== + +**Summary**: This example demonstrates how to create a contact patch for use with wrapping +to avoid meshing into a narrow contact region between two objects. + +Objective +~~~~~~~~~ +This example uses a contact patch for wrapping to avoid the interface of a wheel with the ground +to improve mesh quality when growing prism layers in the region of the contacting faces. + +.. image:: ../../../images/contact_patch.png + :align: center + :width: 600 + +The preceding image shows the following: + + +* Top left: Wheel/ground interface +* Top right: Addition of contact patch +* Lower left: Grouping tolerance at 4 with multiple contact patches +* Lower right: Grouping tolerance at 20 with merged single contact patch + + + +Procedure +~~~~~~~~~ +#. Launch an Ansys Prime Server instance and instantiate the meshing utilities + from the ``lucid`` class. +#. Import the wheel ground geometry. +#. Convert the topo parts to mesh parts so that the contact patch can be created. +#. Create a contact patch between the wheel and the ground. +#. Extract the fluid region using wrapping. +#. Volume mesh with polyhedral and prism cells. +#. Write a CAS file for use in the Fluent solver. +#. 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 + +client = prime.launch_prime() +model = client.model + +mesh_util = prime.lucid.Mesh(model) + +############################################################################### +# Import CAD geometry +# ~~~~~~~~~~~~~~~~~~~ +# Download the wheel ground geometry (FMD) file exported by SpaceClaim. +# Import the CAD geometry. The geometry consists of two topo parts: a wheel and an enclosing box. +# Labels are defined for the ground topo face on the enclosure and for the wheel +# as all the topo faces of the wheel part. + +# For Windows OS users, SCDOC or DSCO is also available. For example: +# wheel_ground_file = prime.examples.download_wheel_ground_scdoc() + +wheel_ground_file = prime.examples.download_wheel_ground_fmd() + +mesh_util.read(wheel_ground_file) + +print(model) + +############################################################################### +# Convert topo parts to mesh parts +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Convert the faceted geometry of the topology to mesh for all parts as the contact patch +# requires face zonelets from mesh parts as input. + +wheel_part = model.get_part_by_name("wheel_body") +enclosure_part = model.get_part_by_name("enclosure") + +[part.delete_topo_entities(prime.DeleteTopoEntitiesParams(model)) for part in model.parts] + +############################################################################### +# Create a contact patch +# ~~~~~~~~~~~~~~~~~~~~~~ +# To create a contact patch, a direction is needed to define the resulting shape of the patch. +# A new part is created containing the patch. +# A prefix can be specified for the label created for the contact patch face zonelets generated. +# The offset distance determines the thickness and extent of the patch. The source face zonelet is +# offset to intersect the planar target face and the intersection used to define the contact patch. +# Due to the depth of the treads on the wheel, 20.0 is used as the offset to reach the tire surface. +# If multiple contact regions are found, they can be merged by grouping them using the grouping +# tolerance distance. With a grouping tolerance of 4.0, separate contact regions are created for +# some of the treads of the tire, see the image at the top of the example. To merge these contact +# regions into a single patch, the grouping tolerance distance is increased to 20.0, avoiding small +# gaps between contact regions. + +# The face zonelets of the wheel are defined as the source. +# The planar surface must be specified as the target. +# In this instance, the ground provides the planar target. + +source = wheel_part.get_face_zonelets() +target = enclosure_part.get_face_zonelets_of_label_name_pattern( + "ground", prime.NamePatternParams(model) +) + +params = prime.CreateContactPatchParams( + model, + contact_patch_axis=prime.ContactPatchAxis.Z, + offset_distance=20.0, + grouping_tolerance=20.0, + suggested_label_prefix="patch", +) +result = prime.SurfaceUtilities(model).create_contact_patch( + source_zonelets=source, target_zonelets=target, params=params +) +print(result.error_code) +print(model) + +display = PrimePlotter() +display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) +display.show() + +############################################################################### +# Wrap the fluid region +# ~~~~~~~~~~~~~~~~~~~~~ +# The largest internal region in this instance is the fluid region around the wheel. +# Intersection loops are created to capture the features at the corners between the +# patch, ground, and wheel. + +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) + +# Create a size control to limit the size of mesh on the wheel. +size_control = model.control_data.create_size_control(prime.SizingType.SOFT) +size_control.set_soft_sizing_params(prime.SoftSizingParams(model=model, max=8.0)) +size_control.set_scope(prime.ScopeDefinition(model=model, label_expression="wheel")) + +wrap_part = mesh_util.wrap( + min_size=4.0, + max_size=100.0, + region_extract=prime.WrapRegion.LARGESTINTERNAL, + create_intersection_loops=True, + wrap_size_controls=[size_control], +) + +display = PrimePlotter() +display.plot( + model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True +) +display.show() + +print(model) + +############################################################################### +# Volume mesh +# ~~~~~~~~~~~ +# Apply five layers of prisms to the wheel, patch, and ground. Mesh with polyhedrals. + +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=4.0, max=100.0, growth_rate=1.4)) +mesh_util.volume_mesh( + volume_fill_type=prime.VolumeFillType.POLY, + prism_layers=5.0, + prism_surface_expression="wheel, patch*, ground", + prism_volume_expression="*", + scope=prime.lucid.VolumeScope(part_expression=wrap_part.name), +) + +display = PrimePlotter() +display.plot( + model, + scope=prime.ScopeDefinition(model, label_expression="!front !side_right !top"), + update=True, +) +display.show() + +mesh_util.create_zones_from_labels() + +wrap_part._print_mesh = True +print(wrap_part) + +############################################################################### +# Write model +# ~~~~~~~~~~~ +# Write a CAS file for use in the Fluent solver. + +# with tempfile.TemporaryDirectory() as temp_folder: +# wheel_model = os.path.join(temp_folder, "wheel_ground_contact.cas.h5") +# prime.FileIO(model).export_fluent_case( +# wheel_model, +# export_fluent_case_params=prime.ExportFluentCaseParams(model, cff_format=True), +# ) +# assert os.path.exists(wheel_model) +# print(f"Fluent case exported at {wheel_model}") + +############################################################################### +# Exit the PyPrimeMesh session +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +client.exit() diff --git a/examples/gallery/11_solder_ball.py b/examples/gallery/11_solder_ball.py new file mode 100644 index 0000000000..75ffbba5c0 --- /dev/null +++ b/examples/gallery/11_solder_ball.py @@ -0,0 +1,404 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_solder_ball_mesh: + +================================= +Mesh a set of solder balls (beta) +================================= + +**Summary**: This example demonstrates how to mesh a set of solder balls with mainly +hexahedral elements. The solder is initially modelled as cylindrical to allow meshing +using stacker and then local match morph controls are applied to recover the +spherical shapes. + +.. note:: This example contains a beta API. The behavior and implementation may change in future. + +Objective +~~~~~~~~~~ +This example uses locally defined match morphing controls to morph a hexahedral mesh, +created using volume sweep, to many spherical solder balls. + +.. image:: ../../../images/solder_ball_mesh_cross_section.png + :align: center + :width: 800 + :alt: Mesh cross section of solder ball model. + +Procedure +~~~~~~~~~~ +#. Import libraries necessary to run the script. +#. Launch an Ansys Prime Server instance. +#. Import stackable simplified CAD geometry with refaceting. +#. Connect the geometry using scaffolding. +#. Mesh with hex dominant elements using the volume sweeper. +#. Import the target CAD geometry for the solders for match morphing. +#. Locally match morph the simplified mesh to the target spherical solders. +#. Delete the target and export the morphed mesh. +#. Exit the PyPrimeMesh session. + + +""" + +############################################################################### +# Import modules +# ~~~~~~~~~~~~~~ +# Import libraries necessary to run the script. +def example(): + import os + import tempfile + + import ansys.meshing.prime as prime + from ansys.meshing.prime.graphics import PrimePlotter + + ############################################################################### + # Launch Ansys Prime Server + # ~~~~~~~~~~~~~~~~~~~~~~~~~ + # Launch an instance of Ansys Prime Server. + # Connect the PyPrimeMesh client and get the model. + + prime_client = prime.launch_prime() + model = prime_client.model + + ############################################################################### + # Import CAD geometry + # ~~~~~~~~~~~~~~~~~~~ + # FMD is exported from SpaceClaim for the geometry. + # Geometry consists of multiple non overlapping and disconnected volumes. + # The model has multiple layers either side of several solder balls with pads + # and contains an infill volume around the solder. During import, the part + # creation type is set to BODY so that each body in the CAD is imported as a + # separate part. Refaceting is specified for more control of the scaffolding + # operation. Consistent faceting for the curved surfaces to be joined can be + # obtained by specifying CadRefacetingMaxEdgeSizeLimit as ABSOLUTE. To avoid + # over refinement of the faceting the max_edge_size is allowed to reach a size + # of 1.0. Labels can be assigned to manage the entities of each volume. + + solder_ball = prime.examples.download_solder_ball_fmd() + + params = prime.ImportCadParams( + model, + append=True, + part_creation_type=prime.PartCreationType.BODY, + refacet=True, + cad_refaceting_params=prime.CadRefacetingParams( + model, + cad_faceter=prime.CadFaceter.PARASOLID, + max_edge_size_limit=prime.CadRefacetingMaxEdgeSizeLimit.ABSOLUTE, + max_edge_size=1.0, + ), + ) + + prime.FileIO(model).import_cad(file_name=solder_ball, params=params) + + for part in model.parts: + part.add_labels_on_topo_entities([part.name], part.get_topo_faces()) + part.add_labels_on_topo_entities([part.name], part.get_topo_volumes()) + + # Display the model without the infill so the cylindrical geometry of the solder + # is visible. + + display = PrimePlotter() + display.plot( + model, scope=prime.ScopeDefinition(model=model, label_expression="solder_cyl*,pad*,layer*") + ) + display.show() + + ############################################################################### + # Connect geometry + # ~~~~~~~~~~~~~~~~ + # Merge all parts into a single part so they can be connected. + # Imprint adjacent topo faces by connecting topo faces and topo edges. + # Mesh all topo faces to allow splitting the imprinted topo faces by mesh regions. + # Delete mesh on topo faces after splitting. + # Merge newly created overlapping topo faces so only a single topo face exists + # between connected volumes. + + part_ids = [part.id for part in model.parts if part.get_topo_faces()] + merge_result = model.merge_parts(part_ids=part_ids, params=prime.MergePartsParams(model)) + print(model) + + merged_part = model.get_part(merge_result.merged_part_id) + + params = prime.ScaffolderParams( + model=model, + absolute_dist_tol=0.01, + intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, + constant_mesh_size=0.1, + ) + + scaffolder = prime.Scaffolder(model, merged_part.id) + res = scaffolder.scaffold_topo_faces_and_beams( + topo_faces=merged_part.get_topo_faces(), topo_beams=[], params=params + ) + + prime.lucid.Mesh(model).surface_mesh(min_size=0.1) + prime.Scaffolder(model, merged_part.id).split_topo_faces_by_mesh_region( + merged_part.get_topo_faces() + ) + + # This is a beta API. The behavior and implementation may change in future. + result = model.topo_data.delete_mesh_on_topo_faces( + merged_part.get_topo_faces(), prime.DeleteMeshParams(model=model) + ) + + scaffolder.merge_overlapping_topo_faces(merged_part.get_topo_faces(), params) + + ############################################################################### + # Volume sweeper + # ~~~~~~~~~~~~~~ + # Setup a size control to refine the mesh around the solder. + # Setup stacker parameters to define the volume sweep mesh. + # Create the base face to quad surface mesh and use for sweeping. + # Stack the base face to create the volume mesh. + # Delete topology on mesh part to allow use of surface utilities and feature extraction. + # A large lateral defeature tolerance of 0.1 is used to avoid additional topo nodes + # from scaffolding impacting the final mesh. + + model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.1, max=0.4)) + + size_control = model.control_data.create_size_control(prime.SizingType.SOFT) + size_control.set_scope( + prime.ScopeDefinition(model, part_expression=merged_part.name, label_expression="*solder*") + ) + size_control.set_soft_sizing_params(prime.SoftSizingParams(model, max=0.1)) + + stacker_params = prime.MeshStackerParams( + model=model, + direction=[0, 1, 0], + max_offset_size=0.4, + delete_base=True, + lateral_defeature_tolerance=0.1, + stacking_defeature_tolerance=0.01, + size_control_ids=[size_control.id], + ) + + sweeper = prime.VolumeSweeper(model) + + createbase_results = sweeper.create_base_face( + part_id=merged_part.id, + topo_volume_ids=merged_part.get_topo_volumes(), + params=stacker_params, + ) + + base_faces = createbase_results.base_face_ids + merged_part.add_labels_on_topo_entities(["base_faces"], base_faces) + + scope = prime.ScopeDefinition(model=model, label_expression="base_faces") + + base_scope = prime.lucid.SurfaceScope( + entity_expression="base_faces", + part_expression=merged_part.name, + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, + ) + + prime.lucid.Mesh(model).surface_mesh( + min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True + ) + + display = PrimePlotter() + display.plot(model, update=True) + display.show() + + stackbase_results = sweeper.stack_base_face( + part_id=merged_part.id, + base_face_ids=base_faces, + topo_volume_ids=merged_part.get_topo_volumes(), + params=stacker_params, + ) + + merged_part.delete_topo_entities( + prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=True, delete_mesh_zonelets=False) + ) + + merged_part._print_mesh = True + print(merged_part) + + display = PrimePlotter() + display.plot(model, update=True) + display.show() + + ############################################################################### + # Import sphere geometry for match morphing + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Create a single part on CAD import by setting the part creation type to MODEL. + # Convert topology to mesh face zonelets to use surface utilities. + + solder_ball_target = prime.examples.download_solder_ball_target_fmd() + + params = prime.ImportCadParams( + model, append=True, part_creation_type=prime.PartCreationType.MODEL + ) + + prime.FileIO(model).import_cad(file_name=solder_ball_target, params=params) + + imported_cad_part_ids = [part.id for part in model.parts if part.get_topo_faces()] + target_part = model.get_part(imported_cad_part_ids[0]) + + display = PrimePlotter() + display.plot(model, scope=prime.ScopeDefinition(model, part_expression=target_part.name)) + display.show() + + print(model) + + target_part.delete_topo_entities( + prime.DeleteTopoEntitiesParams( + model, delete_geom_zonelets=False, delete_mesh_zonelets=False + ) + ) + + ############################################################################### + # Match morph the mesh to the spherical solder + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Delete feature edge zonelets on the mesh source. + # Get lists of the source and target face zonelets for the match morph. + # Pair source and target faces based on overlapping bounding box locations. + # For each source face extract feature edges with nodes attached to faces. + # Define edge pairs for each match pair control as a morph boundary condition. + # Match morph solder faces and edges. + # Delete target sphere part and retain the morphed mesh for export. + + merged_part.delete_zonelets(merged_part.get_edge_zonelets()) + cylinder_faces = merged_part.get_face_zonelets_of_label_name_pattern( + "solder_cyl", prime.NamePatternParams(model) + ) + sphere_faces = target_part.get_face_zonelets() + + match_pairs = [] + + # When the match morph operation is defined by multiple + # separate local match morph source and targets, + # individual match pairs must be specified for each + # contiguous set of face zonelets. + # Separate one to one boundary condition pairs for the + # connected edge zonelets must also be defined for + # each face match pair to ensure the edges remain rigid. + + # For each solder to be morphed we have a match pair + # for the source and target face zonelets. To ensure + # the edge zonelets remain rigid during the morph, + # boundary condition pairs for each of the two edge + # zonelets need to be created. + + # For each cylindrical face zonelet on the source + # mesh find the corresponding target sphere face zonelet + # to match morph the mesh using the position of their bounding box. + # Labels defined in the CAD model would be a more efficient way to pair. + tolerance = 0.2 + for face in cylinder_faces: + box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([face]) + for i in range(len(sphere_faces)): + sphere_box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets( + [sphere_faces[i]] + ) + if ( + (abs(sphere_box.xmin - box.xmin) < tolerance) + and (abs(sphere_box.ymin - box.ymin) < tolerance) + and (abs(sphere_box.zmin - box.zmin) < tolerance) + and (abs(sphere_box.xmax - box.xmax) < tolerance) + and (abs(sphere_box.ymax - box.ymax) < tolerance) + and (abs(sphere_box.zmax - box.zmax) < tolerance) + ): + break + elif i == len(sphere_faces) - 1: + # if no target face is found by the final pass then return an error + prime.PrimeRuntimeError(message=f"Target sphere not found for face {face}.") + match_pair = prime.MatchPair( + model, + source_surfaces=[face], + target_surfaces=[sphere_faces[i]], + target_type=prime.MatchPairTargetType.FACEZONELET, + ) + bc_pairs = [] + + # Once each source and target face zonelet are found and paired, edge zonelets + # are created and used as boundary conditions to ensure they remain rigid. + # Creating edge zonelets not disconnected from the faces ensures that the edge + # zonelet will act as a boundary condition for the morph operation. + # Extracting the edge zonelets from the source face zonelet and using + # them to define both source and target boundary conditions ensures + # the edge zonelets of the solder remain rigid. + result = prime.FeatureExtraction(model).extract_features_on_face_zonelets( + merged_part.id, + [face], + prime.ExtractFeatureParams( + model, + feature_angle=60, + separate_features=True, + separation_angle=60, + replace=False, + disconnect_with_faces=False, + ), + ) + for edge in result.new_edge_zonelets: + bc_pair = prime.BCPair( + model, + int(edge), + int(edge), + type=prime.BCPairType.EDGE, + ) + bc_pairs.append(bc_pair) + match_pair.bc_pairs = bc_pairs + match_pairs.append(match_pair) + + morph = prime.Morpher(model) + + morph_params = prime.MatchMorphParams(model) + bc_params = prime.MorphBCParams(model, morphable_layers=0) + solve_params = prime.MorphSolveParams(model) + + morph.match_morph( + part_id=merged_part.id, + match_pairs=match_pairs, + match_morph_params=morph_params, + bc_params=bc_params, + solve_params=solve_params, + ) + + model.delete_parts([target_part.id]) + + display = PrimePlotter() + display.plot( + model, scope=prime.ScopeDefinition(model=model, label_expression="solder*"), update=True + ) + display.show() + + ############################################################################### + # Export mesh + # ~~~~~~~~~~~ + # Export a CDB file. + + with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, "solder_balls.cdb") + params = prime.ExportMapdlCdbParams(model=model) + prime.FileIO(model=model).export_mapdl_cdb(file_name=mesh_file, params=params) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + + ############################################################################### + # Exit PyPrimeMesh + # ~~~~~~~~~~~~~~~~ + + prime_client.exit() + + +example() From 2f24a2828e272d9a5a6f644e188b5f9cc95f5f12 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 11:38:52 +0100 Subject: [PATCH 14/27] tests --- examples/gallery/06_blade_morph.py | 4 ---- examples/gallery/10_wheel_ground_contact_patch.py | 3 --- examples/gallery/11_solder_ball.py | 3 --- 3 files changed, 10 deletions(-) diff --git a/examples/gallery/06_blade_morph.py b/examples/gallery/06_blade_morph.py index 0c10dda7fe..a1db799e7f 100644 --- a/examples/gallery/06_blade_morph.py +++ b/examples/gallery/06_blade_morph.py @@ -85,10 +85,6 @@ mesh_util.read(file_name=target_geometry, append=True) -display = PrimePlotter() -display.plot(model) -display.show() - print(model) ############################################################################### diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index 1213f9040d..05de0e70dc 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -147,9 +147,6 @@ print(result.error_code) print(model) -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) -display.show() ############################################################################### # Wrap the fluid region diff --git a/examples/gallery/11_solder_ball.py b/examples/gallery/11_solder_ball.py index 75ffbba5c0..13a5f9ad18 100644 --- a/examples/gallery/11_solder_ball.py +++ b/examples/gallery/11_solder_ball.py @@ -215,9 +215,6 @@ def example(): min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True ) - display = PrimePlotter() - display.plot(model, update=True) - display.show() stackbase_results = sweeper.stack_base_face( part_id=merged_part.id, From 55956ee8473e965f2924a999ccf69b9d6bd0d58f Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 12:02:47 +0100 Subject: [PATCH 15/27] fix: add xvfb --- .github/workflows/ci_cd.yml | 1 + examples/gallery/10_wheel_ground_contact_patch.py | 12 ------------ examples/gallery/11_solder_ball.py | 1 - 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index f09ecd4b50..9e569e47c5 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -108,6 +108,7 @@ jobs: with: check-links: false needs-quarto: true + requires-xvfb: true sphinxopts: "-j 1 --keep-going" env: PYPRIMEMESH_LAUNCH_CONTAINER: 1 diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index 05de0e70dc..2fc9f14838 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -170,11 +170,6 @@ wrap_size_controls=[size_control], ) -display = PrimePlotter() -display.plot( - model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True -) -display.show() print(model) @@ -192,13 +187,6 @@ scope=prime.lucid.VolumeScope(part_expression=wrap_part.name), ) -display = PrimePlotter() -display.plot( - model, - scope=prime.ScopeDefinition(model, label_expression="!front !side_right !top"), - update=True, -) -display.show() mesh_util.create_zones_from_labels() diff --git a/examples/gallery/11_solder_ball.py b/examples/gallery/11_solder_ball.py index 13a5f9ad18..8a5800b688 100644 --- a/examples/gallery/11_solder_ball.py +++ b/examples/gallery/11_solder_ball.py @@ -215,7 +215,6 @@ def example(): min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True ) - stackbase_results = sweeper.stack_base_face( part_id=merged_part.id, base_face_ids=base_faces, From 45f023a7065b12cbf5ed5fe173f5de9c76d5413e Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 12:10:07 +0100 Subject: [PATCH 16/27] test --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 9e569e47c5..271c81bf84 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -108,7 +108,7 @@ jobs: with: check-links: false needs-quarto: true - requires-xvfb: true + # requires-xvfb: true sphinxopts: "-j 1 --keep-going" env: PYPRIMEMESH_LAUNCH_CONTAINER: 1 From 17037a21e39092c19631e1b232b7c01d04ec0646 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 12:44:27 +0100 Subject: [PATCH 17/27] tests --- .github/workflows/ci_cd.yml | 2 +- examples/gallery/06_blade_morph.py | 3 --- pyproject.toml | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 271c81bf84..9c9df00164 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -101,7 +101,7 @@ jobs: run: docker pull ${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_IMAGE_TAG }} - name: Setup headless display - uses: pyvista/setup-headless-display-action@v2 + uses: pyvista/setup-headless-display-action@v3 - name: "Run Ansys documentation building action" uses: ansys/actions/doc-build@v8 diff --git a/examples/gallery/06_blade_morph.py b/examples/gallery/06_blade_morph.py index a1db799e7f..da622d1933 100644 --- a/examples/gallery/06_blade_morph.py +++ b/examples/gallery/06_blade_morph.py @@ -125,9 +125,6 @@ # Display the morphed mesh -display = PrimePlotter() -display.plot(model, update=True) -display.show() ############################################################################### # Write mesh # ~~~~~~~~~~ diff --git a/pyproject.toml b/pyproject.toml index 32893e9d00..0bfa448c77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ doc = [ "numpydoc==1.8.0", "sphinx==8.1.3", "sphinx_design==0.6.1", - "pyvista==0.44.1", + "pyvista==0.44.2", "sphinx-autodoc-typehints==2.5.0", "sphinx-copybutton==0.5.2", "sphinx-gallery==0.18.0", From e06f76bedaf91180f1cc51b9d6788957624d1457 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 15:22:53 +0100 Subject: [PATCH 18/27] test --- .github/workflows/ci_cd.yml | 2 +- examples/gallery/11_solder_ball.py | 605 ++++++++++++++--------------- 2 files changed, 303 insertions(+), 304 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 9c9df00164..d35715b23f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -109,7 +109,7 @@ jobs: check-links: false needs-quarto: true # requires-xvfb: true - sphinxopts: "-j 1 --keep-going" + sphinxopts: "-j auto --keep-going" env: PYPRIMEMESH_LAUNCH_CONTAINER: 1 PYPRIMEMESH_SPHINX_BUILD: 1 diff --git a/examples/gallery/11_solder_ball.py b/examples/gallery/11_solder_ball.py index 8a5800b688..0766518abd 100644 --- a/examples/gallery/11_solder_ball.py +++ b/examples/gallery/11_solder_ball.py @@ -63,338 +63,337 @@ # Import modules # ~~~~~~~~~~~~~~ # Import libraries necessary to run the script. -def example(): - import os - import tempfile - - import ansys.meshing.prime as prime - from ansys.meshing.prime.graphics import PrimePlotter - - ############################################################################### - # Launch Ansys Prime Server - # ~~~~~~~~~~~~~~~~~~~~~~~~~ - # Launch an instance of Ansys Prime Server. - # Connect the PyPrimeMesh client and get the model. - - prime_client = prime.launch_prime() - model = prime_client.model - - ############################################################################### - # Import CAD geometry - # ~~~~~~~~~~~~~~~~~~~ - # FMD is exported from SpaceClaim for the geometry. - # Geometry consists of multiple non overlapping and disconnected volumes. - # The model has multiple layers either side of several solder balls with pads - # and contains an infill volume around the solder. During import, the part - # creation type is set to BODY so that each body in the CAD is imported as a - # separate part. Refaceting is specified for more control of the scaffolding - # operation. Consistent faceting for the curved surfaces to be joined can be - # obtained by specifying CadRefacetingMaxEdgeSizeLimit as ABSOLUTE. To avoid - # over refinement of the faceting the max_edge_size is allowed to reach a size - # of 1.0. Labels can be assigned to manage the entities of each volume. - - solder_ball = prime.examples.download_solder_ball_fmd() - - params = prime.ImportCadParams( - model, - append=True, - part_creation_type=prime.PartCreationType.BODY, - refacet=True, - cad_refaceting_params=prime.CadRefacetingParams( - model, - cad_faceter=prime.CadFaceter.PARASOLID, - max_edge_size_limit=prime.CadRefacetingMaxEdgeSizeLimit.ABSOLUTE, - max_edge_size=1.0, - ), - ) - - prime.FileIO(model).import_cad(file_name=solder_ball, params=params) - - for part in model.parts: - part.add_labels_on_topo_entities([part.name], part.get_topo_faces()) - part.add_labels_on_topo_entities([part.name], part.get_topo_volumes()) - # Display the model without the infill so the cylindrical geometry of the solder - # is visible. +import os +import tempfile - display = PrimePlotter() - display.plot( - model, scope=prime.ScopeDefinition(model=model, label_expression="solder_cyl*,pad*,layer*") - ) - display.show() - - ############################################################################### - # Connect geometry - # ~~~~~~~~~~~~~~~~ - # Merge all parts into a single part so they can be connected. - # Imprint adjacent topo faces by connecting topo faces and topo edges. - # Mesh all topo faces to allow splitting the imprinted topo faces by mesh regions. - # Delete mesh on topo faces after splitting. - # Merge newly created overlapping topo faces so only a single topo face exists - # between connected volumes. - - part_ids = [part.id for part in model.parts if part.get_topo_faces()] - merge_result = model.merge_parts(part_ids=part_ids, params=prime.MergePartsParams(model)) - print(model) - - merged_part = model.get_part(merge_result.merged_part_id) - - params = prime.ScaffolderParams( - model=model, - absolute_dist_tol=0.01, - intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, - constant_mesh_size=0.1, - ) - - scaffolder = prime.Scaffolder(model, merged_part.id) - res = scaffolder.scaffold_topo_faces_and_beams( - topo_faces=merged_part.get_topo_faces(), topo_beams=[], params=params - ) - - prime.lucid.Mesh(model).surface_mesh(min_size=0.1) - prime.Scaffolder(model, merged_part.id).split_topo_faces_by_mesh_region( - merged_part.get_topo_faces() - ) +import ansys.meshing.prime as prime +from ansys.meshing.prime.graphics import PrimePlotter - # This is a beta API. The behavior and implementation may change in future. - result = model.topo_data.delete_mesh_on_topo_faces( - merged_part.get_topo_faces(), prime.DeleteMeshParams(model=model) - ) - - scaffolder.merge_overlapping_topo_faces(merged_part.get_topo_faces(), params) - - ############################################################################### - # Volume sweeper - # ~~~~~~~~~~~~~~ - # Setup a size control to refine the mesh around the solder. - # Setup stacker parameters to define the volume sweep mesh. - # Create the base face to quad surface mesh and use for sweeping. - # Stack the base face to create the volume mesh. - # Delete topology on mesh part to allow use of surface utilities and feature extraction. - # A large lateral defeature tolerance of 0.1 is used to avoid additional topo nodes - # from scaffolding impacting the final mesh. - - model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.1, max=0.4)) - - size_control = model.control_data.create_size_control(prime.SizingType.SOFT) - size_control.set_scope( - prime.ScopeDefinition(model, part_expression=merged_part.name, label_expression="*solder*") - ) - size_control.set_soft_sizing_params(prime.SoftSizingParams(model, max=0.1)) - - stacker_params = prime.MeshStackerParams( - model=model, - direction=[0, 1, 0], - max_offset_size=0.4, - delete_base=True, - lateral_defeature_tolerance=0.1, - stacking_defeature_tolerance=0.01, - size_control_ids=[size_control.id], - ) +############################################################################### +# Launch Ansys Prime Server +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Launch an instance of Ansys Prime Server. +# Connect the PyPrimeMesh client and get the model. - sweeper = prime.VolumeSweeper(model) +prime_client = prime.launch_prime() +model = prime_client.model - createbase_results = sweeper.create_base_face( - part_id=merged_part.id, - topo_volume_ids=merged_part.get_topo_volumes(), - params=stacker_params, - ) - - base_faces = createbase_results.base_face_ids - merged_part.add_labels_on_topo_entities(["base_faces"], base_faces) +############################################################################### +# Import CAD geometry +# ~~~~~~~~~~~~~~~~~~~ +# FMD is exported from SpaceClaim for the geometry. +# Geometry consists of multiple non overlapping and disconnected volumes. +# The model has multiple layers either side of several solder balls with pads +# and contains an infill volume around the solder. During import, the part +# creation type is set to BODY so that each body in the CAD is imported as a +# separate part. Refaceting is specified for more control of the scaffolding +# operation. Consistent faceting for the curved surfaces to be joined can be +# obtained by specifying CadRefacetingMaxEdgeSizeLimit as ABSOLUTE. To avoid +# over refinement of the faceting the max_edge_size is allowed to reach a size +# of 1.0. Labels can be assigned to manage the entities of each volume. + +solder_ball = prime.examples.download_solder_ball_fmd() + +params = prime.ImportCadParams( + model, + append=True, + part_creation_type=prime.PartCreationType.BODY, + refacet=True, + cad_refaceting_params=prime.CadRefacetingParams( + model, + cad_faceter=prime.CadFaceter.PARASOLID, + max_edge_size_limit=prime.CadRefacetingMaxEdgeSizeLimit.ABSOLUTE, + max_edge_size=1.0, + ), +) - scope = prime.ScopeDefinition(model=model, label_expression="base_faces") +prime.FileIO(model).import_cad(file_name=solder_ball, params=params) - base_scope = prime.lucid.SurfaceScope( - entity_expression="base_faces", - part_expression=merged_part.name, - scope_evaluation_type=prime.ScopeEvaluationType.LABELS, - ) +for part in model.parts: + part.add_labels_on_topo_entities([part.name], part.get_topo_faces()) + part.add_labels_on_topo_entities([part.name], part.get_topo_volumes()) - prime.lucid.Mesh(model).surface_mesh( - min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True - ) +# Display the model without the infill so the cylindrical geometry of the solder +# is visible. - stackbase_results = sweeper.stack_base_face( - part_id=merged_part.id, - base_face_ids=base_faces, - topo_volume_ids=merged_part.get_topo_volumes(), - params=stacker_params, - ) +display = PrimePlotter() +display.plot( + model, scope=prime.ScopeDefinition(model=model, label_expression="solder_cyl*,pad*,layer*") +) +display.show() - merged_part.delete_topo_entities( - prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=True, delete_mesh_zonelets=False) - ) +############################################################################### +# Connect geometry +# ~~~~~~~~~~~~~~~~ +# Merge all parts into a single part so they can be connected. +# Imprint adjacent topo faces by connecting topo faces and topo edges. +# Mesh all topo faces to allow splitting the imprinted topo faces by mesh regions. +# Delete mesh on topo faces after splitting. +# Merge newly created overlapping topo faces so only a single topo face exists +# between connected volumes. + +part_ids = [part.id for part in model.parts if part.get_topo_faces()] +merge_result = model.merge_parts(part_ids=part_ids, params=prime.MergePartsParams(model)) +print(model) + +merged_part = model.get_part(merge_result.merged_part_id) + +params = prime.ScaffolderParams( + model=model, + absolute_dist_tol=0.01, + intersection_control_mask=prime.IntersectionMask.FACEFACEANDEDGEEDGE, + constant_mesh_size=0.1, +) + +scaffolder = prime.Scaffolder(model, merged_part.id) +res = scaffolder.scaffold_topo_faces_and_beams( + topo_faces=merged_part.get_topo_faces(), topo_beams=[], params=params +) + +prime.lucid.Mesh(model).surface_mesh(min_size=0.1) +prime.Scaffolder(model, merged_part.id).split_topo_faces_by_mesh_region( + merged_part.get_topo_faces() +) + +# This is a beta API. The behavior and implementation may change in future. +result = model.topo_data.delete_mesh_on_topo_faces( + merged_part.get_topo_faces(), prime.DeleteMeshParams(model=model) +) + +scaffolder.merge_overlapping_topo_faces(merged_part.get_topo_faces(), params) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() - merged_part._print_mesh = True - print(merged_part) +############################################################################### +# Volume sweeper +# ~~~~~~~~~~~~~~ +# Setup a size control to refine the mesh around the solder. +# Setup stacker parameters to define the volume sweep mesh. +# Create the base face to quad surface mesh and use for sweeping. +# Stack the base face to create the volume mesh. +# Delete topology on mesh part to allow use of surface utilities and feature extraction. +# A large lateral defeature tolerance of 0.1 is used to avoid additional topo nodes +# from scaffolding impacting the final mesh. + +model.set_global_sizing_params(prime.GlobalSizingParams(model, min=0.1, max=0.4)) + +size_control = model.control_data.create_size_control(prime.SizingType.SOFT) +size_control.set_scope( + prime.ScopeDefinition(model, part_expression=merged_part.name, label_expression="*solder*") +) +size_control.set_soft_sizing_params(prime.SoftSizingParams(model, max=0.1)) + +stacker_params = prime.MeshStackerParams( + model=model, + direction=[0, 1, 0], + max_offset_size=0.4, + delete_base=True, + lateral_defeature_tolerance=0.1, + stacking_defeature_tolerance=0.01, + size_control_ids=[size_control.id], +) + +sweeper = prime.VolumeSweeper(model) + +createbase_results = sweeper.create_base_face( + part_id=merged_part.id, + topo_volume_ids=merged_part.get_topo_volumes(), + params=stacker_params, +) + +base_faces = createbase_results.base_face_ids +merged_part.add_labels_on_topo_entities(["base_faces"], base_faces) + +scope = prime.ScopeDefinition(model=model, label_expression="base_faces") + +base_scope = prime.lucid.SurfaceScope( + entity_expression="base_faces", + part_expression=merged_part.name, + scope_evaluation_type=prime.ScopeEvaluationType.LABELS, +) + +prime.lucid.Mesh(model).surface_mesh( + min_size=0.1, max_size=0.4, scope=base_scope, generate_quads=True +) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() + +stackbase_results = sweeper.stack_base_face( + part_id=merged_part.id, + base_face_ids=base_faces, + topo_volume_ids=merged_part.get_topo_volumes(), + params=stacker_params, +) + +merged_part.delete_topo_entities( + prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=True, delete_mesh_zonelets=False) +) + +merged_part._print_mesh = True +print(merged_part) + +display = PrimePlotter() +display.plot(model, update=True) +display.show() - display = PrimePlotter() - display.plot(model, update=True) - display.show() +############################################################################### +# Import sphere geometry for match morphing +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a single part on CAD import by setting the part creation type to MODEL. +# Convert topology to mesh face zonelets to use surface utilities. - ############################################################################### - # Import sphere geometry for match morphing - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Create a single part on CAD import by setting the part creation type to MODEL. - # Convert topology to mesh face zonelets to use surface utilities. +solder_ball_target = prime.examples.download_solder_ball_target_fmd() - solder_ball_target = prime.examples.download_solder_ball_target_fmd() +params = prime.ImportCadParams(model, append=True, part_creation_type=prime.PartCreationType.MODEL) - params = prime.ImportCadParams( - model, append=True, part_creation_type=prime.PartCreationType.MODEL - ) +prime.FileIO(model).import_cad(file_name=solder_ball_target, params=params) - prime.FileIO(model).import_cad(file_name=solder_ball_target, params=params) +imported_cad_part_ids = [part.id for part in model.parts if part.get_topo_faces()] +target_part = model.get_part(imported_cad_part_ids[0]) - imported_cad_part_ids = [part.id for part in model.parts if part.get_topo_faces()] - target_part = model.get_part(imported_cad_part_ids[0]) +display = PrimePlotter() +display.plot(model, scope=prime.ScopeDefinition(model, part_expression=target_part.name)) +display.show() - display = PrimePlotter() - display.plot(model, scope=prime.ScopeDefinition(model, part_expression=target_part.name)) - display.show() +print(model) - print(model) +target_part.delete_topo_entities( + prime.DeleteTopoEntitiesParams(model, delete_geom_zonelets=False, delete_mesh_zonelets=False) +) - target_part.delete_topo_entities( - prime.DeleteTopoEntitiesParams( - model, delete_geom_zonelets=False, delete_mesh_zonelets=False - ) +############################################################################### +# Match morph the mesh to the spherical solder +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Delete feature edge zonelets on the mesh source. +# Get lists of the source and target face zonelets for the match morph. +# Pair source and target faces based on overlapping bounding box locations. +# For each source face extract feature edges with nodes attached to faces. +# Define edge pairs for each match pair control as a morph boundary condition. +# Match morph solder faces and edges. +# Delete target sphere part and retain the morphed mesh for export. + +merged_part.delete_zonelets(merged_part.get_edge_zonelets()) +cylinder_faces = merged_part.get_face_zonelets_of_label_name_pattern( + "solder_cyl", prime.NamePatternParams(model) +) +sphere_faces = target_part.get_face_zonelets() + +match_pairs = [] + +# When the match morph operation is defined by multiple +# separate local match morph source and targets, +# individual match pairs must be specified for each +# contiguous set of face zonelets. +# Separate one to one boundary condition pairs for the +# connected edge zonelets must also be defined for +# each face match pair to ensure the edges remain rigid. + +# For each solder to be morphed we have a match pair +# for the source and target face zonelets. To ensure +# the edge zonelets remain rigid during the morph, +# boundary condition pairs for each of the two edge +# zonelets need to be created. + +# For each cylindrical face zonelet on the source +# mesh find the corresponding target sphere face zonelet +# to match morph the mesh using the position of their bounding box. +# Labels defined in the CAD model would be a more efficient way to pair. +tolerance = 0.2 +for face in cylinder_faces: + box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([face]) + for i in range(len(sphere_faces)): + sphere_box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([sphere_faces[i]]) + if ( + (abs(sphere_box.xmin - box.xmin) < tolerance) + and (abs(sphere_box.ymin - box.ymin) < tolerance) + and (abs(sphere_box.zmin - box.zmin) < tolerance) + and (abs(sphere_box.xmax - box.xmax) < tolerance) + and (abs(sphere_box.ymax - box.ymax) < tolerance) + and (abs(sphere_box.zmax - box.zmax) < tolerance) + ): + break + elif i == len(sphere_faces) - 1: + # if no target face is found by the final pass then return an error + prime.PrimeRuntimeError(message=f"Target sphere not found for face {face}.") + match_pair = prime.MatchPair( + model, + source_surfaces=[face], + target_surfaces=[sphere_faces[i]], + target_type=prime.MatchPairTargetType.FACEZONELET, ) - - ############################################################################### - # Match morph the mesh to the spherical solder - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Delete feature edge zonelets on the mesh source. - # Get lists of the source and target face zonelets for the match morph. - # Pair source and target faces based on overlapping bounding box locations. - # For each source face extract feature edges with nodes attached to faces. - # Define edge pairs for each match pair control as a morph boundary condition. - # Match morph solder faces and edges. - # Delete target sphere part and retain the morphed mesh for export. - - merged_part.delete_zonelets(merged_part.get_edge_zonelets()) - cylinder_faces = merged_part.get_face_zonelets_of_label_name_pattern( - "solder_cyl", prime.NamePatternParams(model) + bc_pairs = [] + + # Once each source and target face zonelet are found and paired, edge zonelets + # are created and used as boundary conditions to ensure they remain rigid. + # Creating edge zonelets not disconnected from the faces ensures that the edge + # zonelet will act as a boundary condition for the morph operation. + # Extracting the edge zonelets from the source face zonelet and using + # them to define both source and target boundary conditions ensures + # the edge zonelets of the solder remain rigid. + result = prime.FeatureExtraction(model).extract_features_on_face_zonelets( + merged_part.id, + [face], + prime.ExtractFeatureParams( + model, + feature_angle=60, + separate_features=True, + separation_angle=60, + replace=False, + disconnect_with_faces=False, + ), ) - sphere_faces = target_part.get_face_zonelets() - - match_pairs = [] - - # When the match morph operation is defined by multiple - # separate local match morph source and targets, - # individual match pairs must be specified for each - # contiguous set of face zonelets. - # Separate one to one boundary condition pairs for the - # connected edge zonelets must also be defined for - # each face match pair to ensure the edges remain rigid. - - # For each solder to be morphed we have a match pair - # for the source and target face zonelets. To ensure - # the edge zonelets remain rigid during the morph, - # boundary condition pairs for each of the two edge - # zonelets need to be created. - - # For each cylindrical face zonelet on the source - # mesh find the corresponding target sphere face zonelet - # to match morph the mesh using the position of their bounding box. - # Labels defined in the CAD model would be a more efficient way to pair. - tolerance = 0.2 - for face in cylinder_faces: - box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets([face]) - for i in range(len(sphere_faces)): - sphere_box = prime.SurfaceUtilities(model).get_bounding_box_of_zonelets( - [sphere_faces[i]] - ) - if ( - (abs(sphere_box.xmin - box.xmin) < tolerance) - and (abs(sphere_box.ymin - box.ymin) < tolerance) - and (abs(sphere_box.zmin - box.zmin) < tolerance) - and (abs(sphere_box.xmax - box.xmax) < tolerance) - and (abs(sphere_box.ymax - box.ymax) < tolerance) - and (abs(sphere_box.zmax - box.zmax) < tolerance) - ): - break - elif i == len(sphere_faces) - 1: - # if no target face is found by the final pass then return an error - prime.PrimeRuntimeError(message=f"Target sphere not found for face {face}.") - match_pair = prime.MatchPair( + for edge in result.new_edge_zonelets: + bc_pair = prime.BCPair( model, - source_surfaces=[face], - target_surfaces=[sphere_faces[i]], - target_type=prime.MatchPairTargetType.FACEZONELET, - ) - bc_pairs = [] - - # Once each source and target face zonelet are found and paired, edge zonelets - # are created and used as boundary conditions to ensure they remain rigid. - # Creating edge zonelets not disconnected from the faces ensures that the edge - # zonelet will act as a boundary condition for the morph operation. - # Extracting the edge zonelets from the source face zonelet and using - # them to define both source and target boundary conditions ensures - # the edge zonelets of the solder remain rigid. - result = prime.FeatureExtraction(model).extract_features_on_face_zonelets( - merged_part.id, - [face], - prime.ExtractFeatureParams( - model, - feature_angle=60, - separate_features=True, - separation_angle=60, - replace=False, - disconnect_with_faces=False, - ), + int(edge), + int(edge), + type=prime.BCPairType.EDGE, ) - for edge in result.new_edge_zonelets: - bc_pair = prime.BCPair( - model, - int(edge), - int(edge), - type=prime.BCPairType.EDGE, - ) - bc_pairs.append(bc_pair) - match_pair.bc_pairs = bc_pairs - match_pairs.append(match_pair) - - morph = prime.Morpher(model) - - morph_params = prime.MatchMorphParams(model) - bc_params = prime.MorphBCParams(model, morphable_layers=0) - solve_params = prime.MorphSolveParams(model) - - morph.match_morph( - part_id=merged_part.id, - match_pairs=match_pairs, - match_morph_params=morph_params, - bc_params=bc_params, - solve_params=solve_params, - ) + bc_pairs.append(bc_pair) + match_pair.bc_pairs = bc_pairs + match_pairs.append(match_pair) - model.delete_parts([target_part.id]) +morph = prime.Morpher(model) - display = PrimePlotter() - display.plot( - model, scope=prime.ScopeDefinition(model=model, label_expression="solder*"), update=True - ) - display.show() +morph_params = prime.MatchMorphParams(model) +bc_params = prime.MorphBCParams(model, morphable_layers=0) +solve_params = prime.MorphSolveParams(model) - ############################################################################### - # Export mesh - # ~~~~~~~~~~~ - # Export a CDB file. +morph.match_morph( + part_id=merged_part.id, + match_pairs=match_pairs, + match_morph_params=morph_params, + bc_params=bc_params, + solve_params=solve_params, +) - with tempfile.TemporaryDirectory() as temp_folder: - mesh_file = os.path.join(temp_folder, "solder_balls.cdb") - params = prime.ExportMapdlCdbParams(model=model) - prime.FileIO(model=model).export_mapdl_cdb(file_name=mesh_file, params=params) - assert os.path.exists(mesh_file) - print("\nExported file:\n", mesh_file) +model.delete_parts([target_part.id]) - ############################################################################### - # Exit PyPrimeMesh - # ~~~~~~~~~~~~~~~~ +display = PrimePlotter() +display.plot( + model, scope=prime.ScopeDefinition(model=model, label_expression="solder*"), update=True +) +display.show() - prime_client.exit() +############################################################################### +# Export mesh +# ~~~~~~~~~~~ +# Export a CDB file. +with tempfile.TemporaryDirectory() as temp_folder: + mesh_file = os.path.join(temp_folder, "solder_balls.cdb") + params = prime.ExportMapdlCdbParams(model=model) + prime.FileIO(model=model).export_mapdl_cdb(file_name=mesh_file, params=params) + assert os.path.exists(mesh_file) + print("\nExported file:\n", mesh_file) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ -example() +prime_client.exit() \ No newline at end of file From 907aafec5f0f26db26145b42942a0175c134e862 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 15:48:35 +0100 Subject: [PATCH 19/27] revert examples --- examples/gallery/00_lucid_file_IO.py | 1 + examples/gallery/06_blade_morph.py | 7 ++++ .../gallery/09_multi_layer_quad_mesh_pcb.py | 5 +++ .../gallery/10_wheel_ground_contact_patch.py | 35 ++++++++++++++----- examples/gallery/11_solder_ball.py | 2 +- 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/examples/gallery/00_lucid_file_IO.py b/examples/gallery/00_lucid_file_IO.py index bc4523c8ec..1b03377764 100644 --- a/examples/gallery/00_lucid_file_IO.py +++ b/examples/gallery/00_lucid_file_IO.py @@ -64,6 +64,7 @@ # launch an instance of Ansys Prime Server. # Connect the PyPrimeMesh client and get the model. # Instantiate meshing utilities from the ``lucid`` class. + import os import tempfile diff --git a/examples/gallery/06_blade_morph.py b/examples/gallery/06_blade_morph.py index da622d1933..0c10dda7fe 100644 --- a/examples/gallery/06_blade_morph.py +++ b/examples/gallery/06_blade_morph.py @@ -85,6 +85,10 @@ mesh_util.read(file_name=target_geometry, append=True) +display = PrimePlotter() +display.plot(model) +display.show() + print(model) ############################################################################### @@ -125,6 +129,9 @@ # Display the morphed mesh +display = PrimePlotter() +display.plot(model, update=True) +display.show() ############################################################################### # Write mesh # ~~~~~~~~~~ diff --git a/examples/gallery/09_multi_layer_quad_mesh_pcb.py b/examples/gallery/09_multi_layer_quad_mesh_pcb.py index 5ab7d7296b..888671eec0 100644 --- a/examples/gallery/09_multi_layer_quad_mesh_pcb.py +++ b/examples/gallery/09_multi_layer_quad_mesh_pcb.py @@ -242,6 +242,11 @@ # Display the meshed base face in a PyVista window # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +if display_intermediate_steps: + display = PrimePlotter() + display.plot(model, update=True) + display.show() + ############################################################################### # Stack the base face using the volume sweeper # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index 2fc9f14838..0baa047b31 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -97,6 +97,10 @@ mesh_util.read(wheel_ground_file) +display = PrimePlotter() +display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) +display.show() + print(model) ############################################################################### @@ -147,6 +151,9 @@ print(result.error_code) print(model) +display = PrimePlotter() +display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) +display.show() ############################################################################### # Wrap the fluid region @@ -170,6 +177,11 @@ wrap_size_controls=[size_control], ) +display = PrimePlotter() +display.plot( + model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True +) +display.show() print(model) @@ -187,6 +199,13 @@ scope=prime.lucid.VolumeScope(part_expression=wrap_part.name), ) +display = PrimePlotter() +display.plot( + model, + scope=prime.ScopeDefinition(model, label_expression="!front !side_right !top"), + update=True, +) +display.show() mesh_util.create_zones_from_labels() @@ -198,14 +217,14 @@ # ~~~~~~~~~~~ # Write a CAS file for use in the Fluent solver. -# with tempfile.TemporaryDirectory() as temp_folder: -# wheel_model = os.path.join(temp_folder, "wheel_ground_contact.cas.h5") -# prime.FileIO(model).export_fluent_case( -# wheel_model, -# export_fluent_case_params=prime.ExportFluentCaseParams(model, cff_format=True), -# ) -# assert os.path.exists(wheel_model) -# print(f"Fluent case exported at {wheel_model}") +with tempfile.TemporaryDirectory() as temp_folder: + wheel_model = os.path.join(temp_folder, "wheel_ground_contact.cas.h5") + prime.FileIO(model).export_fluent_case( + wheel_model, + export_fluent_case_params=prime.ExportFluentCaseParams(model, cff_format=True), + ) + assert os.path.exists(wheel_model) + print(f"Fluent case exported at {wheel_model}") ############################################################################### # Exit the PyPrimeMesh session diff --git a/examples/gallery/11_solder_ball.py b/examples/gallery/11_solder_ball.py index 0766518abd..3f2605bc52 100644 --- a/examples/gallery/11_solder_ball.py +++ b/examples/gallery/11_solder_ball.py @@ -396,4 +396,4 @@ # Exit PyPrimeMesh # ~~~~~~~~~~~~~~~~ -prime_client.exit() \ No newline at end of file +prime_client.exit() From 89c24979d5a3ca47bc990425e666b2fcfd23ea2f Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 16:13:36 +0100 Subject: [PATCH 20/27] tes --- examples/gallery/10_wheel_ground_contact_patch.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index 0baa047b31..dd6690bd4e 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -151,9 +151,6 @@ print(result.error_code) print(model) -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) -display.show() ############################################################################### # Wrap the fluid region @@ -177,11 +174,6 @@ wrap_size_controls=[size_control], ) -display = PrimePlotter() -display.plot( - model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True -) -display.show() print(model) From 5413ad2176a638784eeeba81f82d3180c0359f55 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 16:37:23 +0100 Subject: [PATCH 21/27] add code as codeblock, reverts --- doc/Makefile | 2 +- doc/make.bat | 3 + doc/source/sg_execution_times.rst | 55 ------------------- .../gallery/10_wheel_ground_contact_patch.py | 18 ++++++ 4 files changed, 22 insertions(+), 56 deletions(-) delete mode 100644 doc/source/sg_execution_times.rst diff --git a/doc/Makefile b/doc/Makefile index 151e09fb5a..5f9d1a9451 100755 --- a/doc/Makefile +++ b/doc/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = -j 1 +SPHINXOPTS = -j auto SPHINXBUILD = sphinx-build SOURCEDIR = source BUILDDIR = _build diff --git a/doc/make.bat b/doc/make.bat index 0048f8e4d8..24f344a4e8 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -7,6 +7,9 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) +if "%SPHINXOPTS%" == "" ( + set SPHINXOPTS=-j auto +) set SOURCEDIR=source set BUILDDIR=_build diff --git a/doc/source/sg_execution_times.rst b/doc/source/sg_execution_times.rst deleted file mode 100644 index 57e9e4e713..0000000000 --- a/doc/source/sg_execution_times.rst +++ /dev/null @@ -1,55 +0,0 @@ - -:orphan: - -.. _sphx_glr_sg_execution_times: - - -Computation times -================= -**15:25.512** total execution time for 7 files **from all galleries**: - -.. container:: - - .. raw:: html - - - - - - - - .. list-table:: - :header-rows: 1 - :class: table table-striped sg-datatable - - * - Example - - Time - - Mem (MB) - * - :ref:`sphx_glr_examples_gallery_examples_gallery_10_wheel_ground_contact_patch.py` (``..\..\examples\gallery\10_wheel_ground_contact_patch.py``) - - 05:39.009 - - 0.0 - * - :ref:`sphx_glr_examples_gallery_examples_gallery_11_solder_ball.py` (``..\..\examples\gallery\11_solder_ball.py``) - - 04:02.654 - - 0.0 - * - :ref:`sphx_glr_examples_gallery_examples_gallery_09_multi_layer_quad_mesh_pcb.py` (``..\..\examples\gallery\09_multi_layer_quad_mesh_pcb.py``) - - 01:42.827 - - 0.0 - * - :ref:`sphx_glr_examples_gallery_examples_gallery_06_blade_morph.py` (``..\..\examples\gallery\06_blade_morph.py``) - - 01:26.560 - - 0.0 - * - :ref:`sphx_glr_examples_gallery_examples_misc_example_template.py` (``..\..\examples\misc\example_template.py``) - - 01:03.457 - - 0.0 - * - :ref:`sphx_glr_examples_gallery_examples_gallery_07_saddle_bracket.py` (``..\..\examples\gallery\07_saddle_bracket.py``) - - 00:51.550 - - 0.0 - * - :ref:`sphx_glr_examples_gallery_examples_gallery_08_lucid_generic_f1_rear_wing.py` (``..\..\examples\gallery\08_lucid_generic_f1_rear_wing.py``) - - 00:39.456 - - 0.0 diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index dd6690bd4e..6bb56b4dcc 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -151,6 +151,14 @@ print(result.error_code) print(model) +################### +# Visualize results +# ================= +# .. code-block:: python +# +# display = PrimePlotter() +# display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) +# display.show() ############################################################################### # Wrap the fluid region @@ -174,6 +182,16 @@ wrap_size_controls=[size_control], ) +######################### +# Open a pyvistaqt window +# ======================= +# .. code-block:: python +# +# display = PrimePlotter() +# display.plot( +# model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True +# ) +# display.show() print(model) From 61d6f06106a95ed3816b960e1d8d5e2bf7e529d1 Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 28 Nov 2024 17:20:30 +0100 Subject: [PATCH 22/27] reverts --- .github/workflows/ci_cd.yml | 3 +-- doc/source/conf.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index d35715b23f..5a8cd71cd9 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -87,7 +87,7 @@ jobs: docs: name: Documentation runs-on: ubuntu-latest - # needs: [docs-style] + needs: [docs-style] steps: - name: Login in Github Container registry @@ -108,7 +108,6 @@ jobs: with: check-links: false needs-quarto: true - # requires-xvfb: true sphinxopts: "-j auto --keep-going" env: PYPRIMEMESH_LAUNCH_CONTAINER: 1 diff --git a/doc/source/conf.py b/doc/source/conf.py index 14970879a9..ac6b17e451 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -171,7 +171,7 @@ "backreferences_dir": None, # Modules for which function level galleries are created. In "doc_module": "ansys-meshing-prime", - "image_scrapers": ("matplotlib"), + "image_scrapers": (DynamicScraper(), "matplotlib"), "ignore_pattern": "flycheck*", "thumbnail_size": (350, 350), "parallel": 1, From 9c02b7371a60c63c1d587071b5eda7fffd8eed07 Mon Sep 17 00:00:00 2001 From: afernand Date: Fri, 29 Nov 2024 09:30:16 +0100 Subject: [PATCH 23/27] fix code style --- .flake8 | 4 ++-- examples/gallery/10_wheel_ground_contact_patch.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index ad9ec1672d..a5b4921ec9 100644 --- a/.flake8 +++ b/.flake8 @@ -1,11 +1,11 @@ [flake8] exclude = venv, __init__.py, doc/_build -select = W191, W291, W293, W391, E115, E117, E122, E124, E125, E225, E231, E301, E303, E501, F401, F403 +select = W191, W291, W293, W391, E115, E117, E122, E124, E125, E225, E301, E303, E501, F401, F403 count = True max-complexity = 10 max-line-length = 100 statistics = True -per-file-ignores = +per-file-ignores = src/ansys/meshing/prime/autogen/*: F401, F403, E501 src/ansys/meshing/prime/params/primestructs.py: F401, F403 src/ansys/meshing/prime/internals/error_handling.py: E501 diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index 6bb56b4dcc..246657197c 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -157,7 +157,9 @@ # .. code-block:: python # # display = PrimePlotter() -# display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel")) +# display.plot( +# model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel") +# ) # display.show() ############################################################################### @@ -189,7 +191,8 @@ # # display = PrimePlotter() # display.plot( -# model, scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True +# model, +# scope=prime.ScopeDefinition(model, label_expression="ground, patch*, wheel"), update=True # ) # display.show() From ef6a6d7502c383ab33b842e4c7895bf0f0fd6e65 Mon Sep 17 00:00:00 2001 From: afernand Date: Fri, 29 Nov 2024 09:34:45 +0100 Subject: [PATCH 24/27] revert last changes --- .github/workflows/ci_cd.yml | 1 - doc/source/conf.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 5a8cd71cd9..763c737366 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -89,7 +89,6 @@ jobs: runs-on: ubuntu-latest needs: [docs-style] steps: - - name: Login in Github Container registry uses: docker/login-action@v3 with: diff --git a/doc/source/conf.py b/doc/source/conf.py index ac6b17e451..c0f876e9c9 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -131,7 +131,7 @@ # The master toctree document. master_doc = 'index' -autosummary_generate = False +autosummary_generate = True autosummary_imported_members = True autosummary_ignore_module_all = False @@ -174,7 +174,6 @@ "image_scrapers": (DynamicScraper(), "matplotlib"), "ignore_pattern": "flycheck*", "thumbnail_size": (350, 350), - "parallel": 1, } supress_warnings = ["docutils"] From 96eecc820991095500fa865575e7f472cda60758 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:20:27 +0000 Subject: [PATCH 25/27] chore: adding changelog file 944.fixed.md [dependabot-skip] --- doc/changelog.d/944.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.d/944.fixed.md b/doc/changelog.d/944.fixed.md index 7963e531e3..d1d2260180 100644 --- a/doc/changelog.d/944.fixed.md +++ b/doc/changelog.d/944.fixed.md @@ -1 +1 @@ -fix(temp): Serialize sphinx jobs \ No newline at end of file +fix: Error appearing in some examples while documentation building \ No newline at end of file From ce0402f1cd07572ab59acd9656f5a7e349ac66bb Mon Sep 17 00:00:00 2001 From: afernand Date: Fri, 29 Nov 2024 10:45:22 +0100 Subject: [PATCH 26/27] fix: Example 10 error --- examples/gallery/10_wheel_ground_contact_patch.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index 246657197c..7b725c4361 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -97,6 +97,16 @@ mesh_util.read(wheel_ground_file) + +################### +# Visualize results +# ================= +# .. code-block:: python +# +# display = PrimePlotter() +# display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) +# display.show() + display = PrimePlotter() display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) display.show() From b52f85540e464009e31c318b5553dd2da789872c Mon Sep 17 00:00:00 2001 From: afernand Date: Fri, 29 Nov 2024 11:35:25 +0100 Subject: [PATCH 27/27] fix: Left out code --- examples/gallery/10_wheel_ground_contact_patch.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/gallery/10_wheel_ground_contact_patch.py b/examples/gallery/10_wheel_ground_contact_patch.py index 7b725c4361..e11002185a 100644 --- a/examples/gallery/10_wheel_ground_contact_patch.py +++ b/examples/gallery/10_wheel_ground_contact_patch.py @@ -107,10 +107,6 @@ # display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) # display.show() -display = PrimePlotter() -display.plot(model, scope=prime.ScopeDefinition(model, label_expression="ground, wheel")) -display.show() - print(model) ###############################################################################