Skip to main content

Building a Hinge Bracket

Finished hinge bracket

In this tutorial, you'll build a hinge bracket from scratch, based on the original design by Too Tall Toby. It covers reusable path sketches, thin symmetric extrusions, projecting edges into a sketch, chamfering and shelling selected features, face-based sketching, filter-based trimming, and building ribs from a projected spine.

Create a new file called hinge-bracket.fluid.js in your project.

Setup

Start with the imports. This model uses core operations together with edge and face filters.

import { arc, chamfer, circle, cut, extrude, fillet, hLine, hMove, move,
plane, project, remove, select, shell, sketch, split, trim, vLine } from "fluidcad/core";
import { edge, face } from "fluidcad/filters";
  • fluidcad/core — all the modeling operations (sketch, extrude, cut, shell, split, trim, etc.)
  • fluidcad/filtersedge and face selection filters for chamfer, shell, trimming, and face-based sketching

Step 1: The spine and base body

Draw the spine

The spine is an open path that traces one side of the bracket's cross-section. Later steps will reuse it as the extrude profile and again as a projection source for the ribs, so we mark it .reusable().

const spine = sketch("front", () => {
hMove(-40)
hLine(40)
hLine(78);
vLine(150);
hLine(-78)
hLine(-40)
fillet(34)
}).reusable();

hMove(-40) shifts the cursor 40 units to the left, then the two hLine calls trace 40 + 78 = 118 units to the right along the bottom. vLine(150) runs up the outside, and the two hLine(-78) / hLine(-40) calls trace back across the top. fillet(34) rounds the two top corners with a 34-unit radius.

Spine profile

Extrude the thin wall

let base = extrude(80).thin(26).symmetric();

extrude(80) pushes the open path 80 units along the normal. .thin(26) gives the path a 26-unit wall thickness — turning the open spine into a C-shaped shell. .symmetric() splits the 80-unit depth evenly on both sides of the sketch plane, so the bracket is centered on the front plane.

Thin symmetric base body

Step 2: The top cutout

Sketch the arc

The top of the bracket gets a rounded pocket. We sketch a centered arc with a guide line to define the cut boundary.

const topPlane = plane(base.sideFaces(4))

sketch(topPlane, () => {
move([-2, 0])
vLine(100, true).guide()
move([0, 0])
arc(40).centered();
});

plane(base.sideFaces(4)) builds a sketch plane on the top face of the base. Inside the sketch, move([-2, 0]) offsets the cursor slightly left, then vLine(100, true).guide() draws a tall vertical guide line — this defines where the thin cut boundary will be. move([0, 0]) resets the cursor to the origin, and arc(40).centered() draws a semicircular arc of radius 40.

Top cutout sketch

Cut through

cut(26).thin(50);

cut(26) removes 26 units of material straight down from the sketch. .thin(50) gives the open arc a 50-unit wall thickness before cutting — deliberately oversized so the offset fully covers all edges of the arc. Any excess beyond the actual body is harmless.

Top cutout

Step 3: Chamfer and shell

Chamfer the vertical edges

select(edge().verticalTo("top").line(26))
chamfer(28);

edge().verticalTo("top").line(26) finds the straight edges that are vertical relative to the top plane and exactly 26 units long — the four short vertical corners left by the top cutout. chamfer(28) breaks them with a 28-unit chamfer, blending the cutout into the side walls.

Select the faces to remove

select(face().cylinderCurve(34 * 2).withTangents());

face().cylinderCurve(34 * 2) matches the cylindrical face whose curvature diameter is 34 * 2 = 68 — that's the rounded end face of the cutout (the fillet radius was 34). .withTangents() extends the selection to every face tangent-connected to it, so the straight side faces of the cutout are included too.

Faces selected before shell

Shell the body

shell(-10)

shell(-10) hollows the body with a 10-unit wall, removing the selected faces so the top cutout becomes an open pocket.

Chamfered and shelled

Step 4: Top boss

sketch(topPlane, () => {
circle([0, 0], 80)
});

extrude(-20)

Back on topPlane, circle([0, 0], 80) places an 80-unit-radius circle on the origin. extrude(-20) pushes it 20 units downward (into the body), building a cylindrical boss that will host the hinge pin.

Top boss added

Step 5: Hinge tab on the base

Sketch the tab profile

The tab sits on the XY plane, 16 units up from the origin. We reuse the face selection twice — once as the sketch plane, once as a projection source — so we mark it .reusable() and remove it manually at the end.

const faceSelection = select(face().onPlane("xy", 16)).reusable()
sketch(faceSelection, () => {
move([0, 0])
project(faceSelection)
vLine(60, true)
split()
trim(edge().above("yz"))
circle([0, 0], 50)
});

remove(faceSelection);

