diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index 132d94fe87..0f4a17ada8 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -3,6 +3,7 @@ on: pull_request permissions: pull-requests: write + contents: write jobs: dependabot: diff --git a/doc/source/images/00_after_cad_import.png b/doc/source/images/00_after_cad_import.png new file mode 100644 index 0000000000..01d960c674 Binary files /dev/null and b/doc/source/images/00_after_cad_import.png differ diff --git a/doc/source/images/00_after_delete_topology.png b/doc/source/images/00_after_delete_topology.png new file mode 100644 index 0000000000..e5aabbc223 Binary files /dev/null and b/doc/source/images/00_after_delete_topology.png differ diff --git a/doc/source/images/00_after_mesh_generation.png b/doc/source/images/00_after_mesh_generation.png new file mode 100644 index 0000000000..31ffc7b80c Binary files /dev/null and b/doc/source/images/00_after_mesh_generation.png differ diff --git a/doc/source/images/00_import_cas.png b/doc/source/images/00_import_cas.png new file mode 100644 index 0000000000..8c652c5fcf Binary files /dev/null and b/doc/source/images/00_import_cas.png differ diff --git a/doc/source/images/00_import_cdb.png b/doc/source/images/00_import_cdb.png new file mode 100644 index 0000000000..828ff08bc0 Binary files /dev/null and b/doc/source/images/00_import_cdb.png differ diff --git a/doc/source/images/00_import_pmdat.png b/doc/source/images/00_import_pmdat.png new file mode 100644 index 0000000000..2923c68962 Binary files /dev/null and b/doc/source/images/00_import_pmdat.png differ diff --git a/doc/source/images/generic_solid_block.png b/doc/source/images/generic_solid_block.png new file mode 100644 index 0000000000..478188a070 Binary files /dev/null and b/doc/source/images/generic_solid_block.png differ diff --git a/doc/source/images/part_type_new.png b/doc/source/images/part_type_new.png new file mode 100644 index 0000000000..2828575417 Binary files /dev/null and b/doc/source/images/part_type_new.png differ diff --git a/doc/source/user_guide/surfer.rst b/doc/source/user_guide/surfer.rst index 913ef00b19..bc93d7f2a6 100644 --- a/doc/source/user_guide/surfer.rst +++ b/doc/source/user_guide/surfer.rst @@ -197,29 +197,3 @@ Initialize surfer parameters and generate a surface mesh on face zonelets: :align: center **Surface mesh displayed** - - -Remesh surface using the Lucid module -------------------------------------- - -This code shows how to replicate the preceding surface mesh results by remeshing -the surface using the Lucid module: - -.. code-block:: python - - import ansys.meshing.prime as prime - - prime_client = prime.launch_prime() - model = prime_client.model - - # Instantiate the Lucid module - mesh_util = prime.lucid.Mesh(model) - - # Import CAD (STL) file - input_file = r"D:/Examples/simple-bracket-holes.stl" - mesh_util.read(input_file) - - # Surface mesh the geometry with curvature sizing - # Set minimum and maximum sizing to use for curvature refinement - mesh_util.surface_mesh(min_size=0.27, max_size=5.5) - diff --git a/doc/source/user_guide/wrapper.rst b/doc/source/user_guide/wrapper.rst index 0d80e22f90..2944227888 100644 --- a/doc/source/user_guide/wrapper.rst +++ b/doc/source/user_guide/wrapper.rst @@ -180,31 +180,3 @@ The basic PyPrimeMesh wrapper-based workflow follows these steps: ) -Surface wrapping using the ``lucid.Mesh`` class ------------------------------------------------ - -This example shows you the method required to replicate the preceding surface mesh results: - -.. code:: python - - model = prime_client.model - mesh_util = prime.lucid.Mesh(model) - input_file = r"D:/PyPrimeMesh/cylinder_with_flange.pmdat" - mesh_util.read(input_file) - - # Create size control for remeshing - size_control2 = model.control_data.create_size_control( - sizing_type=prime.SizingType.HARD - ) - size_control2.set_hard_sizing_params(prime.HardSizingParams(model=model, min=0.8)) - size_control2.set_scope(prime.ScopeDefinition(model=model)) - - # Wrap and remesh the input parts - mesh_util.wrap( - min_size=0.2, - max_size=1.0, - input_parts="flange,pipe", - use_existing_features=True, - recompute_remesh_sizes=True, - remesh_size_controls=[size_control2], - ) diff --git a/examples/gallery/00_lucid_file_IO.py b/examples/gallery/00_lucid_file_IO.py new file mode 100644 index 0000000000..512bcbdbae --- /dev/null +++ b/examples/gallery/00_lucid_file_IO.py @@ -0,0 +1,256 @@ +""" +.. _ref_file_io: + +=================================================================== +Data conversion 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 and 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 import Graphics + +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 = Graphics(model) + +############################################################################### +# 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 = Graphics(model) +display() +part = model.get_part_by_name("pyprime_block_import") +display(scope=prime.ScopeDefinition(model, label_expression="my_group")) +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(scope=scope) + +############################################################################### +# Exit PyPrimeMesh +# ~~~~~~~~~~~~~~~~ + +prime_client.exit() diff --git a/src/ansys/meshing/prime/examples/__init__.py b/src/ansys/meshing/prime/examples/__init__.py index 32b075df06..e517052384 100644 --- a/src/ansys/meshing/prime/examples/__init__.py +++ b/src/ansys/meshing/prime/examples/__init__.py @@ -1,5 +1,8 @@ from .download_utilities import DownloadManager from .examples import ( + download_block_model_fmd, + download_block_model_pmdat, + download_block_model_scdoc, download_bracket_dsco, download_bracket_fmd, download_bracket_scdoc, diff --git a/src/ansys/meshing/prime/examples/examples.py b/src/ansys/meshing/prime/examples/examples.py index 40d209cddc..bfdb68d330 100644 --- a/src/ansys/meshing/prime/examples/examples.py +++ b/src/ansys/meshing/prime/examples/examples.py @@ -41,6 +41,9 @@ "download_multi_layer_quad_mesh_pcb_fmd", "download_multi_layer_quad_mesh_pcb_scdoc", "download_multi_layer_quad_mesh_pcb_pmdat", + "download_block_model_scdoc", + "download_block_model_fmd", + "download_block_model_pmdat", ] @@ -95,6 +98,9 @@ class Examples(Enum): "filename": "multi_layer_quad_mesh_pcb.pmdat", "git_folder": "multi_layer_quad_mesh_pcb", } + BLOCK_MODEL_SCDOC = {"filename": "pyprime_block_import.scdoc", "git_folder": "block_model"} + BLOCK_MODEL_FMD = {"filename": "pyprime_block_import.fmd", "git_folder": "block_model"} + BLOCK_MODEL_PMDAT = {"filename": "pyprime_block_import.pmdat", "git_folder": "block_model"} _DOWNLOADS = [] @@ -1035,7 +1041,7 @@ def download_f1_rw_drs_stl( >>> model = session.model >>> f1_rw_drs = prime_examples.download_f1_rw_drs_stl() >>> with prime.FileIO(model) as io: - >>> _ = io.read_pmdat(pcb, params=prime.FileReadParams(model)) + >>> _ = io.import_cad(f1_rw_drs, params=prime.ImportCADParams(model)) >>> print(model) """ @@ -1070,7 +1076,7 @@ def download_f1_rw_enclosure_stl( >>> model = session.model >>> f1_rw_enclosure = prime_examples.download_f1_rw_enclosure_stl() >>> with prime.FileIO(model) as io: - >>> _ = io.read_pmdat(pcb, params=prime.FileReadParams(model)) + >>> _ = io.import_cad(f1_rw_enclosure, params=prime.ImportCADParams(model)) >>> print(model) """ @@ -1105,7 +1111,7 @@ def download_f1_rw_end_plates_stl( >>> model = session.model >>> f1_rw_end_plates = prime_examples.download_f1_rw_end_plates_stl() >>> with prime.FileIO(model) as io: - >>> _ = io.read_pmdat(pcb, params=prime.FileReadParams(model)) + >>> _ = io.import_cad(f1_rw_end_plates, params=prime.ImportCADParams(model)) >>> print(model) """ @@ -1140,7 +1146,7 @@ def download_f1_rw_main_plane_stl( >>> model = session.model >>> f1_rw_main_plane = prime_examples.download_f1_rw_main_plane_stl() >>> with prime.FileIO(model) as io: - >>> _ = io.read_pmdat(pcb, params=prime.FileReadParams(model)) + >>> _ = io.import_cad(f1_rw_main_plane, params=prime.ImportCADParams(model)) >>> print(model) """ @@ -1320,3 +1326,108 @@ def download_multi_layer_quad_mesh_pcb_pmdat( """ return get_file(Examples.MULTI_LAYER_MESH_PCB_PMDAT, destination, force) + + +def download_block_model_scdoc( + destination: Optional[str] = None, force: bool = False +) -> Union[str, os.PathLike]: + """Download CAD file for the block model example. + + Parameters + ---------- + destination: Optional[str] + Destination for the file to be downloaded. + If nothing is provided, the default path in app data is used. + force: bool + Option to download the file. + If true, the file is always downloaded. + If false, an existing file in the cache may be reused. + + Returns + ------- + str + Local path to the downloaded file. + + Examples + -------- + >>> import ansys.meshing.prime as prime + >>> import ansys.meshing.prime.examples as prime_examples + >>> with prime.launch_prime() as session: + >>> model = session.model + >>> block_model = prime_examples.download_block_model_scdoc() + >>> with prime.FileIO(model) as io: + >>> _ = io.import_cad(block_model, params=prime.ImportCADParams(model)) + >>> print(model) + + """ + return get_file(Examples.BLOCK_MODEL_SCDOC, destination, force) + + +def download_block_model_fmd( + destination: Optional[str] = None, force: bool = False +) -> Union[str, os.PathLike]: + """Download CAD file for the block model example. + + Parameters + ---------- + destination: Optional[str] + Destination for the file to be downloaded. + If nothing is provided, the default path in app data is used. + force: bool + Option to download the file. + If true, the file is always downloaded. + If false, an existing file in the cache may be reused. + + Returns + ------- + str + Local path to the downloaded file. + + Examples + -------- + >>> import ansys.meshing.prime as prime + >>> import ansys.meshing.prime.examples as prime_examples + >>> with prime.launch_prime() as session: + >>> model = session.model + >>> block_model = prime_examples.download_block_model_fmd() + >>> with prime.FileIO(model) as io: + >>> _ = io.import_cad(block_model, params=prime.ImportCADParams(model)) + >>> print(model) + + """ + return get_file(Examples.BLOCK_MODEL_FMD, destination, force) + + +def download_block_model_pmdat( + destination: Optional[str] = None, force: bool = False +) -> Union[str, os.PathLike]: + """Download PMDAT file for the block model example. + + Parameters + ---------- + destination: Optional[str] + Destination for the file to be downloaded. + If nothing is provided, the default path in app data is used. + force: bool + Option to download the file. + If true, the file is always downloaded. + If false, an existing file in the cache may be reused. + + Returns + ------- + str + Local path to the downloaded file. + + Examples + -------- + >>> import ansys.meshing.prime as prime + >>> import ansys.meshing.prime.examples as prime_examples + >>> with prime.launch_prime() as session: + >>> model = session.model + >>> block_model = prime_examples.download_block_model_pmdat() + >>> with prime.FileIO(model) as io: + >>> _ = io.read_pmdat(block_model, params=prime.FileReadParams(model)) + >>> print(model) + + """ + return get_file(Examples.BLOCK_MODEL_PMDAT, destination, force)