Unstructured Mesh VTK Files#

Creates tetrahedral volume meshes for use with OpenMC’s UnstructuredMesh tally.

Note

Volume mesh output requires the cad-to-dagmc-mesher or GMSH backend. The CadQuery meshing backend cannot create volume meshes.

Basic Export#

import cadquery as cq
from cad_to_dagmc import CadToDagmc

assembly = cq.Assembly()
assembly.add(cq.Workplane("XY").sphere(10), name="sphere")

model = CadToDagmc()
model.add_cadquery_object(assembly, material_tags=["tungsten"])

model.export_unstructured_mesh_file(
    filename="umesh.vtk",
    target_edge_length=2.0,
)

This uses the cad-to-dagmc-mesher backend, which writes the vtk file without GMSH. The tetrahedra size is controlled with a single target_edge_length argument, which also selects the backend automatically.

Optionally restrict which volumes are filled with tetrahedra using their material tag names:

model.export_unstructured_mesh_file(
    filename="umesh.vtk",
    target_edge_length=2.0,
    tet_volumes=["tungsten"],
)

Using the GMSH Backend#

The GMSH backend sizes the tetrahedra with min_mesh_size and max_mesh_size:

model.export_unstructured_mesh_file(
    filename="umesh.vtk",
    min_mesh_size=1.0,
    max_mesh_size=5.0,
)

mesh_algorithm controls only the surface mesh. Use mesh_algorithm_3d=10 to select HXT for the tetrahedra, or keep the default 1 (Delaunay). Advanced volume_mesh_options are applied only after the surfaces have been meshed, allowing a coarser interior without changing the surface sizing. See 3D algorithms and volume-only options for an example and caveats.

Complex Geometry Example#

Volume meshes work with complex curved geometries:

import cadquery as cq
from cad_to_dagmc import CadToDagmc
from math import sin, cos, pi, floor

def hypocycloid(t, r1, r2):
    return (
        (r1 - r2) * cos(t) + r2 * cos(r1 / r2 * t - t),
        (r1 - r2) * sin(t) + r2 * sin(-(r1 / r2 * t - t)),
    )

def epicycloid(t, r1, r2):
    return (
        (r1 + r2) * cos(t) - r2 * cos(r1 / r2 * t + t),
        (r1 + r2) * sin(t) - r2 * sin(r1 / r2 * t + t),
    )

def gear(t, r1=4, r2=1):
    if (-1) ** (1 + floor(t / 2 / pi * (r1 / r2))) < 0:
        return epicycloid(t, r1, r2)
    else:
        return hypocycloid(t, r1, r2)

# Create gear with twist
gear_shape = (
    cq.Workplane("XY")
    .parametricCurve(lambda t: gear(t * 2 * pi, 6, 1))
    .twistExtrude(15, 90)
    .faces(">Z")
    .workplane()
    .circle(2)
    .cutThruAll()
)

# Create spline extrusion
spline_points = [
    (2.75, 1.5), (2.5, 1.75), (2.0, 1.5),
    (1.5, 1.0), (1.0, 1.25), (0.5, 1.0), (0, 1.0),
]
s = cq.Workplane("XY")
r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(spline_points, includeCurrent=True).close()
spline_shape = r.extrude(-20)

model = CadToDagmc()
model.add_cadquery_object(gear_shape, material_tags=["mat1"])
model.add_cadquery_object(spline_shape, material_tags=["mat2"])

model.export_unstructured_mesh_file(
    filename="umesh.vtk",
    max_mesh_size=1,
    min_mesh_size=0.1,
)

Selecting Specific Volumes#

Export only certain volumes to the VTK file:

model.export_unstructured_mesh_file(
    filename="partial_mesh.vtk",
    volumes=[1, 3],  # Only include volumes 1 and 3
    min_mesh_size=1.0,
    max_mesh_size=5.0,
)

Note

volumes accepts volume IDs only. To select volumes by material tag name use the cad-to-dagmc-mesher backend and its tet_volumes argument, shown above.

API Reference#

export_unstructured_mesh_file()#

