C API
Two headers, two shared libraries: cadaclysm.h over cadaclysm_capi — reading, meshing, writing — and cadaclysm_blacksmith.h over cadaclysm_blacksmith, the modelling kernel. Plain C, callable from C++ and from any language with a foreign-function interface.
How every call behaves
- Handles.
cadaclysm_openreturns aCadaclysmScene *, freed bycadaclysm_close; the kernel's solids, profiles, paths and sweep paths each have a…_free. A null handle is a failure. - Errors. A call that fails returns null,
false, zero orCADACLYSM_NONEand leaves its reason incadaclysm_last_error()(cadaclysm_blacksmith_last_error()): per thread, valid until the next call on that thread. - Borrowed memory. Strings, meshes and polylines the reader returns point into the scene and live until
cadaclysm_close; copy what must outlive it. The kernel's meshes live in the solid's cache until it is freed or meshed at another tolerance. The one string the caller frees is the kernel's STEP text, withcadaclysm_blacksmith_string_free. - Sized buffers. Where the caller provides the memory, as
cadaclysm_querydoes, call once with a null buffer to learn the count, then again with room for it. - Nodes are indices. A node is a
uint32_tinto its scene,CADACLYSM_NONEfor none; a placement likewise. - Options. Fill a
CadaclysmOpenOptionswithcadaclysm_open_options_initbefore setting fields, so a library newer than your code still reads a valid struct.
The headers are in the SDK's include/; every other language on these pages is a wrapper over exactly these calls. This page is generated from the headers' own comments.
cadaclysm.h
Library and licence
cadaclysm_last_error
const char *cadaclysm_last_error(void);
Why the last call failed, or null if none has.
Borrowed, and good until the next call on this thread.
cadaclysm_version
const char *cadaclysm_version(void);
The library's version, as "0.1.0". Static; never freed.
cadaclysm_license_set
bool cadaclysm_license_set(const char *text_or_path);
Load a license from text_or_path: the certificate text itself, or the path of a file holding it. Replaces the one in use. Null forgets the one in use, so the next call resolves from the environment and the search paths again. Returns false without changing anything when the text does not verify, with the reason at cadaclysm_last_error.
cadaclysm_license_info
const char *cadaclysm_license_info(void);
The license in use, as one line — customer=Acme Ltd expiry=2027-09-15 entitlements=import,kernel seats=20 — or, without one, unlicensed (unlicensed -- <reason> when a license was found but did not verify). Never null. Borrowed, and good until the next call on this thread.
cadaclysm_license_notice_count
uint64_t cadaclysm_license_notice_count(void);
How many unlicensed notices this library has printed in this process; an application can show its own banner instead of the stderr line.
cadaclysm_build_date
const char *cadaclysm_build_date(void);
The date this library was built, "YYYY-MM-DD". A paid license is good for every build dated on or before the day its updates end. Static; never freed.
Opening and closing
cadaclysm_open
struct CadaclysmScene *cadaclysm_open(const char *path,
const struct CadaclysmOpenOptions *options);
Open a CAD file, or a .zip holding one.
path names the model. schema names the EXPRESS schema (.exp) it conforms to, which only STEP and IFC need — they name one and cannot be read without it. An IGES file, a .brep and a .3dm all say what they mean in themselves, so pass null for those.
A .zip opens its shallowest readable member, or the one options->pick names, with the rest of the archive standing in for the file's own directory — an include <lib/box.scad> inside the archive finds the archive's own lib/box.scad. cadaclysm_source_name says which member was chosen.
cadaclysm_open_memory
struct CadaclysmScene *cadaclysm_open_memory(const uint8_t *bytes,
size_t length,
const char *format,
const struct CadaclysmOpenOptions *options);
Open a CAD file already in memory, or a .zip holding one.
format names the kind as an extension would — "step", "ifc", "igs", "brep", "3dm", "scad", "sat", "zip" — since there is no file name to take it from. A leading dot is allowed and ignored.
Those are examples rather than the whole list, which depends on the readers this build was compiled with: cadaclysm_format_extensions is what it actually reads, and the error returned when format is missing names every one of them. Prefer either to a list written out by hand — this doc comment carried one for a while that had gone stale.
cadaclysm_open_options_init
void cadaclysm_open_options_init(struct CadaclysmOpenOptions *options);
Fill options with its size and every default.
The alternative is a caller zeroing the struct themselves and setting size by hand, which works and is one more thing to get wrong.
cadaclysm_close
void cadaclysm_close(struct CadaclysmScene *scene);
Give a scene back. Null is accepted and does nothing.
Everything borrowed from it — names, meshes, attribute text — is invalid afterwards.
cadaclysm_source_name
const char *cadaclysm_source_name(const struct CadaclysmScene *scene);
The archive member a scene was read from, or null for a plain file.
What a viewer titles its window with: cadaclysm_open on a zip chose one member, and this is the only way to learn which. Borrowed from the scene, valid until cadaclysm_close.
The scene
cadaclysm_node_count
uint32_t cadaclysm_node_count(const struct CadaclysmScene *scene);
How many parts it has, geometry or not.
cadaclysm_root_count
uint32_t cadaclysm_root_count(const struct CadaclysmScene *scene);
How many parts nothing else contains.
cadaclysm_root
uint32_t cadaclysm_root(const struct CadaclysmScene *scene, uint32_t index);
One of them, or CADACLYSM_NONE past the end.
cadaclysm_schema
const char *cadaclysm_schema(const struct CadaclysmScene *scene);
The schema the file named, or "" for a format that names none.
cadaclysm_schema_read
const char *cadaclysm_schema_read(const struct CadaclysmScene *scene);
What one length in the file is worth in metres, or 1 where it did not say.
cadaclysm_bounds
struct CadaclysmBounds cadaclysm_bounds(const struct CadaclysmScene *scene);
Everything the model covers, in world coordinates.
The one figure here that is not in a part's own frame, because a bounding box over the whole model has no other frame to be in. It is the union of exactly what iterating the parts would draw: every part cadaclysm_node_can_mesh answers true for, its extent carried through its own transform — the eight corners, since a rotated box's corners are what reach furthest.
This meshes all of it, being the only way to know how far it reaches. A caller that has not the time should frame from the parts it has built.
cadaclysm_query
uint32_t cadaclysm_query(const struct CadaclysmScene *scene,
const char *filter,
uint32_t *out,
uint32_t capacity);
The nodes matching filter, as indices into the scene's node list.
Returns the total number of matches and writes up to capacity of them into out. Call once with capacity = 0 and a null out to learn the size, then again to fill a buffer. A filter that will not parse returns 0 with the reason at cadaclysm_last_error.
A 0 is ambiguous unless you check the error, errno-style. Zero matches is the common, legitimate outcome of a search — a typo'd filter must not look the same as a filter that correctly found nothing, which is the one failure mode this function exists to avoid. So this call — and only this one, not every entry point in this library — clears cadaclysm_last_error to null before it does anything else. With a valid scene, a 0 return paired with a null error is a real empty result, and a 0 paired with a non-null error is a parse failure. A null scene is the one case that does not fit this: like every other accessor here, it is silently neutral rather than an error, so it too returns 0 with a null error — that reflects a null handle, not a filter that matched nothing, and is the caller's own bug to find rather than this function's to report.
cadaclysm_diagnostic_count
uint32_t cadaclysm_diagnostic_count(const struct CadaclysmScene *scene);
How many things this file held that the reader could not build.
cadaclysm_diagnostic
const char *cadaclysm_diagnostic(const struct CadaclysmScene *scene, uint32_t index);
One of them, or null past the end. Borrows from the scene.
cadaclysm_geometry_diagnostic_count
uint32_t cadaclysm_geometry_diagnostic_count(const struct CadaclysmScene *scene);
How many things meshing has complained about so far.
cadaclysm_diagnostic answers for the *parse*: a fixed list, settled the moment the file was read, of what the reader could not build. This answers for the tessellation, which is a different question asked of a different stage — a face whose boundary encloses no region is perfectly well parsed and simply cannot be meshed.
It reports on what has been meshed, and meshing is lazy, so this is empty until something has asked for geometry and grows as more is asked for. Call it after cadaclysm_realize_all, or after meshing whatever parts matter; calling it straight after cadaclysm_open is asking what meshing found before any has happened, and the honest answer to that is none.
cadaclysm_geometry_diagnostic
const char *cadaclysm_geometry_diagnostic(const struct CadaclysmScene *scene, uint32_t index);
One of them, or null past the end.
Borrows from the scene, and only until the next cadaclysm_geometry_diagnostic_count — see there.
cadaclysm_surface_matrix
void cadaclysm_surface_matrix(const struct CadaclysmScene *scene, float *out);
The matrix that takes cadaclysm_node_surfaces into the space everything else is already in, written to out as sixteen floats, column-major.
Only the surfaces need it. Meshes, polylines and Bézier curves arrive in the convention the document was opened with; the surfaces do not, because converting a surface means converting its parameter space too — a cylinder's v is a length and scales, a sphere's is an angle and does not — and getting that wrong moves the trim loops off the face they trim. Handing over the matrix leaves the one product that is unconverted plainly unconverted, rather than converted in a way that is wrong for two of the eight kinds.
Compose it on the left of a node's placement. For a document opened NATIVE at the file's own units this is the identity, and a caller may skip it.
cadaclysm_scene_save
bool cadaclysm_scene_save(const struct CadaclysmScene *scene,
const char *path,
const char *format);
Write the whole scene — every placement of every shape, named and placed as the document's tree is, with a material per colour — to path as glTF or OBJ. False on failure, with cadaclysm_last_error saying why.
format is "glb" (binary, one file), "gltf" (JSON with the vertex buffer embedded, also one file) or "obj" (Wavefront text, every placement baked to its own named object, with a .mtl written beside it under the same stem when anything has a colour). These hold a scene where the cadaclysm_mesh_format rows write one node's mesh; the same names there are the one-mesh forms. Any other name is refused.
Coordinates are the scene's own, in the space it was opened into: a scene opened as CADACLYSM_Y_UP writes the Y-up metres glTF specifies, and one opened CADACLYSM_NATIVE writes the file's own axes and units. Winding is turned for a clockwise convention, as cadaclysm_node_save_mesh turns it, and for the same reason: a file is not a frame.
Nodes
cadaclysm_node_parent
uint32_t cadaclysm_node_parent(const struct CadaclysmScene *scene, uint32_t node);
The part containing this one, or CADACLYSM_NONE for a root.
cadaclysm_node_child_count
uint32_t cadaclysm_node_child_count(const struct CadaclysmScene *scene, uint32_t node);
How many parts this one contains directly.
cadaclysm_node_child
uint32_t cadaclysm_node_child(const struct CadaclysmScene *scene,
uint32_t node,
uint32_t index);
One of them, or CADACLYSM_NONE past the end.
cadaclysm_node_depth
uint32_t cadaclysm_node_depth(const struct CadaclysmScene *scene, uint32_t node);
How far down the tree it sits, a root being zero. For indenting.
Walked from the parent chain rather than stored — CadDocument keeps the links and not the depth, and the answer is the same. The step count is bounded by the node count, so a malformed cycle cannot spin here.
cadaclysm_node_name
const char *cadaclysm_node_name(const struct CadaclysmScene *scene, uint32_t node);
Its name, or null past the end.
cadaclysm_node_kind
const char *cadaclysm_node_kind(const struct CadaclysmScene *scene, uint32_t node);
What the file calls it — an IFC type, an openNURBS class, a shape kind. Null past the end.
cadaclysm_node_id
const char *cadaclysm_node_id(const struct CadaclysmScene *scene, uint32_t node);
What the file calls it — a STEP #N, an IFC GlobalId, a Rhino UUID — or its position for a format with no such notion. Null past the end.
A string rather than a number, because that is what the formats carry: a .3dm object is named by a UUID and an IFC product by a 22-character GlobalId, neither of which fits in a uint64_t. A caller matching ids compares the text.
cadaclysm_node_color
bool cadaclysm_node_color(const struct CadaclysmScene *scene, uint32_t node, float *rgba);
Its colour into rgba, returning whether the file gave one.
Where it did not, rgba is left alone and the caller should use its own — which is the honest answer, most STEP files carrying no colour at all.
Its own colour where it has one, and the colour of the shape it draws otherwise. Both halves matter: an occurrence that overrides its shape's colour — the same window type in white on one storey and grey on the next — must keep the override, while one that says nothing about colour should be drawn in the colour of the geometry it actually puts on screen rather than falling back to the caller's default beside identical parts that are coloured. This is the same CadaclysmScene::shape_of hop cadaclysm_node_mesh follows.
cadaclysm_node_transform
void cadaclysm_node_transform(const struct CadaclysmScene *scene, uint32_t node, double *out);
Where this part's geometry sits, as a 4x4 column-major matrix.
A mesh is in its part's own frame; this carries it to world. Doubles, while the mesh is floats, on purpose: a building at UTM coordinates baked into f32 world positions loses millimetres, where an f32 mesh about its own origin under an f64 transform does not. It is also what lets one mesh be drawn at many placements instead of being copied per placement.
cadaclysm_node_attribute_count
uint32_t cadaclysm_node_attribute_count(const struct CadaclysmScene *scene, uint32_t node);
How many things the file said about this part.
cadaclysm_node_attribute
struct CadaclysmAttribute cadaclysm_node_attribute(const struct CadaclysmScene *scene,
uint32_t node,
uint32_t index);
One of them, or an all-zero one past the end.
cadaclysm_node_can_mesh
bool cadaclysm_node_can_mesh(const struct CadaclysmScene *scene, uint32_t node);
Whether this part is drawn — whether it has geometry of its own to show.
Asks for nothing to be built, and says nothing about whether it has been. Most parts of a model are structure — an assembly, a storey, a layer — and answer false.
Geometry, not triangles: a part drawn as a *curve* — a .3dm curve object, a STEP geometrically-bounded curve — answers true and gives an empty cadaclysm_node_mesh, having no surface to triangulate. A caller drawing solids alone can pass over an empty mesh; one that wants the wireframe reads cadaclysm_node_curves.
cadaclysm_node_visible
bool cadaclysm_node_visible(const struct CadaclysmScene *scene, uint32_t node);
Whether the file says this node should be shown when it is opened.
The file's opening state, not a live switch. A caller that wants to open a document the way its author left it hides what this reports false for; from then on, what is shown is the caller's business. Nothing here changes, and nothing else in this ABI consults it — a hidden node still meshes, still has bounds, and is still drawn by anything that ignores this.
True where the format says nothing, which is most of them. Today only IGES (an entity's blank status) and Rhino .3dm (an object or a layer switched off) report otherwise; STEP's invisibility and IFC's IfcPresentationLayerWithStyle.LayerOn are not read yet, so their documents answer true throughout.
cadaclysm_node_instance_of
uint32_t cadaclysm_node_instance_of(const struct CadaclysmScene *scene, uint32_t node);
The part whose geometry this one is a placement of, or CADACLYSM_NONE.
The point of handing meshes over in their own frame: a shell placed seventy- four times is one mesh and seventy-four transforms, and this is how a caller knows to upload the buffer once.
cadaclysm_node_select_as
uint32_t cadaclysm_node_select_as(const struct CadaclysmScene *scene, uint32_t node);
What a click on this part's geometry should select — itself, usually.
A format that hangs geometry on a child of the object it belongs to (IFC: a representation item under its product) points the child back at the object, so picking the shape selects the thing while the shape stays a part of its own to inspect.
cadaclysm_node_generator
const char *cadaclysm_node_generator(const struct CadaclysmScene *scene, uint32_t node);
What this part's geometry was before it was triangles — "brep", "mesh", "csg". A .3dm holds several kinds side by side and a native mesh calling itself a brep is a plain lie, so this is per part rather than per file.
Null for a part that draws nothing, since there is then no geometry to have come from anything, exactly as cadaclysm_node_mesh is empty there.
cadaclysm_node_save_mesh
bool cadaclysm_node_save_mesh(const struct CadaclysmScene *scene,
uint32_t node,
const char *path,
const char *format);
Write one node's mesh to path in the named format. False on failure, with cadaclysm_last_error saying why.
format is one of the names cadaclysm_mesh_format reports. A node that draws nothing — an assembly, a layer, an empty definition — is a failure rather than an empty file, since a caller asking to export it has almost certainly clicked the wrong row.
The mesh is the node's own, in the space the scene was opened into, and carries no placement: a node instanced six times exports once, where it is defined. Exporting where a *drawing* sits is a different question and this is not it.
Placements
cadaclysm_placement_count
uint32_t cadaclysm_placement_count(const struct CadaclysmScene *scene);
How many drawings this document asks for.
So a renderer walks these, and asks cadaclysm_placement_geometry which part's mesh each one draws and cadaclysm_placement_transform where to put it. Parts remain what a *tree* is built from — names, properties, hierarchy, selection.
cadaclysm_placement_geometry
uint32_t cadaclysm_placement_geometry(const struct CadaclysmScene *scene, uint32_t placement);
Which part's geometry this drawing draws.
Ask that part for its mesh, edges or curves as usual. Two drawings of one shape name the same part and so hand back the same pointers, which is what lets a caller upload it once and draw it at both transforms.
CADACLYSM_NONE for an index past cadaclysm_placement_count.
cadaclysm_placement_select
uint32_t cadaclysm_placement_select(const struct CadaclysmScene *scene, uint32_t placement);
What a click on this drawing should select.
The placement rather than the shape it draws: the shape is somewhere else and is shared with every sibling copy, so selecting it would light them all up.
CADACLYSM_NONE for an index past cadaclysm_placement_count.
cadaclysm_placement_transform
void cadaclysm_placement_transform(const struct CadaclysmScene *scene,
uint32_t placement,
double *out);
Where this drawing sits, as a 4x4 column-major matrix.
Already composed through every frame between the document's root and the drawing, so a caller multiplies nothing itself. Doubles for the reason cadaclysm_node_transform gives.
Geometry
cadaclysm_node_mesh
struct CadaclysmMesh cadaclysm_node_mesh(const struct CadaclysmScene *scene, uint32_t node);
Its triangles, in their own frame and built now if they have not been.
cadaclysm_node_transform says where that frame sits in the world, and it is always *this* part's transform — the one that places the triangles below.
Where the part instances another, these are the instanced part's triangles, in the instanced part's frame. Two occurrences of one shape therefore hand back the same pointers and two different transforms, which is what lets a caller upload the mesh once. Asking the shared shape itself for its mesh gives the same answer again.
cadaclysm_node_triangle_estimate
int64_t cadaclysm_node_triangle_estimate(const struct CadaclysmScene *scene, uint32_t node);
About how many triangles cadaclysm_node_mesh would give for this part, without building it — for a caller sizing a budget before it meshes: what to skip, what to take at a coarser level, how much memory a scene will want. Follows the same CadaclysmScene::shape_of hop the mesh does, so an instance answers for what it draws.
An estimate, from the reader's own knowledge of the part. A b-rep (STEP, ACIS, OCCT .brep, an IGES trimmed surface, a Rhino brep without a render mesh) is counted the way its mesher will count it — the same trims, the same grid stations — and lands within a few tens of percent of the mesh; an untrimmed IGES patch is its grid, exact; a stored or already-evaluated mesh (a .brep file's triangulation, a Rhino mesh or render mesh, an OpenSCAD model, a mesh built by an earlier call) is exact; an IFC product is exact for its extrusions and within a weld for its faceted parts. -1 where the reader cannot say without doing the work — a Rhino extrusion or SubD — and for a node that draws nothing. A caller summing a document treats -1 as unknown, not as zero.
Cheap next to meshing but not free: a STEP body is built as a b-rep to be counted, a tenth or so of meshing it.
cadaclysm_node_surface_proxy_mesh
struct CadaclysmMesh cadaclysm_node_surface_proxy_mesh(const struct CadaclysmScene *scene,
uint32_t node,
uint32_t cells);
A coarse mesh over this part's surfaces, for the things that need triangles and not a picture. All five pointers borrow from the scene.
What it is for. A part drawn from its surfaces — see cadaclysm_realize_meshes — has no triangles, and under ray tracing it is simply absent: it casts no traced shadow, does not occlude or bounce Lumen's light, and has no distance field. Everything that traces wants triangles, and nothing that traces wants *good* ones. This grids each face cells by cells over its trim window, keeps a cell whose centre lies inside the trims, and lays two triangles across it — no welding, no chord error, a hole smaller than a cell vanishes. Never drawn; a consumer feeds it to its acceleration structure and its distance field and leaves the picture to cadaclysm_node_surfaces.
In the space everything else is in, like cadaclysm_node_mesh — a mesh converts like any other, where the surfaces it came from do not.
cadaclysm_node_bounds
struct CadaclysmBounds cadaclysm_node_bounds(const struct CadaclysmScene *scene, uint32_t node);
The extent of the geometry this part draws, in that geometry's own frame, building it if it has not been built. Carry it through cadaclysm_node_transform for world coordinates, exactly as with the mesh it bounds — and through the same indirection, so a part that instances another is bounded by what it actually draws.
cadaclysm_node_bounds_placed
struct CadaclysmBounds cadaclysm_node_bounds_placed(const struct CadaclysmScene *scene,
uint32_t node,
const double *placement);
The extent of what this part draws under a placement, for a part drawn from its surfaces: every sample the bounds are taken from is carried through the document's convention and then placement — sixteen doubles, column-major, as cadaclysm_placement_transform writes them — before it is boxed. The box of the placed surfaces, in the placement's target frame: the same space the placed mesh would be in, the convention included, since the surfaces alone are handed over unconverted (see cadaclysm_surface_matrix) and a placement acts on converted points.
Not the placed box of the surfaces. A caller with only cadaclysm_node_bounds carries its eight corners through the placement, and the box of a rotated box is bigger than the box of the rotated points: by 8% on an assembly of rotated parts, with the centre off by 4% of the diagonal, against the box the mesh path finds from its placed vertices. This closes that gap for the surface path.
Builds the surfaces if they are not built, which a part drawn from them already has. All zeros for a part with no surfaces — a caller then places the corners of cadaclysm_node_bounds as before.
cadaclysm_node_collision
bool cadaclysm_node_collision(const struct CadaclysmScene *scene,
uint32_t node,
uint32_t hull_budget,
struct CadaclysmCollision *out);
The collision body for what this node draws, building its mesh if it has not been built — the same cost cadaclysm_node_bounds already carries.
hull_budget is the most triangles a hull may have; zero asks for CADACLYSM_UNITY_HULL_LIMIT. It is not clamped to it: how closely a hull should follow its part is a trade against simulation cost and belongs to whoever is building the game — Unreal's FKConvexElem has no cap at all. A hull above the limit cannot be used as a Unity convex MeshCollider, which hard-fails there.
Set out->size to sizeof(CadaclysmCollision) before calling. Returns false, writing nothing, for a null scene, an unknown node, one that draws nothing, or an out too small to hold the fields this build writes.
cadaclysm_node_collision_hull
struct CadaclysmCollisionHull cadaclysm_node_collision_hull(const struct CadaclysmScene *scene,
uint32_t node,
uint32_t hull_budget);
The convex hull for what this node draws, or all nulls where its shape is not a hull. Builds the node's mesh if it has not been built.
The pointers borrow from the scene and are good until it is closed or until this node is asked for a different hull_budget, which refits it and frees what the previous call returned. A caller that holds hulls should hold one budget.
cadaclysm_lod_levels
uint32_t cadaclysm_lod_levels(void);
The same, as a function, for a caller that binds to symbols rather than to a header.
cadaclysm_node_mesh_lod
struct CadaclysmMesh cadaclysm_node_mesh_lod(const struct CadaclysmScene *scene,
uint32_t node,
uint32_t level);
A part's triangles at a coarser level of detail.
level 0 is the mesh itself and gives exactly what cadaclysm_node_mesh does; 1 up to CADACLYSM_LOD_LEVELS are progressively coarser, each about a quarter of the triangles of the one before. Past that is an empty mesh.
Every level shares the level-0 vertices. positions, normals and uvs are the same pointers and the same vertex_count at every level — only indices and index_count differ. A caller uploads the vertices once and switches level by drawing a different range, which is what the levels are built the way they are to allow: see cadaclysm-lod, which collapses each edge onto an endpoint it already had rather than to a new point.
cadaclysm_node_lod_error
float cadaclysm_node_lod_error(const struct CadaclysmScene *scene,
uint32_t node,
uint32_t level);
How far a level moved the surface, in the scene's own units — 0 at level 0, and 0 for a level that does not exist.
This is what a renderer should choose levels by, rather than by how large a part is on screen. Size says nothing about whether coarsening would show: a smooth cylinder loses three quarters of its triangles without moving its surface a thousandth of its radius, and a part covered in small features cannot lose one without it being visible. Projected through the view, this number is how many pixels wrong the level would look — which is the question actually being asked.
Measured on a radius-10 sphere: 0.027 at the first level, 0.203 at the second, 1.606 at the third.
cadaclysm_node_edges
struct CadaclysmPolylines cadaclysm_node_edges(const struct CadaclysmScene *scene,
uint32_t node);
This part's feature edges, as polylines to draw an overlay from.
Flattened by the document at the part's own scale — capi does not choose a tolerance, here or anywhere. Built on first ask and kept; both pointers borrow from the scene.
Where the part instances another, these are the instanced part's edges, the same indirection cadaclysm_node_mesh follows — an occurrence carries no geometry of its own, so its edges are the shape's it instances.
cadaclysm_node_surface_edges
struct CadaclysmPolylines cadaclysm_node_surface_edges(const struct CadaclysmScene *scene,
uint32_t node);
This part's face boundaries, taken from its trimmed surfaces. Both pointers borrow from the scene.
The outline that costs no tessellation, and the reason it exists. Both other edge products mesh the part: cadaclysm_node_edges hands back the chords the mesher walked, and cadaclysm_node_edge_beziers resolves the mesh too, because a B-rep reader gives one builder that produces triangles and edges together. So a caller that carefully skipped meshing a body — see cadaclysm_realize_meshes — gets every triangle back the moment it draws an outline, and the memory with them.
These are the same trim loops cadaclysm_node_surfaces hands to a shader, evaluated through their surfaces, so the outline sits on the surface actually being drawn. Against triangles it is the wrong product and cadaclysm_node_edges is the right one: the surface is not where the triangles are, and an outline drawn on it z-fights them. That is the choice, and it belongs to whoever knows which of the two is on screen.
cadaclysm_node_surface_isocurves
struct CadaclysmPolylines cadaclysm_node_surface_isocurves(const struct CadaclysmScene *scene,
uint32_t node);
This part's isocurves, taken from its trimmed surfaces and clipped to the trims. Both pointers borrow from the scene.
These are the surface's own isoparametric curves: lines at its bend lines where it has them — a NURBS's interior knots, a swept profile's breakpoints — and an even spread where it has none, so a cylinder is not left blank. A flat face gets none: an isocurve across a plane says nothing its boundary has not.
Each line is broken wherever it leaves the trims, so a line crossing a hole comes back as two runs rather than one line straight over it.
cadaclysm_node_edge_beziers
struct CadaclysmBeziers cadaclysm_node_edge_beziers(const struct CadaclysmScene *scene,
uint32_t node);
This part's feature edges as Bézier segments — the exact curves the polylines from cadaclysm_node_edges were flattened from.
cadaclysm_node_surfaces
struct CadaclysmSurfaces cadaclysm_node_surfaces(const struct CadaclysmScene *scene,
uint32_t node);
This part's faces as surfaces and trims — see CadaclysmSurfaces.
The parametric product, and the trimmed one. A face carries the surface it sits on and the loops that cut it, both in that surface's own (u, v), so a caller evaluates the surface at whatever density its view needs and tests the point it already has against the loops. Nothing here was flattened to triangles.
In the file's own frame — see cadaclysm_surface_matrix.
cadaclysm_node_surface_pick
bool cadaclysm_node_surface_pick(const struct CadaclysmScene *scene,
uint32_t node,
const double *from,
const double *to,
double *out_point);
Where a segment first meets one part's surfaces, written to out_point as three doubles. False, writing nothing, where it meets none — or for a null scene, an unknown node, or a part with no surfaces.
Why a caller wants this at all. A part drawn from its surfaces has no triangles to trace against, because cadaclysm_realize_meshes never built them. And a part that does have them is traced only as accurately as it was meshed, so the rim of a hole picks to the mesher's chord error. This answers from the surface itself and tests the trims at the answer's own (u, v), which is exact.
In the surfaces' own frame, like cadaclysm_node_surfaces and for the same reason — see cadaclysm_surface_matrix. A caller holding a ray in the space everything else is in carries it through that matrix's inverse first. That is deliberately its job rather than this function's: a renderer drawing these already built the matrix to draw them with, and a pick that agrees with the picture must use the very same one rather than a second copy composed here.
cadaclysm_node_curve_beziers
struct CadaclysmBeziers cadaclysm_node_curve_beziers(const struct CadaclysmScene *scene,
uint32_t node);
This part's free curves as Bézier segments — see cadaclysm_node_edge_beziers.
cadaclysm_node_isocurve_beziers
struct CadaclysmBeziers cadaclysm_node_isocurve_beziers(const struct CadaclysmScene *scene,
uint32_t node);
This part's isocurves as Bézier segments — see cadaclysm_node_edge_beziers, and cadaclysm_node_isocurves for what an isocurve is.
cadaclysm_node_curves
struct CadaclysmPolylines cadaclysm_node_curves(const struct CadaclysmScene *scene,
uint32_t node);
This part's free curves, as polylines — see cadaclysm_node_edges, which this differs from only in which document method it asks.
cadaclysm_node_isocurves
struct CadaclysmPolylines cadaclysm_node_isocurves(const struct CadaclysmScene *scene,
uint32_t node);
This part's isocurves, as polylines — see cadaclysm_node_edges, which this differs from only in which document method it asks.
An isocurve is one of the interior isoparametric lines across a curved face — a cylinder's mid-height ring, a sphere's meridian — not its boundary. A flat face has none of its own, but where the source format gives no isocurves directly, the document falls back to slicing them from the mesh, and that fallback draws a flat face's own outline rather than nothing — so an empty result here is not guaranteed just because a face is planar.
cadaclysm_node_is_meshed
bool cadaclysm_node_is_meshed(const struct CadaclysmScene *scene, uint32_t node);
Whether this part's mesh has been built and is held, by cadaclysm_realize_all, by an ask for it, or by anything else that needed it — the edge overlay, say. A consumer drawing the part from its surfaces, and meaning never to pay for its triangles, checks it did not. False for a null scene or an unknown part; through the same indirection as the mesh, so a part that instances another answers for what it draws.
cadaclysm_node_brep
const struct CadaclysmBrep *cadaclysm_node_brep(const struct CadaclysmScene *scene,
uint32_t node);
The part's exact brep, for the blacksmith library to operate on (cadaclysm_blacksmith_from_brep): a reference of the caller's own on the scene's brep, not a copy, given back with cadaclysm_brep_release. Good past cadaclysm_close: the brep lives until its last holder lets go. Asking twice gives the same pointer and two references.
In the part's own frame, as cadaclysm_node_mesh is — cadaclysm_node_transform places it — and following the same hop from an instance to the shape it draws. In the file's own units and axes, whatever convention the scene was opened with, as cadaclysm_node_surfaces is: a convention converts what is drawn, and converting a brep would mean rebuilding it.
The blacksmith library must come from the same release as this one, and both must allocate from the process heap — see cadaclysm_blacksmith_from_brep, which checks the first and documents the second.
Building everything
cadaclysm_forget_meshes
void cadaclysm_forget_meshes(struct CadaclysmScene *scene);
Drop every mesh built so far, keeping the document and its node tree.
For a caller that is done with the geometry but not with the file. Names, properties, the shape of the assembly and cadaclysm_query all keep working afterwards; what goes is the built geometry this library was holding beside the copy the caller has already taken. A viewer that has uploaded its meshes to the GPU, or an engine plugin that has copied them into its own buffers, is otherwise paying for two of everything for as long as it keeps the scene open to ask questions of it.
Nothing breaks. A node asked for again simply meshes again — the cache is an optimisation, and this trades time later for memory now. Bounds are kept, being a box per node against a mesh of millions of triangles, and re-deriving one would mean building that mesh again just to cull it.
cadaclysm_realize_all
uint32_t cadaclysm_realize_all(const struct CadaclysmScene *scene);
Build every mesh in the document now, rather than as each is asked for.
Reading is lazy so a caller can put the tree on screen while the shapes are still to come. A caller that would rather pay it all up front — one honest progress bar, or an exporter that needs the lot anyway — calls this. Returns how many parts were realized.
Threaded inside. Call it on one thread and watch it with cadaclysm_realized from another, or stop it with cadaclysm_cancel.
cadaclysm_realize_meshes
uint32_t cadaclysm_realize_meshes(const struct CadaclysmScene *scene, uint32_t skip_surfaced);
As cadaclysm_realize_all, leaving alone every node that carries surfaces where skip_surfaced is non-zero.
What makes surfaces cheaper rather than dearer. A body's mesh is built on demand and cached, so a caller who never asks for it never pays — but cadaclysm_realize_all asks for every node, which is right when triangles are what will be drawn and exactly wrong when they are not. A renderer drawing a part from its surfaces passes a one here, takes that part's bounds from cadaclysm_node_bounds (which falls back to the surfaces), and never asks for its mesh at all.
Nodes without surfaces are realized as usual: a part the reader could not express is drawn from triangles and still needs them.
cadaclysm_realized
uint32_t cadaclysm_realized(const struct CadaclysmScene *scene);
How many parts cadaclysm_realize_all has finished with. Safe to read from another thread while it runs.
cadaclysm_realize_total
uint32_t cadaclysm_realize_total(const struct CadaclysmScene *scene);
How many there will be in all — zero until cadaclysm_realize_all starts.
cadaclysm_cancel
void cadaclysm_cancel(const struct CadaclysmScene *scene);
Ask a running cadaclysm_realize_all to stop.
It stops between parts, so this is prompt without abandoning half a mesh. What was already built is kept, and the scene still answers one part at a time afterwards — cancelling gives up on the batch, not on the document.
One-way, and for the life of the scene. Nothing clears the flag, so every later cadaclysm_realize_all on this scene returns 0 at once. A UI that offers "Cancel" and then "Load anyway" cannot do the second on the same handle: it must close the scene and open the file again. (Clearing the flag when realize_all starts is not the fix — it would race a cancel arriving from another thread just as the batch begins, and swallow it.) Meshes stay available one part at a time either way, so a caller that only wants the model on screen need not reopen.
Formats and dialogs
cadaclysm_mesh_format_count
uint32_t cadaclysm_mesh_format_count(void);
How many formats cadaclysm_node_save_mesh accepts.
A caller builds its export menu from this and its neighbours — cadaclysm_mesh_format_label for what to show, cadaclysm_mesh_format for what to pass back — rather than from a list of its own, so a format added to the library appears in every client that already asks.
cadaclysm_mesh_format
const char *cadaclysm_mesh_format(uint32_t index);
The name of one format, or null past the end.
An identifier, not display text: this is the exact string cadaclysm_node_save_mesh's format parameter accepts back ("stl", "stl-ascii", "msh") — it would read poorly in a menu, which is what cadaclysm_mesh_format_label is for. The pointer is static and stays valid for the life of the process.
cadaclysm_mesh_format_extension
const char *cadaclysm_mesh_format_extension(uint32_t index);
The file extension one format should be written with, without the dot, or null past the end.
Not the format name: stl-ascii writes a .stl. A client naming an output file asks for this rather than deriving it, which is the difference between a format whose name and extension differ working everywhere and working nowhere.
The pointer is static and stays valid for the life of the process.
cadaclysm_mesh_format_label
const char *cadaclysm_mesh_format_label(uint32_t index);
The name a person should see for one format in a save dialog or export menu — "STL (binary)", "STL (ASCII)", "Gmsh" — or null past the end.
Display text, not an identifier: unlike cadaclysm_mesh_format, the string this returns cannot be passed back into cadaclysm_node_save_mesh's format parameter — pass the value from cadaclysm_mesh_format at the same index for that. The two exist side by side because they answer different questions ("what do I show?" vs. "what do I pass back?"), the same split cadaclysm_format_name and cadaclysm_format_extensions have on the read side.
The pointer is static and stays valid for the life of the process.
cadaclysm_format_count
uint32_t cadaclysm_format_count(void);
How many input formats this build can read.
The counterpart to cadaclysm_mesh_format_count, which answers for writing. A host builds a file dialog, an import menu or a drag-and-drop test from these rather than hardcoding extensions, so adding a reader updates it for free.
This is what the build can read, and with the schemas built in it is also what opens: a STEP or IFC file needs an extra schema only where it names one the project does not ship.
cadaclysm_format_name
const char *cadaclysm_format_name(uint32_t index);
The name of one input format — "STEP", "Rhino 3DM". Static; never freed. Null if index is past the end.
Display text, not an identifier: this is for showing to a person, the same kind of string cadaclysm_mesh_format_label returns on the write side — unlike cadaclysm_mesh_format, nothing accepts this string back as input. A reader is chosen by file extension, not by this name, which is why there is no read-side counterpart to format in cadaclysm_node_save_mesh.
cadaclysm_format_extensions
const char *cadaclysm_format_extensions(uint32_t index);
That format's extensions, semicolon-separated and without dots — "step;stp".
Semicolons because that is the separator every platform dialog wants, and because a caller who needs them apart can split more easily than one holding an array can join.
These are exactly the strings cadaclysm_open_memory's format argument accepts, split on ; — a host that recognises .step from this list already knows the value to pass there.
cadaclysm_pick_file
const char *cadaclysm_pick_file(const struct CadaclysmWindow *parent);
Ask the user for a file to open, filtered to what this build can read.
Blocks the calling thread until the user picks or cancels — what a modal dialog is. On macOS, call it from the main thread: a non-windowed process cannot open a dialog off it at all, and the symptom is a hang, not an error.
Returns a UTF-8 path that must not be freed and must not be kept past the next picker call on this thread — the same rule as cadaclysm_last_error. Copy it if it needs to outlive that. NULL means the user cancelled, no dialog was available, or this build has no picker; the reason is at cadaclysm_last_error for the last two, and nothing for the first.
cadaclysm_pick_save
const char *cadaclysm_pick_save(const struct CadaclysmWindow *parent,
const char *suggested_name);
Ask the user where to save, offering the formats this build can write.
Same blocking, threading and lifetime rules as cadaclysm_pick_file.
Meshlets
cadaclysm_meshlets_build
struct CadaclysmMeshlets *cadaclysm_meshlets_build(const float *positions,
const float *normals,
size_t vertex_count,
const uint32_t *indices,
size_t index_count,
uint32_t max_triangles,
uint32_t max_vertices,
int32_t levels);
Split a mesh into meshlets, optionally with the coarser levels above them.
positions and normals are three floats a vertex; normals may be null. indices is three per triangle.
max_triangles and max_vertices are the consumer's own limits, and there is no default, because a meshlet built for one consumer cannot be handed to another: Nanite packs a triangle count in 7 bits and a vertex count in 8, so its limits are 128 and 256, while a mesh-shader pipeline caps at 124 and 64. The vertex cap is the one that bites, since vertices are shared and a meshlet can sit well under its triangle limit while walking past its vertex limit.
cadaclysm_meshlets_count
uint32_t cadaclysm_meshlets_count(const struct CadaclysmMeshlets *handle);
How many meshlets a build came to, over every level.
cadaclysm_meshlet_triangle_count
uint32_t cadaclysm_meshlet_triangle_count(const struct CadaclysmMeshlets *handle,
uint32_t index);
How many triangles one meshlet holds.
cadaclysm_meshlet_vertex_count
uint32_t cadaclysm_meshlet_vertex_count(const struct CadaclysmMeshlets *handle, uint32_t index);
How many vertices one meshlet holds.
cadaclysm_meshlet_level
uint32_t cadaclysm_meshlet_level(const struct CadaclysmMeshlets *handle, uint32_t index);
Which level a meshlet belongs to; 0 is full detail.
cadaclysm_meshlet_group
uint32_t cadaclysm_meshlet_group(const struct CadaclysmMeshlets *handle, uint32_t index);
Which group of its level a meshlet belongs to.
Groups are simplified together, and the boundary between them is what must not move.
cadaclysm_meshlet_error
float cadaclysm_meshlet_error(const struct CadaclysmMeshlets *handle, uint32_t index);
How far a meshlet's geometry may sit from the original, in the mesh's own units.
cadaclysm_meshlet_child_count
uint32_t cadaclysm_meshlet_child_count(const struct CadaclysmMeshlets *handle, uint32_t index);
How many meshlets one stands in for. Zero at level 0.
cadaclysm_meshlet_positions
void cadaclysm_meshlet_positions(const struct CadaclysmMeshlets *handle,
uint32_t index,
float *out);
Three floats a vertex.
cadaclysm_meshlet_normals
void cadaclysm_meshlet_normals(const struct CadaclysmMeshlets *handle,
uint32_t index,
float *out);
Three floats a vertex, or nothing where the mesh carried no normals.
cadaclysm_meshlet_indices
void cadaclysm_meshlet_indices(const struct CadaclysmMeshlets *handle,
uint32_t index,
uint32_t *out);
Three indices a triangle, into this meshlet's own vertices.
cadaclysm_meshlet_children
void cadaclysm_meshlet_children(const struct CadaclysmMeshlets *handle,
uint32_t index,
uint32_t *out);
The meshlets this one stands in for, as indices into the same build.
cadaclysm_meshlets_free
void cadaclysm_meshlets_free(struct CadaclysmMeshlets *handle);
Release a build.
Exact B-reps
cadaclysm_brep_layout_id
const char *cadaclysm_brep_layout_id(void);
This library's brep layout: the compiler, target, profile and source it was built from, as one string. Pass it beside a brep to cadaclysm_blacksmith_from_brep, which refuses a brep laid out by any other build. Static; never freed.
cadaclysm_brep_release
void cadaclysm_brep_release(const struct CadaclysmBrep *brep);
Give back a reference cadaclysm_node_brep handed out. The last holder's release frees the brep. Null is a no-op.
cadaclysm_brep_manifold
bool cadaclysm_brep_manifold(const struct CadaclysmBrep *brep, uint32_t *out);
Whether the brep's faces make a manifold, read off its topology — the edges and loops the file gave it — rather than a mesh: nothing is tessellated. Eight counts into out, in order: faces, edges, vertices, boundary edges (bordered by one face), non-manifold edges (by three or more), non-manifold vertices (where the faces round a point make more than one fan: two solids touching at a corner), then 1 if it is a manifold (no non-manifold edge or vertex) and 1 if it is also closed (no boundary edge: it encloses a solid), else 0.
The answer is about the topology the file wrote. Faces that name no shared edge — an IGES body, each surface its own sheet; an IFC face given as one closed polygon; STEP CURVE_BOUNDED_SURFACEs — read as open, every edge a boundary, however well they meet in space. Orientation is not asked. false (and cadaclysm_last_error) on a null argument.
Types
CadaclysmAxis
typedef enum CadaclysmAxis {
CADACLYSM_AXIS_X = 0,
CADACLYSM_AXIS_Y = 1,
CADACLYSM_AXIS_Z = 2,
CADACLYSM_AXIS_NEG_X = 3,
CADACLYSM_AXIS_NEG_Y = 4,
CADACLYSM_AXIS_NEG_Z = 5,
} CadaclysmAxis;
One of the six directions an axis can land on, for CadaclysmConventionSpec. Spelled the way the header spells its other constants, for the same reason CadaclysmConvention is.
CadaclysmWinding
typedef enum CadaclysmWinding {
CADACLYSM_WINDING_COUNTER_CLOCKWISE = 0,
CADACLYSM_WINDING_CLOCKWISE = 1,
} CadaclysmWinding;
Which way round the target's renderer wants a front-facing triangle wound.
CadaclysmValueKind
typedef enum CadaclysmValueKind {
CadaclysmValueNone = 0,
CadaclysmValueText = 1,
CadaclysmValueInteger = 2,
CadaclysmValueReal = 3,
CadaclysmValueBoolean = 4,
CadaclysmValueList = 5,
CadaclysmValueReference = 6,
} CadaclysmValueKind;
What kind of value an attribute holds.
CadaclysmConvention
typedef enum CadaclysmConvention {
CADACLYSM_NATIVE = 0,
CADACLYSM_UNREAL = 1,
CADACLYSM_UNITY = 2,
CADACLYSM_Y_UP = 3,
CADACLYSM_BLENDER = 4,
} CadaclysmConvention;
A target coordinate space, named the way cadaclysm::Convention's presets are — cadaclysm_open and cadaclysm_open_memory take one of these as a uint32_t, optionally OR'd with CADACLYSM_FILE_UNITS.
CadaclysmMeshUvs
typedef enum CadaclysmMeshUvs {
CADACLYSM_UV_NONE = 0,
CADACLYSM_UV_WORLD_SCALE = 1,
} CadaclysmMeshUvs;
Whether meshes carry texture coordinates, and of what kind.
Off by default and deliberately: a (u, v) is eight bytes a vertex, and on a ten-million-triangle assembly that is not a cost to impose on a caller who never asked. What world scale is good for, and what it is not, is on CadaclysmMesh::uvs.
CadaclysmMeshColors
typedef enum CadaclysmMeshColors {
CADACLYSM_COLORS_NONE = 0,
CADACLYSM_COLORS_PER_FACE = 1,
} CadaclysmMeshColors;
Whether meshes carry a colour per vertex.
See CadaclysmMesh::colors for when an array actually arrives.
CadaclysmConventionSpec
typedef struct CadaclysmConventionSpec {
enum CadaclysmAxis x;
enum CadaclysmAxis y;
enum CadaclysmAxis z;
double units_per_metre;
bool file_units;
enum CadaclysmWinding winding;
} CadaclysmConventionSpec;
A target space spelled out in full, for a host none of the presets names.
Everything cadaclysm::Convention carries, so a caller can describe a renderer this library has never heard of rather than waiting for a preset. Passed to cadaclysm_open_custom and cadaclysm_open_memory_custom; the preset entry points are unchanged and remain the shorter way to say one of the five common answers.
CadaclysmCandidate
typedef struct CadaclysmCandidate {
const char *name;
const char *format;
size_t depth;
} CadaclysmCandidate;
One member of a zip that this build has a reader for, as offered to a CadaclysmPick. Both strings are valid only for the duration of the call.
CadaclysmPick
typedef size_t (*CadaclysmPick)(const struct CadaclysmCandidate *candidates,
size_t count,
void *user);
A caller's say in which member of a zip opens. Called once, with every readable member in archive order; returns the index to open. Anything >= count declines the archive, and the open fails with that reason.
CadaclysmOpenOptions
typedef struct CadaclysmOpenOptions {
size_t size;
uint32_t convention;
const struct CadaclysmConventionSpec *spec;
bool file_units;
uint32_t uvs;
uint32_t colors;
double source_meters_per_unit;
const char *const *schemas;
size_t schema_count;
const uint8_t *schema_text;
size_t schema_length;
CadaclysmPick pick;
void *pick_user;
} CadaclysmOpenOptions;
Everything an open takes beyond where the bytes come from.
Zero it, set size, then set what you care about — cadaclysm_open_options_init does the first two. A null pointer where one of these is expected means every default, so cadaclysm_open("part.step", NULL) is the short way in.
CadaclysmBounds
typedef struct CadaclysmBounds {
float min[3];
float max[3];
} CadaclysmBounds;
An axis-aligned box, or all zeros where there is nothing to bound.
CadaclysmAttribute
typedef struct CadaclysmAttribute {
const char *name;
enum CadaclysmValueKind kind;
const char *text;
int64_t integer;
double real;
bool boolean;
} CadaclysmAttribute;
One thing a file said about a part.
kind says which of the fields below it means; the rest are zero. text borrows from the scene like every other string here.
CadaclysmMesh
typedef struct CadaclysmMesh {
const float *positions;
const float *normals;
const float *uvs;
const float *colors;
const uint32_t *indices;
uint32_t vertex_count;
uint32_t index_count;
} CadaclysmMesh;
A part's triangles. All five pointers borrow from the scene.
positions and normals each hold vertex_count * 3 floats, uvs holds vertex_count * 2, colors holds vertex_count * 4, and indices holds index_count of them, three to a triangle. A part with no geometry gives all-null and all-zero.
CadaclysmCollision
typedef struct CadaclysmCollision {
uint32_t size;
uint32_t shape;
uint32_t confidence;
uint32_t axis;
double frame[16];
double half_extent[3];
double radius;
double height;
double error;
uint32_t hull_vertex_count;
uint32_t hull_index_count;
} CadaclysmCollision;
What a node turned out to be, and the body an engine can simulate for it.
half_extent and frame are always the true oriented box and are always usable, whatever shape says, so a consumer that only wants boxes can ignore shape entirely. The fields a given shape does not use are written zero rather than left as the caller had them.
CadaclysmCollisionHull
typedef struct CadaclysmCollisionHull {
const float *positions;
const uint32_t *indices;
uint32_t vertex_count;
uint32_t index_count;
} CadaclysmCollisionHull;
A node's convex hull, borrowed from the scene.
CadaclysmPolylines
typedef struct CadaclysmPolylines {
const float *positions;
const uint32_t *counts;
uint32_t polyline_count;
uint32_t vertex_count;
} CadaclysmPolylines;
A set of polylines. Both pointers borrow from the scene.
positions holds vertex_count * 3 floats, run together end to end; counts holds polyline_count vertex counts saying where each run stops. A part with none gives all-null and all-zero.
CadaclysmFace
typedef struct CadaclysmFace {
uint32_t kind;
uint32_t reversed;
uint32_t transposed;
uint32_t reserved;
float origin[4];
float ax[4];
float ay[4];
float az[4];
float domain[4];
float scalars[4];
uint32_t loop_start;
uint32_t loop_count;
uint32_t profile_start;
uint32_t profile_count;
uint32_t profile2_start;
uint32_t profile2_count;
uint32_t nurbs_start;
uint32_t nurbs_count;
} CadaclysmFace;
A set of rational cubic Bézier segments. Both pointers borrow from the scene.
points holds count * 4 * 3 floats — four control points to a segment, three floats to a point — and weights holds count * 4 beside them. A part with no curves of this kind gives all-null and zero.
CadaclysmSurfaces
typedef struct CadaclysmSurfaces {
const struct CadaclysmFace *faces;
uint32_t face_count;
const uint32_t *loops;
uint32_t loop_count;
const float *points;
uint32_t point_count;
const float *profiles;
uint32_t profile_count;
const float *nurbs;
uint32_t nurbs_count;
} CadaclysmSurfaces;
Every face of one part as surfaces and trims — see CadaclysmFace.
Five borrowed arrays the faces share: a face names a slice of each rather than carrying its own copy, which keeps a part with two hundred faces to one allocation apiece.
CadaclysmWindow
typedef struct CadaclysmWindow {
uint32_t kind;
void *handle;
void *display;
} CadaclysmWindow;
The host window a dialog should belong to. kind is one of the CADACLYSM_WINDOW_* constants, saying which of handle/display's platform types apply.
Those constants are part of the ABI: hosts compile the numbers in, so they must never be renumbered.
cadaclysm_blacksmith.h
Library and licence
cadaclysm_blacksmith_last_error
const char *cadaclysm_blacksmith_last_error(void);
Why the last call failed, or null if it succeeded.
Borrowed, and good until the next call on this thread.
cadaclysm_blacksmith_version
const char *cadaclysm_blacksmith_version(void);
The library's version, as "0.1.0". Static; never freed.
cadaclysm_blacksmith_license_set
bool cadaclysm_blacksmith_license_set(const char *text_or_path);
Load a license from text_or_path: the certificate text itself, or the path of a file holding it. Replaces the one in use. Null forgets the one in use, so the next call resolves from the environment and the search paths again. Returns false without changing anything when the text does not verify, with the reason at cadaclysm_blacksmith_last_error.
cadaclysm_blacksmith_license_info
const char *cadaclysm_blacksmith_license_info(void);
The license in use, as one line — customer=Acme Ltd expiry=2027-09-15 entitlements=import,kernel seats=20 — or, without one, unlicensed (unlicensed -- <reason> when a license was found but did not verify). Never null. Borrowed, and good until the next call on this thread.
cadaclysm_blacksmith_license_notice_count
uint64_t cadaclysm_blacksmith_license_notice_count(void);
How many unlicensed notices this library has printed in this process; an application can show its own banner instead of the stderr line.
cadaclysm_blacksmith_build_date
const char *cadaclysm_blacksmith_build_date(void);
The date this library was built, "YYYY-MM-DD". A paid license is good for every build dated on or before the day its updates end. Static; never freed.
cadaclysm_blacksmith_string_free
void cadaclysm_blacksmith_string_free(char *s);
Release a string this library handed over as owned (cadaclysm_blacksmith_step). Null is a no-op.
Profiles and paths
cadaclysm_blacksmith_profile_free
void cadaclysm_blacksmith_profile_free(struct CadaclysmBlacksmithProfile *profile);
Release a profile. Null is a no-op.
cadaclysm_blacksmith_profile_polylines
struct CadaclysmBlacksmithPolylines cadaclysm_blacksmith_profile_polylines(const struct CadaclysmBlacksmithProfile *profile,
double tolerance);
The outline, then each hole, as polylines at z = 0, within tolerance of its arcs and splines: a closed loop repeats its first point at the end; an open chain (a profile ended open) is the segments it has. What a viewer draws a profile with. The arrays belong to the profile and stay valid until it is freed or this is called on it again with another tolerance.
cadaclysm_blacksmith_profile_rect
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_rect(double w, double h);
A rectangle w by h centred on the origin. Null (and last_error) unless both are positive and finite.
cadaclysm_blacksmith_profile_circle
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_circle(double r);
A circle of radius r about the origin: two semicircular arcs, so an extrusion of it is two exact cylinder walls.
cadaclysm_blacksmith_profile_slot
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_slot(double cx,
double cy,
double length,
double r);
A stadium: a length-long slot of end radius r, centred at (cx, cy), running along x. length must exceed 2 * r.
cadaclysm_blacksmith_profile_polygon
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_polygon(const double *xy,
size_t count);
A closed polygon through count points, xy holding two doubles each, in order, with a side back to the first as its last segment. At least three points.
cadaclysm_blacksmith_profile_regular_polygon
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_regular_polygon(double cx,
double cy,
double radius,
uint32_t sides,
double angle);
A regular polygon of sides sides (at least 3) on the circle of radius about (cx, cy), its first corner at angle radians from the sketch's x axis, the rest counter-clockwise; its side back to the first corner a segment of its own.
cadaclysm_blacksmith_profile_spline
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_spline(const double *xy,
size_t count,
uint32_t degree,
const double *weights,
bool closed);
A spline of degree through the control polygon xy (count points), weights null or one per point. Open, it is clamped — it starts on the first point and ends on the last, an open chain; closed, it is periodic, smooth through its own start, a closed profile. The degree is lowered to fit the points. Refused for a degree of zero, too few points (two open, three closed), a point or weight not finite, a weight not positive.
cadaclysm_blacksmith_profile_round
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_round(const struct CadaclysmBlacksmithProfile *profile,
double radius,
const uint32_t *corners,
size_t count,
bool open);
profile with its corners between two straight segments rounded by radius, as a new profile: both lines cut back and an exact tangent arc put between them. corners null rounds every such corner, the holes' too; otherwise its count indices pick the corners of the boundary to round — corner k is where segment k ends — and a picked corner that is not between two lines is refused. open treats the profile as an open chain, its two ends kept square; closed, the corner where the last segment meets the first (across the closing side, drawn or implicit) is rounded too. Refused where the radius does not fit.
cadaclysm_blacksmith_profile_chain
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_chain(const struct CadaclysmBlacksmithProfile *const *pieces,
size_t count,
double tolerance);
count open profiles joined end to end into one new profile, the kernel's merge: in any order and either way round, each next piece the first of the rest with an end within tolerance of either end of the chain so far, reversed where that makes it meet. Every segment is kept exactly; a joint is the chain's own point. Where the chain's two ends meet within tolerance the result is closed (its last segment landing on its start), otherwise an open chain. Refused (null, last_error "chain: ...") for no pieces, a tolerance not positive and finite, a piece empty, with holes or closed on its own, or a piece that meets none of the others — named by its index. The pieces are untouched.
cadaclysm_blacksmith_profile_from_loops
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_from_loops(const struct CadaclysmBlacksmithProfile *const *loops,
size_t count);
loops (count closed profiles, no holes of their own) as one profile: the loop enclosing the most area its boundary, every other a hole in it, in the order given. Refused — null, with the reason in last_error, naming loops by their index — for a loop that is open, empty or of no area, loops that cross or touch, a hole outside the boundary or inside another.
cadaclysm_blacksmith_profile_close_loop
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_close_loop(const struct CadaclysmBlacksmithProfile *profile);
profile closed, as a new profile — the forge's sketch "close": where its last segment stops short of its start, a straight segment back to it; where it already comes back within 1e-9 of its extent, its last segment made to land on the start exactly. A closed profile comes back as it is. Holes are closed the same way.
cadaclysm_blacksmith_profile_with_hole
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_profile_with_hole(const struct CadaclysmBlacksmithProfile *outer,
const struct CadaclysmBlacksmithProfile *hole);
outer with hole cut from it, as a new profile; both inputs are untouched. A hole must lie inside the outer boundary and clear of other holes — this does not check, the sweep that consumes it reports.
cadaclysm_blacksmith_path_begin
struct CadaclysmBlacksmithPath *cadaclysm_blacksmith_path_begin(double x, double y);
Start an outline at (x, y). Free it with cadaclysm_blacksmith_path_free if it is never ended.
cadaclysm_blacksmith_path_line_to
bool cadaclysm_blacksmith_path_line_to(struct CadaclysmBlacksmithPath *p, double x, double y);
A straight segment to (x, y).
cadaclysm_blacksmith_path_arc_to
bool cadaclysm_blacksmith_path_arc_to(struct CadaclysmBlacksmithPath *p,
double x,
double y,
double cx,
double cy,
bool ccw);
A circular arc to (x, y) about (cx, cy), counter-clockwise if ccw. The centre must be equidistant from the current point and the end; the sweep that consumes the profile reports one that is not.
cadaclysm_blacksmith_path_bezier_to
bool cadaclysm_blacksmith_path_bezier_to(struct CadaclysmBlacksmithPath *p,
double c1x,
double c1y,
double c2x,
double c2y,
double x,
double y);
A cubic Bezier to (x, y) with interior control points c1 and c2; the first control point is the current point.
cadaclysm_blacksmith_path_nurbs_to
bool cadaclysm_blacksmith_path_nurbs_to(struct CadaclysmBlacksmithPath *p,
const double *control_xy,
size_t control_count,
const double *weights,
const double *knots,
size_t knot_count,
uint32_t degree);
A NURBS segment. control_xy holds every control point after the current point, the endpoint last (control_count of them, two doubles each); weights, if not null, one per control point *including* the current point (control_count + 1); knots the full repeated knot vector, knot_count = control_count + 1 + degree + 1. This is Segment::Nurbs's own rule.
cadaclysm_blacksmith_path_end
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_path_end(struct CadaclysmBlacksmithPath *p);
Close the path into a profile, consuming the builder (it is invalid after this call whether or not it succeeds). Fails unless the last segment ends at the start point, within 1e-9 of the outline's own extent.
cadaclysm_blacksmith_path_end_open
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_path_end_open(struct CadaclysmBlacksmithPath *p);
The path as it stands, open, as a profile — consuming the builder as path_end does, but without asking the last segment to reach the start. An open sweep (extrude_open, extrude_open_tapered, sweep_open, loft_open) draws its segments' walls and nothing across the gap; a closed one closes the gap with a straight side, as it closes every profile.
cadaclysm_blacksmith_path_free
void cadaclysm_blacksmith_path_free(struct CadaclysmBlacksmithPath *p);
Release a path that was never ended. Null is a no-op.
cadaclysm_blacksmith_translate_profile
struct CadaclysmBlacksmithProfile *cadaclysm_blacksmith_translate_profile(const struct CadaclysmBlacksmithProfile *profile,
double dx,
double dy);
profile shifted by (dx, dy) in its own plane — to push a revolve's profile off the axis, or a hole off centre.
Sweep paths and slants
cadaclysm_blacksmith_slant_of_plane
bool cadaclysm_blacksmith_slant_of_plane(const double *frame,
const double *point,
const double *normal,
double *out);
The plane through point square to normal, read as heights over frame and written to out (at, grad.x, grad.y, for cadaclysm_blacksmith_extrude_between). false, with last_error set, when the plane holds the sweep direction itself — normal square to frame's z — so no height is on it.
cadaclysm_blacksmith_sweep_path_begin
struct CadaclysmBlacksmithSweepPath *cadaclysm_blacksmith_sweep_path_begin(double x,
double y,
double z);
Start a sweep path at (x, y, z): where the first piece begins. Free it with cadaclysm_blacksmith_sweep_path_free once done with it — sweeping only borrows a path, so a path built here is never consumed on its own.
cadaclysm_blacksmith_sweep_path_line_to
bool cadaclysm_blacksmith_sweep_path_line_to(struct CadaclysmBlacksmithSweepPath *p,
double x,
double y,
double z);
A straight piece to (x, y, z).
cadaclysm_blacksmith_sweep_path_arc
bool cadaclysm_blacksmith_sweep_path_arc(struct CadaclysmBlacksmithSweepPath *p,
double cx,
double cy,
double cz,
double ax,
double ay,
double az,
double angle);
A circular piece turning angle radians about the axis through (cx, cy, cz) with direction (ax, ay, az) (need not be unit) — the axis's own sense is which way it turns, axis × radial the direction of travel. angle must lie in (0, 2π]; the sweep that reads the path is what checks and reports that, not this call.
cadaclysm_blacksmith_sweep_path_along
struct CadaclysmBlacksmithSweepPath *cadaclysm_blacksmith_sweep_path_along(const struct CadaclysmBlacksmithProfile *curve,
const double *frame,
double tolerance,
bool open);
The sweep path the 2D chain curve draws on frame (twelve doubles): a line a straight piece, an arc a circular one about frame's z, a Bezier or spline fitted with biarcs — pairs of arcs tangent to each other and to the curve — within tolerance, so the path is tangent throughout. open walks the segments as given; closed, the path also runs back to the start along the side a profile leaves implicit. Refused for a chain with holes or no length. Free the path with cadaclysm_blacksmith_sweep_path_free.
cadaclysm_blacksmith_sweep_path_free
void cadaclysm_blacksmith_sweep_path_free(struct CadaclysmBlacksmithSweepPath *p);
Release a sweep path. Null is a no-op. Call this whether or not the path was ever swept — and even after it was swept more than once, since cadaclysm_blacksmith_sweep/cadaclysm_blacksmith_sweep_open never take ownership of it.
Construction planes
cadaclysm_blacksmith_frame_midplane
bool cadaclysm_blacksmith_frame_midplane(const double *a, const double *b, double *out);
The plane midway between the planes of frames a and b (twelve doubles each), twelve doubles into out — Fusion's midplane: for parallel planes the one halfway between, on a's axes; for planes that meet, the plane bisecting them through the line they meet on, its x along that line. false and last_error for a frame with no normal.
cadaclysm_blacksmith_frame_through
bool cadaclysm_blacksmith_frame_through(const double *p,
const double *q,
const double *r,
double *out);
The plane through the points p, q and r (three doubles each), twelve doubles into out: its origin p, its x towards q, its z the normal the three turn about counter-clockwise — Fusion's plane through three points. false and last_error for three points on one line.
Building solids
cadaclysm_blacksmith_cuboid
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_cuboid(double x, double y, double z);
A box x by y by z, centred on the origin. Six planes.
cadaclysm_blacksmith_cylinder
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_cylinder(double r, double h);
A cylinder of radius r, height h, based on z=0 and rising along +z.
cadaclysm_blacksmith_cone
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_cone(double r, double h);
A cone of base radius r and height h, apex up.
cadaclysm_blacksmith_sphere
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_sphere(double r);
A sphere of radius r about the origin.
cadaclysm_blacksmith_torus
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_torus(double major, double minor);
A torus of ring radius major and tube radius minor, about z.
cadaclysm_blacksmith_wedge
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_wedge(double x,
double y,
double z,
double top_x);
A wedge: a box x by y by z whose top face is narrowed to top_x along x.
cadaclysm_blacksmith_extrude
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_extrude(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
double height);
profile swept height along the frame's z, closed with two caps. The profile's own x/y are the frame's x/y.
cadaclysm_blacksmith_extrude_open
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_extrude_open(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
double height);
cadaclysm_blacksmith_extrude without the caps: an open sheet of walls.
cadaclysm_blacksmith_extrude_between
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_extrude_between(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
const double *bottom,
const double *top);
cadaclysm_blacksmith_extrude between two planes instead of two heights: bottom and top are each three doubles — at, grad.x, grad.y — a plane read as its height over the sketch plane at each point, at + grad · p. The profile's walls run from where bottom cuts them to where top does, the caps lying on those planes. With both flat this *is* cadaclysm_blacksmith_extrude (bit for bit: a flat slant's height is its at); with a slope it is the mitred end of a sweep's straight piece. cadaclysm_blacksmith_slant_of_plane builds a slant from a plane through a point.
Refused (null, last_error set "extrude_between: ...") where the top plane comes down to or through the bottom across the profile — a mitre too sharp for the profile's width — besides extrude's own refusals.
cadaclysm_blacksmith_extrude_open_between
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_extrude_open_between(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
const double *bottom,
const double *top);
cadaclysm_blacksmith_extrude_between without the caps: an open sheet of walls running from bottom to top, as cadaclysm_blacksmith_extrude_open is to cadaclysm_blacksmith_extrude. The same refusals.
cadaclysm_blacksmith_extrude_tapered
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_extrude_tapered(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
double height,
double taper);
cadaclysm_blacksmith_extrude with a draft: the walls lean out by taper radians as they rise (in, when negative — the profile is walked counter-clockwise, and a positive taper leans to the right of that walk), every wall exact — a plane off a line, a cone off an arc. A taper of zero is extrude itself; one that would close a side before the top is refused.
cadaclysm_blacksmith_extrude_open_tapered
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_extrude_open_tapered(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
double height,
double taper);
cadaclysm_blacksmith_extrude_tapered without the caps: the drafted walls alone, leaning to the right of the curve's walk.
cadaclysm_blacksmith_loft
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_loft(const struct CadaclysmBlacksmithProfile *a,
const double *frame_a,
const struct CadaclysmBlacksmithProfile *b,
const double *frame_b);
The solid between profile a drawn on frame_a and profile b drawn on frame_b: ruled walls between matching sides, capped by the two profiles. The profiles must have the same number of sides and no holes; b is turned to start from the side nearest a's first.
cadaclysm_blacksmith_loft_open
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_loft_open(const struct CadaclysmBlacksmithProfile *a,
const double *frame_a,
const struct CadaclysmBlacksmithProfile *b,
const double *frame_b);
cadaclysm_blacksmith_loft without the caps: the sheet ruled between the two curves.
cadaclysm_blacksmith_loft_through
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_loft_through(const struct CadaclysmBlacksmithProfile *const *profiles,
const double *frames,
size_t count);
The solid smooth through count profiles, each on its frame (frames twelve doubles a profile, in order): every wall interpolates its side across all the profiles — cubic through four or more, quadratic through three, the ruled cadaclysm_blacksmith_loft through two — capped by the first and the last. The profiles must have the same number of sides and no holes.
cadaclysm_blacksmith_loft_through_open
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_loft_through_open(const struct CadaclysmBlacksmithProfile *const *profiles,
const double *frames,
size_t count);
cadaclysm_blacksmith_loft_through without the caps: the sheet through the curves.
cadaclysm_blacksmith_revolve
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_revolve(const struct CadaclysmBlacksmithProfile *profile,
const double *axis,
double angle);
profile swung angle radians about axis (a point and a direction). The profile is read with x as radius and y as height along the axis, so it must lie to one side of the axis.
cadaclysm_blacksmith_coil
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_coil(const struct CadaclysmBlacksmithProfile *profile,
const double *axis,
double pitch,
double turns);
profile coiled about axis (a point and a direction): read like cadaclysm_blacksmith_revolve's — x the distance from the axis, y along it — and turned turns times while climbing pitch along the axis each turn, a spring or a thread. The walls follow the helix to a few millionths of the radius; the two ends are the profile itself, flat. Null and last_error for an open profile or one with holes, one reaching the axis, turns that are not positive, or — from a full turn up — a pitch no taller than the profile.
cadaclysm_blacksmith_revolve_open
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_revolve_open(const struct CadaclysmBlacksmithProfile *profile,
const double *axis,
double angle);
cadaclysm_blacksmith_revolve without the end caps of a partial turn.
cadaclysm_blacksmith_revolve_in_plane
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_revolve_in_plane(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
const double *axis,
double angle);
profile, drawn on frame, swung angle radians about the axis through the sketch points (axis[0], axis[1]) and (axis[2], axis[3]), in the frame's own x and y, into a closed solid — the profile and its axis drawn together, as a sketch draws them, rather than the profile in (radius, height) as cadaclysm_blacksmith_revolve reads it. The profile may lie on either side of the axis and touch it, not cross it; the sweep starts where the profile is drawn, turning right-handed about the axis.
cadaclysm_blacksmith_revolve_open_in_plane
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_revolve_open_in_plane(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
const double *axis,
double angle);
As revolve_in_plane, for a curve: the profile's own segments swung into a sheet, no caps.
cadaclysm_blacksmith_extrude_faces
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_extrude_faces(const struct CadaclysmBlacksmithSolid *sheet,
double height);
Every face of sheet pushed height along its own normal, walled and closed: the sheet as a solid of that thickness.
cadaclysm_blacksmith_face
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_face(const struct CadaclysmBlacksmithProfile *profile,
const double *frame);
The planar sheet profile bounds on frame (twelve doubles: origin, x, y, z): one face on the plane of frame, its normal frame's z whichever way round the profile was drawn, each hole a hole through it, every edge the exact line, arc or spline its segment is. An open sheet: raise it with cadaclysm_blacksmith_extrude_faces, trim it with cadaclysm_blacksmith_trim. Refused where the profile encloses no area or a hole does not lie inside the boundary.
cadaclysm_blacksmith_sweep
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_sweep(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
const struct CadaclysmBlacksmithSweepPath *path);
profile, drawn on frame (its x/y the profile's own axes), carried along path into a closed solid: a straight piece of the path is an extrusion of the profile, a circular piece a revolution about the arc's axis — so a circle along an arc is an exact torus wall, a rectangle along a line an exact box, nothing approximated. Two straight pieces may meet at a mitred corner; a corner next to a curved piece may not. Caps close the two ends of an open path; a path that returns to its start with matching tangents (a full circle, a mitred loop) has none. frame's origin must be where path starts, and path must leave square to frame's plane (either face of it). path is only *borrowed* here — it is not consumed, and the same path may be swept again, open or closed, or by another call to this function.
cadaclysm_blacksmith_pipe
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_pipe(const struct CadaclysmBlacksmithSweepPath *path,
double radius,
double thickness);
A circle of radius swept along path, square to its start — Fusion's Pipe: a rod, or with a positive thickness a tube whose walls are that thick. path is borrowed, as by cadaclysm_blacksmith_sweep. Null and last_error for a radius that is not positive, a thickness that is negative or reaches the radius, or what the sweep refuses.
cadaclysm_blacksmith_sweep_open
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_sweep_open(const struct CadaclysmBlacksmithProfile *profile,
const double *frame,
const struct CadaclysmBlacksmithSweepPath *path);
cadaclysm_blacksmith_sweep for a curve rather than a face: the profile's own segments carried into a *sheet*, one wall per segment per piece, no caps and no closing wall — the way cadaclysm_blacksmith_extrude_open is to cadaclysm_blacksmith_extrude. The profile need not enclose anything, only have a segment. path is only borrowed, as in sweep.
Placing
cadaclysm_blacksmith_place
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_place(const struct CadaclysmBlacksmithSolid *solid,
const double *frame);
solid, built about the origin, moved onto frame: its origin to the frame's origin, its axes to the frame's. What Workplane::cuboid and ::cylinder do after building the primitive.
cadaclysm_blacksmith_translate
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_translate(const struct CadaclysmBlacksmithSolid *solid,
double dx,
double dy,
double dz);
solid moved by (dx, dy, dz).
cadaclysm_blacksmith_rotate
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_rotate(const struct CadaclysmBlacksmithSolid *solid,
const double *axis,
double radians);
solid turned radians about axis (a point and a direction).
cadaclysm_blacksmith_mirror
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_mirror(const struct CadaclysmBlacksmithSolid *solid,
const double *plane);
solid reflected across plane (a frame; its z is the plane's normal).
Booleans
cadaclysm_blacksmith_join
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_join(const struct CadaclysmBlacksmithSolid *a,
const struct CadaclysmBlacksmithSolid *b,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
a ∪ b, an exact B-rep whose faces are pieces of the inputs' own faces; only the new edges, where the two meet, are found on the meshes at tolerance.
cadaclysm_blacksmith_cut
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_cut(const struct CadaclysmBlacksmithSolid *a,
const struct CadaclysmBlacksmithSolid *b,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
a − b. See cadaclysm_blacksmith_join.
cadaclysm_blacksmith_common
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_common(const struct CadaclysmBlacksmithSolid *a,
const struct CadaclysmBlacksmithSolid *b,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
a ∩ b. See cadaclysm_blacksmith_join.
cadaclysm_blacksmith_split_sheet
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_split_sheet(const struct CadaclysmBlacksmithSolid *sheet,
const struct CadaclysmBlacksmithSolid *tool,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
sheet cut along tool's boundary and nothing removed: every face of sheet comes back in its pieces outside tool and its pieces inside, each piece a face. The faces come out in sheet's own face order, each face's outside pieces before its inside pieces, so an index into the result names a piece for as long as sheet and tool stand. sheet may be an open sheet; tool must be a closed solid. What a surface trim starts from — the pieces to throw away are chosen afterwards.
cadaclysm_blacksmith_push_pull
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_push_pull(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face,
double distance,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
Face face of solid pushed out by distance along its outward normal (pulled in, negative) the way a CAD program extrudes a face: the prism over it joined on (cut out) at tolerance, and the result's flush faces merged — a box's top raised is one taller box of six faces. A face on a cylinder, a cone, a sphere or a torus moves out along its normal instead, the surface a step out (a boss fatter, a bore or a countersink narrower, a dome fuller), the planes beside it carried along. Null and last_error for any other curved face, a curved one with anything but a plane it can follow beside it, reaching a cone's apex, pushed to its axis or centre, off a plane beside it or into another edge, a face the solid does not have, a zero or non-finite distance, or what the boolean refuses.
cadaclysm_blacksmith_push_pull_faces
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_push_pull_faces(const struct CadaclysmBlacksmithSolid *solid,
const uint32_t *faces,
size_t count,
double distance,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
solid with the count faces at faces pushed out by distance together (pulled in, negative) — Fusion's press-pull on a selection: each face by cadaclysm_blacksmith_push_pull's rule for it, one after another in the order given, each found again by a point inside it after the pushes before it renumbered the faces. A box's top and a side pushed 5 is the box 5 taller and 5 wider. A face on the same curved surface as one before it, and joined to it, moved with that one and is not pushed twice. Null and last_error for no faces, a face an earlier push took away, and whatever cadaclysm_blacksmith_push_pull refuses of a face.
cadaclysm_blacksmith_split
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_split(const struct CadaclysmBlacksmithSolid *solid,
const struct CadaclysmBlacksmithSolid *tool,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
solid split by tool into bodies — Fusion's Split Body. A closed tool gives the part outside it, then the part inside; a flat sheet splits by the whole plane it lies on. Every connected part is a body, and the bodies come back side by side in one solid: take them apart with cadaclysm_blacksmith_lump_count and cadaclysm_blacksmith_lump. Null and last_error for a tool that does not cross the solid, or a curved sheet.
cadaclysm_blacksmith_split_by_plane
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_split_by_plane(const struct CadaclysmBlacksmithSolid *solid,
const double *plane,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
solid split by the plane through plane's origin square to its z (a frame, twelve doubles): the bodies in front of it, then those behind, side by side in one solid — see cadaclysm_blacksmith_split.
cadaclysm_blacksmith_lump_count
uint32_t cadaclysm_blacksmith_lump_count(const struct CadaclysmBlacksmithSolid *solid);
How many connected bodies solid is — faces sharing an edge are one body. A split's result is several; a boolean's can be. 0 and last_error for a null solid.
cadaclysm_blacksmith_lump
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_lump(const struct CadaclysmBlacksmithSolid *solid,
uint32_t index);
Body index of solid (see cadaclysm_blacksmith_lump_count) as a solid of its own, its faces in solid's order and colours. Null and last_error for an index the solid has no body at.
cadaclysm_blacksmith_merge_flush
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_merge_flush(const struct CadaclysmBlacksmithSolid *solid);
solid with its flush faces merged, as a new solid: planar faces on one plane, facing one way and meeting along their edges, made one face, and every vertex left in the middle of a straight edge taken out — the seams a join leaves where two parts are flush. A solid with nothing to merge comes back as it was.
Faces and sheets
cadaclysm_blacksmith_face_sheet
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_face_sheet(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face);
Face face of solid (an index below cadaclysm_blacksmith_face_count) alone, as an open sheet: its surface, its loops and the exact curves its edges carry, the rest of the solid left behind. What extruding a solid's face starts from.
cadaclysm_blacksmith_drop_faces
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_drop_faces(const struct CadaclysmBlacksmithSolid *solid,
const uint32_t *faces,
size_t count);
solid without the faces at faces (count indices; repeats allowed): the rest keep their surfaces, loops and curves, in their order, so an index into the result is the input's with the dropped ones closed up. Refused for an index the solid has no face at, or where nothing would be left.
cadaclysm_blacksmith_trim
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_trim(const struct CadaclysmBlacksmithSolid *sheet,
const struct CadaclysmBlacksmithSolid *tool,
bool keep_inside,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
sheet cut along the closed tool's boundary and the pieces on one side thrown away — cadaclysm_blacksmith_split_sheet and cadaclysm_blacksmith_drop_faces in one: keep_inside false keeps what lies outside the tool (a hole punched through the sheet), true what lies inside it (the sheet cut to the tool's outline). The kept pieces come out in sheet's face order. Refused where nothing lies on the kept side, and as the split refuses.
Edges and finishing
cadaclysm_blacksmith_edge_polylines
struct CadaclysmBlacksmithPolylines cadaclysm_blacksmith_edge_polylines(const struct CadaclysmBlacksmithSolid *solid,
double tolerance);
The feature edges the mesher locked at tolerance, as polylines that lie on the mesh cadaclysm_blacksmith_mesh gives at the same tolerance. Same cache and lifetime rule as the mesh.
cadaclysm_blacksmith_edge_count
uint32_t cadaclysm_blacksmith_edge_count(const struct CadaclysmBlacksmithSolid *solid);
Edges in a stable order (by each edge's first trim), computed on first ask. An index into this list is what cadaclysm_blacksmith_fillet takes. Edges without an exact curve — a boolean's edge on surfaces the intersection module does not know — are not listed.
cadaclysm_blacksmith_edge
bool cadaclysm_blacksmith_edge(const struct CadaclysmBlacksmithSolid *solid,
uint32_t i,
struct CadaclysmBlacksmithEdge *out);
Edge i into out. false (and last_error) if i is out of range.
cadaclysm_blacksmith_fillet
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_fillet(const struct CadaclysmBlacksmithSolid *solid,
const uint32_t *edges,
size_t count,
double radius,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
solid with the edges at edges (indices into the list cadaclysm_blacksmith_edge walks) rounded to radius.
cadaclysm_blacksmith_chamfer
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_chamfer(const struct CadaclysmBlacksmithSolid *solid,
const uint32_t *edges,
size_t count,
double distance,
double tolerance);
cadaclysm_blacksmith_fillet with a flat bevel instead of a ball: every picked edge cut back by distance along both its faces, the cut a plane (a cone round a circular edge). The same edges, the same refusals.
cadaclysm_blacksmith_refillet
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_refillet(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face,
double radius,
double tolerance);
solid with the round face face belongs to made again at radius — the fillet's bands, balls and rim bands joined to that face taken back to the sharp edges they replaced and those rounded again, as Fusion's press-pull on a fillet face. Null and last_error for a face that is not a round of straight edges between planes or of circular rims, or a radius that does not fit.
cadaclysm_blacksmith_unfillet
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_unfillet(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face);
solid with the round face face belongs to taken off, the faces beside it made sharp again — Fusion's delete of a fillet face. The same refusals as cadaclysm_blacksmith_refillet.
cadaclysm_blacksmith_rechamfer
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_rechamfer(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face,
double distance,
double tolerance);
solid with the chamfer face face belongs to cut again at distance — its bevels (flat between two planes, cones round rims) and the corner triangles joined to that face taken back to the sharp edges they cut and those bevelled again, as Fusion's press-pull on a chamfer face. Null and last_error for a face that is not a chamfer's bevel, or a distance that does not fit.
cadaclysm_blacksmith_unchamfer
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_unchamfer(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face);
solid with the chamfer face face belongs to taken off, the faces beside it made sharp again. The same refusals as cadaclysm_blacksmith_rechamfer.
cadaclysm_blacksmith_shell
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_shell(const struct CadaclysmBlacksmithSolid *solid,
double thickness,
const uint32_t *open_faces,
size_t count,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
solid hollowed to a wall thickness thick (inward for a positive thickness, outward — the solid becoming the cavity — for a negative one), with the faces at open_faces removed so the hollow is reachable.
cadaclysm_blacksmith_thicken
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_thicken(const struct CadaclysmBlacksmithSolid *solid,
double thickness,
double tolerance,
CadaclysmBlacksmithProgress progress,
void *user);
solid, a sheet, made a solid thickness thick — Fusion's Thicken: its faces, their twins moved thickness along the faces' normals (against them for a negative thickness), and a wall round every open edge. Two faces of a folded sheet meet on their offsets' mitre; a closed sheet thickens to a hollow. Free-form (NURBS) faces offset by a fit held to tolerance. Null and last_error for a thickness a face cannot take (a radius used up, a free-form offset folding over).
Asking
cadaclysm_blacksmith_bounds
bool cadaclysm_blacksmith_bounds(const struct CadaclysmBlacksmithSolid *solid,
double tolerance,
double *min,
double *max);
The solid's axis-aligned bounds, over the positions of the solid's cached tessellation at tolerance (so curved faces are within that of the truth) — the same cache cadaclysm_blacksmith_mesh fills and reuses, so a second call at the same tolerance costs nothing extra. false on a null solid or a non-positive, non-finite tolerance.
cadaclysm_blacksmith_face_count
uint32_t cadaclysm_blacksmith_face_count(const struct CadaclysmBlacksmithSolid *solid);
Faces in the solid's own order; indices into this are what cadaclysm_blacksmith_select_face returns and shell takes.
cadaclysm_blacksmith_select_face
uint32_t cadaclysm_blacksmith_select_face(const struct CadaclysmBlacksmithSolid *solid,
uint32_t kind,
const double *v,
uint32_t index);
The face a selector picks, or CADACLYSM_BLACKSMITH_NONE (and last_error) if none does. kind 0 = the face furthest along axis index (0 x, 1 y, 2 z); 1 = furthest against it; 2 = the face whose outward normal is nearest the direction in v (three doubles, need not be unit); 3 = the face at index. v is read only for kind 2, index only for kinds 0, 1 and 3.
cadaclysm_blacksmith_face_frame
bool cadaclysm_blacksmith_face_frame(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face,
double *out);
The workplane on a face, twelve doubles into out: origin at the face's boundary centroid, z its outward normal, x world X laid onto the face (world Y on a face facing close to X) — what Workplane::workplane adopts. false if the face's boundary or normal cannot be read.
cadaclysm_blacksmith_face_kind
const char *cadaclysm_blacksmith_face_kind(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face);
The face's surface kind: "plane", "cylinder", "cone", "sphere", "torus", "nurbs", "revolution", "extrusion", "other", or "none" for a face without a surface. Static; never freed. Null (and last_error) for a face out of range.
cadaclysm_blacksmith_leaked_edges
uint32_t cadaclysm_blacksmith_leaked_edges(const struct CadaclysmBlacksmithSolid *solid,
double tolerance);
How many edges of a fresh mesh of the solid at tolerance are bound by anything other than exactly two triangles — zero for a closed solid. Meshes at full double precision every call (bs::leaked_edges) rather than reusing cadaclysm_blacksmith_mesh's cache: that cache is stored as f32 for a renderer's sake, and f32's precision falls off with a coordinate's own magnitude — a solid modelled a long way from the origin can drop enough precision in the cache to weld (or fail to weld) a shared edge wrongly, which this check exists to catch, not to repeat. A seam two solids share along a line — four triangles meeting on it, two running each way — pairs off and does *not* count here; a genuine hole (one triangle) or a fold (two running the same way) does. CADACLYSM_BLACKSMITH_NONE (and last_error) on a null solid or a non-positive, non-finite tolerance.
cadaclysm_blacksmith_unpaired_edges
uint32_t cadaclysm_blacksmith_unpaired_edges(const struct CadaclysmBlacksmithSolid *solid,
double tolerance);
How many edges of a fresh mesh of the solid at tolerance have directed triangle uses that do not pair off — zero for a closed, consistently oriented solid. Where cadaclysm_blacksmith_leaked_edges asks for exactly two triangles on an edge, this asks that they run opposite ways and cancel: the seam two solids share along a line pairs off (two triangles each way) and is *not* counted here even though four triangles meet there, while a fold — two triangles running the same way — is. The question a boolean's operands must answer, since inside and outside are as well defined either side of such a seam as of any other edge. Meshes at full double precision every call, for the same reason cadaclysm_blacksmith_leaked_edges does rather than reading the f32 render cache. Same failure shape as cadaclysm_blacksmith_leaked_edges.
cadaclysm_blacksmith_manifold
bool cadaclysm_blacksmith_manifold(const struct CadaclysmBlacksmithSolid *solid, uint32_t *out);
Whether the solid's faces make a manifold, read off its topology — the edges and loops it is made of — rather than a mesh: nothing is tessellated, so it takes no tolerance. Eight counts into out, in order: faces, edges, vertices, boundary edges (bordered by one face), non-manifold edges (by three or more), non-manifold vertices (where the faces round a point make more than one fan: two solids touching at a corner), then 1 if it is a manifold (no non-manifold edge or vertex) and 1 if it is also closed (no boundary edge: it encloses a solid), else 0. A sheet is a manifold that is not closed.
Orientation is not asked — whether the faces all face out is cadaclysm_blacksmith_unpaired_edges's question, on a mesh. false (and last_error) on a null argument.
Colour
cadaclysm_blacksmith_colour
bool cadaclysm_blacksmith_colour(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face,
double *out);
The solid's colour, or with face not CADACLYSM_BLACKSMITH_NONE that face's as drawn (its own, else the solid's), as three doubles into out. false where there is none — and, with last_error set, on a bad face or a null argument.
cadaclysm_blacksmith_coloured
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_coloured(const struct CadaclysmBlacksmithSolid *solid,
uint32_t face,
double r,
double g,
double b);
solid coloured (r, g, b), each in 0..1: the whole solid, or with face not CADACLYSM_BLACKSMITH_NONE just that face, whose colour then wins over the solid's. What is made from a coloured solid inherits: a rigid move keeps every colour, and a boolean, fillet, chamfer or shell gives each face the colour of the input face it lies on — a cut's bore the tool's — and a new face (a round, a shell's inner wall) the solid's.
From files
cadaclysm_blacksmith_brep_layout_id
const char *cadaclysm_blacksmith_brep_layout_id(void);
This library's brep layout: the compiler, target, profile and source it was built from, as one string. Equal to the reader library's cadaclysm_brep_layout_id() exactly when the two can share a brep. Static; never freed.
cadaclysm_blacksmith_from_brep
struct CadaclysmBlacksmithSolid *cadaclysm_blacksmith_from_brep(const void *brep,
const char *layout_id);
A solid over an imported body's exact brep, shared with the reader, not copied: brep is what the reader library's cadaclysm_node_brep returned, layout_id what its cadaclysm_brep_layout_id returns.
The solid takes a reference of its own; the caller's is still the caller's, to give back with cadaclysm_brep_release whether this succeeded or not. The scene the brep came from may be closed before or after the solid is freed — the brep lives until the last holder lets go of it.
Also refused: a null brep or layout_id, and a brep with no faces. What a solid from a file can then do depends on its geometry: fillet and chamfer want line and circle edges; booleans take any surface, but the new edges they trace on a free-form (NURBS) face are not always writable back to STEP; and every verb meshes its operands at its tolerance first, so its cost scales with the body's face count.
Output and freeing
cadaclysm_blacksmith_solid_free
void cadaclysm_blacksmith_solid_free(struct CadaclysmBlacksmithSolid *solid);
Release a solid. Null is a no-op.
cadaclysm_blacksmith_mesh
struct CadaclysmBlacksmithMesh cadaclysm_blacksmith_mesh(const struct CadaclysmBlacksmithSolid *solid,
double tolerance);
Tessellate at tolerance (chordal deviation, in the solid's own units) — or hand back the tessellation already made at exactly this tolerance. The pointers borrow from the solid and stay valid until it is freed or meshed again at a *different* tolerance. Faces are meshed watertight, vertices welded by position. All-null and last_error on failure.
cadaclysm_blacksmith_mesh_face_triangles
struct CadaclysmBlacksmithFaceTriangles cadaclysm_blacksmith_mesh_face_triangles(const struct CadaclysmBlacksmithSolid *solid,
double tolerance);
The triangles each face contributed to the mesh cadaclysm_blacksmith_mesh gives at the same tolerance: one count per face in face order, summing to that mesh's index_count / 3. The mesher writes a face's triangles together and the faces in order, so face f's triangles are the counts[f] that follow the first sum(counts[..f]); a face that meshed to nothing counts zero. What a viewer colours a face by. Same cache and lifetime rule as the mesh. Null counts and last_error on failure.
cadaclysm_blacksmith_step
char *cadaclysm_blacksmith_step(const struct CadaclysmBlacksmithSolid *const *solids,
size_t count,
const char *schema,
uint32_t unit);
count solids as one STEP part file, each its own MANIFOLD_SOLID_BREP, through cadaclysm_step_ap::write_breps. schema is NULL for the built-in AP203 (CONFIG_CONTROL_DESIGN), the name of a built-in schema (AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF, …; case-insensitive) — a built-in schema must carry every entity the writer emits, as AP203 and AP242 do and AP214's AUTOMOTIVE_DESIGN does not — or the EXPRESS source text of a custom schema. unit 0 = metre, 1 = millimetre, 2 = inch, and says what the solids' lengths are. The text is owned: release it with cadaclysm_blacksmith_string_free. Null and last_error on failure.
Types
CadaclysmBlacksmithMesh
typedef struct CadaclysmBlacksmithMesh {
const float *positions;
const float *normals;
const uint32_t *indices;
uint32_t vertex_count;
uint32_t index_count;
} CadaclysmBlacksmithMesh;
A solid's triangles, borrowed from it.
CadaclysmBlacksmithFaceTriangles
typedef struct CadaclysmBlacksmithFaceTriangles {
const uint32_t *counts;
uint32_t face_count;
} CadaclysmBlacksmithFaceTriangles;
How many triangles each face of a solid meshed to, borrowed from it.
CadaclysmBlacksmithPolylines
typedef struct CadaclysmBlacksmithPolylines {
const float *points;
const uint32_t *offsets;
uint32_t point_count;
uint32_t polyline_count;
} CadaclysmBlacksmithPolylines;
Polylines borrowed from a solid (its feature edges) or a profile (its outline): polyline i is points[offsets[i] .. offsets[i + 1]], three floats a point.
CadaclysmBlacksmithEdge
typedef struct CadaclysmBlacksmithEdge {
const char *kind;
const uint32_t *faces;
uint32_t face_count;
const double *segments;
uint32_t segment_count;
} CadaclysmBlacksmithEdge;
One edge of a solid, borrowed from it: valid until the solid is freed.
CadaclysmBlacksmithProgress
typedef void (*CadaclysmBlacksmithProgress)(const char *phase, size_t done, size_t total, void *user);
Where a long operation reports: phase is a short static name ("snap", "clip", ...), done of total steps within it, user whatever was passed in. Called on the calling thread, often — once per vertex in some phases — so a sink that forwards elsewhere throttles itself. Must not call back into this library. Null means silent.