Skip to content

Lofts, Sweeps, Revolves

Three operations turn a 2D profile (or several) into a 3D solid by motion: extrude moves the profile linearly, revolve rotates it around an axis, sweep drags it along a path, loft interpolates between profiles. Combined with sketching, they cover everything that isn't a primitive boolean.

Extrude — linear motion

The simplest case. A profile pushed along its normal:

typescript
import { sketchRectangle, sketchCircle, measureVolume } from 'brepjs/quick';

const block = sketchRectangle(30, 20).extrude(10);
const cyl = sketchCircle(10).extrude(20);

console.log('Block:', measureVolume(block).toFixed(2));
console.log('Cylinder:', measureVolume(cyl).toFixed(2));

The functional equivalent — extrude(face, height) — takes an OrientedFace and a distance:

typescript
import { sketchCircle, extrude, unwrap } from 'brepjs/quick';

const profile = sketchCircle(10).face();
const cyl = unwrap(extrude(profile, 20));
void cyl;

Extrude with a vector

Pass a 3D vector to extrude in an arbitrary direction (not perpendicular to the sketch plane):

typescript
import { sketchRectangle, extrudeAlong, unwrap } from 'brepjs/quick';

const profile = sketchRectangle(20, 10).face();
const slanted = unwrap(extrudeAlong(profile, [0, 5, 20])); // extrude along (0,5,20)
void slanted;

The vector replaces the height — its length determines extrusion distance, its direction determines the axis.

Tapered extrude

extrude(face, height, { taper }) adds a draft angle. Common in moulded parts:

typescript
import { sketchRectangle, extrude, unwrap } from 'brepjs/quick';

const profile = sketchRectangle(30, 20).face();
const tapered = unwrap(extrude(profile, 20, { taper: 5 })); // 5° draft
void tapered;

Negative taper widens upward; positive narrows.

Revolve — rotational motion

A 2D profile rotated around an axis — the basis of every wineglass, vase, axle, and spindle:

typescript
import { Sketcher } from 'brepjs/quick';

const goblet = new Sketcher('XZ')
  .movePointerTo([0, 0])
  .hLine(8)
  .vLine(2)
  .hLine(-6)
  .vLine(20)
  .hLine(15)
  .tangentArc(0, 4)
  .hLine(-17)
  .vLine(-26)
  .close()
  .revolve();
void goblet;

The axis defaults to the sketch plane's vertical axis. The profile must lie entirely on one side of the axis — the kernel rejects profiles that cross.

Partial revolves

By default, revolve sweeps 360°. For partial revolutions:

typescript
import { Sketcher, revolve, unwrap } from 'brepjs/quick';

const profile = new Sketcher('XZ')
  .movePointerTo([5, 0])
  .lineTo([10, 0])
  .lineTo([10, 5])
  .lineTo([5, 5])
  .close()
  .face();

const halfRing = unwrap(revolve(profile, { angle: 180 })); // half-torus segment
void halfRing;

{ angle: degrees } controls the sweep. Use this for hemisphere caps, pie slices, partial bushings.

Revolves around an arbitrary axis

typescript
import { Sketcher, revolve, unwrap } from 'brepjs/quick';

const profile = new Sketcher('XY')
  .movePointerTo([20, 0])
  .lineTo([22, 0])
  .lineTo([22, 5])
  .lineTo([20, 5])
  .close()
  .face();

const ring = unwrap(revolve(profile, { axis: { origin: [0, 0, 0], direction: [0, 0, 1] } }));
void ring;

Sweep — profile along a path

Drag a 2D profile along a 3D wire:

typescript
import { sketchCircle, line, sweep, unwrap } from 'brepjs/quick';

const cross = sketchCircle(2).face(); // 2mm tube radius
const path = line([0, 0, 0], [0, 0, 50]); // 50mm straight up

const tube = unwrap(sweep(cross, path));
void tube;

The path can be any 3D curve — straight, arced, helical, B-spline. The profile rides along it.

Helical sweeps for threads and springs

Build a helix as the path, sweep a circle along it:

typescript
import { sketchCircle, helix, sweep, unwrap } from 'brepjs/quick';

const thread = sketchCircle(0.5).face(); // thread cross-section
const path = helix({ pitch: 1.5, height: 30, radius: 5 });

