AI CAD examples with tested OpenSCAD code
These nine AI CAD examples each pair a prompt for the PrintPal CAD Agent with OpenSCAD code that compiles as written. Send the prompt to watch the agent build its own version, or open the code in the editor and change the numbers.
- Pick an example below. Each has a prompt card and a code block.
- Try in CAD Agent opens the agent with the prompt typed in. Nothing is sent until you press Enter.
- Open in editor loads the reference code into the CAD Agent's Advanced mode, with every parameter as a slider in the Params tab.
- Measure your real parts with calipers and change the numbers to match.
- Export STL or 3MF from the File menu and slice as usual.
Note: the code here is reference code, written and compile-checked for this page. It is not saved agent output. The agent writes a new program every time, so expect different variable names and structure for the same prompt.
The OpenSCAD examples at a glance
| Example | What it shows | Library |
|---|---|---|
| L-bracket | Holes cut after the union, side gussets | None |
| Mounting plate | Counterbores, rounded corners with offset() | None |
| Project enclosure | Shell, heat-set insert standoffs, lid lip clearance | None |
| Spur gear | Involute teeth, bore and keyway | BOSL2 |
| Twisted vase | linear_extrude() with twist and scale | None |
| Storage bin | Grid maths, magnet pockets, a 45° shelf | None |
| Cable clip | A 2D profile extruded sideways, snap interference | None |
| Thumb knob | Hex pocket from across-flats size, chamfers with hull() | None |
| Lithophane stand | Slot clearance and a lead-in chamfer | None |
How the example code is laid out
Every example follows the same pattern, which is also what the agent writes:
- Parameters first. Each
name = value;line at the top, with a comment directly above it, becomes a labelled slider in the Params tab (called Adjust in Simple mode). - Parameters above
include. The slider panel reads from the top of the file and stops at the firstinclude,moduleor shape. That's why the gear's parameters sit above its BOSL2 lines. - Fixed values below the first module. Numbers you shouldn't tune, like the 42 mm grid pitch, go after a module so they don't show up as sliders.
// @feature:tags. These name each part of the model so you can point the agent at it, for example@feature[usb_slot].- Millimetres, bottom at Z = 0. Parts sit on the bed as modelled, so the slicer doesn't have to move them.
For the reasoning behind this layout, see the parametric design guide.
1. L-bracket with M3 holes and gussets
The "hello world" of parametric CAD. The holes are cut after the two legs are joined. Cutting them per leg before the union is a common bug, because the other leg fills the hole back in.
L-bracket with two 40 mm legs, 20 mm wide and 4 mm thick. Two M3 clearance holes (3.4 mm) centred across the width of each leg, 8 mm and 24 mm from the end of the leg. Add a 3 mm thick triangular gusset along each side edge, reaching 20 mm along both legs.
// Leg length from the outside corner (mm)
leg_length = 40;
// Bracket width (mm)
bracket_width = 20;
// Plate thickness (mm)
plate_thickness = 4;
// Screw clearance hole, M3 normal fit (mm)
hole_diameter = 3.4;
// Distance from the end of each leg to the outer hole (mm)
hole_inset = 8;
// Spacing between the two holes on a leg (mm)
hole_spacing = 16;
// Side gusset thickness (mm)
gusset_thickness = 3;
$fn = 48;
hole_positions = [leg_length - hole_inset, leg_length - hole_inset - hole_spacing];
// @feature: legs
module legs() {
cube([leg_length, bracket_width, plate_thickness]);
cube([plate_thickness, bracket_width, leg_length]);
}
// @feature: gussets
module gussets() {
for (y = [0, bracket_width - gusset_thickness])
translate([0, y + gusset_thickness, 0])
rotate([90, 0, 0])
linear_extrude(gusset_thickness)
polygon([[0, 0], [leg_length / 2, 0], [0, leg_length / 2]]);
}
// @feature: screw_holes
module screw_holes() {
for (p = hole_positions) {
translate([p, bracket_width / 2, -1])
cylinder(h = plate_thickness + 2, d = hole_diameter);
translate([-1, bracket_width / 2, p])
rotate([0, 90, 0])
cylinder(h = plate_thickness + 2, d = hole_diameter);
}
}
difference() {
union() {
legs();
gussets();
}
screw_holes();
}
- Print it as modelled, with the outside of the base leg on the bed. No supports needed.
- The upright leg is weakest when pulled away from the base, because that pulls its layer lines apart. The gussets help. So do more walls.
- The upright leg's holes print sideways. At 3.4 mm they bridge fine, or swap in teardrop holes from the design guide.
2. Mounting plate with counterbored M3 holes
A flat plate with socket-head counterbores. An M3 socket head is 5.5 mm across and 3 mm tall, so a 6.5 mm × 3.5 mm counterbore leaves a little room and sinks the head just below the surface.
Mounting plate 60 × 40 × 6 mm with 4 mm rounded corners. Four M3 holes 6 mm in from each edge: 3.4 mm through, with 6.5 mm counterbores 3.5 mm deep so socket-head screws sit just below the surface.
// Plate length (mm)
plate_length = 60;
// Plate width (mm)
plate_width = 40;
// Plate thickness (mm)
plate_thickness = 6;
// Outer corner radius (mm)
corner_radius = 4;
// Hole distance from each edge (mm)
hole_inset = 6;
// Screw clearance hole, M3 normal fit (mm)
hole_diameter = 3.4;
// Counterbore diameter for an M3 socket head (mm)
counterbore_diameter = 6.5;
// Counterbore depth so the head sits flush (mm)
counterbore_depth = 3.5;
$fn = 48;
// @feature: plate
module plate() {
linear_extrude(plate_thickness)
offset(r = corner_radius)
square([plate_length - 2 * corner_radius, plate_width - 2 * corner_radius], center = true);
}
// @feature: counterbored_holes
module counterbored_holes() {
for (sx = [-1, 1], sy = [-1, 1])
translate([sx * (plate_length / 2 - hole_inset), sy * (plate_width / 2 - hole_inset), 0]) {
translate([0, 0, -1]) cylinder(h = plate_thickness + 2, d = hole_diameter);
translate([0, 0, plate_thickness - counterbore_depth]) cylinder(h = counterbore_depth + 1, d = counterbore_diameter);
}
}
difference() {
plate();
counterbored_holes();
}
- Print flat. The counterbore step faces up, so it needs no support.
- For flat-head (countersunk) screws, ask for a 90° countersink instead of a counterbore.
3. Project enclosure with insert standoffs and a lid
A box with four standoffs for M3 heat-set inserts, a USB-C cable slot and a separate lid that drops in with 0.2 mm clearance per side.
Project box 100 × 60 × 35 mm outside, 2.4 mm walls, 2 mm floor, 4 mm outside corner radius. Four standoffs, 8 mm tall and 8 mm across, on an 80 × 40 mm pattern, each with a 4.2 mm × 6.7 mm deep hole for an M3 heat-set insert. A 12 × 7 mm USB-C cable slot in one short wall. A separate lid with a 3 mm lip that fits inside the walls with 0.2 mm clearance per side, placed beside the box.
// Outside length (mm)
box_length = 100;
// Outside width (mm)
box_width = 60;
// Outside height of the base (mm)
box_height = 35;
// Wall thickness (mm)
wall_thickness = 2.4;
// Floor and lid thickness (mm)
floor_thickness = 2;
// Lid lip gap per side (mm)
lid_clearance = 0.2;
// Standoff pattern along X (mm)
standoff_spacing_x = 80;
// Standoff pattern along Y (mm)
standoff_spacing_y = 40;
// Standoff height above the floor (mm)
standoff_height = 8;
// USB-C cable slot width x height (mm)
usb_slot = [12, 7];
$fn = 48;
module rounded_rect(size, r) {
offset(r = r) square([size[0] - 2 * r, size[1] - 2 * r], center = true);
}
corner_radius = 4;
insert_hole = [4.2, 6.7];
// @feature: shell
module shell() {
difference() {
linear_extrude(box_height) rounded_rect([box_length, box_width], corner_radius);
translate([0, 0, floor_thickness])
linear_extrude(box_height)
rounded_rect([box_length - 2 * wall_thickness, box_width - 2 * wall_thickness], corner_radius - wall_thickness);
}
}
// @feature: standoffs
module standoffs() {
for (sx = [-1, 1], sy = [-1, 1])
translate([sx * standoff_spacing_x / 2, sy * standoff_spacing_y / 2, floor_thickness - 0.01])
difference() {
cylinder(h = standoff_height, d = 8);
translate([0, 0, standoff_height - insert_hole[1]]) cylinder(h = insert_hole[1] + 1, d = insert_hole[0]);
}
}
// @feature: usb_slot
module usb_cutout() {
translate([box_length / 2 - wall_thickness / 2, 0, floor_thickness + standoff_height + 1.6 + usb_slot[1] / 2])
cube([wall_thickness + 2, usb_slot[0], usb_slot[1]], center = true);
}
// @feature: lid
module lid() {
lip = [box_length - 2 * (wall_thickness + lid_clearance), box_width - 2 * (wall_thickness + lid_clearance)];
lip_r = corner_radius - wall_thickness - lid_clearance;
linear_extrude(floor_thickness) rounded_rect([box_length, box_width], corner_radius);
translate([0, 0, floor_thickness - 0.01])
linear_extrude(3)
difference() {
rounded_rect(lip, lip_r);
offset(delta = -1.6) rounded_rect(lip, lip_r);
}
}
difference() {
union() {
shell();
standoffs();
}
usb_cutout();
}
translate([0, box_width + 10, 0]) lid();
- Print the box open side up and the lid with its lip facing up.
- Insert makers list a 4.0 mm hole for a standard M3 × 5.7 mm insert, and a blind hole at least 1 mm deeper than the insert. This model uses 4.2 mm because printed holes usually come out small. Print one standoff first and adjust.
- The slot's top edge is a 12 mm bridge. Most printers handle it. If yours sags, ask the agent to make the slot top pointed or arched.
4. Involute spur gear with BOSL2
Real involute teeth from BOSL2's spur_gear(). With module 2 and 24 teeth, the pitch diameter is 2 × 24 = 48 mm and the outside diameter is about 52 mm.
Involute spur gear using BOSL2: 24 teeth, module 2, 20° pressure angle, 6 mm thick, 8 mm bore with a 3 mm wide keyway 1.5 mm deep, 0.1 mm backlash. Gear lying flat with its bottom face at Z = 0.
// Number of teeth
gear_teeth = 24;
// Gear module, the tooth size (mm)
gear_module = 2;
// Gear thickness (mm)
gear_thickness = 6;
// Shaft bore diameter (mm)
bore_diameter = 8;
// Keyway width (mm)
keyway_width = 3;
// Keyway depth past the bore wall (mm)
keyway_depth = 1.5;
// Backlash between meshing teeth (mm)
gear_backlash = 0.1;
include <BOSL2/std.scad>
include <BOSL2/gears.scad>
// @feature: gear
difference() {
spur_gear(mod = gear_module, teeth = gear_teeth, thickness = gear_thickness,
shaft_diam = bore_diameter, backlash = gear_backlash, anchor = BOTTOM);
// @feature: keyway
translate([bore_diameter / 2 + (keyway_depth - 0.5) / 2, 0, gear_thickness / 2])
cube([keyway_depth + 0.5, keyway_width, gear_thickness + 2], center = true);
}
- Meshing gears need the same module and pressure angle. BOSL2 defaults to 20°. Their centre distance is module × (teeth A + teeth B) ÷ 2, so 24 and 12 teeth at module 2 sit 36 mm apart.
- The editor loads BOSL2 the first time your code includes it. That first fetch is about 1 MB compressed, then it's cached for the session.
- For a quieter gear, try
helical = 15in thespur_gear()call. Its mate then needs the opposite helix angle.
5. Twisted hexagonal vase for vase mode
One linear_extrude() call with twist and scale. The model is solid on purpose: in spiral vase mode the slicer prints only the outer wall plus a solid bottom.
Hexagonal vase 120 mm tall, 70 mm across the corners at the base, 15% wider at the top, twisted 90° from bottom to top. Make it one solid body so I can print it in spiral vase mode.
// Vase height (mm)
vase_height = 120;
// Base radius, centre to corner (mm)
base_radius = 35;
// Top size relative to the base
top_scale = 1.15;
// Total twist (degrees)
twist_angle = 90;
// Number of sides
side_count = 6;
// @feature: vase_body
linear_extrude(height = vase_height, twist = twist_angle, scale = top_scale, slices = 120)
circle(r = base_radius, $fn = side_count);
- Turn on Spiral vase in PrusaSlicer, Bambu Studio or OrcaSlicer. The wall is a single line, so it's fine for dried flowers but may weep water.
- Keep the twist moderate. More twist leans each face further from vertical, which adds overhang.
- Want ribs, spirals or a wavy rim without code? The vase generator does that with sliders.
6. Gridfinity-style storage bin
Uses Gridfinity's sizes: a 42 mm grid, bins 0.5 mm smaller than the grid, 7 mm height units, and 6.5 mm × 2.4 mm magnet pockets on a 26 mm square in each cell. The bottom is flat, so it sits in a drawer but won't lock into a Gridfinity baseplate.
Gridfinity-style bin, 2 × 1 cells on a 42 mm grid (83.5 × 41.5 mm outside), 3 units (21 mm) tall, 1.2 mm walls, 3.75 mm corner radius. Flat bottom with four 6.5 mm × 2.4 mm deep magnet pockets per cell on a 26 mm square, 1.2 mm of solid floor above them, and a 45° label shelf 10 mm deep along one long wall.
// Grid cells along X
cells_x = 2;
// Grid cells along Y
cells_y = 1;
// Height in 7 mm units
height_units = 3;
// Wall thickness (mm)
wall_thickness = 1.2;
// Solid floor above the magnet pockets (mm)
floor_thickness = 1.2;
// Magnet pocket diameter (mm)
magnet_diameter = 6.5;
// Magnet pocket depth (mm)
magnet_depth = 2.4;
// Label shelf depth (mm)
label_depth = 10;
$fn = 48;
module rounded_rect(size, r) {
offset(r = r) square([size[0] - 2 * r, size[1] - 2 * r], center = true);
}
pitch = 42;
outer = [cells_x * pitch - 0.5, cells_y * pitch - 0.5];
inner = [outer[0] - 2 * wall_thickness, outer[1] - 2 * wall_thickness];
total_height = height_units * 7;
// @feature: bin_body
module bin_body() {
difference() {
linear_extrude(total_height) rounded_rect(outer, 3.75);
translate([0, 0, magnet_depth + floor_thickness])
linear_extrude(total_height) rounded_rect(inner, 3.75 - wall_thickness);
}
}
// @feature: label_shelf
module label_shelf() {
intersection() {
linear_extrude(total_height) rounded_rect(outer, 3.75);
translate([-inner[0] / 2, inner[1] / 2 + wall_thickness / 2, total_height])
rotate([0, 90, 0])
linear_extrude(inner[0])
polygon([[0, 0], [label_depth + wall_thickness / 2, 0], [0, -(label_depth + wall_thickness / 2)]]);
}
}
// @feature: magnet_pockets
module magnet_pockets() {
for (i = [0 : cells_x - 1], j = [0 : cells_y - 1], dx = [-13, 13], dy = [-13, 13])
translate([(i - (cells_x - 1) / 2) * pitch + dx, (j - (cells_y - 1) / 2) * pitch + dy, -0.01])
cylinder(h = magnet_depth + 0.01, d = magnet_diameter);
}
difference() {
union() {
bin_body();
label_shelf();
}
magnet_pockets();
}
- The magnet pockets open on the bed side. Each pocket's ceiling is a short bridge, so no supports.
- 6 × 2 mm magnets press into the pockets. Add a drop of glue if they're loose.
- The label shelf has a 45° underside, so it prints without supports.
7. Snap-in cable clip
A 2D profile, a ring with a slot, extruded sideways so the cable runs along the stick-on pad. The hull() under the ring gives it vertical sides instead of a steep overhang.
Stick-on cable clip for a 6 mm cable: a 25 × 10 × 2 mm pad for VHB tape, and a 2 mm thick snap ring 10 mm wide with its opening 0.5 mm narrower than the cable. The cable runs parallel to the pad. Fill under the ring so nothing overhangs more than 45°.
// Cable diameter (mm)
cable_diameter = 6;
// How much narrower the opening is than the cable (mm)
snap_interference = 0.5;
// Clip wall thickness (mm)
clip_wall = 2;
// Clip width along the cable (mm)
clip_width = 10;
// Adhesive pad length (mm)
base_length = 25;
// Adhesive pad thickness (mm)
base_thickness = 2;
$fn = 64;
outer_diameter = cable_diameter + 2 * clip_wall;
cable_center = base_thickness + clip_wall + cable_diameter / 2;
// @feature: clip_profile
module clip_profile() {
difference() {
hull() {
translate([0, cable_center]) circle(d = outer_diameter);
translate([-outer_diameter / 2, 0]) square([outer_diameter, 0.01]);
}
translate([0, cable_center]) circle(d = cable_diameter);
translate([-(cable_diameter - snap_interference) / 2, cable_center])
square([cable_diameter - snap_interference, outer_diameter]);
}
}
// @feature: base_pad
translate([-base_length / 2, -clip_width / 2, 0])
cube([base_length, clip_width, base_thickness]);
// @feature: clip
rotate([90, 0, 0])
linear_extrude(height = clip_width, center = true)
clip_profile();
- Print pad side down. PETG flexes more than PLA before it cracks, so it suits snap clips.
- If the cable is hard to push in, drop
snap_interferenceto 0.3 mm. If it falls out, raise it.
8. Thumb knob for an M5 hex bolt
A knob that captures the head of an M5 hex bolt. An M5 hex head is 8 mm across the flats. OpenSCAD's $fn = 6 cylinder is sized across the corners, so the code divides by cos(30°).
Thumb knob for an M5 hex-head bolt: 30 mm diameter, 14 mm tall, 18 rounded grip scallops around the edge, 1 mm chamfer on the top and bottom edges. A hex pocket 8.2 mm across the flats and 4 mm deep in the bottom, and a 5.5 mm through hole for the bolt.
// Knob diameter (mm)
knob_diameter = 30;
// Knob height (mm)
knob_height = 14;
// Number of grip scallops
grip_count = 18;
// Scallop diameter (mm)
grip_diameter = 3;
// Hex pocket across flats, M5 head is 8 mm (mm)
hex_across_flats = 8.2;
// Hex pocket depth (mm)
hex_depth = 4;
// Bolt clearance hole, M5 normal fit (mm)
bolt_hole = 5.5;
// Edge chamfer (mm)
edge_chamfer = 1;
$fn = 96;
// @feature: knob_body
module knob_body() {
hull() {
translate([0, 0, edge_chamfer]) cylinder(h = knob_height - 2 * edge_chamfer, d = knob_diameter);
cylinder(h = knob_height, d = knob_diameter - 2 * edge_chamfer);
}
}
// @feature: grip_scallops
module grip_scallops() {
for (i = [0 : grip_count - 1])
rotate([0, 0, i * 360 / grip_count])
translate([knob_diameter / 2 + 0.3, 0, -1])
cylinder(h = knob_height + 2, d = grip_diameter, $fn = 24);
}
// @feature: hex_pocket
module hex_pocket() {
translate([0, 0, -0.01])
cylinder(h = hex_depth + 0.01, d = hex_across_flats / cos(30), $fn = 6);
translate([0, 0, -1])
cylinder(h = knob_height + 2, d = bolt_hole);
}
difference() {
knob_body();
grip_scallops();
hex_pocket();
}
- Print with the hex pocket on the bed. The pocket's ceiling is an 8 mm bridge.
- The 1 mm bottom chamfer also hides elephant's foot, so the bolt head isn't pinched at the opening.
- Press the bolt head in. If it spins, add a drop of glue or reduce
hex_across_flatsby 0.1 mm.
9. Lithophane stand
A desk base with a slot for a lithophane. The defaults suit a 100 mm wide, 3 mm thick panel, which matches the lithophane generator's default settings.
Desk stand for a 100 mm wide, 3 mm thick lithophane: a 120 × 30 × 12 mm base with chamfered top edges and a centred slot 8 mm deep, 0.3 mm wider than the panel on each side, with a 0.8 mm lead-in chamfer at the top of the slot.
// Lithophane width (mm)
litho_width = 100;
// Lithophane thickness (mm)
litho_thickness = 3;
// Slot gap per side (mm)
slot_clearance = 0.3;
// Slot depth (mm)
slot_depth = 8;
// Base length (mm)
base_length = 120;
// Base depth, front to back (mm)
base_depth = 30;
// Base height (mm)
base_height = 12;
$fn = 48;
slot = [litho_width + 2 * slot_clearance, litho_thickness + 2 * slot_clearance];
module rounded_rect(size, r) {
offset(r = r) square([size[0] - 2 * r, size[1] - 2 * r], center = true);
}
// @feature: base
module base() {
hull() {
linear_extrude(base_height - 2) rounded_rect([base_length, base_depth], 4);
linear_extrude(base_height) rounded_rect([base_length - 4, base_depth - 4], 2);
}
}
// @feature: slot
module litho_slot() {
translate([0, 0, base_height - slot_depth])
linear_extrude(slot_depth + 1) square(slot, center = true);
hull() {
translate([0, 0, base_height - 0.8]) linear_extrude(0.01) square(slot, center = true);
translate([0, 0, base_height]) linear_extrude(0.01) square([slot[0] + 1.6, slot[1] + 1.6], center = true);
}
}
difference() {
base();
litho_slot();
}
- Print flat with no supports. Set the light source a few centimetres behind the panel.
- Measure your lithophane first. If you changed its thickness, change
litho_thicknessto match.
More AI CAD prompts to try
These are written the way you'd say them out loud, which suits Simple mode. The agent may ask a question or two before it builds.
A coat hook I can screw to the wall, about 80 mm tall, with two countersunk screw holes and a rounded tip so coats don't snag.
A pen and pencil cup about 80 mm across and 100 mm tall, with two compartments.
A 40 × 40 × 10 mm block that holds a 608 skateboard bearing (22 mm outside diameter, 7 mm wide) with a snug press fit, plus two M4 mounting holes.
A hook that clamps under a desk edge up to 25 mm thick and holds headphones, with a rounded cradle about 30 mm wide.
For how to write your own, see how to prompt AI for CAD.
Check yourself
You put include <BOSL2/std.scad> on line 1, above all your parameters. What happens in the Params tab?
The model still renders fine. But the slider panel only reads parameters above the first include, module or shape. Move your parameters above the include lines and the sliders come back.
Frequently asked questions
Will the CAD Agent write exactly this code?
No. The agent writes a fresh program for every request, so names, structure and small dimensions will differ from the reference code on this page. The prompts are written so the result should match the same envelope and features. Compare the agent's brief and the rendered size against the prompt before you export.
Do these OpenSCAD examples need any libraries?
Only the spur gear uses a library, BOSL2. The PrintPal editor ships BOSL2 and MCAD and loads them the first time your code includes them. In desktop OpenSCAD, install BOSL2 into your OpenSCAD library folder first. Every other example is plain OpenSCAD.
Will the Gridfinity-style bin fit a Gridfinity baseplate?
It has the standard 42 mm grid footprint, 7 mm height units and magnet pattern, but a flat bottom. Without the stepped foot profile it will not lock into a baseplate. For bins that do, use the PrintPal bin generator.
How do I change the size of an example?
Open the code in the editor and change the values in the Params tab, or edit the numbers at the top of the file. Each top-of-file value with a comment above it becomes a labelled slider. You can also ask the agent in plain words, for example: make it 10 mm taller.
Sources
- OpenSCAD User Manual: 2D to 3D extrusion (twist, scale and slices in
linear_extrude()) - BOSL2 wiki: gears.scad (
spur_gear()arguments and the 20° default pressure angle) - CNC Kitchen heat-set insert dimensions and design guidelines (PDF) (4.0 mm hole for M3 × 5.7 mm inserts, blind hole depth)
- Gridfinity specification by Zack Freedman and the community (42 mm grid, 7 mm units, 6 × 2 mm magnets)
- ISO 4762 and ISO 4017 screw dimensions (M3 socket head 5.5 mm, M5 hex head 8 mm across flats)
Send any prompt above to the agent
You'll get a parametric model you can adjust with sliders, then export as STL or 3MF.
Tools for thisAll tools
Watch it doneVideo library
Thanks! We read every note and use them to decide what to update next.