
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "examples/05_workflows/cad_to_fea_tet_mesh.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_examples_05_workflows_cad_to_fea_tet_mesh.py>`
        to download the full example code.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_examples_05_workflows_cad_to_fea_tet_mesh.py:


CAD shape to tetrahedral FEA mesh
=================================

Drive a CAD shape through gmsh to obtain a tetrahedral mesh suitable
for finite-element analysis, then bring the volumetric result back
into PyVista as an :class:`pyvista.UnstructuredGrid`.

This imports a real STEP part, the NIST AM Bench 2022 LPBF bridge
specimen (public domain, https://doi.org/10.18434/mds2-2607),
directly into gmsh via ``gmsh.model.occ.importShapes``, the canonical
way to move from CAD to FEA, and meshes the volume tetrahedrally.

.. GENERATED FROM PYTHON SOURCE LINES 16-24

.. code-block:: Python


    import gmsh
    import pandas as pd
    import pyvista as pv

    import pyvista_cad
    from pyvista_cad.examples import downloads








.. GENERATED FROM PYTHON SOURCE LINES 25-26

Import the real STEP into gmsh's OCC kernel and mesh the volume.

.. GENERATED FROM PYTHON SOURCE LINES 26-46

.. code-block:: Python


    gmsh.initialize()
    try:
        gmsh.model.add('part_fea')
        gmsh.model.occ.importShapes(downloads.step_part_path())
        gmsh.model.occ.synchronize()

        gmsh.option.setNumber('Mesh.MeshSizeMax', 2.0)
        gmsh.option.setNumber('Mesh.MeshSizeMin', 0.5)
        gmsh.model.mesh.generate(3)
        grid = pyvista_cad.from_gmsh('part_fea')
    finally:
        gmsh.finalize()

    # gmsh returns a mixed grid (vertex, line, triangle, tet). For a
    # volumetric FEA mesh we only keep VTK_TETRA (cell type 10).
    VTK_TETRA = 10
    grid = grid.extract_cells(grid.celltypes == VTK_TETRA)
    grid






.. raw:: html

    <div class="output_subarea output_html rendered_html output_result">
    <div><style>/* PyVista HTML repr stylesheet.
     * Uses pv- prefix to avoid conflicts with other libraries.
     */

    :root {
      --pv-font-color0: var(--jp-content-font-color0, rgba(0, 0, 0, 1));
      --pv-font-color2: var(--jp-content-font-color2, rgba(0, 0, 0, 0.54));
      --pv-font-color3: var(--jp-content-font-color3, rgba(0, 0, 0, 0.38));
      --pv-border-color: var(--jp-border-color2, #e0e0e0);
      --pv-disabled-color: var(--jp-layout-color3, #bdbdbd);
      --pv-background-color-row-even: var(--jp-layout-color1, #f5f5f5);
      --pv-background-color-row-odd: var(--jp-layout-color2, #eeeeee);
      --pv-badge-active: #1b5e20;
      --pv-badge-normals: #0d47a1;
      --pv-badge-vectors: #00695c;
      --pv-badge-tcoords: #4527a0;
    }

    body[data-jp-theme-light="false"] {
      --pv-font-color0: var(--jp-content-font-color0, rgba(255, 255, 255, 1));
      --pv-font-color2: var(--jp-content-font-color2, rgba(255, 255, 255, 0.54));
      --pv-font-color3: var(--jp-content-font-color3, rgba(255, 255, 255, 0.38));
      --pv-border-color: var(--jp-border-color2, #424242);
      --pv-disabled-color: var(--jp-layout-color3, #616161);
      --pv-background-color-row-even: var(--jp-layout-color1, #1a1a1a);
      --pv-background-color-row-odd: var(--jp-layout-color2, #252525);
      --pv-badge-active: #66bb6a;
      --pv-badge-normals: #64b5f6;
      --pv-badge-vectors: #4db6ac;
      --pv-badge-tcoords: #b39ddb;
    }

    html[theme="dark"],
    html[data-theme="dark"],
    body[data-theme="dark"],
    body.vscode-dark {
      --pv-font-color0: rgba(255, 255, 255, 1);
      --pv-font-color2: rgba(255, 255, 255, 0.54);
      --pv-font-color3: rgba(255, 255, 255, 0.38);
      --pv-border-color: #424242;
      --pv-disabled-color: #616161;
      --pv-background-color-row-even: #1a1a1a;
      --pv-background-color-row-odd: #252525;
      --pv-badge-active: #66bb6a;
      --pv-badge-normals: #64b5f6;
      --pv-badge-vectors: #4db6ac;
      --pv-badge-tcoords: #b39ddb;
    }

    /* OS-level dark mode fallback: applies when no explicit data-theme is set */
    @media (prefers-color-scheme: dark) {
      html:not([data-theme="light"]) {
        --pv-font-color0: rgba(255, 255, 255, 1);
        --pv-font-color2: rgba(255, 255, 255, 0.54);
        --pv-font-color3: rgba(255, 255, 255, 0.38);
        --pv-border-color: #424242;
        --pv-disabled-color: #616161;
        --pv-background-color-row-even: #1a1a1a;
        --pv-background-color-row-odd: #252525;
        --pv-badge-active: #66bb6a;
        --pv-badge-normals: #64b5f6;
        --pv-badge-vectors: #4db6ac;
        --pv-badge-tcoords: #b39ddb;
      }
    }

    .pv-wrap {
      display: block !important;
      min-width: 300px;
      max-width: 700px;
      line-height: 1.6;
      padding-bottom: 4px;
      font-family: var(--jp-ui-font-family, sans-serif);
      font-size: var(--jp-ui-font-size1, 13px);
      color: var(--pv-font-color0);
    }

    .pv-text-repr-fallback {
      display: none;
    }

    /* Header */
    .pv-header {
      display: flex;
      align-items: center;
      gap: 8px;
      padding-top: 6px;
      padding-bottom: 6px;
      border-bottom: solid 1px var(--pv-border-color);
      margin-bottom: 4px;
    }

    .pv-header-text {
      display: flex;
      flex-direction: column;
      gap: 1px;
      min-width: 0;
      flex: 1;
    }

    .pv-obj-type {
      font-weight: 600;
      color: var(--pv-font-color0);
    }

    .pv-header-badge {
      display: inline-block;
      font-size: 0.75em;
      font-weight: 600;
      padding: 2px 7px;
      border-radius: 3px;
      color: var(--pv-font-color2);
      border: 1px solid var(--pv-border-color);
      white-space: nowrap;
    }

    /* Metadata (always-visible key-value rows) */
    .pv-metadata {
      margin: 4px 0 6px 0;
      font-size: 0.92em;
      line-height: 1.5;
    }

    .pv-meta-row {
      display: flex;
      flex-wrap: wrap;
      gap: 1px 14px;
      padding: 1px 0;
    }

    .pv-meta-row-label {
      color: var(--pv-font-color2);
      font-weight: 500;
      white-space: nowrap;
      min-width: 60px;
    }

    .pv-meta-entry {
      white-space: nowrap;
    }

    /* Copy-to-clipboard button */
    .pv-copy-btn {
      display: inline-block;
      cursor: pointer;
      opacity: 0.5;
      font-size: 0.85em;
      padding: 0 3px;
      vertical-align: middle;
      transition: opacity 0.15s;
      user-select: none;
      border: none;
      background: none;
      color: var(--pv-font-color3);
    }

    .pv-copy-btn:hover {
      opacity: 1;
      color: var(--pv-font-color0);
    }

    .pv-meta-label {
      color: var(--pv-font-color3);
      font-weight: 400;
      padding-right: 2px;
    }

    /* Sections grid */
    .pv-sections {
      padding-left: 0 !important;
      display: grid;
      grid-template-columns: 150px auto auto auto 1fr 20px 20px;
      margin-block-start: 0;
      margin-block-end: 0;
      list-style: none;
    }

    .pv-section-item {
      display: contents;
    }

    /* Hidden checkbox for expand/collapse */
    .pv-section-item > input {
      display: block;
      opacity: 0;
      height: 0;
      margin: 0;
    }

    .pv-section-item > input + label {
      color: var(--pv-disabled-color);
    }

    .pv-section-item > input:enabled + label {
      cursor: pointer;
      color: var(--pv-font-color2);
    }

    .pv-section-item > input:enabled + label:hover {
      color: var(--pv-font-color0);
    }

    /* Section summary (left column label) */
    .pv-section-summary {
      grid-column: 1;
      color: var(--pv-font-color2);
      font-weight: 500;
      white-space: nowrap;
    }

    .pv-section-summary > span {
      display: inline-block;
      padding-left: 0.3em;
    }

    .pv-section-summary-in:disabled + label {
      color: var(--pv-font-color2);
    }

    /* Expand/collapse arrows */
    .pv-section-summary-in + label:before {
      display: inline-block;
      content: "\25b6";
      font-size: 11px;
      width: 15px;
      text-align: center;
    }

    .pv-section-summary-in:disabled + label:before {
      color: var(--pv-disabled-color);
    }

    .pv-section-summary-in:checked + label:before {
      content: "\25bc";
    }

    .pv-section-summary-in:checked + label > span {
      display: none;
    }

    .pv-section-summary,
    .pv-section-inline-details {
      padding-top: 4px;
    }

    .pv-section-inline-details {
      grid-column: 2 / -1;
    }

    .pv-section-details {
      grid-column: 1 / -1;
      margin-top: 4px;
      margin-bottom: 5px;
    }

    .pv-section-summary-in ~ .pv-section-details {
      display: none;
    }

    .pv-section-summary-in:checked ~ .pv-section-inline-details {
      display: none;
    }

    .pv-section-summary-in:checked ~ .pv-section-details {
      display: block;
    }

    .pv-section-summary-in:checked ~ .pv-section-details:has(.pv-var-list) {
      display: contents;
    }

    /* Variable (array) list */
    .pv-var-list,
    .pv-var-item {
      display: contents;
    }

    .pv-var-item > div,
    .pv-var-item label,
    .pv-var-item > .pv-var-name span {
      background-color: var(--pv-background-color-row-even);
      border-color: var(--pv-background-color-row-odd);
      margin-bottom: 0;
      padding-top: 2px;
    }

    .pv-var-list > li:nth-child(odd) > div,
    .pv-var-list > li:nth-child(odd) > label,
    .pv-var-list > li:nth-child(odd) > .pv-var-name span {
      background-color: var(--pv-background-color-row-odd);
      border-color: var(--pv-background-color-row-even);
    }

    .pv-var-name {
      grid-column: 1;
    }

    .pv-var-dims {
      grid-column: 2;
    }

    .pv-var-dtype {
      grid-column: 3;
      text-align: right;
      color: var(--pv-font-color2);
    }

    .pv-var-range {
      grid-column: 4;
      color: var(--pv-font-color3);
      font-size: 0.92em;
    }

    .pv-var-badges {
      grid-column: 5;
      padding-left: 8px;
    }

    .pv-var-name,
    .pv-var-dims,
    .pv-var-dtype,
    .pv-var-range {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      padding-right: 10px;
    }

    .pv-var-name:hover,
    .pv-var-dims:hover,
    .pv-var-dtype:hover,
    .pv-var-range:hover {
      overflow: visible;
      width: auto;
      z-index: 1;
    }

    .pv-var-name span {
      padding-left: 25px !important;
    }

    .pv-var-name-active span {
      font-weight: 600;
    }

    /* Badges */
    .pv-badge {
      display: inline-block;
      font-size: 0.75em;
      font-weight: 600;
      padding: 1px 5px;
      border-radius: 3px;
      vertical-align: middle;
      line-height: 1.4;
    }

    .pv-badge-active {
      color: var(--pv-badge-active);
      border: 1px solid var(--pv-badge-active);
    }

    .pv-badge-normals {
      color: var(--pv-badge-normals);
      border: 1px solid var(--pv-badge-normals);
    }

    .pv-badge-vectors {
      color: var(--pv-badge-vectors);
      border: 1px solid var(--pv-badge-vectors);
    }

    .pv-badge-tcoords {
      color: var(--pv-badge-tcoords);
      border: 1px solid var(--pv-badge-tcoords);
    }

    /* Logo and Icons */
    .pv-logo {
      display: flex;
      align-items: center;
      flex-shrink: 0;
    }

    .pv-logo svg {
      width: 28px;
      height: 28px;
    }

    .pv-brand-logo {
      display: flex;
      align-items: center;
      flex-shrink: 0;
    }

    .pv-brand-logo svg {
      height: 20px;
      width: auto;
    }

    /* Children list (MultiBlock / PartitionedDataSet) */
    .pv-children-list {
      padding-left: 25px !important;
      list-style: none;
    }

    .pv-children-list li {
      padding: 1px 0;
    }

    .pv-child-name {
      font-weight: 500;
    }

    .pv-child-type {
      color: var(--pv-font-color2);
      font-style: italic;
    }

    .pv-child-type:before {
      content: "\00b7";
      padding: 0 6px;
      font-style: normal;
    }

    .pv-child-detail {
      color: var(--pv-font-color3);
      font-size: 0.9em;
    }

    .pv-child-detail:not(:empty):before {
      content: "\00b7";
      padding: 0 6px;
    }
    </style><pre class='pv-text-repr-fallback'>UnstructuredGrid (0x7f6fa8111120)
      N Cells:    6470
      N Points:   2041
      X Bounds:   0.000e+00, 7.500e+01
      Y Bounds:   -2.446e-13, 5.000e+00
      Z Bounds:   0.000e+00, 1.225e+01
      N Arrays:   3</pre><div class='pv-wrap' style='display:none'><div class='pv-header'><span class='pv-logo'><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
      <defs>
        <linearGradient id="pv-ug-g1" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%" stop-color="#ffd040"/>
          <stop offset="100%" stop-color="#376fa0"/>
        </linearGradient>
      </defs>
      <polygon points="3,10 8,5 14,7 13,13 6,14" fill="#376fa0" opacity="0.9"/>
      <polygon points="14,7 22,4 20,12" fill="url(#pv-ug-g1)" opacity="0.85"/>
      <polygon points="22,4 29,9 27,17 20,12" fill="#1a4a70" opacity="0.85"/>
      <polygon points="13,13 20,12 16,20" fill="#ffd040" opacity="0.8"/>
      <polygon points="6,14 13,13 16,20 10,24 4,20" fill="#376fa0" opacity="0.8"/>
      <polygon points="16,20 10,24 18,28" fill="#1a4a70" opacity="0.85"/>
      <polygon points="20,12 27,17 26,25 18,28 16,20" fill="url(#pv-ug-g1)" opacity="0.8"/>
      <g stroke="rgba(255,255,255,0.6)" stroke-width="0.5" fill="none">
        <polygon points="3,10 8,5 14,7 13,13 6,14"/>
        <polygon points="14,7 22,4 20,12"/>
        <polygon points="22,4 29,9 27,17 20,12"/>
        <line x1="13" y1="13" x2="20" y2="12"/>
        <line x1="13" y1="13" x2="16" y2="20"/>
        <line x1="20" y1="12" x2="16" y2="20"/>
        <polygon points="6,14 13,13 16,20 10,24 4,20"/>
        <line x1="16" y1="20" x2="10" y2="24"/>
        <line x1="10" y1="24" x2="18" y2="28"/>
        <line x1="16" y1="20" x2="18" y2="28"/>
        <polygon points="20,12 27,17 26,25 18,28 16,20"/>
      </g>
      <g fill="rgba(255,255,255,0.8)">
        <circle cx="3" cy="10" r="0.9"/>
        <circle cx="8" cy="5" r="0.9"/>
        <circle cx="14" cy="7" r="0.9"/>
        <circle cx="22" cy="4" r="0.9"/>
        <circle cx="29" cy="9" r="0.9"/>
        <circle cx="13" cy="13" r="0.9"/>
        <circle cx="20" cy="12" r="0.9"/>
        <circle cx="6" cy="14" r="0.9"/>
        <circle cx="27" cy="17" r="0.9"/>
        <circle cx="16" cy="20" r="0.9"/>
        <circle cx="4" cy="20" r="0.9"/>
        <circle cx="10" cy="24" r="0.9"/>
        <circle cx="26" cy="25" r="0.9"/>
        <circle cx="18" cy="28" r="0.9"/>
      </g>
    </svg>
    </span><div class='pv-header-text'><div class='pv-obj-type'>UnstructuredGrid <span class='pv-header-badge'>2,041 points</span> <span class='pv-header-badge'>6,470 cells</span> <span class='pv-header-badge'>377 KiB</span></div></div><span class='pv-brand-logo'><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 24">
      <text x="0" y="18" font-family="system-ui,-apple-system,sans-serif" font-size="18" font-weight="700" font-style="italic" letter-spacing="-0.5">
        <tspan fill="#3776AB" opacity="0.7">P</tspan><tspan fill="#FFD43B" opacity="0.7">y</tspan><tspan fill="#008c9e">Vista</tspan>
      </text>
    </svg>
    </span></div><div class='pv-metadata'><div class='pv-meta-row pv-copyable'><span class='pv-meta-row-label'>Bounds</span><button class='pv-copy-btn' title='Copy to clipboard' data-copy='(0.0, 75.00000000000003, -2.445960101127298e-13, 5.000000000000262, 0.0, 12.25)' onclick="navigator.clipboard.writeText(this.dataset.copy)">⧉</button><span class='pv-meta-entry'><span class='pv-meta-label'>X</span> [0.000e+00, 7.500e+01]</span><span class='pv-meta-entry'><span class='pv-meta-label'>Y</span> [-2.446e-13, 5.000e+00]</span><span class='pv-meta-entry'><span class='pv-meta-label'>Z</span> [0.000e+00, 1.225e+01]</span></div></div><ul class='pv-sections'><li class='pv-section-item'><input id='section-da7337cd-96b8-47d5-86ed-9b83be2f355d' class='pv-section-summary-in' type='checkbox' checked /><label for='section-da7337cd-96b8-47d5-86ed-9b83be2f355d' class='pv-section-summary' title='Expand/collapse section'>Point Data: <span>(1)</span></label><div class='pv-section-inline-details'></div><div class='pv-section-details'><ul class='pv-var-list'><li class='pv-var-item'><div class='pv-var-name'><span>vtkOriginalPointIds</span><button class='pv-copy-btn' title='Copy to clipboard' data-copy='vtkOriginalPointIds' onclick="navigator.clipboard.writeText(this.dataset.copy)">⧉</button></div><div class='pv-var-dims'>scalar</div><div class='pv-var-dtype'>int64</div><div class='pv-var-range'>[0.000e+00, 2.040e+03]</div><div class='pv-var-badges'></div></li></ul></div></li><li class='pv-section-item'><input id='section-93a3bb12-68c4-4cdd-9463-5520d721492c' class='pv-section-summary-in' type='checkbox' checked /><label for='section-93a3bb12-68c4-4cdd-9463-5520d721492c' class='pv-section-summary' title='Expand/collapse section'>Cell Data: <span>(1)</span></label><div class='pv-section-inline-details'></div><div class='pv-section-details'><ul class='pv-var-list'><li class='pv-var-item'><div class='pv-var-name'><span>vtkOriginalCellIds</span><button class='pv-copy-btn' title='Copy to clipboard' data-copy='vtkOriginalCellIds' onclick="navigator.clipboard.writeText(this.dataset.copy)">⧉</button></div><div class='pv-var-dims'>scalar</div><div class='pv-var-dtype'>int64</div><div class='pv-var-range'>[4.456e+03, 1.092e+04]</div><div class='pv-var-badges'></div></li></ul></div></li><li class='pv-section-item'><input id='section-11cdbf77-7378-4cd1-bca9-57dc85fd48a8' class='pv-section-summary-in' type='checkbox' checked /><label for='section-11cdbf77-7378-4cd1-bca9-57dc85fd48a8' class='pv-section-summary' title='Expand/collapse section'>Field Data: <span>(1)</span></label><div class='pv-section-inline-details'></div><div class='pv-section-details'><ul class='pv-var-list'><li class='pv-var-item'><div class='pv-var-name'><span>cad.source_format</span><button class='pv-copy-btn' title='Copy to clipboard' data-copy='cad.source_format' onclick="navigator.clipboard.writeText(this.dataset.copy)">⧉</button></div><div class='pv-var-dims'>(1,)</div><div class='pv-var-dtype'>&lt;U4</div><div class='pv-var-range'></div><div class='pv-var-badges'></div></li></ul></div></li></ul></div></div>
    </div>
    <br />
    <br />

.. GENERATED FROM PYTHON SOURCE LINES 47-48

Quality check: a healthy tet mesh has no zero-volume elements.

.. GENERATED FROM PYTHON SOURCE LINES 48-58

.. code-block:: Python


    vols = grid.compute_cell_sizes(length=False, area=False, volume=True)['Volume']

    pd.DataFrame(
        {
            'metric': ['min', 'mean', 'max'],
            'tet_volume': [vols.min(), vols.mean(), vols.max()],
        }
    )






.. raw:: html

    <div class="output_subarea output_html rendered_html output_result">
    <div>
    <style scoped>
        .dataframe tbody tr th:only-of-type {
            vertical-align: middle;
        }

        .dataframe tbody tr th {
            vertical-align: top;
        }

        .dataframe thead th {
            text-align: right;
        }
    </style>
    <table border="1" class="dataframe">
      <thead>
        <tr style="text-align: right;">
          <th></th>
          <th>metric</th>
          <th>tet_volume</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th>0</th>
          <td>min</td>
          <td>0.005826</td>
        </tr>
        <tr>
          <th>1</th>
          <td>mean</td>
          <td>0.551671</td>
        </tr>
        <tr>
          <th>2</th>
          <td>max</td>
          <td>3.264683</td>
        </tr>
      </tbody>
    </table>
    </div>
    </div>
    <br />
    <br />

.. GENERATED FROM PYTHON SOURCE LINES 59-60

Slice through the part to expose the tet interior.

.. GENERATED FROM PYTHON SOURCE LINES 60-69

.. code-block:: Python


    bnds = grid.bounds
    clip = grid.clip(normal='x', crinkle=True)
    clip['Z'] = clip.points[:, 2]

    pl = pv.Plotter()
    pl.add_mesh(grid, color='lightgray', opacity=0.15)
    pl.add_mesh(clip, scalars='Z', cmap='inferno', show_edges=True, edge_color='black')
    pl.show()







.. tab-set::



   .. tab-item:: Static Scene



            
     .. image-sg:: /examples/05_workflows/images/sphx_glr_cad_to_fea_tet_mesh_001.png
        :alt: cad to fea tet mesh
        :srcset: /examples/05_workflows/images/sphx_glr_cad_to_fea_tet_mesh_001.png
        :class: sphx-glr-single-img
     


   .. tab-item:: Interactive Scene



       .. offlineviewer:: /home/runner/work/pyvista-cad/pyvista-cad/doc/examples/05_workflows/images/sphx_glr_cad_to_fea_tet_mesh_001.vtksz







.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (0 minutes 0.884 seconds)


.. _sphx_glr_download_examples_05_workflows_cad_to_fea_tet_mesh.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: cad_to_fea_tet_mesh.ipynb <cad_to_fea_tet_mesh.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: cad_to_fea_tet_mesh.py <cad_to_fea_tet_mesh.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: cad_to_fea_tet_mesh.zip <cad_to_fea_tet_mesh.zip>`