const screw = unwrap(sweep(thread, path));
void screw;

helix({ pitch, height, radius }) returns a wire of the helical curve.

Frenet vs auxiliary frame

When the path bends, the kernel has to decide how the profile orients itself. Two modes:

  • 'frenet' (default) — the profile rotates with the path's tangent and normal
  • 'auxiliary' — the profile's normal stays aligned with a fixed reference axis

Most parts work with the default. Specify { frame: 'auxiliary' } when you need the profile to keep a constant up-direction (think extruded handrails).

Loft — interpolation between profiles

A solid built by smoothly interpolating between two or more profiles:

typescript
import { sketchCircle, sketchRectangle, loft, unwrap } from 'brepjs/quick';

const bottom = sketchCircle(20); // round base
const top = sketchRectangle(30, 30).translate([0, 0, 50]); // square top

const bowl = unwrap(loft([bottom, top]));
void bowl;

The loft passes through every input profile in order. Profiles must be on parallel planes (or roughly so — non-coplanar lofting works but can produce twisted results).

Multi-section lofts

typescript
import { sketchCircle, loft, unwrap } from 'brepjs/quick';

const sections = [
  sketchCircle(10),
  sketchCircle(20).translate([0, 0, 30]),
  sketchCircle(5).translate([0, 0, 60]),
];

const vase = unwrap(loft(sections));
void vase;

Three or more sections produces a smooth blend through every one. Useful for vases, ducts, ergonomic handles.

Ruled vs smooth

loft([a, b], { ruled: true }) builds a ruled surface (straight lines between corresponding points on the profiles). The default produces a smooth (B-spline) blend. Ruled is faster but visually angular — common in sheet-metal modelling.

Hollowing: shell

After building a solid, hollow it out by removing one or more faces and offsetting the rest by a wall thickness:

typescript
import { sketchCircle, faceFinder, shell, unwrap } from 'brepjs/quick';

const closed = sketchCircle(20).extrude(40);
const topFaces = faceFinder().inDirection('Z').withZ({ min: 39 }).findAll(closed);
const cup = unwrap(shell(closed, topFaces, 2)); // 2mm wall, top open
void cup;

shell(solid, openFaces, thickness). The top face becomes the open mouth; everything else gains the wall thickness inward.

The fluent equivalent:

typescript
import { shape, sketchCircle } from 'brepjs/quick';

const cup = shape(sketchCircle(20).extrude(40)).shell(
  (f) => f.inDirection('Z').withZ({ min: 39 }),
  2
).val;
void cup;

Common patterns

Threaded fastener

typescript
import { sketchCircle, helix, sweep, sketchRoundedRectangle, fuse, unwrap } from 'brepjs/quick';

const shaft = sketchCircle(3).extrude(20);
const threadProfile = sketchCircle(0.5).face();
const threadPath = helix({ pitch: 1.5, height: 18, radius: 3 });
const thread = unwrap(sweep(threadProfile, threadPath));
const head = sketchRoundedRectangle(8, 8, 0.5).extrude(3).translate([0, 0, 20]);

const screw = unwrap(fuse(unwrap(fuse(shaft, thread)), head));
void screw;

Funnel (loft + revolve combined)

A funnel is two truncated cones — but easier as a revolved profile:

typescript
import { Sketcher } from 'brepjs/quick';

const funnel = new Sketcher('XZ')
  .movePointerTo([15, 0])
  .lineTo([15, 1])
  .lineTo([3, 30])
  .lineTo([3, 50])
  .lineTo([2, 50])
  .lineTo([2, 30])
  .lineTo([14, 1])
  .lineTo([14, 0])
  .close()
  .revolve();
void funnel;

A profile, two lineTos, a revolve. Simpler than building two cones and fusing.

When operations fail

FailureCause
EXTRUDE_INVALID_FACEThe face isn't OrientedFace — usually means the wire isn't closed
REVOLVE_AXIS_CROSSES_PROFILEThe axis passes through the profile (would self-intersect)
SWEEP_PATH_INVALIDPath has self-intersections, sharp corners, or non-tangent meeting edges
LOFT_PROFILE_MISMATCHProfiles have very different topologies (e.g. one closed, one open)
SHELL_TOO_THICKWall thickness larger than the local geometry can support

Error Codes covers each in detail.

Next steps

Released under the Apache 2.0 License.