Thanks to visit codestin.com
Credit goes to lib.rs

3 unstable releases

0.2.2 Dec 8, 2025
0.2.1 Dec 8, 2025
0.2.0 Dec 8, 2025
0.1.1 Jun 13, 2025
0.1.0 May 25, 2025

#473 in Rust patterns


Used in scadman_helper

MIT license

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 like ScadObject2D, ScadObject3D, and ScadObjectMixed.
  • Comprehensive Primitive Support: Create basic shapes like square, circle, sphere, cube, cylinder, polygon, polyhedron, text, and import.
  • 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, and intersection. Blocks ({ ... }) are also explicitly supported.
  • Operator Overloading: Use standard Rust operators (+, -, *) for union, difference, and intersection operations. These operators work on the ScadObject (untyped alias) and perform a runtime check, requiring both operands to have the same internal dimension (Object2D, Object3D, or ObjectMixed). 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 ScadDisplay trait.
  • Code Generation: Generate clean, correctly indented OpenSCAD code strings from your Rust object structures using the to_code() method.
  • Prelude: A convenient prelude module 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 a ScadObject2D (which wraps ScadObjectGeneric<D2>). For example, primitive_2d(Square::new()).
  • primitive_3d(body): Creates a ScadObject3D (which wraps ScadObjectGeneric<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 a ScadObject2D, returning a new ScadObject2D. 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 a ScadObject3D, returning a new ScadObject3D.

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 be ScadObject2D, ScadObject3D, or ScadObjectMixed. This function will implicitly convert the child_object into its untyped ScadObject (i.e., ScadObjectGeneric<DMixed>) representation before applying the modifier. The result will be a ScadObjectMixed.

Understanding Conversions (.into())

scadman provides automatic conversions to make composition flexible:

  • Any ScadObject2D or ScadObject3D can be converted into ScadObject (which is an alias for ScadObjectGeneric<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 to block_mixed or modifier_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 a ScadObject2D containing other ScadObject2D instances.
  • block_3d(objects): Creates a ScadObject3D containing other ScadObject3D instances.
  • block_mixed(objects): Creates a ScadObjectMixed containing any combination of ScadObjects (i.e., ScadObjectGeneric<DMixed>). When passing ScadObject2D or ScadObject3D to block_mixed, they will be implicitly converted to ScadObject.

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:

  1. 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.
  2. 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 a ScadObject representing a component of the final assembly.
  3. Composition via Modifiers and Blocks: The helper functions return ScadObjects, which are then combined using modifier_3d, and operator overloading (+, -) to build the final structure. This mirrors how objects are combined in OpenSCAD itself.
  4. 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.
  5. 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 with ScadObject.
  • 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 by ScadObject and its internal body types, providing core functionality like to_code() (generating the SCAD string) and get_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). The repr_scad() method generates the SCAD string for that specific value or object part.
  • ScadCommentDisplay: A trait (delegated from ScadObjectTrait) 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_with is 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 in ScadObjectGeneric.
  • Value Types: Custom types in value_type.rs (like Angle, RGBA, RoundSize, etc.) and standard types (f64 for Unit, bool, String, vectors from nalgebra) implement ScadDisplay to ensure correct formatting in the generated SCAD code.

Dependencies

~4.5MB
~99K SLoC