Skip to content

CAD, glTF and IGA ​

A STEP solid opened as a triangle-skin preview beside the File ▸ Export ▸ CAD submenu, and the fit ask for the STEP export

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:

ChoiceWhat 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 firstFor 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 onlyNo 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.json companion 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 deviation is 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 as max 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. Pass faceDeviation: false in the fitCad object 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=true

Exporting 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:

KindWhat it doesOptions
igaRefineh/p-refines the patchessubdivisions per knot span and direction (one number or one per direction), elevate degree elevation, B-rep edges/trim
igaSampletessellates the patches into Lagrange cellssampleSubdivisions (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 ​

jsonc
// 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: segment and refinePatches are ordinary Mesh Modification operations that write a SEGMENT_PATCH field, and mesh_fit_primitives names what each patch is (plane, sphere, cylinder, cone, torus, with radius and axis). fitCad still 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, tolerance refineTolerance; the standalone refinePatches stays 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 (freeformMode adaptive/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 skin auto had not finished after 45 minutes. They remain available as MCP fitCad options; 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 through mesh_convert, but no extension maps to it — nothing in this extension emits glTF yet.

A NURBS box refined with igaRefine and re-sampled, shown beside mesh_info's nurbs section

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.

Released under the GNU AGPL-3.0-or-later License.