Reference

OpenSCAD boolean operations: difference, union and intersection

OpenSCAD boolean operations combine solids: union() merges them, difference() subtracts every later child from the first, and intersection() keeps only the volume they all share. This is called constructive solid geometry (CSG), and almost every printable part is a stack of these three operations.

6 min read Updated October 2026 Beginner OpenSCAD reference · 6 of 8
In short
  1. difference() keeps the first child and removes all the others from it.
  2. Make cutters overshoot by about 1 mm past every face they cut.
  3. Put # in front of a cutter to see it in the desktop preview.
  4. In the PrintPal editor, wrap a multi-piece part in union() so it exports as one solid.

The three boolean operations at a glance

OperationKeepsChild order matters?Typical use
union()Everything in any childNoBosses, ribs and tabs on a base
difference()The first child minus all the othersYesHoles, pockets, slots, hollow shells
intersection()Only the volume inside every childNoDomes, trimming, splitting a part in half

union(): merge solids into one

union() joins its children into one solid. Overlapping volume is merged and the internal faces disappear:

union() {
  cube([20, 20, 4]);
  translate([10, 10, 0]) cylinder(h=14, d=8, $fn=48);
}

Top-level objects in the PrintPal editor

Desktop OpenSCAD unions everything at the top level of a file for you when it renders. The PrintPal editor runs OpenSCAD with the experimental lazy-union feature, which skips that step: separate top-level objects are exported as separate, overlapping bodies. This file gives you two shells that pass through each other, not one solid:

cube([20, 20, 4]);
translate([10, 10, 0]) cylinder(h=14, d=8, $fn=48);
In the PrintPal editorResult
Several shapes at the top level of the fileSeparate bodies
A for loop at the top levelOne body per loop pass
Shapes inside union(), difference() or intersection()Merged
Shapes inside a module, or inside one transform's { } blockMerged

Wrap the part. Overlapping shells usually slice fine, but mesh checkers flag them as self-intersecting and booleans in other programs can fail. Put the whole part inside union() { … } or one top-level module call, such as bracket();.

difference(): cut holes and pockets

difference() keeps its first child and subtracts every child after it. A through hole in a plate looks like this:

difference() {
  cube([40, 40, 4]);                          // base: the first child
  translate([20, 20, -1])
    cylinder(h=6, d=6, $fn=48);               // cutter: starts 1 mm below, ends 1 mm above
}

Order matters. Swap the two children and OpenSCAD keeps the cylinder minus the plate, which leaves two 1 mm discs where the cylinder stuck out above and below:

difference() {
  translate([20, 20, -1]) cylinder(h=6, d=6, $fn=48);
  cube([40, 40, 4]);
}

What does this produce? difference() { cylinder(h=10, d=5); cube(20, center=true); }

  1. A 20 mm cube with a 5 mm hole through it
  2. A 5 mm cylinder, unchanged
  3. Nothing: the result is empty

The cylinder is the first child, so it is the part being cut. The 20 mm cube completely contains it, so nothing is left. Put the cube first to get a cube with a hole.

Why cutters must overshoot: coplanar faces

When a cutter's face lies exactly on a face of the part, the two surfaces are coplanar. Whether the cut goes through then depends on floating-point rounding. Exact whole numbers on axis-aligned shapes often work, but tiny errors leave an invisible skin or ragged geometry. Here 0.1 * 3 is really 0.30000000000000004, so the plate is a hair taller than the 0.3 mm cutter and the hole stays closed:

t = 0.1 * 3;   // 0.30000000000000004
difference() {
  cube([20, 20, t]);
  translate([10, 10, 0]) cylinder(h=0.3, d=6, $fn=32);
}

Cutter faces flush with the part: a skin can survive, and the preview flickers (z-fighting).

Start the cutter 1 mm below and make it 2 mm taller than the plate, as in translate([x, y, -1]) cylinder(h=t + 2, …).

The same rule applies to unions: let a boss sink a little into the plate rather than touching it at exactly one face. Rotated parts make the problem worse because their coordinates are never exact.

Debug cutters with modifier characters

Cutters are invisible once they have done their job, which makes a misplaced one hard to find. Modifier characters go in front of any shape or operation:

CharacterIn the F5 previewIn a full render
# debugShows the shape in transparent red and still uses itShape is used as normal
% backgroundShows a transparent grey ghostShape is left out
* disableHiddenShape is left out
! rootShows only this shapeRenders only this shape
difference() {
  cube([40, 40, 4]);
  #translate([20, 20, -1]) cylinder(h=6, d=6, $fn=48);
}

In the PrintPal editor every run is a full render, so the # and % highlights do not show. To see a cutter there, change difference() to union() for one run, or put ! in front of the cutter to render it alone.

intersection(): keep the overlap

intersection() keeps only the space that is inside every child. A sphere intersected with a cylinder that starts halfway up gives a domed button cap:

intersection() {
  sphere(r=12, $fn=64);
  translate([0, 0, 6]) cylinder(h=10, d=26, $fn=64);
}

Intersecting with a large cube is the quickest way to split a part so each half prints flat on the bed:

intersection() {
  sphere(r=20, $fn=64);                       // stand-in for your part
  translate([-50, -50, 0]) cube([100, 100, 50]);
}   // keeps the top half

Practical boolean patterns

Open-top box (shell)

Subtract a smaller block that starts one wall thickness in and pokes out of the top. For printing, 1.2 mm is a sensible minimum wall with a 0.4 mm nozzle; see the 3D printing design guide.

wall = 2;
size = [40, 30, 25];
difference() {
  cube(size);
  translate([wall, wall, wall])
    cube([size[0] - 2*wall, size[1] - 2*wall, size[2]]);
}

Plate with screw bosses

Union the bosses with the plate first, then cut every hole in one difference() so the holes run through both:

holes = [[5, 5], [35, 5], [5, 25], [35, 25]];
difference() {
  union() {
    cube([40, 30, 4]);
    for (p = holes)
      translate([p[0], p[1], 0]) cylinder(h=12, d=7, $fn=32);
  }
  for (p = holes)
    translate([p[0], p[1], -1]) cylinder(h=14, d=3.4, $fn=32);
}

Countersunk holes

A 90° countersink is a cone whose depth is half the difference between the head and shank diameters. Set head_d to your screw head plus a little clearance:

module csk_hole(d=3.4, head_d=6.5, plate_t=4) {
  head_h = (head_d - d) / 2;                        // depth of a 90° countersink
  translate([0, 0, -1]) cylinder(h=plate_t + 2, d=d, $fn=32);
  translate([0, 0, plate_t - head_h])
    cylinder(h=head_h + 0.01, d1=d, d2=head_d + 0.02, $fn=48);
}

difference() {
  cube([30, 20, 4]);
  translate([8, 10, 0])  csk_hole();
  translate([22, 10, 0]) csk_hole();
}

Cutaway view

Subtract a big block to look inside a part while you design it:

difference() {
  difference() {                              // your part: a hollow tube
    cylinder(h=30, d=20, $fn=64);
    translate([0, 0, 2]) cylinder(h=30, d=16, $fn=64);
  }
  translate([0, -50, -1]) cube([50, 100, 50]); // remove the +X half to look inside
}

render() and performance

render(convexity=4)
  difference() {
    cube(20, center=true);
    sphere(r=13, $fn=64);
  }

In desktop OpenSCAD, render() computes its child as a real mesh during the F5 preview and caches it, which speeds up previews of heavy booleans and fixes preview glitches. convexity only helps the preview draw shapes correctly: it is the most surfaces a ray through the object can cross. The PrintPal editor always does a full render with the fast Manifold engine, so render() makes no visible difference there. Lowering $fn while you design is the bigger speed win; see 3D primitives.

Ask the CAD Agent for boolean-heavy parts

The CAD Agent writes these unions and differences for you. Give it finished sizes, wall thickness and where each cut goes.

Design an open-top storage box 80 x 60 x 40 mm with 2 mm walls and a 2 mm floor. Add four 6 mm screw bosses with 2.5 mm pilot holes in the inside corners, and a 12 x 6 mm cable slot centred on one short wall, open at the top edge. Make every cut overshoot by 1 mm and wrap the part in one union.

Make a 60 mm sphere and split it into two printable halves with flat bottoms. Add two 3 mm alignment pin holes, 4 mm deep, in each flat face, 15 mm from the centre, and place the halves side by side.

AI tool · free to tryCAD AgentDescribe the part and get editable OpenSCAD with clean, overshooting cuts.Open tool Free PrintPal toolMesh Repair & SimplifierCheck an exported STL for holes, overlapping shells and non-manifold edges before slicing.Open tool

Frequently asked questions

Why does my OpenSCAD difference() not cut anything?

Usually the cutter does not reach all the way through, or it is in the wrong place. Make cutters start 1 mm below and end 1 mm above the faces they cut, check that the part you are cutting is the first child, and look at the cutter by putting # in front of it in a desktop preview or by swapping difference() for union() for a moment.

Does the order of children matter in OpenSCAD booleans?

Only for difference(). It keeps the first child and subtracts every child after it, so swapping them gives a different shape. union() and intersection() give the same result in any order.

What does the # character do in OpenSCAD?

It highlights a shape in transparent red in the F5 preview while still using it normally, which makes invisible cutters visible. % shows a ghost that is left out of the render, * disables a shape, and ! renders only that shape.

Do I need to wrap everything in union()?

Desktop OpenSCAD unions top-level objects for you when it renders. The PrintPal editor runs with the lazy-union feature, which keeps top-level objects as separate bodies, so wrap a multi-piece part in union() or a single module call to export one solid.

How do I cut a model in half in OpenSCAD?

Intersect the model with a large cube that covers the half you want to keep, or subtract a cube that covers the half you want to remove. Make the cube bigger than the model in every direction.

Sources & further reading

  • OpenSCAD User Manual — CSG modelling (union, difference, intersection, render)
  • OpenSCAD User Manual — Modifier characters (#, %, * and !)
  • OpenSCAD — Downloads (development snapshots with the Manifold engine)