← Back to Documentation

PPSCAD Language Reference

PPSCAD (PrintParams Scripted CAD) is a language for parametric 3D modeling. Takes cues from OpenSCAD and JSCAD with a cleaner syntax.

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.

polygon([[x,y], ...])

2D shape from point array.

polygon([outer, hole1, ...])

2D shape with holes from multiple contours.

text("ABC", size)

2D text (built-in bitmap font). Extrude to make 3D.

// Custom 2D profile from points
polygon([[0,0], [20,0], [20,10], [10,10], [10,20], [0,20]])

// Polygon with a hole (multiple contours)
polygon([
  [[0,0], [30,0], [30,20], [0,20]],
  [[5,5], [25,5], [25,15], [5,15]]
])

// 3D text: create 2D text then extrude
extrude(2) text("HELLO", 10)

// Text with string parameter
param label: string = "Part A" "Label"
extrude(1.5) text(label, 8)

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

Chain method calls on geometry objects to transform and combine them inline.

// 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()

// Offset: grow or shrink 2D shapes
circle(10).offset(2)              // grow outward by 2mm
square(20, 10).offset(-1)        // shrink inward by 1mm

// Slice: extract 2D cross-section from 3D at height z
let profile = sphere(10).slice(5) // cross-section at z=5

// Project: flatten 3D to 2D outline
let outline = cube(10).project()

// Trim: cut model by a plane
cube(20, center: true).trimByPlane(0, 0, 1, 0) // keep bottom half

Math functions

All trigonometric functions use degrees (not radians), matching OpenSCAD convention.

FunctionDescription
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/string length
str(values...)Convert to string
concat(a, b, ...)Concatenate strings
chr(code)Character from ASCII code
ord(char)ASCII code from character
echo(values...)Print to console (debugging)

Constants

PI3.14159...
TAU6.28318... (2 * PI)
E2.71828...
INFInfinity

Operators

// Arithmetic (+ also concatenates strings)
+  -  *  /  %  ^     // add, subtract, multiply, divide, modulo, power

// Comparison
==  !=  <  <=  >  >=

// Logical
&&  ||  !             // and, or, not

// Ternary
condition ? value_if_true : value_if_false

Named 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

Uses parameters, functions, loops, transforms, conditionals, and CSG together.

// 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)
        }
      }
}