model.export_unstructured_mesh_file(
    filename="umesh.vtk",     # Output file path
    min_mesh_size=1.0,        # Minimum element size (gmsh backend)
    max_mesh_size=10.0,       # Maximum element size (gmsh backend)
    mesh_algorithm=1,         # GMSH 2D surface algorithm
    mesh_algorithm_3d=1,      # GMSH 3D algorithm (1=Delaunay, 10=HXT)
    volume_mesh_options=None, # GMSH options applied only after surface meshing
    set_size=None,            # Per-volume sizes (gmsh backend)
    volumes=None,             # Specific volumes to include (gmsh backend)
    scale_factor=1.0,         # Geometry scaling
    imprint="partial",        # Imprint shared surfaces
    imprint_threads="all",    # Keyword-only imprint thread count
    mesh_threads="all",       # Keyword-only meshing thread count (cad-to-dagmc-mesher backend)
    method="file",            # CAD transfer method (gmsh backend)
    meshing_backend=None,     # "gmsh", "cad-to-dagmc-mesher" or auto-select
    target_edge_length=None,  # Tet edge length (cad-to-dagmc-mesher backend)
    tet_volumes=None,         # Volumes to fill with tets (cad-to-dagmc-mesher backend)
)

Parameters:

Parameter

Type

Default

Description

filename

str

“umesh.vtk”

Output VTK file path

min_mesh_size

float

None

Minimum mesh element size (gmsh)

max_mesh_size

float

None

Maximum mesh element size (gmsh)

mesh_algorithm

int

1

GMSH 2D surface meshing algorithm

mesh_algorithm_3d

int

1

GMSH 3D volume algorithm: 1 = Delaunay, 10 = HXT

volume_mesh_options

dict

None

Numeric/string GMSH options applied after surface meshing, before volume meshing

set_size

dict

None

Per-volume mesh sizes (gmsh)

volumes

list

None

Specific volumes to mesh (gmsh)

scale_factor

float

1.0

Geometry scale factor

imprint

"partial", "full", None, or False

"partial"

Shared-surface imprint mode. None still imprints, with glue disabled; only False skips imprinting. See Imprinting for options.

imprint_threads

"all" or positive int

"all"

Keyword-only thread count for imprinting. See Imprinting for validation and scope.

mesh_threads

"all" or positive int

"all"

Keyword-only thread count for cad-to-dagmc-mesher meshing after the imprint. Fewer threads lower peak RAM; the mesh is the same. The gmsh backend warns that it is unused. See Parallel Processing.

method

str

“file”

CAD transfer method (gmsh)

meshing_backend

str

None

“gmsh” or “cad-to-dagmc-mesher”; auto-selected when not set

target_edge_length

float

None

Tetrahedron edge length (cad-to-dagmc-mesher)

tet_volumes

list[str]

None

Material tags of volumes to fill with tets (cad-to-dagmc-mesher)

tolerance

float

0.5

Surface mesh linear tolerance (cad-to-dagmc-mesher)

angular_tolerance

float

0.2

Surface mesh angular tolerance (cad-to-dagmc-mesher)

Using in OpenMC#

Load the unstructured mesh for tallies:

import openmc

# Create mesh from VTK file
mesh = openmc.UnstructuredMesh(filename="umesh.vtk", library="moab")

# Create tally with mesh filter
mesh_filter = openmc.MeshFilter(mesh)
tally = openmc.Tally()
tally.filters = [mesh_filter]
tally.scores = ["flux", "heating"]

tallies = openmc.Tallies([tally])

Complete OpenMC Workflow#

import cadquery as cq
from cad_to_dagmc import CadToDagmc
import openmc

# Create geometry
sphere = cq.Workplane("XY").sphere(10)
assembly = cq.Assembly()
assembly.add(sphere, name="sphere")

# Export both surface and volume mesh
model = CadToDagmc()
model.add_cadquery_object(assembly, material_tags=["mat1"])

model.export_dagmc_h5m_file(filename="dagmc.h5m")
model.export_unstructured_mesh_file(filename="umesh.vtk")

# Set up OpenMC
mat1 = openmc.Material(name="mat1")
mat1.add_nuclide("H1", 1, percent_type="ao")
mat1.set_density("g/cm3", 0.001)
materials = openmc.Materials([mat1])

# Geometry from DAGMC
universe = openmc.DAGMCUniverse("dagmc.h5m").bounded_universe()
geometry = openmc.Geometry(universe)

# Unstructured mesh tally
umesh = openmc.UnstructuredMesh("umesh.vtk", library="moab")
mesh_filter = openmc.MeshFilter(umesh)
tally = openmc.Tally(name="mesh_tally")
tally.filters = [mesh_filter]
tally.scores = ["flux"]
tallies = openmc.Tallies([tally])

# Settings
settings = openmc.Settings()
settings.batches = 10
settings.particles = 5000
settings.run_mode = "fixed source"

# Run
model = openmc.Model(geometry, materials, settings, tallies)
model.run()

See Also#