Rust API
The Rust wrappers over the two libraries: cadaclysm_capi reads, meshes and writes; cadaclysm_blacksmith builds exact solids. Every type and call, with the signature as the wrapper declares it — read from its source when this page is built.
Install and load
The crate cadaclysm-sdk (use cadaclysm_sdk): the reader in the crate root, the kernel in cadaclysm_sdk::blacksmith. It opens the libraries when the program runs, through libloading — no C toolchain, no import library, and no cadaclysm source. Rust 1.70 or later.
From version 0.4.3 the libraries come with the crate (0.4.2 finds them only as below — get them with the SDK's fetch.py): when it builds, it downloads this platform's for its own version from the SDK's release, checks them against the release's SHA256SUMS, and copies them beside your binaries in target/debug or target/release — cargo run works as it is, and shipping is copying those two files with the executable. Turn it off with default-features = false (or CADACLYSM_NO_DOWNLOAD=1 in the build's environment) and get the libraries from the SDK's fetch.py instead; a download that fails is a build warning, with the same fallback.
cargo add cadaclysm-sdk
# or without the download, with the SDK's libraries:
cargo add cadaclysm-sdk --no-default-features
Where the library is found
CADACLYSM_LIBRARY for the reader and CADACLYSM_BLACKSMITH_LIBRARY for the kernel, each a file or a directory; else beside the executable, where the download copies them; else where the build downloaded them; else a lib/ directory in any parent of the executable or the working directory; else a target/release or target/debug there. Nothing else is searched: past that it stops and says where it looked (and why the download failed, if it did). cadaclysm_sdk::library_path() says which was used, and load(path) names one outright.
The licence
Unlicensed, everything works and a notice is printed on every open and export. A licence file removes it: set CADACLYSM_LICENSE, or put cadaclysm.lic beside the executable or in the working directory, or load it from code — once per library:
cadaclysm_sdk::license("cadaclysm.lic")?;
cadaclysm_sdk::blacksmith::license("cadaclysm.lic")?;
Build a part, then read it back
The kernel builds a plate with a boss, bores it and rounds its corners, then writes STEP — the Examples page's first part:
use cadaclysm_sdk::blacksmith::{Axis, Profile, Selector, Unit, Workplane, DEFAULT_TOLERANCE, FILLET_TOLERANCE};
fn main() -> cadaclysm_sdk::Result<()> {
let plate = Workplane::xy().extrude(&Profile::rect(120.0, 80.0)?, 14.0)?.solid()?;
let boss = Workplane::from_solid(&plate)
.faces(&Selector::Max(Axis::Z))?
.on_face()?
.extrude(&Profile::circle(22.0)?, 26.0)?
.solid()?;
let joined = plate.join(&boss, DEFAULT_TOLERANCE)?;
let bore = Workplane::xy().extrude(&Profile::circle(11.0)?, 60.0)?.solid()?.translate(0.0, 0.0, -10.0)?;
let bored = joined.cut(&bore, DEFAULT_TOLERANCE)?;
// The plate's four vertical corners: the lines along Z between two planes
// (the boss has vertical seams too, but those lie on its cylinder).
let mut corners = Vec::new();
for edge in bored.edges()? {
let vertical = edge.direction().is_some_and(|d| d[2].abs() > 0.99);
let mut planes = true;
for &face in &edge.faces {
planes = planes && bored.face_kind(face)? == "plane";
}
if vertical && planes {
corners.push(edge.index);
}
}
let part = bored.fillet(&corners, 12.0, FILLET_TOLERANCE)?;
part.step("plate.stp", None, Unit::Millimetre)
}
The reader opens that file, walks its tree, meshes what it draws and writes glTF:
fn main() -> cadaclysm_sdk::Result<()> {
let scene = cadaclysm_sdk::open("plate.stp")?;
println!("{} {} m per unit", scene.schema(), scene.metres_per_unit());
// The tree: assemblies, parts and bodies, parents before children.
for node in scene.walk() {
println!("{}{} [{}]", " ".repeat(node.depth() as usize), node.label(), node.kind());
}
// What to draw: every placement of every shape, meshed on first ask.
for placement in scene.placements() {
let geometry = placement.geometry();
println!("{} {} triangles", geometry.label(), geometry.mesh().triangle_count());
}
scene.save("plate.glb", "glb")
}
Reading, meshing, writing
Module functions
Loading the library, the licence, and opening a file — from disk or from bytes already in memory. Every other object on this page comes out of cadaclysm_sdk::open() or cadaclysm_sdk::open_memory().
cadaclysm_sdk::open()
fn open(path: impl AsRef<Path>) -> Result<Scene>
Open a CAD file and read its tree. The format comes from the extension (a .zip opens its first readable member — Scene::source_name() says which). The tree is read now and the geometry is built lazily, node by node, when it is first asked for.
convention is the space to read into — a Convention, optionally with the file-units and world-UV flags — and the library converts everything it hands back into it. schema names an extra EXPRESS schema (.exp, or a directory of them); every schema the SDK ships is already built in, so it is needed only for one the library does not carry. colours asks for per-vertex colours on bodies the file painted in more than one colour.
Never returns an empty handle: on failure it returns Err(Error) carrying the library's reason.
cadaclysm_sdk::open_memory()
fn open_memory(data: &[u8], format: &str) -> Result<Scene>
Open a file already in bytes — a download, a database blob, an archive member. With no file name to take the format from, it is named as an extension would name it: step, ifc, igs, 3dm, brep, scad (a leading dot is fine). The bytes are copied; the buffer can be reused as soon as this returns. Otherwise as cadaclysm_sdk::open().
cadaclysm_sdk::version()
fn version() -> Result<String>
The version of the library actually loaded — the one worth reporting in a bug.
cadaclysm_sdk::build_date()
fn build_date() -> Result<String>
When the loaded library was built, YYYY-MM-DD. A licence covers every build dated on or before its expiry.
cadaclysm_sdk::license()
fn license(text_or_path: impl AsRef<OsStr>) -> Result<()>
Load a licence: the certificate text, or the path of a file holding it. Without this call the library looks in the CADACLYSM_LICENSE environment variable, then for cadaclysm.lic beside the executable and in the working directory. On a licence that does not verify it returns Err(Error) with the reason, and the previous licence (if any) stays in use.
cadaclysm_sdk::license_info()
fn license_info() -> Result<String>
One line about the licence in use — customer=… expiry=… entitlements=… — or unlicensed (unlicensed -- <reason> when a licence was found but did not verify). Never None.
cadaclysm_sdk::license_notice_count()
fn license_notice_count() -> Result<u64>
How many unlicensed notices the library has printed to stderr in this process. An application with no console to watch (a GUI, a game) can poll this and show its own banner.
cadaclysm_sdk::mesh_formats()
fn mesh_formats() -> Result<Vec<MeshFormat>>
Every mesh format Node::save_mesh() writes, with its file extension: stl, stl-ascii, msh (Gmsh), glb, gltf and obj in this release. Build a save menu from this list rather than hard-coding it, and a format added to the library appears without a code change.
cadaclysm_sdk::pick_file()
fn pick_file() -> Result<Option<PathBuf>>
Ask the user for a file through the platform's own open dialog, filtered to what this build can read. None when they cancel or no dialog is available (on Linux, neither an XDG portal nor zenity). Blocks until the user acts; on macOS call it from the main thread.
cadaclysm_sdk::declared_schema()
fn declared_schema(model: impl AsRef<Path>) -> Result<String>
The schema a STEP or IFC file says it speaks (its FILE_SCHEMA line), read from the first few kilobytes — cheap even on a very large file. Empty when it names none.
cadaclysm_sdk::resolve_schema()
fn resolve_schema(model: impl AsRef<Path>, schema: impl AsRef<Path>) -> Result<(Option<PathBuf>, Vec<PathBuf>)>
Which .exp of a schema directory matches a model: the chosen file, or — when the file's declared name resembles none of them — the whole list as fallbacks to try in turn. cadaclysm_sdk::open() does this itself when given a directory; this is for a caller that wants to report the choice.
cadaclysm_sdk::library_path()
fn library_path() -> Result<PathBuf>
Where the shared library was found: CADACLYSM_LIBRARY (a file or a directory) first, then beside the wrapper, then a lib/ directory in any parent (the SDK's layout).
cadaclysm_sdk::NONE
const NONE: u32 = sys::CADACLYSM_NONE
The node index the C API uses for "no such node" (CADACLYSM_NONE, 0xFFFFFFFF). The wrappers turn it into None where a node may be missing (Node::parent(), Node::instance_of()), so it matters only when reading raw indices.
OpenOptions Rust only
struct OpenOptions
fn new() -> OpenOptions
fn convention(mut self, packed: impl Into<u32>) -> OpenOptions
fn schema(mut self, path: impl Into<PathBuf>) -> OpenOptions
fn colors(mut self, yes: bool) -> OpenOptions
fn source_metres_per_unit(mut self, metres: f64) -> OpenOptions
fn name(mut self, name: impl Into<String>) -> OpenOptions
fn open(&self, path: impl AsRef<Path>) -> Result<Scene>
fn open_memory(&self, data: &[u8], format: &str) -> Result<Scene>
cadaclysm_sdk::open() and cadaclysm_sdk::open_memory() take Python's keyword arguments as a builder: OpenOptions::new().convention(Convention::YUp).open(path). name is what an in-memory scene reports as its Scene::path().
load Rust only
fn load(path: impl AsRef<Path>) -> Result<()>
Load the library from a path before anything else asks for it. Optional: the first call that needs the library finds it as described under Install.
Scene
struct Scene
An open document. Close it when done — Scene::close(), or let the language's scope do it (with, using, defer, try-with-resources). Everything it hands back borrows from it: node handles, meshes, polylines. See Lifetimes for what survives a close.
Scene::close()
fn close(self)
Give the document back. Idempotent. Every mesh and polyline view still held reads freed memory afterwards (the wrappers that can tell refuse to read them).
Takes the scene by value, as dropping it does: a node, mesh or polyline still borrowed from it is a compile error, not freed memory.
Scene.closed
Whether Scene::close() has run.
A closed scene has been dropped, so there is none to ask.
Scene::path()
fn path(&self) -> &Path
The file it was read from, or the name given to cadaclysm_sdk::open_memory().
Scene::schema_path()
fn schema_path(&self) -> Option<&Path>
The .exp actually used, or None — worth reporting when a directory was passed.
Scene::convention()
fn convention(&self) -> u32
The convention it was opened with. Nothing the library hands back says what space it is in, and every array out of this scene is in this one.
Scene::schema()
fn schema(&self) -> String
The schema the file named, or empty for a format that names none.
Scene::schema_read()
fn schema_read(&self) -> String
The schema that actually read it. A file declaring a release candidate reads under the finished schema of the same version where that is what is built in; a file whose schema is unknown reads under the one that defines its entity types.
Scene::substituted()
fn substituted(&self) -> bool
Whether something other than the file's own schema read it — Scene::schema() and Scene::schema_read() differ.
Scene::metres_per_unit()
fn metres_per_unit(&self) -> f64
What one length unit in the file is worth in metres; 1 where the file did not say.
Scene::bounds()
fn bounds(&self) -> Bounds
Everything the model covers, in world coordinates — the one figure not in a node's own frame. This meshes the whole model, being the only way to know how far it reaches; to frame a view quickly, use the bounds of the nodes already built.
Scene::diagnostics()
fn diagnostics(&self) -> Vec<String>
What the file held that the reader could not build, one line each.
Scene::source_name()
fn source_name(&self) -> Option<String>
The archive member this was read from, or None for a plain file.
Scene::nodes()
fn nodes(&self) -> Vec<Node<'_>>
Every node, in index order: assemblies, shapes, layers, storeys — structure as well as geometry. To draw, iterate Scene::placements() instead.
Scene::roots()
fn roots(&self) -> Vec<Node<'_>>
The nodes nothing else contains: where a tree view starts.
Scene::walk()
fn walk(&self) -> impl Iterator<Item = Node<'_>> + '_
Every node reachable from the roots, parents before children.
Scene::query()
fn query(&self, filter: &str) -> Result<Vec<u32>>
The indices of the nodes a filter matches, in document order. The filter is one boolean expression in the query language — class == ON_Brep and within(name == Walls). A filter that does not parse returns Err(Error) with the parser's message and position; one that matches nothing is an empty result, not an error.
Scene::placements()
fn placements(&self) -> Vec<Placement<'_>>
What the document draws, and where. Not the nodes: a block or an instanced part is one node of geometry drawn at several places, and a node walk draws it once at its definition's frame. Iterate this to draw, and the nodes to build a tree. See Placement.
Scene::realize_all()
fn realize_all(&self) -> u32
Build every mesh now, across all cores, and return how many were built. Reading is lazy so a tree can be on screen while the shapes are still coming; asking node by node meshes on one core, this uses them all. Watch it from another thread with Scene::realized() and Scene::realize_total(); stop it with Scene::cancel().
Scene::realized()
fn realized(&self) -> u32
How many nodes Scene::realize_all() has finished. Safe to read from another thread.
Scene::realize_total()
fn realize_total(&self) -> u32
How many it will build in all; zero until it starts.
Scene::cancel()
fn cancel(&self)
Ask a running Scene::realize_all() to stop. One-way for the life of the scene: later calls return at once, and meshes are still built one node at a time on request.
Scene::save()
fn save(&self, path: impl AsRef<Path>, format: &str) -> Result<()>
Write the whole scene: glb (binary glTF), gltf (text glTF, one file) or obj (every placement baked to its own named object, with a .mtl beside it when anything has a colour). Every placement of every shape, named and placed as the tree is, one material per colour; in the scene's convention (use Y-up metres for the space glTF specifies). A format outside these three, or a failed write, returns Err(Error).
Scene::surface_matrix()
fn surface_matrix(&self) -> [[f64; 4]; 4]
The 4×4 that puts Node::surfaces() into the space everything else is already in. Meshes and polylines arrive in the scene's convention; surfaces arrive in the file's own frame, because converting a surface means converting its parameter space too. Identity for a document opened in its native convention and units.
Scene.show
Draw every visible placement — each block instance where the file puts it — with the viewer in use. Keywords: view= (front, back, left, right, top, bottom, iso), az=, el=, zoom=, up= (default from the convention the scene was opened with), edges= (the B-rep edges over the shapes; free curves are drawn either way), width=, height=, hint=. No tolerance=: a document is drawn at the tolerance it was read with.
Comes with the viewers follow-up.
Scene.view
Orbit the model with the viewer in use; returns (azimuth, elevation, zoom) where it was left.
Comes with the viewers follow-up.
Scene::node Rust only
fn len(&self) -> usize
fn is_empty(&self) -> bool
fn node(&self, index: u32) -> Option<Node<'_>>
fn iter(&self) -> impl Iterator<Item = Node<'_>> + '_
The node count, one node by index (None past the end), and every node in index order without building a list.
Node
struct Node<'s>
One node of the document — an assembly, a part, a body, a layer, a placement. A handle, not a snapshot: each property asks the scene when read, so nothing goes stale and nothing is built that is never looked at. Names and attributes are cheap; Node::bounds() and Node::mesh() build the geometry.
Node::index()
fn index(&self) -> u32
Its index in the scene, stable while the scene is open: a key for a map of what has been uploaded.
Node::id()
fn id(&self) -> String
What the file calls it: a STEP #N, an IFC GlobalId, a Rhino object id.
Node::kind()
fn kind(&self) -> String
Its type in the file: an IFC class, an openNURBS class, a STEP shape kind.
Node::label()
fn label(&self) -> String
Something to put in a tree row: the name, else the kind, else #index.
Node::generator()
fn generator(&self) -> String
What its geometry was before it was triangles — brep, mesh, csg — or empty for a node that draws nothing.
Node::visible()
fn visible(&self) -> bool
Whether the file says to show it when opened. The node's own switch, not inherited; true where the format has no such switch.
Node::visible_now()
fn visible_now(&self) -> bool
Node::visible() with every ancestor consulted: a layer switched off hides what hangs under it.
Node::locked()
fn locked(&self) -> bool
Whether the file says it cannot be selected or edited (Rhino's lock, own or by layer). A locked node is still drawn.
Node::parent()
fn parent(&self) -> Option<Node<'s>>
The node containing this one, or None for a root.
Node::instance_of()
fn instance_of(&self) -> Option<Node<'s>>
The node whose geometry this one places, or None. A part placed seventy times is one mesh and seventy transforms; this is how a caller knows to upload it once.
Node::select_as()
fn select_as(&self) -> Node<'s>
What a click on this node's geometry should select — usually itself. Formats that hang geometry under the object it belongs to (an IFC representation under its product) point back at the object.
Node::attributes()
fn attributes(&self) -> Vec<Attribute>
Everything the file said about the node, as Attribute values.
Node::can_mesh()
fn can_mesh(&self) -> bool
Whether the node has geometry of its own to draw. Builds nothing; most nodes are structure and answer false.
Node::colour()
fn colour(&self) -> Option<[f32; 4]>
The colour the file gave it as RGBA in 0–1, or None — most STEP files carry none, and the caller's default is the right answer.
Node::transform()
fn transform(&self) -> [[f64; 4]; 4]
Where the node's geometry sits: a 4×4 in double precision, composed through every frame above it. Meshes stay single precision in their own frame under a double transform, so a model at survey coordinates keeps its millimetres.
Node::raw_transform()
fn raw_transform(&self) -> [f64; 16]
The same matrix as 16 numbers in the C API's column-major order, ready for a GPU uniform.
Node::bounds()
fn bounds(&self) -> Bounds
The extent of the node's geometry in that geometry's own frame. Builds the geometry if needed; carry it through Node::transform() for world coordinates.
Node::mesh()
fn mesh(&self) -> Mesh<'s>
Its triangles in their own frame, built now if they have not been. A node that instances another hands back the instanced node's arrays — the same memory for every placement. The arrays are views into the scene; see Lifetimes.
Node::surfaces()
fn surfaces(&self) -> Surfaces<'s>
Its faces as exact surfaces plus the trim loops that cut them, each in the surface's own (u, v). Nothing is meshed to produce it, and reading it costs Node::mesh() nothing. Empty where the reader has no parametric description (a tessellated body, a mesh format). In the file's frame — see Scene::surface_matrix().
Node::edges()
fn edges(&self) -> Polylines<'s>
Its feature edges as polylines, for an outline overlay. Builds the geometry if needed.
Node::brep()
fn brep(&self) -> Option<Brep>
Its exact B-rep, as a Brep, for the kernel's Solid::from_node() to operate on — or None where it has none (a mesh, a curve, a CSG body, a JT or OpenSCAD part). Shared with the scene, not copied.
Node::curves()
fn curves(&self) -> Polylines<'s>
Its free curves as polylines; a 2D drawing is all of these.
Node::isocurves()
fn isocurves(&self) -> Polylines<'s>
Lines ruled across its surfaces, so a curved face reads as curved in a wireframe. A flat face yields its outline, so these can overlap Node::edges().
Node::save_mesh()
fn save_mesh(&self, path: impl AsRef<Path>, format: &str) -> Result<()>
Write this node's own mesh — where it is defined, without its placement — in one of cadaclysm_sdk::mesh_formats(). A node that draws nothing, or an unknown format, returns Err(Error); ask Node::can_mesh() first to grey out a menu entry. To write the whole model, see Scene::save().
Node.show
Draw what this node and everything under it places with the viewer in use. Keywords as Scene.show.
Comes with the viewers follow-up.
Node.view
Orbit this node and everything under it; returns (azimuth, elevation, zoom) where it was left.
Comes with the viewers follow-up.
Brep
struct Brep
A body's exact B-rep — the trimmed surfaces its mesh is cut from — shared with the scene rather than copied, and held by this object until it is released. It is what Node::brep() hands the kernel's Solid::from_node(), which operates on it without a copy, and it can say whether it is a Manifold. It outlives the scene it came from for as long as anything holds it. In the node's own frame and the file's own units and axes; the kernel library must come from the same release as the reader.
Brep::pointer()
fn pointer(&self) -> *const sys::CadaclysmBrep
The brep's C pointer, which the kernel's wrapper hands across. returns Err(Error) once released.
Brep::layout_id()
fn layout_id() -> Result<String>
How this library lays a brep out in memory: its compiler, target and source. The kernel shares a brep only with a reader whose id equals its own.
Brep::manifold()
fn manifold(&self) -> Result<Manifold>
Whether its faces make a manifold — every edge bordered by one face or two, the faces round every vertex one fan — and whether it is closed, as a Manifold. Read off the topology the file wrote, not a mesh. returns Err(Error) once released.
Brep::release()
fn release(self)
Give the reference back now. Leaving a with block, or the garbage collector, does it otherwise.
Placement
struct Placement<'s>
One drawing of one node's geometry at one place: what Scene::placements() lists. Two drawings of the same shape name the same geometry node, and so the same arrays — upload once, draw twice.
Placement::geometry()
fn geometry(&self) -> Node<'s>
The node whose mesh, edges and curves this draws.
Placement::select()
fn select(&self) -> Node<'s>
What a click on this drawing selects: the placement's own node rather than the shared shape, which would light up every copy.
Placement::transform()
fn transform(&self) -> [[f64; 4]; 4]
Where to draw it: a 4×4, already composed through every frame from the root.
Placement::raw_transform()
fn raw_transform(&self) -> [f64; 16]
The same matrix as 16 numbers, column-major.
Mesh
struct Mesh<'s>
A node's triangles, in the node's own frame: what Node::mesh() returns. The arrays are read-only views into the scene's memory, not copies — a large assembly is tens of millions of triangles, and most of them go straight to a GPU. Mesh::copy() makes arrays of your own.
Mesh::normals
normals: Option<&'s [[f32; 3]]>
Three floats a vertex, or None for a mesh that carries none.
Mesh::uvs
uvs: Option<&'s [[f32; 2]]>
Two floats a vertex, or None: only readers asked for world-scale UVs fill them. One unit of u or v is one world unit, so faces overlap in UV space — a tiling material, not a lightmap.
Mesh::colors
colors: Option<&'s [[f32; 4]]>
Four floats (RGBA) a vertex, or None — the common case. Only a body painted in several colours, opened with colours on, carries them.
Mesh::copy()
fn copy(&self) -> MeshData
The same arrays in memory of your own, safe to keep after Scene::close(). Deliberately visible: on a large model this is where the gigabytes go.
Polylines
struct Polylines<'s>
Edges or curves already flattened to points, in the node's own frame: what Node::edges(), Node::curves() and Node::isocurves() return. Views into the scene, like Mesh.
Polylines::segment_indices()
fn segment_indices(&self) -> Vec<u32>
Index pairs into the positions, two per line segment — what GL_LINES and every pair-taking API want. Indices rather than points, so a caller can transform the points once and expand afterwards.
Polylines::segments()
fn segments(&self) -> Vec<[f32; 3]>
The segment endpoints themselves, two points per segment.
Surfaces and Face
struct Surfaces<'s>
A node's faces as exact surfaces and trims: what Node::surfaces() returns. Iterate it for Face values. In the file's own frame; Scene::surface_matrix() brings it into the scene's.
Face
struct Face<'s>
kind: u32
origin: [f32; 3]
domain: [f32; 4]
scalars: [f32; 4]
loops: Vec<&'s [[f32; 2]]>
nurbs: &'s [f32]
One trimmed face. kind is the surface: 0 plane, 1 cylinder, 2 cone, 3 sphere, 4 torus, 5 revolution, 6 extrusion, 7 NURBS, 8 sum. origin, ax, ay, az are its frame, scalars its kind-dependent sizes (radius, angle…) and domain its (u min, v min, u max, v max). loops holds the trim loops as (u, v) points, each closing implicitly; profile, profile2 and nurbs carry what a swept or NURBS surface needs. reversed flips the normal; transposed swaps u and v. The C header's CadaclysmFace is the full description.
Bounds
struct Bounds
An axis-aligned box: what Node::bounds() and Scene::bounds() return. All zeros means "nothing here".
Bounds::is_empty()
fn is_empty(&self) -> bool
Whether this is the all-zero box that stands for nothing.
Attribute
struct Attribute
One thing the file said about a node: what Node::attributes() lists.
Attribute::kind
kind: ValueKind
Which kind of value it holds — a ValueKind. Lets a caller tell a reference from prose, or total the numbers.
Attribute::value
value: Value
The value, in the language's own type where it has one for the kind.
A Value: Text, Integer, Real, Boolean or None (lists and references arrive as Text).
Attribute::text()
fn text(&self) -> String
The value rendered for display, identically in every wrapper: true/false, reals in their shortest exact form, lists as [a, b, c].
Value Rust only
enum Value
None
Text(String)
Integer(i64)
Real(f64)
Boolean(bool)
An attribute's value as the type its kind names.
Convention
enum Convention
The coordinate space to open a file into. The library converts on the way out, so a caller names the space it draws in and reads geometry already in it — nothing to rotate or scale afterwards.
Convention
enum Convention
Native = 0
Unreal = 1
Unity = 2
YUp = 3
Blender = 4
The presets: NATIVE keeps the file's own axes and units; UNREAL is Z up, left-handed, centimetres; UNITY Y up, left-handed, metres; Y_UP Y up, right-handed, metres (glTF, three.js, most real-time engines); BLENDER Z up, right-handed, metres.
cadaclysm_sdk::FILE_UNITS
const FILE_UNITS: u32 = 0x100
Combine with a preset to keep its axes but the file's own units.
cadaclysm_sdk::UV_WORLD
const UV_WORLD: u32 = 0x200
Combine with a preset to ask for world-scale texture coordinates in Mesh::uvs. Off by default: eight bytes a vertex nobody asked for.
Convention::parse()
fn parse(text: &str) -> Result<u32>
A convention from a name a user typed: unreal, or unreal+file-units. An unknown name returns Err(Error) listing the accepted ones, rather than silently reading as native.
ValueKind
enum ValueKind
Which kind of value an Attribute holds.
ValueKind
enum ValueKind
None
Text
Integer
Real
Boolean
List
Reference
TEXT, INTEGER, REAL, BOOLEAN; LIST (the elements rendered as [a, b, c]); REFERENCE (another entity, by the id the file gave it, so it can be followed rather than shown as prose); NONE for an attribute that had no value.
Manifold
struct Manifold
Whether a body's faces make a manifold — every edge bordered by one face or two, the faces round every vertex one fan — told from its topology rather than a mesh: what a brep's manifold and the kernel's Solid::manifold() return. Orientation is not asked. The topology is the file's: faces that name no shared edge (IGES, each surface its own sheet; an IFC face written as one polygon) read as open however well they meet in space.
Manifold::boundary_edges
boundary_edges: u32
Edges only one face borders: a sheet's rim, a hole in a shell.
Manifold::non_manifold_edges
non_manifold_edges: u32
Edges three or more faces border: a fin, or two solids meeting along a line.
Manifold::non_manifold_vertices
non_manifold_vertices: u32
Vertices whose faces make more than one fan: two solids touching at a corner.
Manifold::is_manifold
is_manifold: bool
No non-manifold edge or vertex: a manifold, possibly with a boundary.
Errors
struct Error
A call into the library failed; the message is the library's own reason. One type for every reader failure.
Building solids
Frames and axes
A frame is twelve numbers: an origin, then the x, y and z axes, each three numbers (0,0,0, 1,0,0, 0,1,0, 0,0,1 is the world). A profile is drawn in its frame's x/y and extruded along its z. An axis is six numbers: a point and a direction, which need not be unit. Workplane::xy() and friends start on the three world planes; Solid::face_frame() gives the frame on a face. A Frame builds one for you — Frame::xy() at any origin, Frame::at() from a point and a normal — and checks that its axes are square and right-handed, which a bare twelve numbers are not.
Frames are &Frame, always checked: Frame::xy, Frame::at, Frame::new and Frame::of build them. An axis is an AxisLine, [[px, py, pz], [dx, dy, dz]].
Module functions
The kernel's own library, licence and STEP writer. It is a separate shared library (cadaclysm_blacksmith) from the reader, with its own licence call; one licence file serves both.
blacksmith::write_step()
fn write_step(path: impl AsRef<std::path::Path>, solids: &[&Solid], schema: Option<&str>, unit: Unit) -> Result<()>
Write several solids as one STEP file, each its own body. unit is mm, m or in. schema is left out for AP203 (built in — no file needed), the name of another built-in schema such as AP242's AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF (AP214's AUTOMOTIVE_DESIGN cannot carry the writer's mechanical_context), or a custom EXPRESS schema: a path to its .exp or its text.
blacksmith::write_step_text()
fn write_step_text(solids: &[&Solid], schema: Option<&str>, unit: Unit) -> Result<String>
The same STEP file as text, for a caller that stores or sends it rather than writing a file.
blacksmith::default_schema()
fn default_schema() -> Result<PathBuf>
Where an ap203.exp file is found (CADACLYSM_SCHEMAS, else schemas/ in a parent). No longer needed to write STEP: the kernel's AP203 is built in.
blacksmith::version()
fn version() -> Result<String>
The version of the kernel library actually loaded.
blacksmith::build_date()
fn build_date() -> Result<String>
When the loaded kernel was built, YYYY-MM-DD.
blacksmith::license()
fn license(text_or_path: impl AsRef<std::ffi::OsStr>) -> Result<()>
Load a licence into the kernel — the text, or a file's path. The reader has its own call; the same file works for both.
blacksmith::license_info()
fn license_info() -> Result<String>
One line about the kernel's licence, or unlicensed. Never None.
blacksmith::license_notice_count()
fn license_notice_count() -> Result<u64>
How many unlicensed notices the kernel has printed to stderr in this process.
blacksmith::brep_layout_id()
fn brep_layout_id() -> Result<String>
How the loaded kernel lays a brep out in memory: its compiler, target and source. Solid::from_node() works only where this equals the reader's Brep::layout_id() — the two libraries from the same release.
blacksmith::library_path()
fn library_path() -> Result<PathBuf>
Where the kernel library was found: CADACLYSM_BLACKSMITH_LIBRARY first, then as the reader's.
Tolerances Rust only
const DEFAULT_TOLERANCE: f64 = 0.05
const FILLET_TOLERANCE: f64 = 1e-6
Rust has no default arguments: pass DEFAULT_TOLERANCE (0.05) where Python's booleans, meshes and bounds default, and FILLET_TOLERANCE (1e-6) for fillet, chamfer and shell.
Unit Rust only
enum Unit
Metre = 0
Millimetre = 1
Inch = 2
The unit a STEP file is written in; Unit::default() is millimetres.
rgb Rust only
fn rgb(hex: &str) -> Result<[f64; 3]>
[r, g, b] in 0..1 from "#rgb" or "#rrggbb", for Solid::coloured().
load Rust only
fn load(path: impl AsRef<std::path::Path>) -> Result<()>
Load the kernel library from a path before anything else asks for it.
Profile
struct Profile
A closed outline with holes, in its own x/y — what gets extruded, revolved, lofted or swept. Immutable: every method returns a new one.
Its loops must be simple: an outline that crosses or touches itself (a figure-eight, a vertex landing on another side), a hole that runs into the boundary, or two holes that overlap are refused by every call that builds a face or a closed solid, naming the loops — extrude: hole 0 crosses the boundary. The open calls (extrude_open, revolve_open, sweep_open, loft_open) build sheets, and take such a profile as it is.
Profile::slot()
fn slot(centre: [f64; 2], length: f64, r: f64) -> Result<Profile>
A slot (stadium) length long overall, with end radius r, centred on centre and running along x. length must exceed 2 * r.
Profile::polygon()
fn polygon(points: &[[f64; 2]]) -> Result<Profile>
A closed polygon through the points, in order, its side back to the first point a segment of its own. At least three points.
Profile::regular_polygon()
fn regular_polygon(centre: [f64; 2], radius: f64, sides: u32, angle: f64) -> Result<Profile>
A regular polygon of sides sides (at least 3) on the circle of radius about centre, its first corner at angle radians from the sketch's x axis (0 by default), the rest counter-clockwise.
Profile::spline()
fn spline(points: &[[f64; 2]], degree: u32, weights: Option<&[f64]>, closed: bool) -> Result<Profile>
A spline of degree (3 by default) through the control polygon points, weights one per point or None. Open, it starts on the first point and ends on the last: an open chain, for Solid::extrude_open() or Profile::chain(). Closed, it is periodic — smooth through its own start, no corner there — and a closed profile. The degree is lowered to fit the points; a degree of zero, too few points (two open, three closed) or a weight not positive returns Err(BuildError).
Profile::path()
fn path(start: [f64; 2]) -> Result<Path>
Start drawing an outline segment by segment at start; see Path.
Profile::chain()
fn chain(pieces: &[&Profile], tolerance: f64) -> Result<Profile>
Open profiles — paths ended open — joined end to end into one: the forge's merge. They may come in any order and either way round: each next piece is the first of the rest with an end within tolerance (1e-6 by default) of either end of the chain so far, reversed where that makes it meet. Every segment is kept exactly — a line a line, an arc an arc, a spline the same spline. Closed where the chain's two ends meet, otherwise an open chain. A piece that is empty, has holes, is closed on its own or meets none of the others returns Err(BuildError) naming it by its index.
Profile::from_loops()
fn from_loops(loops: &[&Profile]) -> Result<Profile>
Closed loops, in any order, as one profile: the loop enclosing the most area is the boundary and every other a hole in it, in the order given — a sketch's rectangle and the circles drawn inside it. Each loop is a closed profile with no holes of its own, wound either way; one that closes within rounding is closed exactly. A loop that is open, empty or encloses no area, loops that cross or touch, a hole outside the boundary, or one inside another hole (an island) returns Err(BuildError), naming the loops by their index.
Profile::close_loop()
fn close_loop(&self) -> Result<Profile>
This profile closed — the forge's sketch "close": where its last segment stops short of its start (a path ended open), 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, and holes are closed the same way.
Profile::with_hole()
fn with_hole(&self, hole: &Profile) -> Result<Profile>
This outline with hole cut out of it.
Profile::translate()
fn translate(&self, dx: f64, dy: f64) -> Result<Profile>
This outline moved by (dx, dy).
Profile::round()
fn round(&self, radius: f64, corners: Option<&[u32]>, open: bool) -> Result<Profile>
This outline with its corners rounded by radius: where two straight segments meet, both are cut back and an exact arc tangent to both goes between them; a corner next to an arc or a spline is left as it is. With no corners every such corner is rounded, the holes' too; a list picks corners of the outline — corner k is where segment k ends. open reads the profile as an open chain whose two ends stay square. A radius that does not fit returns Err(BuildError) naming the corner.
Profile.polylines
The outline, then each hole, as polylines at z = 0 within tolerance of its arcs and splines — what a viewer draws it with. A closed loop repeats its first point at the end; an open chain (a profile ended open) stays open. Views, like Solid::mesh().
Declared; the method comes with the viewers.
Profile.show
Draw the outline and holes with the viewer in use, from the top by default. Keywords as Solid.show; edges= is ignored, the lines being the whole picture.
Comes with the viewers follow-up.
Profile.view
Orbit the outline with the viewer in use; returns (azimuth, elevation, zoom) where it was left.
Comes with the viewers follow-up.
Path
struct Path
An outline drawn a segment at a time — lines, arcs, Béziers, NURBS — then closed into a Profile. Ending it consumes the builder.
Path::arc_to()
fn arc_to(self, x: f64, y: f64, centre: [f64; 2], ccw: bool) -> Result<Path>
A circular arc to (x, y) about centre, counter-clockwise unless ccw is false.
Path::bezier_to()
fn bezier_to(self, c1: [f64; 2], c2: [f64; 2], to: [f64; 2]) -> Result<Path>
A cubic Bézier through control points c1, c2 to to.
Path::nurbs_to()
fn nurbs_to(self, control: &[[f64; 2]], knots: &[f64], degree: u32, weights: Option<&[f64]>) -> Result<Path>
A NURBS segment: control is every control point after the current one, the endpoint last; knots the full knot vector; weights one per control point including the current one, or None for a non-rational curve.
Path::end()
fn end(self) -> Result<Profile>
Close the outline back to its start and return the Profile.
Path::end_open()
fn end_open(self) -> Result<Profile>
The path as it stands, not closed: an open chain for Solid::extrude_open(), Solid::sweep_open() or Solid::loft_open().
Path::begin Rust only
fn begin(start: [f64; 2]) -> Result<Path>
Python's Path(start), the same as Profile::path(). Each step takes the builder by value and hands it back, so a chain reads Path::begin([0.0, 0.0])?.line_to(10.0, 0.0)?.end()?.
SweepPath
struct SweepPath
The 3D path a profile is carried along by Solid::sweep(): lines and circular arcs. Sweeping only borrows it, so one path can be swept many times; close it when done.
SweepPath::along()
fn along(curve: &Profile, frame: &Frame, tolerance: f64, open: bool) -> Result<SweepPath>
The path a 2D chain (usually from Path::end_open()) draws on frame: a line a straight piece, an arc a circular one, a Bézier or spline fitted with biarcs — arcs tangent to each other and to the curve — within tolerance, so the path is tangent throughout and the sweep exact along it. open false closes the path back to its start.
SweepPath::line_to()
fn line_to(self, point: [f64; 3]) -> Result<SweepPath>
A straight piece to a 3D point.
SweepPath::arc()
fn arc(self, centre: [f64; 3], axis: [f64; 3], angle: f64) -> Result<SweepPath>
Turn angle radians (in (0, 2π]) about the axis through centre along axis.
Slant
struct Slant
A plane a Solid::extrude_between() starts or ends on, read as a height over the sketch plane at each point: at + grad · (x, y). Flat for an ordinary cap; sloped for a mitre.
Slant::of_plane()
fn of_plane(frame: &Frame, point: [f64; 3], normal: [f64; 3]) -> Result<Slant>
The plane through point square to normal, as heights over frame. A plane that contains the extrusion direction has no height and returns Err(BuildError).
Frame
struct Frame
A frame built for you instead of twelve numbers typed out: an origin and three unit axes, square to each other and right-handed (z = x × y). It goes wherever a frame does. Immutable. The constructor takes the origin and the three axes, normalises them, and returns Err(BuildError) when they are not square or not right-handed.
Frame::xy()
fn xy(origin: [f64; 3]) -> Frame
The world XY plane through origin: z up, as Workplane::xy().
Frame::xz()
fn xz(origin: [f64; 3]) -> Frame
The world XZ plane through origin: x along X, y along Z, so z is -Y, as Workplane::xz().
Frame::yz()
fn yz(origin: [f64; 3]) -> Frame
The world YZ plane through origin: x along Y, y along Z, so z is +X, as Workplane::yz().
Frame::at()
fn at(origin: [f64; 3], normal: [f64; 3], x: Option<[f64; 3]>) -> Result<Frame>
The plane through origin square to normal, which becomes the frame's z (it need not be unit). Its x axis is x laid onto that plane; with none, world X laid onto it, or world Y when the normal is within about 25° of X — the axes Solid::face_frame() gives a face facing normal. So a normal along +Z, -Y or +X gives exactly Frame::xy(), Frame::xz() or Frame::yz(). A zero normal, or an x along the normal, returns Err(BuildError).
Frame::of()
fn of(raw: [f64; 12]) -> Result<Frame>
Twelve numbers — what Solid::face_frame() and Workplane::frame() hand back — as a checked frame, to read its axes or move it.
Frame::midplane()
fn midplane(a: &Frame, b: &Frame) -> Result<Frame>
The plane midway between the planes of frames a and b — 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.
Frame::through()
fn through(p: [f64; 3], q: [f64; 3], r: [f64; 3]) -> Result<Frame>
The plane through the points p, q and r: its origin p, its x towards q, its z the normal the three turn about counter-clockwise. Three points on one line returns Err(BuildError).
Frame::origin()
fn origin(&self) -> [f64; 3]
fn x(&self) -> [f64; 3]
fn y(&self) -> [f64; 3]
fn z(&self) -> [f64; 3]
The origin and the three axes, each three numbers.
Frame::translate()
fn translate(&self, dx: f64, dy: f64, dz: f64) -> Frame
This frame moved by (dx, dy, dz) in world coordinates.
Frame::offset()
fn offset(&self, distance: f64) -> Frame
This frame moved distance along its own z: Frame.xy().offset(5) is the XY plane at z = 5.
Frame::new Rust only
fn new(origin: [f64; 3], x: [f64; 3], y: [f64; 3], z: [f64; 3]) -> Result<Frame>
fn raw(&self) -> [f64; 12]
Python's Frame(origin, x, y, z), checked, and the twelve numbers a frame stands for.
Workplane
struct Workplane<'a>
The fluent chain: a frame, the solid built so far, and the face last picked. A build step replaces the solid rather than adding to it — combine solids explicitly with Solid::join(). Every step returns Err(BuildError) at once rather than holding the error for later.
Workplane::xy()
fn xy() -> Workplane<'static>
Start on the XY plane at the origin (Z up). Workplane::xz() and Workplane::yz() start on the other two.
Workplane::from_solid()
fn from_solid(solid: &'a Solid) -> Workplane<'a>
Start from an existing solid, on the XY plane — the usual way to pick one of its faces and build on it.
Workplane::cuboid()
fn cuboid(self, x: f64, y: f64, z: f64) -> Result<Workplane<'a>>
A box on the current frame; replaces the solid.
Workplane::cylinder()
fn cylinder(self, r: f64, h: f64) -> Result<Workplane<'a>>
A cylinder of radius r and height h standing on the current frame; replaces the solid.
Workplane::face()
fn face(self, profile: &Profile) -> Result<Workplane<'a>>
The planar sheet the profile bounds on this frame — see Solid::face(); replaces the solid.
Workplane::extrude()
fn extrude(self, profile: &Profile, height: f64) -> Result<Workplane<'a>>
The profile extruded height along the frame's z; replaces the solid.
Workplane::revolve()
fn revolve(self, profile: &Profile, angle: f64) -> Result<Workplane<'a>>
The profile revolved angle radians about the frame's y axis; replaces the solid.
Workplane::translate()
fn translate(mut self, dx: f64, dy: f64, dz: f64) -> Result<Workplane<'a>>
Slide the current solid. Keeps the face selection — a rigid move keeps every face's index.
Workplane::faces()
fn faces(mut self, selector: &Selector) -> Result<Workplane<'a>>
Pick a face of the current solid with a Selector.
Workplane::on_face()
fn on_face(mut self) -> Result<Workplane<'a>>
Move the frame onto the face last picked (outward normal as z), so the next step builds on it.
Workplane::solid()
fn solid(self) -> Result<Solid>
The solid built so far. On an empty chain it returns Err(BuildError).
Selector and Axis
enum Selector
Which face to pick: the one furthest along an axis, furthest against it, the one facing a direction, or by index. Used by Workplane::faces() and Solid::select_face().
Selector::Normal()
Normal([f64; 3])
The face whose outward normal is nearest direction (need not be unit).
Solid
struct Solid
An exact B-rep solid (or an open sheet): planes, cylinders, cones, spheres, tori and NURBS, trimmed and joined, never approximated by triangles. Immutable — every operation returns a new one. Close it when done, or let the language's scope do it; see Lifetimes.
Primitives
Solid::cuboid()
fn cuboid(x: f64, y: f64, z: f64) -> Result<Solid>
A box x × y × z, centred on the origin.
Solid::cylinder()
fn cylinder(r: f64, h: f64) -> Result<Solid>
A cylinder of radius r, from z = 0 to h.
Solid::cone()
fn cone(r: f64, h: f64) -> Result<Solid>
A cone of base radius r and height h, apex up.
Solid::torus()
fn torus(major: f64, minor: f64) -> Result<Solid>
A torus about the z axis: major to the tube's centre, minor the tube's radius.
Solid::wedge()
fn wedge(x: f64, y: f64, z: f64, top_x: f64) -> Result<Solid>
A box whose top face is top_x long instead of x: a ramp.
From a profile
Solid::extrude()
fn extrude(profile: &Profile, frame: &Frame, height: f64) -> Result<Solid>
The profile on frame, extruded height along the frame's z.
Solid::extrude_open()
fn extrude_open(profile: &Profile, frame: &Frame, height: f64) -> Result<Solid>
The walls only, no caps: an open sheet. Takes an open Path::end_open() chain as well as a closed profile.
Solid::extrude_tapered()
fn extrude_tapered(profile: &Profile, frame: &Frame, height: f64, taper: f64) -> Result<Solid>
Extrude with a draft: the walls lean out by taper radians as they rise (in, when negative). Every wall stays exact — a plane off a line, a cone off an arc.
Solid::extrude_open_tapered()
fn extrude_open_tapered(profile: &Profile, frame: &Frame, height: f64, taper: f64) -> Result<Solid>
The tapered walls without caps.
Solid::extrude_between()
fn extrude_between(profile: &Profile, frame: &Frame, bottom: impl Into<Slant>, top: impl Into<Slant>) -> Result<Solid>
Extrude between two planes rather than two heights: bottom and top are each a Slant (a bare number is a flat one). With both flat this is Solid::extrude(); with a slope it is the mitred end of a frame member. A top that comes down to or through the bottom returns Err(BuildError).
Solid::extrude_open_between()
fn extrude_open_between(profile: &Profile, frame: &Frame, bottom: impl Into<Slant>, top: impl Into<Slant>) -> Result<Solid>
Solid::extrude_between() without the caps.
Solid::revolve()
fn revolve(profile: &Profile, axis: &AxisLine, angle: f64) -> Result<Solid>
The profile swung angle radians about axis (a point and a direction — see Frames). The profile's x is read as the radius and its y as the height along the axis, so it must lie to one side of it.
Solid::revolve_open()
fn revolve_open(profile: &Profile, axis: &AxisLine, angle: f64) -> Result<Solid>
The revolved surface of an open profile: a sheet.
Solid::revolve_in_plane()
fn revolve_in_plane(profile: &Profile, frame: &Frame, a: [f64; 2], b: [f64; 2], angle: f64) -> Result<Solid>
The profile on frame swung angle radians about the axis through the sketch points a and b (each (x, y) on the frame) — the profile and its axis drawn together, as a sketch draws them, where Solid::revolve() reads the profile as (radius, height). The profile may lie on either side of the axis and touch it (a half-disc with its diameter on the axis turns into a ball), but not cross it. The sweep starts where the profile is drawn and turns right-handed about b - a, so a partial turn leaves one end of the solid over the profile itself.
Solid::revolve_open_in_plane()
fn revolve_open_in_plane(profile: &Profile, frame: &Frame, a: [f64; 2], b: [f64; 2], angle: f64) -> Result<Solid>
Solid::revolve_in_plane() for a curve: its segments swung into a sheet, no caps.
Solid::coil()
fn coil(profile: &Profile, axis: &AxisLine, pitch: f64, turns: f64) -> Result<Solid>
The profile coiled about axis (a point and a direction): read as Solid::revolve() reads it — x the distance from the axis, y along it — and turned turns times while climbing pitch along the axis each turn: a spring, a thread, Fusion's Coil. The walls follow the helix to a few millionths of the radius (a helix is not a NURBS curve, so they are a close fit, exact at both ends); the two ends are the profile itself, flat. A profile reaching the axis, one with holes, or — from a full turn up — a pitch no taller than the profile returns Err(BuildError).
Solid::loft()
fn loft(a: &Profile, frame_a: &Frame, b: &Profile, frame_b: &Frame) -> Result<Solid>
The solid between profile a on one frame and b on another: ruled walls between matching sides (both profiles need the same number of sides, and no holes), capped by the two.
Solid::loft_open()
fn loft_open(a: &Profile, frame_a: &Frame, b: &Profile, frame_b: &Frame) -> Result<Solid>
The ruled walls without the caps.
Solid::loft_through()
fn loft_through(sections: &[(&Profile, &Frame)]) -> Result<Solid>
The solid smooth through every section — a profile on its frame, in order: each wall interpolates its side across all the profiles (cubic through four or more, quadratic through three, Solid::loft() through two), capped by the first and the last. Every section of the result is its profile exactly, arcs and all. The profiles must have the same number of sides and no holes; otherwise returns Err(BuildError).
Solid::loft_through_open()
fn loft_through_open(sections: &[(&Profile, &Frame)]) -> Result<Solid>
The walls through the curves without the caps: an open sheet.
Solid::sweep()
fn sweep(profile: &Profile, frame: &Frame, path: &SweepPath) -> Result<Solid>
The profile on frame, carried along a SweepPath. A straight piece is an extrusion and an arc a revolution about the arc's axis, so nothing is approximated — a circle along an arc is an exact torus wall.
Solid::sweep_open()
fn sweep_open(profile: &Profile, frame: &Frame, path: &SweepPath) -> Result<Solid>
The swept walls without caps: an open sheet.
Solid::pipe()
fn pipe(path: &SweepPath, radius: f64, thickness: f64) -> Result<Solid>
A circle of radius carried along a SweepPath, square to where it starts — Fusion's Pipe: a solid rod, or with a positive thickness a tube whose walls are that thick. The path is only borrowed, as by Solid::sweep(), and refused the same way.
Solid::extrude_faces()
fn extrude_faces(&self, height: f64) -> Result<Solid>
Every face of a sheet pushed height along its own normal, walled and closed: the sheet as a solid of that thickness.
Solid::face()
fn face(profile: &Profile, frame: &Frame) -> Result<Solid>
The flat sheet a profile bounds on frame: one planar face, each hole a hole through it, its normal the frame's z however the profile winds, every edge the exact line, arc or spline its segment is. An open sheet — raise it with Solid::extrude_faces(), cut it with Solid::trim().
Placing
Solid::place()
fn place(&self, frame: &Frame) -> Result<Solid>
A solid built about the origin moved onto frame: its origin to the frame's origin, its axes to the frame's (see Frames).
Solid::translate()
fn translate(&self, dx: f64, dy: f64, dz: f64) -> Result<Solid>
Moved by (dx, dy, dz).
Solid::rotate()
fn rotate(&self, axis: &AxisLine, radians: f64) -> Result<Solid>
Turned radians about axis (a point and a direction).
Solid::mirror()
fn mirror(&self, plane: &Frame) -> Result<Solid>
Reflected across plane: a frame whose z is the mirror plane's normal.
Booleans
Solid::join()
fn join(&self, other: &Solid, tolerance: f64) -> Result<Solid>
The union with other, as an exact B-rep. merge (off by default, so face and edge numbers stay as they were) merges the flush faces the join leaves, as Solid::merge_flush() does — Go takes it as a trailing true, Java as an overload; Solid::cut() and Solid::common() take it too. tolerance (0.05 by default) is the mesh tolerance the boolean decides at: both solids are meshed at it, so a tighter one is as correct and slower. progress, where the wrapper takes one, is called with a phase name and a done/total count.
No merge argument and no progress callback: call merge_flush() on the result. The tolerance is always passed — DEFAULT_TOLERANCE is 0.05.
Solid::cut()
fn cut(&self, other: &Solid, tolerance: f64) -> Result<Solid>
This solid with other removed.
Solid::common()
fn common(&self, other: &Solid, tolerance: f64) -> Result<Solid>
What this solid and other share.
Solid::split_sheet()
fn split_sheet(&self, tool: &Solid, tolerance: f64) -> Result<Solid>
This solid or sheet cut along tool's boundary with nothing removed: each face comes back as its pieces outside tool and then its pieces inside, in the original face order — the start of a surface trim. tool must be a closed solid. Keep the pieces you want with Solid::drop_faces(), or split and drop in one call with Solid::trim().
Faces and sheets
Solid::face_sheet()
fn face_sheet(&self, face: u32) -> Result<Solid>
One face alone, as an open sheet: its surface, its loops and the exact curves on its edges, the rest of the solid left behind — raised by Solid::extrude_faces() it is the prism over that face. Keeps the face's colour.
Solid::drop_faces()
fn drop_faces(&self, faces: &[u32]) -> Result<Solid>
This solid without the faces listed: the rest keep their surfaces, curves and colours in their order, so an index into the result is this one's with the dropped ones closed up. Dropping every face returns Err(BuildError).
Solid::trim()
fn trim(&self, tool: &Solid, keep: Keep, tolerance: f64) -> Result<Solid>
This sheet (or solid) cut along the closed tool's boundary and the pieces on one side thrown away: keep "outside" (the default) keeps what lies outside the tool — a hole punched through — and "inside" what lies within it. Nothing on the kept side returns Err(BuildError). tolerance and progress as for Solid::join().
keep is Keep::Outside or Keep::Inside.
Solid::push_pull()
fn push_pull(&self, face: u32, distance: f64, tolerance: f64) -> Result<Solid>
fn push_pull_faces(&self, faces: &[u32], distance: f64, tolerance: f64) -> Result<Solid>
Face face pushed out by distance along its outward normal — pulled in, negative — the way Fusion and Rhino extrude a face: the prism over it joined on (cut out) at tolerance, and the flush faces merged, so a box's top raised is one taller box of six faces rather than a box and a prism with every side wall split at the seam. A face on a cylinder, a cone, a sphere or a torus moves out along its normal instead, as Fusion's press-pull does: the surface a step out — a boss fatter, a bore or a countersink narrower, a dome fuller — with the flat faces beside it carried along in their own planes. Any other curved face is refused, as is a curved face 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 run into another edge. A flat face keeps its own colour where it now lies.
Several faces push together, as Fusion's press-pull on a selection: each by its own rule, one after another in the order given, each found again 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 boss's top and wall the boss taller and fatter. A face on the same curved surface as one before it, and joined to it, moved with that one and is not pushed twice. No faces, or a face an earlier push took away, returns Err(BuildError).
Solid::refillet()
fn refillet(&self, face: u32, radius: f64, tolerance: f64) -> Result<Solid>
The round face belongs to — a fillet's bands, balls and rim bands joined to that face — made again at radius, as Fusion's press-pull on a fillet face: taken back to the sharp edges it replaced, and those rounded again, so the round is the one Solid::fillet() makes at that radius. Rounds of straight edges between planes (their ends square corners, mitres, balls, or a cylinder, cone or sphere the edge runs into — a D-cut shaft's top edge, a rib's into a boss) and of circular rims between a plane and a cylinder or cone (a boss's foot, a bore's mouth, a counterbore's step); a face that is not one, or a radius that does not fit, returns Err(BuildError).
Solid::unfillet()
fn unfillet(&self, face: u32) -> Result<Solid>
The round face belongs to taken off, the faces beside it made sharp again, meeting on the edges the round replaced — Fusion's delete of a fillet face. The same rounds as Solid::refillet().
Solid::rechamfer()
fn rechamfer(&self, face: u32, distance: f64, tolerance: f64) -> Result<Solid>
The chamfer face belongs to — its bevels (flat between two planes, cones round rims) and the corner triangles joined to that face — cut again at distance, as Fusion's press-pull on a chamfer face: taken back to the sharp edges it cut, and those bevelled again, so the chamfer is the one Solid::chamfer() cuts at that distance. A flat bevel's ends may run into a cylinder, cone or sphere, as a round's may. A face that is not a chamfer's bevel, or a distance that does not fit, returns Err(BuildError).
Solid::unchamfer()
fn unchamfer(&self, face: u32) -> Result<Solid>
The chamfer face belongs to taken off, the faces beside it made sharp again — Fusion's delete of a chamfer face. The same chamfers as Solid::rechamfer().
Solid::merge_flush()
fn merge_flush(&self) -> Result<Solid>
This solid with its flush faces merged: flat faces on one plane, facing one way and meeting along their edges — the seams Solid::join() leaves where two parts are flush — made one face, and the vertices left mid-way along a straight edge taken out.
Solid::split()
fn split(&self, tool: &Solid, tolerance: f64) -> Result<Vec<Solid>>
This solid split by tool into bodies — Fusion's Split Body — returned as a list: a closed tool gives the parts outside it, then the parts inside; a flat sheet (a Solid::face()) splits by the whole plane it lies on. Each connected part is a body of its own, so a U cut across both arms is three. The new faces are pieces of the tool's, the colours carried over. A tool that does not cross the solid, or a curved sheet, returns Err(BuildError). tolerance and progress as for Solid::join().
Solid::split_by_plane()
fn split_by_plane(&self, plane: &Frame, tolerance: f64) -> Result<Vec<Solid>>
This solid split by the plane through plane's origin, square to its z (a frame): the bodies in front of it first, then those behind.
Solid::lumps()
fn lumps(&self) -> Result<Vec<Solid>>
This solid's connected bodies, each a solid of its own — faces sharing an edge are one body — in the order of their first faces. One body comes back as itself; a boolean that leaves two parts gives two.
Finishing
Solid::edges()
fn edges(&self) -> Result<Vec<Edge>>
The solid's edges as Edge values — what Solid::fillet() and Solid::chamfer() take. Copied; safe to keep.
Solid::fillet()
fn fillet(&self, edges: &[u32], radius: f64, tolerance: f64) -> Result<Solid>
Round the given edges (Edge values or their indices) with radius. Exact: the blend faces are cylinders, tori and NURBS, and the neighbours are trimmed back onto them.
Solid::chamfer()
fn chamfer(&self, edges: &[u32], distance: f64, tolerance: f64) -> Result<Solid>
A flat bevel instead of a round: each edge cut back distance along both its faces.
Solid::shell()
fn shell(&self, thickness: f64, open: &[u32], tolerance: f64) -> Result<Solid>
Hollow the solid to walls thickness thick — inward for a positive thickness, outward (the solid becoming the cavity) for a negative one. The faces listed in open are removed so the hollow is reachable.
Solid::thicken()
fn thicken(&self, thickness: f64, tolerance: f64) -> Result<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. A thickness a face cannot take — a radius used up, a free-form offset folding over — returns Err(BuildError).
Asking
Solid::face_kind()
fn face_kind(&self, face: u32) -> Result<String>
A face's surface: plane, cylinder, cone, sphere, torus, nurbs, revolution, extrusion or other.
Solid::select_face()
fn select_face(&self, selector: &Selector) -> Result<u32>
The index of the face a Selector picks.
Solid::face_frame()
fn face_frame(&self, face: u32) -> Result<Frame>
The frame on a face: origin at its centre, z its outward normal, x world X laid onto the face (world Y on a face facing close to X) — Frame::at()'s rule, so the top of a box gets the XY plane's axes. What Workplane::on_face() moves onto.
Solid::bounds()
fn bounds(&self) -> Result<([f64; 3], [f64; 3])>
The axis-aligned bounds, over the tessellation at 0.05.
Solid::bounds_at()
fn bounds_at(&self, tolerance: f64) -> Result<([f64; 3], [f64; 3])>
The bounds over the tessellation at tolerance — the same cache Solid::mesh() fills, so asking both costs one mesh.
Solid::leaked_edges()
fn leaked_edges(&self, tolerance: f64) -> Result<u32>
How many mesh edges at tolerance are bound by anything other than two triangles: zero for a closed solid. A seam two solids share along a line does not count; a hole or a fold does.
Solid::unpaired_edges()
fn unpaired_edges(&self, tolerance: f64) -> Result<u32>
How many mesh edges have triangle uses that do not cancel out: zero for a closed, consistently oriented solid. Unlike Solid::leaked_edges() this catches a fold — two triangles running the same way.
Solid::is_watertight()
fn is_watertight(&self, tolerance: f64) -> Result<bool>
Whether Solid::leaked_edges() is zero.
Solid::manifold()
fn manifold(&self) -> Result<Manifold>
Whether the faces make a manifold — every edge bordered by one face or two, the faces round every vertex one fan — and whether it is closed, as a Manifold. Read off the solid's topology, not a mesh, so it takes no tolerance; whether the faces all face out is Solid::unpaired_edges()'s question.
Colour
Solid::coloured()
fn coloured(&self, colour: [f64; 3], face: Option<u32>) -> Result<Solid>
A new solid coloured (r, g, b), each 0..1 — Python and Node.js also take "#rgb" or "#rrggbb" — or, given a face (Go: ColouredFace), just that face, whose colour then wins over the solid's. What is made from a coloured solid inherits: a move keeps every colour; a boolean, fillet, chamfer or shell gives each face the colour of the input face it lies on (a cut's bore takes the tool's), and a new face — a round, a shell's inner wall — the solid's. STEP output carries no colour.
Takes [r, g, b] and an Option<u32> face; blacksmith::rgb("#rrggbb") reads hex.
Solid::colour()
fn colour(&self) -> Result<Option<[f64; 3]>>
The solid's own colour as (r, g, b), or none.
Solid::face_colour()
fn face_colour(&self, face: u32) -> Result<Option<[f64; 3]>>
A face's colour as drawn: its own, else the solid's, else none.
From files
Solid::open()
fn open(path: impl AsRef<std::path::Path>, body: Option<usize>) -> Result<Solid>
The body a CAD file holds, as a solid: STEP (AP203/214/242), ACIS .sat, Rhino .3dm, OCCT .brep, IGES or IFC, read where it draws, in the file's own units and axes. A file drawing several bodies needs body (0-based, in drawing order) or Solid::open_all(). What such a solid can do is what its geometry allows: fillet and chamfer want line and circle edges; booleans take any surface, but new edges traced on a free-form face are not always writable back to STEP; and every verb meshes its operands first, so its cost grows with the body's face count. Reads through the reader library, which must be from the same release.
Solid::open_all()
fn open_all(path: impl AsRef<std::path::Path>) -> Result<Vec<Solid>>
Every body a CAD file draws, as solids placed where it draws them: one per placement, so a part placed twice is two solids.
Solid::from_node()
fn from_node(node: &Node<'_>, placed: bool) -> Result<Solid>
The body a reader Node draws, as a solid — sharing the reader's brep (Node::brep()), not copying it; the scene can be closed first. placed (the default) puts it where the node's transform does, where its mesh draws; otherwise it keeps the node's own frame. The two libraries' layouts (blacksmith::brep_layout_id()) must agree, or it returns Err(Error).
Output
Solid::mesh()
fn mesh(&mut self, tolerance: f64) -> Result<Mesh<'_>>
Triangles at tolerance: positions, normals (three floats a vertex) and indices. Views into the solid's own cache — valid until the solid is closed or meshed again at a different tolerance; copy what must outlive either.
Takes &mut self, so while the mesh is borrowed nothing can mesh the solid again; returns the reader's Mesh.
Solid.face_triangles
How many triangles each face meshed to at tolerance, one count per face in face order: the triangles of Solid::mesh() at the same tolerance run face by face, so face f's are the counts[f] after the first counts[:f].sum(), and the counts sum to the mesh's triangle count. What a viewer colours a face by. A view, like Solid::mesh().
Declared; the method comes with the viewers.
Solid::edge_polylines()
fn edge_polylines(&mut self, tolerance: f64) -> Result<Vec<&[[f32; 3]]>>
The feature edges as polylines at tolerance, one run of points per edge. Views, like Solid::mesh().
Solid.show
Draw the solid with the viewer in use — in a terminal, the picture is left in the scrollback. Each face keeps its own colour (Solid::face_colour(): a colour of its own, else the solid's). Keywords: view= (front, back, left, right, top, bottom, iso), az=, el=, zoom=, up=, edges=, width=, height=, hint=, tolerance=.
Comes with the viewers follow-up.
Solid.view
Orbit the solid with the viewer in use until it is closed; returns (azimuth, elevation, zoom) where it was left. Keywords as Solid.show.
Comes with the viewers follow-up.
Solid::step()
fn step(&self, path: impl AsRef<std::path::Path>, schema: Option<&str>, unit: Unit) -> Result<()>
Write this solid as an AP203 STEP file; see blacksmith::write_step() for schema and unit.
Solid::step_text()
fn step_text(&self, schema: Option<&str>, unit: Unit) -> Result<String>
The same STEP file as text.
Solid::to_scene()
fn to_scene(&self, schema: Option<&str>) -> Result<Scene>
This solid as a reader Scene, through STEP in memory: the door from the kernel to everything the reader does — its tree, meshes, glTF/OBJ/STL export. Needs the reader library as well.
Solid::close()
fn close(self)
Free the solid now. The garbage collector, or the language's scope, does it otherwise.
AxisLine and Keep Rust only
type AxisLine = [[f64; 3]; 2]
enum Keep
Outside
Inside
An axis is [[px, py, pz], [dx, dy, dz]]: a point and a direction. Solid::trim() keeps the side Keep names.
Edge
struct Edge
One edge of a solid as plain data, copied out of it: what Solid::edges() lists and Solid::fillet() takes.
Edge::direction()
fn direction(&self) -> Option<[f64; 3]>
The unit direction of a straight edge, or None for a curved one. Picking the vertical edges of a plate is a filter on this.
Manifold
What Solid::manifold() returns: whether the solid's faces make a manifold, and whether it is closed, told from its topology rather than a mesh.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.faces
How many faces.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.edges
How many distinct edges: one shared by two faces counts once.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.vertices
How many distinct vertices.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.boundary_edges
Edges only one face borders: a sheet's rim, a hole in a shell.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.non_manifold_edges
Edges three or more faces border: a fin, or two solids meeting along a line.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.non_manifold_vertices
Vertices whose faces make more than one fan: two solids touching at a corner.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.is_manifold
No non-manifold edge or vertex: a manifold, possibly with a boundary.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Manifold.is_closed
A manifold with no boundary edge either: it encloses a solid.
Solid::manifold returns the reader's cadaclysm_sdk::Manifold, the same struct.
Errors
type BuildError = Error
What the kernel refused, in its own words: a profile that crosses itself, a fillet too large for its faces, a boolean with nothing left. Raised by the call that failed, at once.
What borrows, what to close
Scene, Solid, Profile, Path and SweepPath free themselves when dropped; close() does it early. Every call that can fail returns Result<T, Error> — the kernel's BuildError is the same type — so ? carries the library's reason up.
A Node borrows its Scene, and Node::mesh() hands back slices of the scene's own memory with the same lifetime: keeping one past the scene does not compile. Mesh::copy() gives vectors of your own. Solid::mesh() borrows the solid mutably: meshing it again at another tolerance replaces the cache the slices point into, and the borrow rules that out while the mesh is in use.
Strings are always copied on the way out. A Scene is Send and Sync: read it from several threads, and call Scene::cancel() from one while another runs Scene::realize_all(). A Solid is Send but not Sync — the kernel caches its tessellation — so it may move to another thread, not be shared by two.