PPSCAD Language Reference
PPSCAD (PrintParams Scripted CAD) is a simple language for creating parametric 3D models. Inspired by OpenSCAD and JSCAD, designed for ease of use.
Quick start
// A box with a hole through it
param width: float = 30 [10:100] "Width (mm)"
param height: float = 20 [5:50] "Height (mm)"
param hole_r: float = 4 [1:15] "Hole radius (mm)"
difference {
cube(width, height, 10)
translate(width / 2, height / 2, -1)
cylinder(h: 12, r: hole_r)
}Parameters appear as sliders in the side panel. Change a slider and the model updates automatically.
Parameters
Parameters are first-class in PPSCAD. Declare them at the top of your code with the param keyword.
Syntax
param name: type = default [min:max:step] "Label" "Description"Types
// Number with slider
param teeth: int = 20 [6:200:1] "Number of teeth"
// Float with custom step
param module: float = 2.0 [0.5:10:0.01] "Module (mm)"
// Boolean toggle (checkbox)
param chamfer: bool = true "Add chamfer"
// Dropdown select
param style: choice = round ["round", "square", "hex"] "Head style"
// Text input
param label: string = hello "Engrave text"Variables
Use let to declare variables. They can hold numbers, booleans, strings, arrays, or geometry.
let outer_r = teeth * module / 2
let offset = [10, 0, -5]
let part = cube(20, 10, 5)3D Primitives
cube(size)Cube with equal sides.
cube(x, y, z)Box with specified dimensions.
cube(size: [x,y,z], center: true)Centered box with named args.
cylinder(h, r)Cylinder with height and radius.
cylinder(h: 20, r1: 5, r2: 3)Cone (different top/bottom radii).
cylinder(h: 20, d: 10)Cylinder specified by diameter.
sphere(r)Sphere with given radius.
sphere(d: 20, segments: 64)Sphere with diameter and resolution.
2D Primitives
2D shapes are used with extrude and revolve to create 3D geometry.
circle(r)2D circle.
square(w, h)2D rectangle.
square(size, center: true)Centered square.
Boolean operations (CSG)
Combine shapes using Constructive Solid Geometry. Each takes a block of children.
// Union: combine all children into one shape
union {
cube(10)
translate(5, 5, 0) sphere(8)
}
// Difference: first child minus all others
difference {
cube(20) // base shape
translate(10, 10, -1) // shapes to cut away
cylinder(h: 22, r: 5)
}
// Intersection: keep only the overlapping volume
intersection {
cube(20, center: true)
sphere(14)
}Transforms
Transforms move, rotate, or scale their child geometry. They can take a single child or a block.
// Move by x, y, z
translate(10, 0, 5) cube(10)
// Rotate around x, y, z axes (in degrees)
rotate(0, 0, 45) cube(10)
// Scale uniformly or per axis
scale(2) sphere(5)
scale(2, 1, 0.5) cube(10)
// Mirror across an axis
mirror(1, 0, 0) {
translate(5, 0, 0) cube(10)
}
// Chaining: transforms apply to the next statement
translate(20, 0, 0)
rotate(0, 0, 45)
cube(10)Extrude and revolve
Turn 2D shapes into 3D geometry.
// Linear extrude a circle into a cylinder
extrude(20) circle(5)
// Extrude with twist and scale
extrude(h: 30, twist: 90, slices: 50)
square(10, 10)
// Revolve a 2D shape around the Y axis
revolve()
translate(15, 0) circle(3)
// Partial revolve
revolve(angle: 180)
translate(10, 0) square(5, 8)Control flow
// Conditional geometry
if (add_holes) {
translate(10, 10, -1) cylinder(h: 22, r: 3)
}
// If-else
if (style == "round") {
sphere(10)
} else {
cube(16, center: true)
}
// For loop with range [start:end] (inclusive)
for (i in [0:4]) {
translate(i * 15, 0, 0) cube(10)
}
// Range with step [start:step:end]
for (angle in [0:30:330]) {
rotate(0, 0, angle)
translate(25, 0, 0)
cylinder(h: 10, r: 3)
}
// For with array
for (size in [5, 10, 15, 20]) {
translate(size * 2, 0, 0) cube(size)
}Functions and modules
Functions compute values. Modules create reusable geometry.
// Function: returns a computed value
fn gear_radius(teeth, module) = teeth * module / 2
// Function with block body
fn clamp(val, lo, hi) {
if (val < lo) { return lo }
if (val > hi) { return hi }
return val
}
let r = gear_radius(20, 2.5)
// Module: creates reusable geometry
mod rounded_box(w, h, d, r) {
hull {
translate(r, r, 0) cylinder(h: d, r: r)
translate(w - r, r, 0) cylinder(h: d, r: r)
translate(r, h - r, 0) cylinder(h: d, r: r)
translate(w - r, h - r, 0) cylinder(h: d, r: r)
}
}
rounded_box(40, 20, 5, 3)Method chaining
Geometry objects support method chaining for inline transformations and boolean operations.
// Chain transforms
cube(20, 10, 5)
.translate(5, 0, 0)
.rotate(0, 0, 45)
// Boolean operations
let base = cube(30, 20, 10)
let hole = cylinder(h: 12, r: 4).translate(15, 10, -1)
base.subtract(hole)
// 2D to 3D via method
circle(5).extrude(20)
square(10, 5).translate(15, 0).revolve()Math functions
All trigonometric functions use degrees (not radians), matching OpenSCAD convention.
| Function | Description |
|---|---|
| sin(deg), cos(deg), tan(deg) | Trigonometric (input in degrees) |
| asin(x), acos(x), atan(x) | Inverse trig (output in degrees) |
| atan2(y, x) | Two-argument arctangent (degrees) |
| abs(x) | Absolute value |
| ceil(x), floor(x), round(x) | Rounding |
| sqrt(x) | Square root |
| pow(x, y) | Power (or use x^y operator) |
| ln(x), log(x) | Natural log / log base 10 |
| exp(x) | e^x |
| min(a, b, ...), max(a, b, ...) | Minimum / maximum |
| len(array) | Array length |
| echo(values...) | Print to console (debugging) |
Constants
| PI | 3.14159... |
| TAU | 6.28318... (2 * PI) |
| E | 2.71828... |
| INF | Infinity |
Operators
// Arithmetic
+ - * / % ^ // add, subtract, multiply, divide, modulo, power
// Comparison
== != < <= > >=
// Logical
&& || ! // and, or, not
// Ternary
condition ? value_if_true : value_if_falseNamed arguments
Primitives and transforms accept both positional and named arguments.
// These are equivalent:
cylinder(20, 5)
cylinder(h: 20, r: 5)
// Named args can be in any order:
cylinder(r: 5, h: 20)
// Mix positional and named:
cylinder(20, r: 5)Comments
// Single-line comment
/* Multi-line
comment */Units
All dimensions are in millimeters. STL exports are also in mm.
Complete example: twisted tower
This example showcases most of PPSCAD's features: parameters, functions, for loops, computed variables, nested transforms, conditionals, and CSG.
// Twisted Tower — stacked layers with rotation and taper
param layers: int = 20 [4:60] "Number of layers"
param size: float = 25 [10:50] "Base size (mm)"
param height: float = 80 [20:200] "Total height (mm)"
param twist: float = 90 [0:720:5] "Total twist (deg)"
param taper: float = 0.5 [0.2:1.0:0.05] "Top/bottom ratio"
param hollow: bool = true "Hollow interior"
param wall: float = 2 [1:5:0.5] "Wall thickness (mm)"
fn lerp(a, b, t) = a + (b - a) * t
let layer_h = height / layers
for (i in [0:layers - 1]) {
let t = i / max(layers - 1, 1)
let s = lerp(size, size * taper, t)
let angle = twist * t
translate(0, 0, i * layer_h)
rotate(0, 0, angle)
translate(-s / 2, -s / 2, 0) {
if (hollow) {
difference {
cube(s, s, layer_h + 0.1)
translate(wall, wall, -0.05)
cube(s - wall * 2, s - wall * 2, layer_h + 0.2)
}
} else {
cube(s, s, layer_h + 0.1)
}
}
}