Building a Drafted Box

In this tutorial, you'll build a shelled box with a drafted central pipe and four ribs, based on the original design by Too Tall Toby. It covers drafted extrudes, plane offsets, shelling with face filters, reusable sketches with guide-driven angle lines and in-sketch mirroring, extruding up to a target face, and circular patterns.
Create a new file called drafted-box.fluid.js in your project.
Setup
Start with the imports. This model uses core operations together with edge and face filters.
import { aLine, back, circle, cut, extrude, fillet, hLine, line, local, mirror, plane, rect, remove, repeat, select, shell, sketch, vMove } from "fluidcad/core";
import { edge, face } from "fluidcad/filters";
fluidcad/core— modeling operations (sketch, extrude, cut, shell, fillet, repeat, etc.) and sketch primitivesfluidcad/filters—edgeandfaceselection filters used to pick faces and edges by plane
Step 1: Drafted base box
Sketch the footprint on an offset plane
The base footprint sits on a plane offset 1.5 above the top plane — that way the body extrudes downward through the origin instead of starting at it.
sketch(plane("top", 1.50), () => {
rect(7, 5).centered()
});
plane("top", 1.50) builds a plane parallel to "top" and offset 1.5 along its normal. Inside the sketch, rect(7, 5).centered() draws a 7×5 rectangle centered on the origin.
Extrude with draft and round the edges
const base = extrude(-1.5).draft(-8);
fillet(.750, base.sideEdges())
fillet(.50, select(edge().onPlane("top")))
extrude(-1.5) pushes the rectangle 1.5 units downward (along the negative plane normal). .draft(-8) tapers the side walls inward at 8° — a negative draft narrows the body as the extrude advances, giving the box its trapezoidal silhouette.
fillet(.750, base.sideEdges()) rounds the four vertical corners of the box with a 0.75 radius. select(edge().onPlane("top")) picks every edge that lies on the top plane (the top rim of the now-trapezoidal box, since its top face sits exactly on "top"), and fillet(.50) rounds those with a 0.5 radius.

Step 2: Shell the top
shell(-.250, select(face().onPlane("top", 1.5)))
select(face().onPlane("top", 1.5)) picks the face that sits on the offset plane used in step 1 — the top face of the box. shell(-.250) hollows the body inward with a 0.25 wall thickness, removing the selected face so the box opens upward.

Step 3: Drafted pipe body
Sketch the pipe footprint
sketch(plane("top", 2), () => {
circle(2)
});
A new offset plane at "top" + 2 hosts a circle of radius 2. Putting the sketch above the box means the resulting extrude will pass through the open top and seat itself inside the shell.
Extrude with positive draft
const pipeBody = extrude(-2).draft(8);
extrude(-2) pushes the circle 2 units downward. .draft(8) is a positive draft — the opposite of the box. Where negative draft narrows the body in the direction of travel, positive draft widens it, so the pipe flares outward as it descends into the box.

Step 4: Reusable rib profile
A single rib is built from two extrudes — one going inward toward the box wall, one going outward toward the pipe — both consuming the same profile. A normal sketch is consumed by the first extrude that uses it, so we mark it .reusable() to keep it alive for the second. We'll remove() it explicitly once we're done.
const ribSketch = sketch(plane("right", 1.5), () => {
vMove(.250)
const g = hLine(2).centered().guide()
back();
vMove(1.250 - .250)
hLine(.250).centered()
const l1 = aLine(-90 + 8, g)
const l2 = mirror(local("y"), l1);
line(l1.end(), l2.end())
}).reusable();
The sketch lives on plane("right", 1.5) — the right plane offset 1.5, lined up with the pipe axis. Inside:
vMove(.250)lifts the cursor up to where the rib's bottom edge will sit, thenhLine(2).centered().guide()draws a horizontal guide line of length 2, centered on the cursor. Guide lines don't appear in the final geometry —gis just a reference for the angled lines below.back()returns the cursor to the previous position.vMove(1.250 - .250)moves up to the top of the rib, andhLine(.250).centered()draws the short horizontal cap of the rib (the flat top).aLine(-90 + 8, g)draws an angle line at-82°(i.e. 8° off vertical) until it hits the guideg. That's the right-hand sloped edge of the rib.mirror(local("y"), l1)mirrorsl1across the sketch's local Y axis to produce the matching left-hand edge.line(l1.end(), l2.end())connects the two endpoints along the guide, closing the trapezoidal profile.
Step 5: Build the ribs and pattern them
Each rib needs to extrude outward from its sketch plane in two directions: inward, until it meets the inner shell wall, and outward, until it meets the pipe's curved side. We build each half separately, then circular-pattern the whole arrangement around the Z axis.
const ribHalf1 = extrude('first-face', face().planar());
const ribHalf2 = extrude(pipeBody.sideFaces());
const rmSketch = remove(ribSketch);
repeat("circular", "z", {
count: 4,
angle: 360
}, ribHalf1, ribHalf2, rmSketch)
extrude('first-face', face().planar()) extrudes the rib until it hits the first planar face it encounters — that's the inner wall of the shelled box. The face().planar() filter restricts the search to planar faces only, which lets it skip past the pipe's curved face.
extrude(pipeBody.sideFaces()) extrudes the rib in the opposite direction up to the pipe's side faces — the curved cylindrical surface — closing the gap between the pipe and the inner box wall.
remove(ribSketch) clears the reusable sketch now that both extrudes have consumed it. We capture the removal as rmSketch and include it in the repeat so each rotated copy of the rib cleans up its own cloned sketch — without that, the patterned sketches would linger in the scene.
repeat("circular", "z", { count: 4, angle: 360 }, ribHalf1, ribHalf2, rmSketch) circularly patterns the two extrudes plus the sketch removal — 4 copies evenly distributed across a full 360° around the Z axis.

