vcad 0.1.0

Parametric CAD in Rust — CSG modeling with multi-format export
Documentation

vcad

Parametric CAD in Rust. Define parts with CSG operations and export to STL, glTF, USD, and DXF.

Built on manifold for boolean operations and mesh generation.

Quick start

use vcad::{centered_cube, centered_cylinder, Part};

// Plate with four mounting holes
let plate = centered_cube("plate", 100.0, 60.0, 5.0);

let hole = centered_cylinder("hole", 3.0, 10.0, 32);
let holes = hole.linear_pattern(80.0, 0.0, 0.0, 2)
    .linear_pattern(0.0, 40.0, 0.0, 2)
    .translate(-40.0, -20.0, 0.0);

let part = plate - holes;  // operator overloads for CSG
part.write_stl("plate.stl").unwrap();

Features

Primitives — cube, cylinder, cone, sphere, centered variants

CSG — union (+), difference (-), intersection (&), plus named methods

Transforms — translate, rotate, scale, mirror, linear/circular pattern

Inspection — volume, surface area, bounding box, center of mass, triangle count

Export formats:

Format Use case Feature flag
STL 3D printing, CNC always on
glTF/GLB Web viewers, PBR materials gltf (default)
USD/USDA Isaac Sim, Omniverse usd
DXF Laser cutting (2D profiles) always on
STEP Interchange (requires OCCT) step

Materials — PBR material database loaded from TOML, with part-to-material assignments.

Scenes — Multi-part assemblies that preserve per-part materials for rendering.

Installation

[dependencies]
vcad = "0.1"

Without glTF support:

[dependencies]
vcad = { version = "0.1", default-features = false }

Examples

Boolean operations

use vcad::{centered_cube, centered_cylinder, Part};

let block = centered_cube("block", 30.0, 30.0, 20.0);
let bore = centered_cylinder("bore", 10.0, 25.0, 64);
let result = block.difference(&bore);

Bolt pattern

use vcad::{centered_cube, bolt_pattern};

let flange = centered_cube("flange", 80.0, 80.0, 6.0);
let holes = bolt_pattern(6, 60.0, 5.5, 10.0, 32);
let part = flange.difference(&holes);

Multi-material scene (glTF)

use vcad::{Part, Scene};
use vcad::export::{Materials, export_scene_glb};

