8.9 KiB
SCAD → Cordial Translation Reference
Every SCAD operation has a Cordial equivalent. Where the operation can be parallelized, the conditions and a Cordial example are shown.
Primitives
sphere
SCAD
sphere(r=5);
sphere(5);
Cordial
sphere(5.0)
Parallelism: n/a — leaf node; always evaluable at any point independently.
cube
SCAD
cube([10, 20, 30]);
cube(5);
cube([10, 20, 30], center=true);
Cordial
box3(5.0, 10.0, 15.0) // half-extents, always centered
cube(5.0) // equal half-extents
Note: SCAD cube() uses full sizes and defaults to corner-aligned.
Cordial uses half-extents and is always centered. The lowerer handles
the translation offset automatically when ingesting SCAD.
cylinder
SCAD
cylinder(h=10, r=3);
cylinder(h=10, r=3, center=true);
Cordial
cylinder(3.0, 10.0) // always centered on Z
Transforms
translate
SCAD
translate([10, 0, 0]) sphere(1);
Cordial
sphere(1.0).translate(10.0, 0.0, 0.0)
Parallelism: a translation wraps its child — the child subtree is independently evaluable. Multiple translates on independent shapes are always parallel.
rotate
SCAD
rotate([45, 0, 0]) cube(5);
rotate([0, 90, 0]) cube(5);
Cordial
cube(5.0).rotate_x(45.0)
cube(5.0).rotate_y(90.0)
SCAD rotate([x,y,z]) decomposes into sequential X, Y, Z rotations.
Cordial exposes each axis independently.
scale
SCAD
scale([2, 1, 1]) sphere(1);
scale(3) sphere(1);
Cordial
sphere(1.0).scale(2.0, 1.0, 1.0)
sphere(1.0).scale_uniform(3.0)
Boolean Operations
union
SCAD
union() {
sphere(1);
cube(2);
}
Cordial
sphere(1.0) | cube(2.0)
// or explicitly:
sphere(1.0).union(cube(2.0))
// or variadic:
Shape::union_all([sphere(1.0), cube(2.0), cylinder(1.0, 3.0)])
Parallelism: always parallelizable.
Condition: union children share no intermediate state. In an SDF,
every child is a separate distance field — min(d_a, d_b) evaluates
d_a and d_b independently.
// Parallel union — each branch evaluates on its own thread
par::branch()
.add(sphere(1.0))
.add(cube(2.0).translate(5.0, 0.0, 0.0))
.add(cylinder(1.0, 3.0).rotate_x(90.0))
.union()
difference
SCAD
difference() {
cube(10, center=true);
sphere(5);
}
Cordial
cube(10.0) - sphere(5.0)
// or explicitly:
cube(10.0).difference(sphere(5.0))
// or multiple subtractions:
cube(10.0).difference_all([sphere(5.0), cylinder(2.0, 20.0)])
Parallelism: parallelizable when operands are independent subtrees.
Condition: the base shape and the subtracted shapes share no
intermediate nodes. The base evaluates independently from the
subtracted shapes. The subtracted shapes themselves are a union
of independent evaluations (each contributes to max(base, -sub)).
// The base and each subtracted shape are independent branches
par::branch()
.add(cube(10.0))
.add(sphere(5.0))
.add(cylinder(2.0, 20.0))
.intersection() // difference = intersection with complement
intersection
SCAD
intersection() {
sphere(5);
cube(4, center=true);
}
Cordial
sphere(5.0) & cube(4.0)
Parallelism: parallelizable when operands are independent subtrees.
Same condition as difference — max(d_a, d_b) evaluates both
sides independently.
par::branch()
.add(sphere(5.0))
.add(cube(4.0))
.intersection()
Control Flow
for loop
SCAD
for (i = [0:5])
translate([i*10, 0, 0]) sphere(1);
for (i = [0:2:10])
translate([i, 0, 0]) sphere(1);
for (x = [1, 5, 10])
translate([x, 0, 0]) cube(2);
Cordial (.crd source)
// Linear array — 6 spheres spaced 10 apart
map(i, 0..6) { translate(sphere(1), i * 10, 0, 0) }
// 8 bolts around a circle
map(i, 0..8) { rotate_z(translate(cylinder(0.5, 2), 5, 0, 0), i * pi/4) }
Cordial (Rust DSL)
pattern::linear_array(&sphere(1.0), 6, [10.0, 0.0, 0.0])
par::polar(&cylinder(0.5, 2.0).translate(5.0, 0.0, 0.0), 8)
Parallelism: always parallelizable when bounds are constant.
map unrolls at parse time into N independent branches joined by
union. Each iteration is independent — no iteration reads state
written by another. This is the fundamental serial-to-parallel
transformation: what looks like a sequential loop is actually N
independent geometric evaluations.
if / else
SCAD
if (use_sphere)
sphere(5);
else
cube(5, center=true);
x = 10;
if (x > 5) sphere(x);
Cordial — direct conditional geometry isn't needed because Rust
has native if:
let shape = if use_sphere {
sphere(5.0)
} else {
cube(5.0)
};
Parallelism: constant conditions → dead code elimination.
Condition: if the condition evaluates to a constant at lowering time, only the taken branch produces geometry. The other branch is eliminated entirely — zero cost.
When the condition is variable (unknown at compile time), both branches are included as a union. This is conservative but correct — the SDF field is defined everywhere.
Ternary
SCAD
r = big ? 10 : 1;
sphere(r);
Cordial — native Rust:
let r = if big { 10.0 } else { 1.0 };
sphere(r)
Evaluated at lowering time when all inputs are constant.
Patterns (Cordial-only)
These have no direct SCAD equivalent — they're higher-level abstractions that compile to parallel-friendly structures.
linear_array
// 5 spheres along X, spaced 3 units apart
pattern::linear_array(&sphere(1.0), 5, [3.0, 0.0, 0.0])
Always parallel — each instance is independent.
polar_array
// 12 bolts around Z
pattern::polar_array(&cylinder(0.3, 2.0).translate(5.0, 0.0, 0.0), 12)
Always parallel — equivalent to N rotations of the same shape.
grid_array
// 4x6 grid of cylinders
pattern::grid_array(&cylinder(0.5, 1.0), 4, 6, 3.0, 3.0)
Always parallel — N×M independent instances.
mirror
pattern::mirror_x(&sphere(1.0).translate(3.0, 0.0, 0.0))
// Original at (3,0,0) + mirror at (-3,0,0)
Always parallel — 2 branches, one original, one reflected.
Parallel Composition (Cordial-only)
par::branch — explicit parallel grouping
// N independent shapes, explicitly grouped for parallel evaluation
let part = par::branch()
.add(sphere(2.0))
.add(cylinder(1.0, 5.0).translate(0.0, 0.0, 1.0))
.add(cube(1.5).rotate_z(45.0).translate(3.0, 0.0, 0.0))
.union();
Each .add() is an independent branch. The join operation (.union(),
.intersection(), .smooth_union(k)) combines results after all
branches complete.
par::map — multiplicative parallelism
// Same shape, N different transforms — all independent
par::map(&sphere(0.5), (0..20).map(|i| {
let t = i as f64 / 20.0;
let x = (t * std::f64::consts::TAU).cos() * 5.0;
let y = (t * std::f64::consts::TAU).sin() * 5.0;
let z = t * 10.0;
move |s: Shape| s.translate(x, y, z)
}))
par::symmetric — mirror parallelism
par::symmetric_x(&part) // 2 branches
par::symmetric_xyz(&part) // 8 branches (all octants)
par::polar — rotational parallelism
par::polar(&fin, 6) // 6 branches around Z
Parallelism Summary
| Operation | Parallelizable? | Condition |
|---|---|---|
| Primitive (sphere, cube, etc.) | Always | Leaf node — independent by definition |
| Transform (translate, rotate, scale) | Always | Wraps child; child evaluates independently |
| Union | Always | min(a, b) — operands share no state |
| Difference | Yes | Operands are independent subtrees |
| Intersection | Yes | Operands are independent subtrees |
| For loop (constant bounds) | Always | Unrolls to N independent branches |
| For loop (variable bounds) | No | Cannot unroll at compile time |
| If/else (constant condition) | n/a | Dead code eliminated; only one branch exists |
| If/else (variable condition) | Yes | Both branches included as union |
par::branch |
Always | Explicit parallel grouping |
par::map |
Always | Same shape, N transforms |
par::polar |
Always | N rotations around axis |
pattern::* |
Always | Compile to union of independent instances |
The threshold: an operation becomes parallelizable when it crosses into calculus — when accumulated structure becomes continuous and differentiable, every point in the field is independently evaluable. SDFs are inherently in this territory: the distance function is defined at every point in space, and evaluating it at point A tells you nothing about point B. Serial operations that build up an SDF tree are just describing the function — once described, evaluation is embarrassingly parallel.