CAD, glTF and IGA

Since meshio++ 17.2.0 the extension ships the kernel twice: the lean MIT build that serves every ordinary format, and a FULL build — OpenCASCADE, zstd, lz4 and bzip2 linked in, under the mixed licence MIT AND (LGPL-2.1-only WITH OCCT-exception-1.0); the kernel's own THIRD_PARTY_NOTICES.md ships beside it in dist/meshio/. The full artifact is loaded lazily, only where the lean build cannot serve, and this page is what that unlocks: opening CAD files, exporting a fitted STEP solid, glTF scenes, and IGA refinement of NURBS.
mesh_capabilities reports both builds live — the lean one's flavour and libraries, and the full artifact's own backend, flags and reader/writer lists — so an agent (or you, in a pinch) can ask what is linked instead of trusting this page.
Opening STEP, IGES and glTF
.step/.stp, .igs/.iges and .glb/.gltf open in the mesh preview like any other format. The CAD readers need OpenCASCADE, which only the full artifact links, so those extensions dispatch to it directly; glTF is served by the lean build. What you get is the solid's triangulation — the same surface preview every other format gives, with fields where the file carries any.
These are static previews by design: a STEP file holds one solid, and a folder of .step files is a set of parts, not a <prefix>_<rank>_<step> series — so there is no timeline bar and no directory watcher, and nothing is invented.
A .glb is one self-contained file. A .gltf (the JSON spelling) carries its heavy arrays in external files the JSON's buffers[].uri names — meshio++'s own writer emits <stem>.bin beside it — and those are staged with the scene, so both flavours open; a self-contained (data-URI) .gltf reads alone.
Exporting STEP: Skin to CAD
File ▸ Export ▸ CAD ▸ STEP (CAD, auto-fitted) is one target with auto-fit behaviour, and the export asks once how far to take it:
| Choice | What runs |
|---|---|
| Fit CAD surfaces — kernel defaults (recommended) | fitCad segments the closed skin itself and fits every patch: an analytic face where a primitive fits (plane/sphere/cylinder/cone/torus, fillets, blends, threads), a freeform B-spline patch where none does, a faceted patch as the last demotion |
| Fit CAD surfaces — refine the patches first | For a skin that is not a CAD tessellation (a topology optimizer's output, a scan), where the defaults leave almost everything faceted. Sends {refineSegmentation: true, refineTolerance: 0.002}: patch boundaries are moved onto the surfaces before fitting. On the 22 236-triangle hook a Structural Optimization run ends with, the faceted area fell from 94.7 % to 41.9 % (20 planes, 13 cylinders, 7 cones, 12 B-splines, a sphere, 3 tori) in about three minutes; the solid may lie up to ~0.4 % of the diagonal from the skin |
| Fit — analytic primitives only | No freeform B-spline or thread faces; everything they would have covered stays faceted. Faster, and portable where a receiver accepts only analytic surfaces |
| Skip fitting — faceted solid (fast) | The skin's triangles as planar faces, exactly like the faceted writer always wrote |
The fit runs on the model's boundary skin: a volume model exports its boundary as the solid (the export says so — STEP: volume interiors are not representable — wrote the boundary skin (N faces)), because the underlying writer treats every cell it is handed as surface.
What you get back, and how you know which path ran:
- A fitted export writes the AP214 STEP and states the census in its diagnostics and the export report — for a unit box:
STEP: fitted CAD B-rep via fitCad — 6 planes (closed solid; skin deviation 1.24e-16 (RMS 5.12e-17)); the B-rep also rides beside it as box.cad.json.The<stem>.cad.jsoncompanion is the fitted solid's B-rep in Kratos CAD JSON shape, written automatically beside the.step. - How far the solid lies from your mesh. Every fit measures the fitted B-rep against the skin it was fitted from and reports it:
skin deviationis the largest distance between a face and the skin, with its RMS beside it — the tessellation's own chordal error, not the fit's tolerance (which is reported separately asmax deviation). A unit box fits exactly (1.24e-16); a cylinder tessellated with 32 facets lands 4.81e-3 from its skin. The fit also states whether the solid came out closed (every edge of a shell borders two faces), so an open shell shows up in the message instead of in the CAD reader later. PassfaceDeviation: falsein thefitCadobject to switch the measurement off. - A skipped or failed fit falls back to the faceted STEP writer (planar faces, no B-rep, no companion) and says so, naming why. The faceted writer refuses an open or inconsistently wound surface by name (
… is not a closed, consistently wound solid: N open … run repair first), and the eligibility check refuses the same thing up front with the remedy: Extract Skin gives a volume mesh its boundary, Repair closes a leaky surface.
The export report (and verify) treat the .step like any other target — the file is re-read through the CAD reader when verification is on.
Command line
export step to=solid.step # the fit ask appears, then the save dialog
export step to=solid.step overwrite=trueExporting an optimization result as CAD
An optimization run's final geometry goes through the same STEP pipeline: the Kratos Runs context menu (Export Optimized Geometry as CAD…) and the Optimization progress panel's Export CAD button build the solid from the latest finished design-field frame and ask the same fit question. Two modes, because the two control families leave different geometry behind:
- Shape writes the frame mesh: vertex morphing moves the nodes, so the frame is the optimized geometry.
- Topology contours a density field at a threshold first (an Elemental field is averaged to the nodes; a threshold the field never crosses is a refusal, not an empty solid). The threshold is always reported because it defines the solid.
MCP: { "tool": "case_optimization_cad", "args": { "meshPath": "case.mdpa", "mode": "topology", "densityField": "DENSITY", "threshold": 0.5 } } — same core, outputPath defaulting to <case>_optimized.step, fitCad as in mesh_convert, excludeNewest dropping a possibly half-written newest frame.
IGA: refining and sampling NURBS
A read of a format whose side channel is a NURBS payload — a .cad.json, a .step/.igs file, or an MFEM NURBS .mesh — keeps the patches on the model. mesh_info reports them: nurbs: {format, patches, faces}. Two mesh_derive kinds run the IGA kernel (the lean build — no OpenCASCADE needed) on that channel:
| Kind | What it does | Options |
|---|---|---|
igaRefine | h/p-refines the patches | subdivisions per knot span and direction (one number or one per direction), elevate degree elevation, B-rep edges/trim |
igaSample | tessellates the patches into Lagrange cells | sampleSubdivisions (cells per knot span; 0 = automatic), trimSamples (points per knot span a trimming curve gets), edges, trim |
Both are derived meshes — the source is untouched — and neither carries the side channel on its result, because the chain runs through files: write .cad.json (or an MFEM NURBS .mesh) and reopen it to refine again.
The side channel has one sharp rule: any applied edit drops it. A mesh you translated, remeshed or merged is no longer the patches' sampling — upstream refuses exactly that ("a mesh moved or edited since") — so the channel survives only while the model is unedited (undo back to the pristine read brings it back). mesh_info's nurbs section is the tell: if it is there, IGA will run.
MCP
// Read a CAD solid (the full artifact's reader) — mesh_info reports its B-rep:
{ "tool": "mesh_info", "args": { "path": "bracket.step" } }
// → { "format": ".step", "nodeCount": 1758, …, "nurbs": { "format": "cad_json", "patches": 6, "faces": 6 } }
// Export a fitted STEP from a closed triangle skin, with fit controls:
{ "tool": "mesh_convert", "args": { "path": "skin.vtu", "outputPath": "solid.step",
"fitCad": { "tolerance": 0.05, "freeform": false } } }
// → solid.step + solid.cad.json; report.warnings name the fitted face types.
// Refine the B-rep you just wrote, then sample it:
{ "tool": "mesh_derive", "args": { "path": "solid.cad.json", "kind": "igaRefine",
"subdivisions": 2, "outputPath": "refined.vtu" } }
{ "tool": "mesh_derive", "args": { "path": "solid.cad.json", "kind": "igaSample",
"sampleSubdivisions": 24, "outputPath": "sampled.vtu" } }mesh_convert's fitCad object takes upstream's options minus stepPath (the write owns that path); unknown keys are refused by the kernel itself, and fitCad: false writes the faceted solid only. The fit always measures the solid against the skin (faceDeviation, meshio++ 17.11.0) unless the object says faceDeviation: false.
What is deliberately not here
- Segmentation is an op, the fit is a report:
segmentandrefinePatchesare ordinary Mesh Modification operations that write aSEGMENT_PATCHfield, andmesh_fit_primitivesnames what each patch is (plane, sphere, cylinder, cone, torus, with radius and axis).fitCadstill segments the skin itself for the STEP export, and its fit controls are the export's. - Segmentation refinement is a choice, not the default: meshio++ 17.11.0 can refine the segmentation before fitting (
refineSegmentation, tolerancerefineTolerance; the standalonerefinePatchesstays unexposed). On the CAD-style shapes first tried it changed nothing — 0 iterations, 0 splits, 0 merges — at about 30 % more time, which is why the kernel defaults stay first; on a rough skin it is the difference between a faceted solid and one with analytic faces (see the table above). The freeform modes (freeformModeadaptive/network/auto) are not in the pick: on a 3 000-triangle copy of the same hook they left ~71–74 % faceted (against 94.7 %) but took 8–12x the time (200–325 s against 27 s) with a larger skin deviation (~0.8 against 0.13), and on the full 22k-triangle skinautohad not finished after 45 minutes. They remain available as MCPfitCadoptions; try refinement first. - No
igaEvaluate: patch-wise parametric evaluation is reachable later if a workflow asks for it; refine and sample cover the mesh workflows. - No glTF writer routing: the kernel has one, and an explicit
outputFormat: "gltf"reaches it throughmesh_convert, but no extension maps to it — nothing in this extension emits glTF yet.

Meshing the faces of a B-rep
Advanced ▸ Mesh CAD faces… (and mesh_derive with kind: "cadSurface") triangulates the faces of the open .cad.json (or a STEP/IGES read) into one conforming surface mesh, one SubModelPart and a CAD_FACE value per face. Since meshio++ 17.21.0 it can also refine to a minimum triangle angle, smooth, produce curved quadratic triangle6/line3 elements (order: 2; order 3 is refused because the extension cannot hold triangle10/line4), size the mesh from curvature, gaps (cellsAcrossGaps), per-face / per-edge sizes or a background size field from another mesh, and mesh sliver faces with a neighbour. Every option is optional; see the mesh editing guide for the JSON prompt and what the summary reports.