Skip to main content

Building a 3D Printable Desk Organizer

In this tutorial, you'll build a four-compartment octagonal desk organizer with a square inner pocket, based on the original design by TooTallToby. You build it entirely from the FluidCAD workspace: the toolbar, the sketch tools, the constraint bar and the feature dialogs. Every dialog writes a line of code into your file as you apply it, so the full code at the end is what the UI produced, not something you type.

It covers sketching a regular octagon with the Polygon tool, rounding its corners with the sketch Fillet tool, extruding, chamfering and shelling a body, drawing a dimensioned rectangle and offsetting it, making a rotated construction plane, building a rib and patterning it around an axis, and trimming with a thin revolve cut.

Before you start​

Open a workspace and create a new part file: click + in the top bar and type desk-organizer.fluid.js. The new tab opens on an empty scene. The screenshots use the light theme; the sun/moon button at the top right switches it.

A few UI habits you will use throughout:

  • Ctrl+click adds an entity to the current selection. A plain click replaces it.
  • The constraint bar appears under the toolbar while you sketch. Its buttons are icons; hover one for its name. A button lights up only when the current selection makes sense for it.
  • The DOF pill at the bottom of the viewport counts the degrees of freedom left in a sketch and turns into Fully constrained when you are done.
  • Every feature dialog shows the statement it is about to write, just under the dialog. Apply (or Enter) writes it; Exit (or Escape) discards it.

Step 1: Octagonal base​

Start a sketch on the XY plane​

Click Sketch in the toolbar. The Sketch dialog asks for a face or a plane, and the three origin planes appear in the viewport.

Sketch tool armed, waiting for a plane

Click the XY plane. The camera locks onto it, the sketch toolbar replaces the feature toolbar, and the constraint bar appears above the viewport. The dialog now lists the sketch options: section view, camera lock, snapping, automatic constraint inference while drawing, and which constraint badges to display. Leave them all on.

An empty sketch on the XY plane

Draw the octagon with the Polygon tool​

Pick the Polygon tool. Its button carries a small option menu for circumscribed or inscribed; leave it on circumscribed, so the size you type is measured across the flats rather than across the corners.

Click the origin for the center. A ⌀ readout follows the mouse; type 140 and press Enter.

Polygon tool: the center is placed and the diameter is being typed

The readout switches to N, the number of sides. Moving the mouse previews the polygon; type 8 and press Enter.

Polygon tool: choosing the number of sides

One tool, and the octagon arrives fully described: eight lines joined corner to corner, a dash-dot guide circle of diameter 140 centered on the origin, every side tangent to it, all sides equal, and a 45° angle between two neighbors. The badges around the profile are those constraints. The DOF pill reads 1 DOF remaining: the octagon can still spin about its center.

The octagon as the Polygon tool emits it, one degree of freedom left

Click the top side, away from its midpoint where the guide circle touches it, and click Horizontal on the constraint bar. A flat now faces the X axis instead of a corner, and the pill reads Fully constrained. That orientation matters later: it lines up the inner square's diagonals with four of the flats.

The octagon with a horizontal side, fully constrained

Round the corners with the Fillet tool​

Pick Fillet in the sketch toolbar. Click each of the eight sides, near a corner so the pick does not land on the guide circle; the dialog counts them and reports 8 corners will be filleted. Set Radius to 20. The thin blue arcs at the corners are the preview.

Fillet dialog with the eight sides picked and the 20 mm corner preview

Click Apply. Each corner is replaced by an arc that is tangent to both of its sides, one arc carries the R20 dimension and the other seven are equal to it. The pill still reads Fully constrained.

The finished octagon with eight tangent arcs

Extrude the body​

The 3D operations that take a sketch as input stay on the toolbar while the sketch is open. Click Extrude. The Extrude dialog opens with the sketch already in its Sketch slot. Set Distance to 110. The green ghost is a live preview of the solid the extrude will add, and the statement under the dialog reads extrude(110).

Extrude dialog with the 110 mm ghost preview

Click Apply. The prism appears, an Extrude row joins the History panel, and the Shapes panel lists the new solid.

The extruded octagonal prism

