docs.rs failed to build cadrum-0.4.5
Please check the
build logs for more information.
See
Builds for ideas on how to fix a failed build,
or
Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault,
open an issue.
cadrum

Rust CAD library powered by OpenCASCADE (OCCT 8.0.0-rc5).
Usage
More examples with source code are available at lzpel.github.io/cadrum.
Add this to your Cargo.toml:
[dependencies]
cadrum = "^0.4"
Example
Primitives
Primitive solids: box, cylinder, sphere, cone, torus — colored and exported as STEP + SVG.
cargo run --example 01_primitives
use cadrum::Solid;
use glam::DVec3;
fn main() {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let solids = [
Solid::cube(10.0, 20.0, 30.0)
.color("#4a90d9"),
Solid::cylinder(8.0, DVec3::Z, 30.0)
.translate(DVec3::new(30.0, 0.0, 0.0))
.color("#e67e22"),
Solid::sphere(8.0)
.translate(DVec3::new(60.0, 0.0, 15.0))
.color("#2ecc71"),
Solid::cone(8.0, 0.0, DVec3::Z, 30.0)
.translate(DVec3::new(90.0, 0.0, 0.0))
.color("#e74c3c"),
Solid::torus(12.0, 4.0, DVec3::Z)
.translate(DVec3::new(130.0, 0.0, 15.0))
.color("#9b59b6"),
];
let mut f = std::fs::File::create(format!("{example_name}.step")).expect("failed to create file");
cadrum::io::write_step(&solids, &mut f).expect("failed to write STEP");
let mut svg = std::fs::File::create(format!("{example_name}.svg")).expect("failed to create SVG file");
cadrum::io::write_svg(&solids, DVec3::new(1.0, 1.0, 1.0), 0.5, true, false, &mut svg).expect("failed to write SVG");
}
Write read
Read and write: chain STEP, BRep text, and BRep binary round-trips with progressive rotation.
cargo run --example 02_write_read
use cadrum::{Solid, Transform};
use glam::DVec3;
use std::f64::consts::FRAC_PI_8;
fn main() -> Result<(), cadrum::Error> {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let step_path = format!("{example_name}.step");
let text_path = format!("{example_name}_text.brep");
let brep_path = format!("{example_name}.brep");
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let original = cadrum::io::read_step(
&mut std::fs::File::open(format!("{manifest_dir}/steps/colored_box.step")).expect("open file"),
)?;
let a_written = original.clone().rotate_x(FRAC_PI_8);
cadrum::io::write_step(&a_written, &mut std::fs::File::create(&step_path).expect("create file"))?;
let a = cadrum::io::read_step(&mut std::fs::File::open(&step_path).expect("open file"))?;
let b_written = a.clone().rotate_x(FRAC_PI_8);
cadrum::io::write_brep_text(&b_written, &mut std::fs::File::create(&text_path).expect("create file"))?;
let b = cadrum::io::read_brep_text(&mut std::fs::File::open(&text_path).expect("open file"))?;
let c_written = b.clone().rotate_x(FRAC_PI_8);
cadrum::io::write_brep_binary(&c_written, &mut std::fs::File::create(&brep_path).expect("create file"))?;
let c = cadrum::io::read_brep_binary(&mut std::fs::File::open(&brep_path).expect("open file"))?;
let [min, max] = original[0].bounding_box();
let spacing = (max - min).length() * 1.5;
let all: Vec<Solid> = [original, a, b, c].into_iter()
.enumerate()
.flat_map(|(i, solids)| solids.translate(DVec3::X * spacing * i as f64))
.collect();
let mut svg = std::fs::File::create(format!("{example_name}.svg")).expect("create file");
cadrum::io::write_svg(&all, DVec3::new(1.0, 1.0, 2.0), 0.5, true, false, &mut svg)?;
let mut stl = std::fs::File::create(format!("{example_name}.stl")).expect("create file");
cadrum::io::write_stl(&all, 0.1, &mut stl)?;
let stl_path = format!("{example_name}.stl");
for (label, path) in [("STEP", &step_path), ("BRep text", &text_path), ("BRep binary", &brep_path), ("STL", &stl_path)] {
let size = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
println!("{label:12} {path:30} {size:>8} bytes");
}
Ok(())
}
Transform
Transform operations: translate, rotate, scale, and mirror applied to a cone.
cargo run --example 03_transform
use cadrum::Solid;
use glam::DVec3;
use std::f64::consts::PI;
fn main() {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let base = Solid::cone(8.0, 0.0, DVec3::Z, 20.0)
.color("#888888");
let solids = [
base.clone(),
base.clone()
.color("#4a90d9")
.translate(DVec3::new(40.0, 0.0, 20.0)),
base.clone()
.color("#e67e22")
.rotate_x(PI / 2.0)
.translate(DVec3::new(80.0, 0.0, 0.0)),
base.clone()
.color("#2ecc71")
.scale(DVec3::ZERO, 1.5)
.translate(DVec3::new(120.0, 0.0, 0.0)),
base.clone()
.color("#e74c3c")
.mirror(DVec3::ZERO, DVec3::Z)
.translate(DVec3::new(160.0, 0.0, 0.0)),
];
let mut f = std::fs::File::create(format!("{example_name}.step")).expect("failed to create file");
cadrum::io::write_step(&solids, &mut f).expect("failed to write STEP");
let mut svg = std::fs::File::create(format!("{example_name}.svg")).expect("failed to create SVG file");
cadrum::io::write_svg(&solids, DVec3::new(1.0, 1.0, 1.0), 0.5, true, false, &mut svg).expect("failed to write SVG");
}
Boolean
Boolean operations: union, subtract, and intersect between a box and a cylinder.
cargo run --example 04_boolean
use cadrum::{Solid, Transform};
use glam::DVec3;
fn main() -> Result<(), cadrum::Error> {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let make_box = Solid::cube(20.0, 20.0, 20.0)
.color("#4a90d9");
let make_cyl = Solid::cylinder(8.0, DVec3::Z, 30.0)
.translate(DVec3::new(10.0, 10.0, -5.0))
.color("#e67e22");
let union = make_box
.union(&[make_cyl.clone()])?;
let subtract = make_box
.subtract(&[make_cyl.clone()])?
.translate(DVec3::new(40.0, 0.0, 0.0));
let intersect = make_box
.intersect(&[make_cyl])?
.translate(DVec3::new(80.0, 0.0, 0.0));
let shapes: Vec<Solid> = [union, subtract, intersect].concat();
let mut f = std::fs::File::create(format!("{example_name}.step")).expect("failed to create file");
cadrum::io::write_step(&shapes, &mut f).expect("failed to write STEP");
let mut svg = std::fs::File::create(format!("{example_name}.svg")).expect("failed to create SVG file");
cadrum::io::write_svg(&shapes, DVec3::new(1.0, 1.0, 2.0), 0.5, true, false, &mut svg).expect("failed to write SVG");
Ok(())
}
Extrude
Demo of Solid::extrude: push a closed 2D profile along a direction vector.
cargo run --example 05_extrude
use cadrum::{BSplineEnd, Edge, Error, Solid};
use glam::DVec3;
fn build_box() -> Result<Solid, Error> {
let profile = Edge::polygon([
DVec3::new(0.0, 0.0, 0.0),
DVec3::new(5.0, 0.0, 0.0),
DVec3::new(5.0, 5.0, 0.0),
DVec3::new(0.0, 5.0, 0.0),
])?;
Solid::extrude(&profile, DVec3::new(0.0, 0.0, 8.0))
}
fn build_oblique_cylinder() -> Result<Solid, Error> {
let profile = [Edge::circle(3.0, DVec3::Z)?];
Solid::extrude(&profile, DVec3::new(-4.0, 6.0, 8.0))
}
fn build_l_beam() -> Result<Solid, Error> {
let profile = Edge::polygon([
DVec3::new(0.0, 0.0, 0.0),
DVec3::new(4.0, 0.0, 0.0),
DVec3::new(4.0, 1.0, 0.0),
DVec3::new(1.0, 1.0, 0.0),
DVec3::new(1.0, 3.0, 0.0),
DVec3::new(0.0, 3.0, 0.0),
])?;
Solid::extrude(&profile, DVec3::new(0.0, 0.0, 12.0))
}
fn build_heart() -> Result<Solid, Error> {
let profile = [Edge::bspline(
[
DVec3::new(0.0, -4.0, 0.0), DVec3::new(2.0, -1.5, 0.0),
DVec3::new(4.0, 1.5, 0.0),
DVec3::new(2.5, 3.5, 0.0), DVec3::new(0.0, 2.0, 0.0), DVec3::new(-2.5, 3.5, 0.0), DVec3::new(-4.0, 1.5, 0.0),
DVec3::new(-2.0, -1.5, 0.0),
],
BSplineEnd::Periodic,
)?];
Solid::extrude(&profile, DVec3::new(0.0, 0.0, 7.0))
}
fn main() -> Result<(), Error> {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let box_solid = build_box()?.color("#b0d4f1");
let oblique = build_oblique_cylinder()?.color("#f1c8b0").translate(DVec3::new(12.0, 0.0, 0.0));
let l_beam = build_l_beam()?.color("#b0f1c8").translate(DVec3::new(28.0, 0.0, 0.0));
let heart = build_heart()?.color("#f1b0b0").translate(DVec3::new(38.0, 0.0, 0.0));
let result = [box_solid, oblique, l_beam, heart];
let step_path = format!("{example_name}.step");
let mut f = std::fs::File::create(&step_path).expect("failed to create STEP file");
cadrum::io::write_step(&result, &mut f).expect("failed to write STEP");
println!("wrote {step_path}");
let svg_path = format!("{example_name}.svg");
let mut f = std::fs::File::create(&svg_path).expect("failed to create SVG file");
cadrum::io::write_svg(&result, DVec3::new(1.0, 1.0, 1.0), 0.5, true, false, &mut f).expect("failed to write SVG");
println!("wrote {svg_path}");
Ok(())
}
Loft
Demo of Solid::loft: skin a smooth solid through cross-section wires.
cargo run --example 06_loft
use cadrum::{Edge, Error, Solid};
use glam::DVec3;
fn build_frustum() -> Result<Solid, Error> {
let lower = [Edge::circle(3.0, DVec3::Z)?];
let upper = [Edge::circle(1.5, DVec3::Z)?.translate(DVec3::Z * 8.0)];
Ok(Solid::loft(&[lower, upper])?.color("#cd853f"))
}
fn build_morph() -> Result<Solid, Error> {
let r = 2.5;
let square = Edge::polygon([
DVec3::new(-r, -r, 0.0),
DVec3::new(r, -r, 0.0),
DVec3::new(r, r, 0.0),
DVec3::new(-r, r, 0.0),
])?;
let circle = Edge::circle(r, DVec3::Z)?.translate(DVec3::Z * 10.0);
Ok(Solid::loft([square.as_slice(), std::slice::from_ref(&circle)])?.color("#808000"))
}
fn build_tilted() -> Result<Solid, Error> {
let bottom = [Edge::circle(2.5, DVec3::Z)?];
let mid = [Edge::circle(2.0, DVec3::new(0.3, 0.0, 1.0).normalize())?
.translate(DVec3::new(1.0, 0.0, 5.0))];
let top = [Edge::circle(1.5, DVec3::new(-0.2, 0.3, 1.0).normalize())?
.translate(DVec3::new(-0.5, 1.0, 10.0))];
Ok(Solid::loft(&[bottom, mid, top])?.color("#4682b4"))
}
fn main() -> Result<(), Error> {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let frustum = build_frustum()?;
let morph = build_morph()?.translate(DVec3::new(10.0, 0.0, 0.0));
let tilted = build_tilted()?.translate(DVec3::new(20.0, 0.0, 0.0));
let result = [frustum, morph, tilted];
let step_path = format!("{example_name}.step");
let mut f = std::fs::File::create(&step_path).expect("failed to create STEP file");
cadrum::io::write_step(&result, &mut f).expect("failed to write STEP");
println!("wrote {step_path}");
let svg_path = format!("{example_name}.svg");
let mut f = std::fs::File::create(&svg_path).expect("failed to create SVG file");
cadrum::io::write_svg(&result, DVec3::new(1.0, 1.0, 1.0), 0.5, true, false, &mut f).expect("failed to write SVG");
println!("wrote {svg_path}");
Ok(())
}
Sweep
Sweep showcase: M2 screw (helix spine) + U-shaped pipe (line+arc+line spine).
cargo run --example 07_sweep
use cadrum::{Edge, Error, ProfileOrient, Solid, SolidExt, Transform};
use glam::DVec3;
fn build_m2_screw() -> Result<Vec<Solid>, Error> {
let r = 1.0;
let h_pitch = 0.4;
let h_thread = 6.0;
let r_head = 1.75;
let h_head = 1.3;
let r_delta = 3f64.sqrt() / 2.0 * h_pitch;
let helix = Edge::helix(r - r_delta, h_pitch, h_thread, DVec3::Z, DVec3::X)?;
let profile = Edge::polygon([DVec3::new(0.0, -h_pitch / 2.0, 0.0), DVec3::new(r_delta, 0.0, 0.0), DVec3::new(0.0, h_pitch / 2.0, 0.0)])?;
let profile = profile.align_z(helix.start_tangent(), helix.start_point()).translate(helix.start_point());
let thread = Solid::sweep(&profile, &[helix], ProfileOrient::Up(DVec3::Z))?;
let shaft = Solid::cylinder(r - r_delta * 6.0 / 8.0, DVec3::Z, h_thread);
let crest = Solid::cylinder(r - r_delta / 8.0, DVec3::Z, h_thread);
let thread_shaft = thread.union([&shaft])?.intersect([&crest])?;
let head = Solid::cylinder(r_head, DVec3::Z, h_head).translate(DVec3::Z * h_thread);
thread_shaft.union([&head])
}
fn build_u_pipe() -> Result<Vec<Solid>, Error> {
let pipe_radius = 0.4;
let leg_length = 6.0;
let gap = 3.0;
let bend_radius = gap / 2.0;
let a = DVec3::ZERO;
let b = DVec3::new(0.0, 0.0, leg_length);
let arc_mid = DVec3::new(bend_radius, 0.0, leg_length + bend_radius);
let c = DVec3::new(gap, 0.0, leg_length);
let d = DVec3::new(gap, 0.0, 0.0);
let up_leg = Edge::line(a, b)?;
let bend = Edge::arc_3pts(b, arc_mid, c)?;
let down_leg = Edge::line(c, d)?;
let profile = Edge::circle(pipe_radius, DVec3::Z)?;
let pipe = Solid::sweep(&[profile], &[up_leg, bend, down_leg], ProfileOrient::Up(DVec3::Y))?;
Ok(vec![pipe])
}
fn main() {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let x_offset = 6.0;
let mut all: Vec<Solid> = Vec::new();
match build_m2_screw() {
Ok(screw) => {
all.extend(screw.color("red"));
println!("✓ screw built (red, centered at origin)");
}
Err(e) => eprintln!("✗ screw failed: {e}"),
}
match build_u_pipe() {
Ok(pipe) => {
let placed: Vec<Solid> = pipe.translate(DVec3::X * x_offset).color("blue");
all.extend(placed);
println!("✓ U-pipe built (blue, offset x={x_offset})");
}
Err(e) => eprintln!("✗ U-pipe failed: {e}"),
}
if all.is_empty() {
eprintln!("nothing to write");
return;
}
let mut f = std::fs::File::create(format!("{example_name}.step")).expect("failed to create STEP file");
cadrum::io::write_step(&all, &mut f).expect("failed to write STEP");
let mut f_svg = std::fs::File::create(format!("{example_name}.svg")).expect("failed to create SVG file");
cadrum::io::write_svg(&all, DVec3::new(1.0, 1.0, -1.0), 0.5, false, false, &mut f_svg).expect("failed to write SVG");
println!("wrote {example_name}.step / {example_name}.svg ({} solids)", all.len());
}
Bspline
cargo run --example 08_bspline
use cadrum::Solid;
use glam::{DQuat, DVec3};
use std::f64::consts::TAU;
const M: usize = 48; const N: usize = 24; const RING_R: f64 = 6.0;
fn point(i: usize, j: usize) -> DVec3 {
let phi = TAU * (i as f64) / (M as f64);
let theta = TAU * (j as f64) / (N as f64);
let two_phi = 2.0 * phi;
let a = 1.8 + 0.6 * two_phi.sin();
let b = 1.0 + 0.4 * two_phi.cos();
let psi = two_phi; let z_shift = 1.0 * two_phi.sin();
let local_raw = DVec3::X * (a * theta.cos()) + DVec3::Z * (b * theta.sin());
let local_twisted = DQuat::from_axis_angle(DVec3::Y, psi) * local_raw;
let local_shifted = local_twisted + DVec3::Z * z_shift;
let translated = local_shifted + DVec3::X * RING_R;
DQuat::from_axis_angle(DVec3::Z, phi) * translated
}
fn main() {
let example_name = std::path::Path::new(file!()).file_stem().unwrap().to_str().unwrap();
let grid: [[DVec3; N]; M] = std::array::from_fn(|i| std::array::from_fn(|j| point(i, j)));
let plasma = Solid::bspline(grid, true).expect("2-period bspline torus should succeed");
let objects = [plasma.color("cyan")];
let mut f = std::fs::File::create(format!("{example_name}.step")).unwrap();
cadrum::io::write_step(&objects, &mut f).unwrap();
let mut f_svg = std::fs::File::create(format!("{example_name}.svg")).unwrap();
cadrum::io::write_svg(&objects, DVec3::new(0.05, 0.05, 1.0), 0.1, false, true, &mut f_svg).unwrap();
eprintln!("wrote {0}.step / {0}.svg", example_name);
}
Requirements
- A C++17 compiler (GCC, Clang, or MSVC)
- CMake
Tested with GCC 15.2.0 (MinGW-w64) and CMake 3.31.11 on Windows.
Build
By default, cargo build downloads OCCT 8.0.0-rc5 source and builds it automatically.
The built library is placed in target/occt/ and removed by cargo clean.
To cache the OCCT build across cargo clean, set OCCT_ROOT to a persistent directory:
export OCCT_ROOT=~/occt
cargo build
- If
OCCT_ROOT is set and the directory already contains OCCT libraries, they are linked directly (no rebuild).
- If
OCCT_ROOT is set but the directory is empty or missing, OCCT is built and installed there.
- To force a rebuild, remove the directory:
rm -rf ~/occt
Features
color (default): Colored STEP I/O via XDE (STEPCAFControl). Enables write_step_with_colors,
read_step_with_colors, and per-face color on Solid.
Colors are preserved through boolean operations and other transformations.
Showcase
Try it now →
A browser-based configurator that lets you tweak dimensions of a STEP model and get an instant 3D preview and quote. cadrum powers the parametric reshaping and meshing on the backend.
Release Notes
0.4.5
Solid::bspline<const M, const N>(grid, periodic) — new constructor: build a periodic B-spline solid from a 2D control-point grid. V (cross-section) is always periodic; U (longitudinal) is controlled by the periodic flag (torus when true, capped pipe when false). Implemented via GeomAPI_PointsToBSplineSurface::Interpolate over an augmented grid plus SetUPeriodic/SetVPeriodic.
write_svg / Mesh::to_svg now take shading: bool — opt-in Lambertian shading with head-on light. When true, triangles are tinted by 0.5 + 0.5 * (normal · dir) so curved/organic shapes read clearly; false reproduces the pre-0.4.5 flat rendering. Breaking: existing callers must add the flag (pass false to preserve earlier output).
examples/08_bspline.rs rewritten: 2 field-period stellarator-like torus with twisted + vertically undulating elliptic cross-sections, exercising Solid::bspline and shading=true.
tests/bspline.rs added: verifies 180° point symmetry of the stellarator shape via XZ/YZ half-space intersection (s1 ≈ s3, s2 ≈ s4).
Error::BsplineFailed(String) new variant. Breaking for downstream code that does exhaustive match on Error.
- OCCT 8.0.0 deprecation warnings resolved in
make_bspline_edge and make_bspline_solid (NCollection_HArray1<gp_Pnt> via local using alias to bypass the Handle() macro comma-splitting issue; NCollection_Array2<gp_Pnt> directly).
License
This project is licensed under the MIT License.
Compiled binaries include OpenCASCADE Technology (OCCT),
which is licensed under the LGPL 2.1.
Users who distribute applications built with cadrum must comply with the LGPL 2.1 terms.
Since cadrum builds OCCT from source, end users can rebuild and relink OCCT to satisfy this requirement.