Step 6: Drafted pipe hole
Sketch the hole on the pipe's top face
sketch(pipeBody.startFaces(), () => {
circle(1.5)
});
pipeBody.startFaces() returns the face the pipe extrude started from — its flat top. Sketching directly on a face starts the cursor at that face's center, so circle(1.5) places a 1.5-radius circle at the pipe's axis.

Cut with draft
cut().draft(-8);
cut() with no depth runs the cut as far as the underlying body allows. .draft(-8) tapers the cut walls inward at 8° — the same draft angle as the pipe's outer wall, so the bore narrows as it descends and stays parallel to the pipe's outside surface.

Full code
// @screenshot waitForInput
import { aLine, back, circle, cut, extrude, fillet, hLine, line, local, mirror, plane, rect, remove, repeat, select, shell, sketch, vMove } from "fluidcad/core";
import { edge, face } from "fluidcad/filters";
sketch(plane("top", 1.50), () => {
rect(7, 5).centered()
});
const base = extrude(-1.5).draft(-8);
fillet(.750, base.sideEdges())
fillet(.50, select(edge().onPlane("top")))
shell(-.250, select(face().onPlane("top", 1.5)))
sketch(plane("top", 2), () => {
circle(2)
});
const pipeBody = extrude(-2).draft(8);
const ribSketch = sketch(plane("right", 1.5), () => {
vMove(.250)
const g = hLine(2).centered().guide()
back();
vMove(1.250 - .250)
hLine(.250).centered()
const l1 = aLine(-90 + 8, g)
const l2 = mirror(local("y"), l1);
line(l1.end(), l2.end())
}).reusable();
const ribHalf1 = extrude('first-face', face().planar());
const ribHalf2 = extrude(pipeBody.sideFaces());
const rmSketch = remove(ribSketch);
repeat("circular", "z", {
count: 4,
angle: 360
}, ribHalf1, ribHalf2, rmSketch)
sketch(pipeBody.startFaces(), () => {
circle(1.5)
});
cut().draft(-8);
What you practiced
plane("top", offset)— building a sketch plane parallel to a principal plane at a given offsetextrude().draft(angle)— tapering an extrude's side walls; negative draft narrows, positive draft widenscut().draft(angle)— applying the same taper to a cut for matched-angle boresselect(face().onPlane(...))/select(edge().onPlane(...))— picking faces or edges by the plane they lie onshell(thickness, face)— hollowing a body and removing a selected face to open itaLine(angle, guide)— drawing an angle line that runs until it meets a guidehLine().guide()— construction geometry that informs other sketch primitives but doesn't appear in the resultmirror(local("y"), entity)— mirroring a sketch entity about a local sketch axisextrude('first-face', face().planar())— extruding up to the first planar face encountered, ignoring curved surfacesextrude(faces)— extruding up to a specific set of target facessketch().reusable()+remove()— keeping a sketch alive across multiple operations and cleaning it up at the endrepeat("circular", "z", ...)— circular-patterning operations around an axis