Step 2: Chamfer the bottom and shell from the top​

Chamfer the bottom edges​

Click Chamfer in the toolbar. The dialog asks you to pick edges. Click one edge of the bottom rim, then double-click it: a double-click expands the pick to the whole classified group the edge belongs to, here all sixteen edges of the bottom rim. Set Distance to 8.

The red band is the chamfer preview. The statement reads chamfer(8, e.startEdges()): FluidCAD recognised the rim as the start edges of the extrude and wrote the selection that way, so it survives edits to the sketch.

Chamfer dialog with all sixteen bottom edges picked and the 8 mm preview

Click Apply.

The chamfered foot

Shell from the top​

Click Shell. Click the top face of the prism; it highlights and lands in the Selection slot. Set Thickness to -5. A negative thickness hollows the body inward, keeping the outside dimensions; the picked face is the one that gets removed. The statement reads shell(-5, e.endFaces()).

Shell dialog with the top face picked

Click Apply. The prism is now a cup with 5 mm walls and a 5 mm floor.

The shelled cup

Step 3: Inner square pocket​

Sketch a 50 × 50 square​

Click Sketch and pick the XY plane again. Because the sketch plane cuts through the cup, Section view clips the walls away so you see the floor as a flat outline.

Pick the Rectangle tool. Click the first corner at (-25, -25), then type 50 for the width, press Enter, type 50 for the height and press Enter again. Typed sizes become dimensions in the sketch rather than guesses.

Rectangle tool: the width is typed, the height comes next

The rectangle arrives as four lines with horizontal, vertical and coincident constraints and the two 50 mm dimensions. It can still slide around as a whole, so click its bottom-left corner point and click Fix on the constraint bar. The pill reads Fully constrained.

The 50 × 50 square with its corner fixed

Offset an inner loop​

Pick Offset in the sketch toolbar. Click one side of the square and Ctrl+click the other three. Set Distance to -5; the negative value offsets inward. The blue outline is the preview of the second loop.

Offset dialog with the four sides picked and the -5 mm inner loop preview

Click Apply. The sketch now holds a closed outer loop and a closed inner loop, which extrudes as a square tube rather than a solid block.

Extrude the column​

Click Extrude on the toolbar. Set Distance to 160, taller than the 110 mm cup so the column will stand above the rim. The ghost shows the tube rising from the floor and through the open top.

Extrude dialog with the 160 mm column preview

Click Apply. With the Add tab selected the new solid fuses with the cup, so the column and the floor become one body.

The inner square column fused to the cup

Step 4: Add a rib and pattern it around​

Make a rotated construction plane​

The ribs run diagonally from the column's corners to the outer wall. To sketch one you need a plane on that diagonal. Click Plane in the toolbar, then click the XZ origin plane in the viewport to use it as the base. Set Offset to 0 and the middle Rotation field, the rotation about Y, to 45.

The yellow quad previews the plane, and the statement reads plane('xz', { rotateY: 45 }).

Plane dialog with the XZ plane rotated 45° about Y

Click Apply. A Plane row is added to History.

Sketch the rib spine​

Click Sketch and click the new plane (either its quad in the viewport or its row in the History panel). Section view now slices the model along the diagonal, so you see the column and the outer wall in cross-section.

Pick the Line tool and draw a short line just inside the outer wall, near the top of the cup, angled up and to the right. Then dimension it with the constraint bar:

  1. Click the line and click Dimension, type 20 for its length.
  2. Click the line's start point, Ctrl+click the horizontal axis and click Dimension, type 115. That puts the spine 5 mm above the 110 mm rim.
  3. Click the start point, Ctrl+click the vertical axis, Dimension, type 60.
  4. Click the line, Ctrl+click the horizontal axis and click Angle, type 45.

The rib spine: a 20 mm line dimensioned from the sketch axes

The spine does not need to reach either wall. The rib will be extended to meet them.

Build the rib​

Click Rib on the toolbar. Set Thickness to 5, and switch on Parallel to sketch plane and Extend to walls. Parallel grows the rib in the sketch plane, so it stands as a wall rather than being pushed out normal to the plane; Extend stretches the spine until it meets the surrounding solids. The green ghost shows the wall between the column and the outer shell, and the statement reads rib(5).parallel().extend().

