3 unstable releases
| 0.2.2 | Dec 8, 2025 |
|---|---|
| 0.2.1 |
|
| 0.2.0 |
|
| 0.1.1 | Jun 13, 2025 |
| 0.1.0 | May 25, 2025 |
#473 in Rust patterns
Used in scadman_helper
210KB
4K
SLoC
scadman
scadman is a Rust library for generating OpenSCAD code programmatically. It provides a
type-safe and structured way to define 2D and 3D geometric objects, apply transformations
and operations, and output valid OpenSCAD code.
What is this library?
Instead of writing OpenSCAD code directly in .scad files, scadman allows you to define
your parametric models using Rust code. This approach leverages Rust's strong typing,
module system, testing capabilities, and build tools to manage your designs.
The library represents OpenSCAD primitives, modifiers, and blocks as distinct Rust types,
managed by a powerful generic type system centered around ScadObjectGeneric<D>. This system
provides enhanced type safety by tracking object dimensions (2D, 3D, Mixed) at compile time
where possible, while allowing for flexible runtime handling when necessary. It provides
traits and helper functions to build, combine, and transform these objects, handling the
complexities of OpenSCAD syntax generation, including parameter formatting, object nesting,
and indentation.
Features
- Type-Safe Object Model: Define 2D, 3D, and mixed-dimension objects using a clear
type hierarchy based on
ScadObjectGeneric<D>and public-facing wrappers likeScadObject2D,ScadObject3D, andScadObjectMixed. - Comprehensive Primitive Support: Create basic shapes like
square,circle,sphere,cube,cylinder,polygon,polyhedron,text, andimport. - Extensive Modifier Support: Apply transformations and operations such as
translate,rotate,scale,resize,mirror,multmatrix,offset,projection,color,hull,render,children,linear_extrude,rotate_extrude, etc. - Boolean and Block Operations: Combine objects using
union,difference, andintersection. Blocks ({ ... }) are also explicitly supported. - Operator Overloading: Use standard Rust operators (
+,-,*) forunion,difference, andintersectionoperations. These operators work on theScadObject(untyped alias) and perform a runtime check, requiring both operands to have the same internal dimension (Object2D,Object3D, orObjectMixed). Operations between objects of different dimensions will result in a panic. - Comment Support: Easily add comments to individual objects or blocks using the
.commented()method or dedicated factory functions. - Builder Pattern: Many complex primitives and modifiers provide a type-safe builder
pattern (
...Builder) for configuring optional parameters. - Value Handling: Type-safe representation and formatting for various OpenSCAD value
types (numbers, vectors, strings, booleans, angles, colors, matrices) via the
ScadDisplaytrait. - Code Generation: Generate clean, correctly indented OpenSCAD code strings from your
Rust object structures using the
to_code()method. - Prelude: A convenient
preludemodule to easily import commonly used items and factory functions.
Installation
Add this to your Cargo.toml:
[dependencies]
scadman
Working with Typed Dimensions: Primitives, Modifiers, and Conversions
scadman uses a robust generic type system to ensure dimensional correctness (2D, 3D, Mixed) at compile time wherever possible, while providing flexibility through conversions.
Creating Typed Objects: primitive_* Functions
To create an object with a specific dimension, use the corresponding factory function:
primitive_2d(body): Creates aScadObject2D(which wrapsScadObjectGeneric<D2>). For example,primitive_2d(Square::new()).primitive_3d(body): Creates aScadObject3D(which wrapsScadObjectGeneric<D3>). For example,primitive_3d(Cube::new()).
These functions typically take a primitive "body" (e.g., Square, Cube) that is often constructed using its build_with builder pattern.
Applying Typed Modifiers: modifier_* Functions
Similarly, to apply a modifier to a typed object, use the matching factory function:
modifier_2d(modifier_body, child_object_2d): Applies a 2D modifier (e.g.,Translate2D) to aScadObject2D, returning a newScadObject2D. This ensures that you're not trying to apply a 2D translation to a 3D object at compile time.modifier_3d(modifier_body, child_object_3d): Applies a 3D modifier (e.g.,Rotate3D) to aScadObject3D, returning a newScadObject3D.
Some modifiers, like Color, can apply to any dimension. For these, use:
modifier_mixed(modifier_body, child_object_any_dimension): Applies a mixed-dimension modifier to an object that can beScadObject2D,ScadObject3D, orScadObjectMixed. This function will implicitly convert thechild_objectinto its untypedScadObject(i.e.,ScadObjectGeneric<DMixed>) representation before applying the modifier. The result will be aScadObjectMixed.
Understanding Conversions (.into())
scadman provides automatic conversions to make composition flexible:
-
Any
ScadObject2DorScadObject3Dcan be converted intoScadObject(which is an alias forScadObjectGeneric<DMixed>) using the.into()method. This is useful when you need to treat a dimensionally specific object in a more generic (untyped) context, such as passing it toblock_mixedormodifier_mixed.use scadman::prelude::*; let square_2d = primitive_2d(Square::new()); // ScadObject2D let cube_3d = primitive_3d(Cube::new()); // ScadObject3D // Convert to untyped ScadObject for a mixed context let generic_square: ScadObject = square_2d.into(); let generic_cube: ScadObject = cube_3d.into(); // Now they can be used together in a mixed block let mixed_objects = block_mixed(&[generic_square, generic_cube]); println!("{}", mixed_objects.to_code()); /* Output: union() { // block_mixed defaults to union if not specified square(); cube(); } */
Typed Blocks: block_* Functions
Similar to primitives and modifiers, blocks also come in typed variants:
block_2d(objects): Creates aScadObject2Dcontaining otherScadObject2Dinstances.block_3d(objects): Creates aScadObject3Dcontaining otherScadObject3Dinstances.block_mixed(objects): Creates aScadObjectMixedcontaining any combination ofScadObjects (i.e.,ScadObjectGeneric<DMixed>). When passingScadObject2DorScadObject3Dtoblock_mixed, they will be implicitly converted toScadObject.
This type system allows you to catch dimensional mismatches at compile time for common operations, while still providing the flexibility to work with mixed-dimension constructs when OpenSCAD's nature requires it.
Import the prelude to get access to common types and functions:
use scadman::prelude::*;
Creating Primitives
Use primitives with the build_with factory functions with direct value:
// Create a square with size 10
let square = Square::build_with(|sb| {
let _ = sb.size(10.0);
});
println!("{}", square.to_code());
// Output: square(size = 10);
// Create a sphere with radius 5 using the builder
let sphere = Sphere::build_with(|cb| {
let _ = cb.r(5.0);
});
println!("{}", sphere.to_code());
// Output: sphere(r = 5);
to
// Create a cylinder with height 10 and radius 3
let cylinder = Cylinder::build_with(|cb| {
let _ = cb.h(10.0).r(3.0);
});
println!("{}", cylinder.to_code());
// Output: cylinder(h = 10, r = 3);
Applying Modifiers
Use the apply_to factory functions.
Use apply_to_2d, apply_to_3d for universal modifiers without parameters like Union.
Modifiers with parameters like Translate is splitted in like Translate2D, Translate3D,
due to parameters' dimension.
// Translate the square by (5, 5)
let translated_square = Translate2D::build_with(|tb| {
let _ = tb.v([5.0, 5.0]);
}).apply_to(square);
println!("{}", translated_square.to_code());
/* Output:
translate([5, 5])
square(size = 10);
*/
// Rotate the sphere by 90 degrees around the Y axis
let rotated_sphere = Rotate3D::build_with(|rb| {
let _ = rb.deg([0.0, 90.0, 0.0]);
}).apply_to(sphere);
println!("{}", rotated_sphere.to_code());
/* Output:
rotate(a = [0, 90, 0])
sphere(r = 5);
*/
// Apply a color modifier
let colored_cylinder = Color::build_with(|cb| {
let _ = cb.c(RGB::new(1.0, 0.0, 0.0));
}).apply_to_3d(cylinder);
println!("{}", colored_cylinder.to_code());
/* Output:
color(c = [1, 0, 0])
cylinder(h = 10, r = 3);
*/
Using Blocks and Boolean Operations
Vector or arrays of ScadObject2D or ScadObject3D can be converted as block object.
You can make this with ScadObject2D::from, ScadObject3D::from, or
automatically converted in apply_to function.
Leverage operators overload for boolean operations
(+ for union, - for difference, * for intersection).
let sphere = Sphere::build_with(|cb| {
let _ = cb.r(10.0);
});
let cube = Cube::build_with(|cb| {
let _ = cb.size(15.0).center(true);
});
// Subtract the cube from the sphere using the difference modifier
let result_modifier = Difference::new().apply_to_3d([sphere.clone(), cube.clone()]);
println!("{}", result_modifier.to_code());
/* Output:
difference() {
sphere(r = 10);
cube(size = 15, center = true);
}
*/
// Achieve the same result using operator overloading
let result_operator = sphere - cube;
println!("{}", result_operator.to_code());
/* Output:
difference() {
sphere(r = 10);
cube(size = 15, center = true);
}
*/
Adding Comments
Use the .commented() method:
let commented_cube = Cube::build_with(|cb| {
let _ = cb.size(5.0);
}).commented("This is a simple cube");
println!("{}", commented_cube.to_code());
/* Output:
/* This is a simple cube */
cube(size = 5);
*/
Example: Building Complex Models (like tests/desk_clamp.rs)
The tests/desk_clamp.rs file serves as a practical example of building a more complex model.
It demonstrates several key techniques facilitated by scadman:
- Parametric Design with Constants: Dimensions and other parameters are defined as
Rust constants (
CLAMP_Z_SIZE,HOOK_OUTER_R, etc.). This makes the design easily adjustable and readable. - Modular Design with Helper Functions: The complex clamp shape is broken down into
smaller, manageable parts (
generate_lattice_r_void,generate_clamp,generate_body). Each function constructs and returns aScadObjectrepresenting a component of the final assembly. - Composition via Modifiers and Blocks: The helper functions return
ScadObjects, which are then combined usingmodifier_3d, and operator overloading (+,-) to build the final structure. This mirrors how objects are combined in OpenSCAD itself. - Leveraging Builders for Clarity: Builders (
Translate2D::build_with,Polygon::build_with,Cylinder::build_with, etc.) are used extensively to set parameters for primitives and modifiers, improving code readability compared to positional arguments. - Adding Comments for Readability: Comments are added to significant parts of the
model (
.commented()) to explain the purpose of different objects or sections of the code, which translates directly to comments in the generated OpenSCAD file.
This example showcases how scadman enables a structured, modular, and maintainable
approach to creating complex parametric designs in OpenSCAD using the power of Rust.
Key Concepts
ScadObject: The main container struct. It wraps the actual object body (ScadObjectBody) and holds an optional comment. All functions that build or manipulate SCAD geometry ultimately work withScadObject.ScadObjectBody: An enum (Object2D,Object3D,ObjectMixed) that holds the specific type of SCAD object (Primitive, Modifier, or Block) for a given dimension.ScadObjectTrait: A trait implemented byScadObjectand its internal body types, providing core functionality liketo_code()(generating the SCAD string) andget_type()(determining the object's dimension).ScadDisplay: A fundamental trait implemented by any type that can be represented as a string in OpenSCAD code (numbers, vectors, strings, booleans, and the specific primitive/modifier/block body types). Therepr_scad()method generates the SCAD string for that specific value or object part.ScadCommentDisplay: A trait (delegated fromScadObjectTrait) that adds the ability to generate SCAD code with a comment (repr_scad_with_comment).ScadBuilder/ScadBuildable: Traits supporting the builder pattern for configuring complex SCAD sentences with optional parameters.ScadBuildable::build_withis the primary entry point for using builders.- Primitives, Modifiers, and Blocks: These correspond directly to OpenSCAD's
structural elements. The library provides specific types (
ScadPrimitive2D,ScadModifier3D,ScadBlockMixed, etc.) and enums (ScadPrimitiveBody2D,ScadModifierBody3D,ScadModifierBodyMixed) to represent them. These are stored inScadObjectGeneric. - Value Types: Custom types in
value_type.rs(likeAngle,RGBA,RoundSize, etc.) and standard types (f64forUnit,bool,String, vectors fromnalgebra) implementScadDisplayto ensure correct formatting in the generated SCAD code.
Dependencies
~4.5MB
~99K SLoC