Sketching on a face starts at the face center, so move([0, 0]) resets the cursor to the sketch origin. project(faceSelection) pulls the face outline into the sketch. vLine(60, true) draws a vertical construction line that splits the profile in half. split() splits all edges at their intersection points, then trim(edge().above("yz")) removes every edge that lies above (to the right of) the YZ plane — keeping only the left half that extends out past the base. Finally, circle([0, 0], 50) adds the circle that forms the tab's rounded end. remove(faceSelection) clears the reusable selection so it doesn't leak into later operations.

Hinge tab sketch

Extrude the tab

extrude(16)

extrude(16) pushes the trimmed profile 16 units up to form the hinge tab.

Hinge tab extruded

Step 6: Support ribs

Sketch the rib profile from the spine

const ribSketch = sketch("front", () => {
project(spine);
trim(edge().below("yz", { partial: true }))
});

On the front plane, project(spine) pulls the original spine path back into a new sketch. trim(edge().below("yz", { partial: true })) removes every edge that is at least partially below the YZ plane — this strips away the bottom horizontal segments, leaving only the corner-and-fillet region that becomes the rib profile. The partial: true option means an edge is removed if even one of its vertices is below the plane, which cleanly catches edges that cross the boundary.

Rib profile sketch

Extrude the ribs

extrude(10, ribSketch).thin(16).symmetric()

remove(spine)

extrude(10, ribSketch) extrudes the trimmed rib profile 10 units. .thin(16) gives it a 16-unit wall thickness, and .symmetric() mirrors it across the sketch plane so the rib is centered on the bracket. Once the ribs are built, remove(spine) clears the reusable spine sketch — we won't need it again.

Ribs added

Step 7: Pin holes

Top pin hole

circle(16, "top")
cut(-26)

circle(16, "top") drops a circle of radius 16 on the top plane at the origin. cut(-26) removes 26 units of material downward, boring the hinge-pin hole through the top boss.

Bore through the boss

sketch(topPlane, () => {
circle([0, 0], 50);
});

cut(26);

A larger circle of radius 50, sketched back on topPlane, opens up the inside of the boss. cut(26) removes 26 units of material to finish the bore.

Finished hinge bracket

Full code

// @screenshot waitForInput
import { arc, chamfer, circle, cut, extrude, fillet, hLine, hMove, move, plane, project, remove, select, shell, sketch, split, trim, vLine } from "fluidcad/core";
import { edge, face } from "fluidcad/filters";

const spine = sketch("front", () => {
hMove(-40)
hLine(40)
hLine(78);
vLine(150);
hLine(-78)
hLine(-40)
fillet(34)
}).reusable();

let base = extrude(80).thin(26).symmetric();

const topPlane = plane(base.sideFaces(4))

sketch(topPlane, () => {
move([-2, 0])
vLine(100, true).guide()
move([0, 0])
arc(40).centered();
});

cut(26).thin(50);

select(edge().verticalTo("top").line(26))
chamfer(28);

select(face().cylinderCurve(34 * 2).withTangents());
shell(-10)

sketch(topPlane, () => {
circle([0, 0], 80)
});

extrude(-20)

const faceSelection = select(face().onPlane("xy", 16)).reusable()
sketch(faceSelection, () => {
move([0, 0])
project(faceSelection)
vLine(60, true)
split()
trim(edge().above("yz"))
circle([0, 0], 50)
});

remove(faceSelection);

extrude(16)

const ribSketch = sketch("front", () => {
project(spine);
trim(edge().below("yz", { partial: true }))
});

extrude(10, ribSketch).thin(16).symmetric()

remove(spine)

circle(16, "top")
cut(-26)

sketch(topPlane, () => {
circle([0, 0], 50);
});

cut(26);

What you practiced

  • sketch().reusable() — keeping a sketch alive so later operations can reuse it as a profile or projection source
  • extrude().thin().symmetric() — turning an open path into a symmetric thin-walled body
  • cut().thin() — limiting a cut to a specific wall thickness
  • vLine().guide() — drawing a construction line that guides cut boundaries without appearing in the final geometry
  • project() inside a sketch — pulling existing edges or a reusable sketch into the active sketch plane
  • plane(face) — deriving a sketch plane directly from a model face
  • edge().verticalTo().line() �� selecting straight edges by orientation and length
  • face().cylinderCurve().withTangents() — selecting a cylindrical face by its curvature and expanding to tangent-connected faces
  • chamfer() / shell() — breaking sharp edges and hollowing the body with face removal
  • face().onPlane() + .reusable() + remove() — using a face selection twice, then releasing it
  • split() + trim(edge().above()) — splitting edges at intersections, then removing edges by position with a filter instead of fragile point-based trimming
  • trim(edge().below("yz", { partial: true })) — removing projected edges by spatial position, using partial to catch edges that cross the boundary
  • extrude(depth, sketch) — extruding a named sketch (the ribs) rather than the most recent one