Rib dialog with the parallel, extended rib preview

Click Apply.

Pattern it around the Z axis​

Click Repeat in the toolbar and set Type to Circular. Repeat takes its targets from the History panel: click the Rib row, and it lands in the Features slot. The axis defaults to World Z axis, which is what you want. Set Total Count to 4 and leave the angle at a total of 360. The ghost shows the three extra ribs.

Repeat dialog with the four-rib circular preview

Click Apply. Four rib walls now divide the cup into four compartments.

Four ribs around the column

Step 5: Trim the top with a thin revolve cut​

Sketch the trim arc​

The column and the ribs poke above the rim. You will dome them off by revolving a quarter arc into a hemispherical shell and subtracting it.

Click Sketch and pick the XZ plane. Pick the Center Arc tool: click the origin for the center, click a point on the horizontal axis to the right for the start, and sweep up to the vertical axis for the end. Constrain it:

  1. Click the arc's center point and click Fix, so it stays on the revolve axis.
  2. Click the arc, click Dimension and type 140 for the radius.
  3. Click the arc's start point, Ctrl+click the horizontal axis and click Coincident. Do the same for the end point and the vertical axis.

A quarter arc of radius 140 on the XZ plane

Revolve it as a thin cut​

Click Revolve on the toolbar. Select the Remove tab so the revolved shape is subtracted. The axis defaults to World Z axis. Switch on Thin walls and set Thickness to 30: an open arc cannot enclose a solid on its own, so the thin option turns it into a 30 mm thick hemispherical shell. The red ghost is the shell that will be removed; the 30 mm is deliberately generous so it consumes everything above the dome.

Revolve dialog with the thin, removed hemisphere preview

Click Apply. The column's corners are trimmed to a spherical cap and the rib tops follow the same curve.

The finished desk organizer

Full code​

Everything above was written into desk-organizer.fluid.js by the dialogs. Open the code editor with the </> button on the left rail or Ctrl+B to read it. The listing below is the same model with tidier names; the coordinates you clicked will differ, but the constraints make the geometry identical.

Show the full code
// @screenshot waitForInput
import { yAxis, origin, circle, breakpoint, plane, sketch, extrude, fillet, chamfer, repeat, rotate, arc, shell, offset, rib, revolve, line, xAxis } from 'fluidcad/core';
import { tangent, diameter, angle, coincident, distance, equal, fix, horizontal, radius, vertical } from "fluidcad/constraints";

