| Crates.io | scadman |
| lib.rs | scadman |
| version | 0.2.2 |
| created_at | 2025-05-25 02:41:08.620025+00 |
| updated_at | 2025-12-08 23:20:58.078589+00 |
| description | Code generator for OpenSCAD. |
| homepage | |
| repository | https://github.com/lum1narie/scadman |
| max_upload_size | |
| id | 1687951 |
| size | 284,241 |
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.
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.
ScadObjectGeneric<D> and public-facing wrappers like
ScadObject2D, ScadObject3D, and ScadObjectMixed.square, circle,
sphere, cube, cylinder, polygon, polyhedron, text, and import.translate, rotate, scale, resize, mirror, multmatrix, offset,
projection, color, hull, render, children, linear_extrude,
rotate_extrude, etc.union, difference, and
intersection. Blocks ({ ... }) are also explicitly supported.+, -, *) 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..commented() method or dedicated factory functions....Builder) for configuring optional parameters.ScadDisplay trait.to_code() method.prelude module to easily import commonly used items and
factory functions.Add this to your Cargo.toml:
[dependencies]
scadman
scadman uses a robust generic type system to ensure dimensional correctness (2D, 3D, Mixed) at compile time wherever possible, while providing flexibility through conversions.
primitive_* FunctionsTo 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.
modifier_* FunctionsSimilarly, 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..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();
}
*/
block_* FunctionsSimilar 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::*;
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);
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);
*/
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);
}
*/
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);
*/
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:
CLAMP_Z_SIZE, HOOK_OUTER_R, etc.). This makes the design easily
adjustable and readable.generate_lattice_r_void, generate_clamp, generate_body).
Each function constructs and returns a ScadObject representing a component of the
final assembly.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.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..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.
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.ScadPrimitive2D,
ScadModifier3D, ScadBlockMixed, etc.) and enums (ScadPrimitiveBody2D,
ScadModifierBody3D, ScadModifierBodyMixed) to represent them.
These are stored in ScadObjectGeneric.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.