let materials = Materials::parse(r#"
    [materials.steel]
    color = [0.7, 0.7, 0.72]
    metallic = 0.9
    roughness = 0.4

    [materials.rubber]
    color = [0.1, 0.1, 0.1]
    metallic = 0.0
    roughness = 0.9
"#).unwrap();

let mut scene = Scene::new("assembly");
scene.add(Part::cube("frame", 100.0, 50.0, 30.0), "steel");
scene.add(
    Part::cylinder("wheel", 20.0, 10.0, 32).translate(60.0, 0.0, 0.0),
    "rubber",
);

export_scene_glb(&scene, &materials, "assembly.glb").unwrap();

DXF for laser cutting

use vcad::export::DxfDocument;

let mut doc = DxfDocument::new();
doc.add_rectangle(100.0, 60.0, 0.0, 0.0);  // outer profile
doc.add_circle(0.0, 0.0, 15.0);             // center hole
doc.add_circle(-35.0, 0.0, 3.0);            // mounting hole
doc.add_circle(35.0, 0.0, 3.0);             // mounting hole
doc.add_bend_line(-50.0, 20.0, 50.0, 20.0); // bend (BEND layer)
doc.export("bracket.dxf").unwrap();

Materials from TOML

# materials.toml
[materials.aluminum_6061]
color = [0.85, 0.85, 0.88]
metallic = 0.95
roughness = 0.35
density = 2700
description = "6061-T6 Aluminum"

[materials.abs_black]
color = [0.08, 0.08, 0.08]
metallic = 0.0
roughness = 0.7
density = 1040

[part_materials]
frame = "aluminum_6061"
cover = "abs_black"
use vcad::export::Materials;

let mats = Materials::load("materials.toml").unwrap();
let frame_mat = mats.get_for_part("frame").unwrap();
assert_eq!(frame_mat.name, "aluminum_6061");

API reference

Primitives

Constructor Description
Part::cube(name, x, y, z) Box with corner at origin
Part::cylinder(name, r, h, segments) Cylinder along Z
Part::cone(name, r_bot, r_top, h, segments) Tapered cylinder
Part::sphere(name, r, segments) Sphere at origin
Part::empty(name) Empty geometry (identity for union)
centered_cube(name, x, y, z) Box centered at origin
centered_cylinder(name, r, h, segments) Cylinder centered at origin
counterbore_hole(d, cb_d, cb_depth, depth, seg) Through hole + counterbore
bolt_pattern(n, bcd, hole_d, depth, seg) Circle of holes

CSG operations

Method / Operator Description
a.union(&b) or a + b Boolean union
a.difference(&b) or a - b Boolean difference
a.intersection(&b) or a & b Boolean intersection

All operators work on both Part and &Part.

Transforms

Method Description
.translate(x, y, z) Move
.translate_vec(v) Move by nalgebra Vector3
.rotate(x_deg, y_deg, z_deg) Rotate (degrees)
.scale(x, y, z) Non-uniform scale
.scale_uniform(s) Uniform scale
.mirror_x() / .mirror_y() / .mirror_z() Mirror across plane
.linear_pattern(dx, dy, dz, count) N copies along vector
.circular_pattern(radius, count) N copies around Z axis

Inspection

Method Returns
.volume() f64 — mesh volume
.surface_area() f64 — total surface area
.bounding_box() ([f64; 3], [f64; 3]) — AABB min/max
.center_of_mass() [f64; 3] — volume-weighted centroid
.num_triangles() usize — triangle count
.is_empty() bool — has geometry?

Export

Method / Function Format
part.write_stl(path) Binary STL file
part.to_stl() Binary STL bytes
export_glb(part, material, path) glTF binary (single part)
export_scene_glb(scene, materials, path) glTF binary (multi-material)
export_usd(part, material, path) USD with physics
export_robot_usd(body, wheels, ...) USD articulated robot
DxfDocument::new() + .export(path) DXF R12 for laser cutting

Cookbook

Mounting plate

let plate = centered_cube("plate", 120.0, 80.0, 4.0);
let holes = centered_cylinder("hole", 2.5, 10.0, 32)
    .linear_pattern(100.0, 0.0, 0.0, 2)
    .linear_pattern(0.0, 60.0, 0.0, 2)
    .translate(-50.0, -30.0, 0.0);
let part = plate - holes;

Symmetric bracket

let arm = centered_cube("arm", 40.0, 10.0, 5.0).translate(25.0, 0.0, 0.0);
let bracket = &arm + &arm.mirror_x();  // symmetric about YZ plane

Flanged hub

let hub = centered_cylinder("hub", 15.0, 20.0, 64);
let flange = centered_cylinder("flange", 30.0, 4.0, 64).translate(0.0, 0.0, -10.0);
let bore = centered_cylinder("bore", 5.0, 25.0, 32);
let bolt_holes = bolt_pattern(6, 45.0, 3.0, 8.0, 32).translate(0.0, 0.0, -10.0);
let part = hub + flange - bore - bolt_holes;

Enclosure with lid

let wall = 2.0;
let outer = centered_cube("outer", 60.0, 40.0, 30.0);
let inner = centered_cube("inner", 60.0 - wall * 2.0, 40.0 - wall * 2.0, 30.0 - wall)
    .translate(0.0, 0.0, wall);
let box_part = outer - inner;

let lid = centered_cube("lid", 60.0, 40.0, 3.0).translate(0.0, 0.0, 30.0);

Radial vent pattern

let slot = centered_cube("slot", 15.0, 2.0, 10.0);
let vents = slot.circular_pattern(20.0, 8);
let panel = centered_cylinder("panel", 35.0, 3.0, 64) - vents;

Blender integration

vcad pairs well with the Blender MCP server for AI-assisted 3D workflows. Export a GLB from vcad, then import and preview it in Blender — all from a single conversation with an AI agent.

// Generate and export
let plate = centered_cube("plate", 100.0, 60.0, 5.0);
let holes = bolt_pattern(4, 80.0, 6.0, 10.0, 32);
let part = plate - holes;
part.write_stl("plate.stl").unwrap();

// Export multi-material scene as GLB
export_scene_glb(&scene, &materials, "assembly.glb").unwrap();

Then in Blender (via MCP):

# Import the GLB into the current scene
bpy.ops.import_scene.gltf(filepath="assembly.glb")

The MCP server exposes tools for scene inspection, viewport screenshots, and Python execution — so an AI agent can generate geometry with vcad, import it into Blender, position cameras, and render previews in a single loop.

Units

vcad is unit-agnostic — coordinates are just f64. By convention, the projects using vcad treat values as millimeters (matching STL/DXF conventions for manufacturing).

License

MIT