sketch('xy', () => {
const l1 = line([75.77, 0], [53.58, 53.58]);
const l2 = line([53.58, 53.58], [0, 75.77]);
const l3 = line([0, 75.77], [-53.58, 53.58]);
const l4 = line([-53.58, 53.58], [-75.77, 0]);
const l5 = line([-75.77, 0], [-53.58, -53.58]);
const l6 = line([-53.58, -53.58], [0, -75.77]);
const l7 = line([0, -75.77], [53.58, -53.58]);
const l8 = line([53.58, -53.58], [75.77, 0]);
const c1 = circle([0, 0], 140).guide();
const a1 = arc([-20.71, 70], [-34.85, 64.14], [-20.71, 50]);
const a2 = arc([20.71, 70], [34.85, 64.14], [20.71, 50]).cw();
const a3 = arc([-64.14, 34.85], [-70, 20.71], [-50, 20.71]);
const a4 = arc([-70, -20.71], [-64.14, -34.85], [-50, -20.71]);
const a5 = arc([-34.85, -64.14], [-20.71, -70], [-20.71, -50]);
const a6 = arc([20.71, -70], [34.85, -64.14], [20.71, -50]);
const a7 = arc([64.14, -34.85], [70, -20.71], [50, -20.71]);
const a8 = arc([70, 20.71], [64.14, 34.85], [50, 20.71]);
equal(l1, l2, l3, l4, l5, l6, l7);
tangent(l1, c1);
tangent(l2, c1);
tangent(l3, c1);
tangent(l4, c1);
tangent(l5, c1);
tangent(l6, c1);
tangent(l7, c1);
tangent(l8, c1);
angle(l1, l2, 45);
diameter(c1, 140);
coincident(c1.center(), origin());
horizontal(l3);
coincident(a1.start(), l3.end());
coincident(a1.end(), l4.start());
tangent(l3, a1);
tangent(a1, l4);
coincident(a2.start(), l3.start());
coincident(a2.end(), l2.end());
tangent(l3, a2);
tangent(a2, l2);
coincident(a3.start(), l4.end());
coincident(a3.end(), l5.start());
tangent(l4, a3);
tangent(a3, l5);
coincident(a4.start(), l5.end());
coincident(a4.end(), l6.start());
tangent(l5, a4);
tangent(a4, l6);
coincident(a5.start(), l6.end());
coincident(a5.end(), l7.start());
tangent(l6, a5);
tangent(a5, l7);
coincident(a6.start(), l7.end());
coincident(a6.end(), l8.start());
tangent(l7, a6);
tangent(a6, l8);
coincident(a7.start(), l8.end());
coincident(a7.end(), l1.start());
tangent(l8, a7);
tangent(a7, l1);
coincident(a8.start(), l1.end());
coincident(a8.end(), l2.start());
tangent(l1, a8);
tangent(a8, l2);
radius(a1, 20);
equal(a1, a2);
equal(a1, a3);
equal(a1, a4);
equal(a1, a5);
equal(a1, a6);
equal(a1, a7);
equal(a1, a8);
});

const outer = extrude(110);

chamfer(8, outer.startEdges())

shell(-5, outer.endFaces())

sketch('xy', () => {
const sg1 = line([-25, -25], [25, -25]);
const sg2 = line([25, -25], [25, 25]);
const sg3 = line([25, 25], [-25, 25]);
const sg4 = line([-25, 25], [-25, -25]);
coincident(sg1.end(), sg2.start());
coincident(sg2.end(), sg3.start());
coincident(sg3.end(), sg4.start());
coincident(sg4.end(), sg1.start());
horizontal(sg1);
vertical(sg2);
horizontal(sg3);
vertical(sg4);
fix(sg1.start(), [-25, -25]);
distance(sg1.start(), sg1.end(), 50);
distance(sg2.start(), sg2.end(), 50);

offset(-5);
});

extrude(160);

const p = plane("front", { rotateY: 45 })

sketch(p, () => {
const l9 = line([-60.61, 110], [-46.46, 124.14]);
distance(l9.start(), xAxis(), 115);
angle(xAxis(), l9, 45);
distance(l9.start(), l9.end(), 20);
distance(l9.start(), yAxis(), 60);

});

rib(5).parallel().extend();

repeat("circular", "z", {
count: 4,
angle: 360
})

sketch('xz', () => {
const a = arc([140, 0], [51.22, 130.29], [0, 0]);
fix(a.center(), [0, 0]);
radius(a, 140);
coincident(a.start(), xAxis());
coincident(a.end(), yAxis());
});

revolve('z').thin(30).remove()
Open this model in the 3D viewer

What you practiced​

  • Starting a sketch from the Sketch tool and an origin plane, with section view slicing the model at the sketch plane
  • Polygon with a typed diameter and side count, and what it emits: a guide circle, tangent and equal sides, and an angle between neighbors
  • Horizontal, Fix, Dimension, Angle and Coincident from the constraint bar, and reading the DOF pill until a sketch is fully constrained
  • Sketch Fillet on a run of sides, which swaps every corner for a tangent arc with one shared radius
  • Extrude, Rib and Revolve applied straight from the toolbar to the open sketch, and the live ghost preview in each dialog
  • Chamfer with a double-click to pick a whole edge group, and Shell with a negative thickness and a picked face to open
  • Rectangle with typed sizes and Fix to pin a corner, then Offset for an inner loop
  • Plane with a rotation about an axis, and dimensioning a sketch against its own axes
  • Rib with parallel and extend, and Repeat picking its targets from the History panel
  • Revolve on the Remove tab with Thin walls, as a trim cut