Coming from Replicad
Replicad and brepjs are siblings: both wrap OpenCascade WASM and target code-first CAD in JavaScript. If you have working Replicad code, switching to brepjs is mostly a search-and-replace plus a couple of pattern shifts. This chapter is the cheat sheet.
What's the same
- The kernel is OpenCascade in both libraries — same precision, same tolerances, same booleans, same fillets.
- 2D drawing → sketch → extrude is the canonical shape-building flow.
- STEP / STL / glTF export.
- Web-first, ESM-first, browser-friendly.
What's different
- Branded types and validity types. brepjs distinguishes
Edge,Wire,Faceat the type level, and stampsClosedWire,OrientedFace,ValidSolidon shapes proven to satisfy invariants. Replicad treats most shapes as a singleShapetype. Result<T, BrepError>for fallible operations. brepjs returnsResultfrom booleans, fillets, imports, exports — Replicad throws.- Two API styles. Functional (
fuse(a, b),fillet(s, edges, r)) is the canonical surface; the fluentshape()wrapper provides a chainable view that matches Replicad's.fuse().fillet()style. - Pluggable kernel. brepjs supports OpenCascade and brepkit (Rust-based) behind one API; Replicad is OpenCascade-only.
Function-by-function map
Primitives
| Replicad | brepjs |
|---|---|
makeBox(w, d, h) | box(w, d, h) |
makeCylinder(r, h) | cylinder(r, h) |
makeSphere(r) | sphere(r) |
makeCone(r1, r2, h) | cone(r1, r2, h) |
makeBaseBox(w, d, h) | box(w, d, h, { centered: true }) |
2D drawing
| Replicad | brepjs |
|---|---|
drawCircle(r) | drawCircle(r) |
drawRectangle(w, h) | drawRectangle(w, h) |
drawRoundedRectangle(w, h, r) | drawRoundedRectangle(w, h, r) |
drawing.cut(other) | drawingCut(a, b) |
drawing.fuse(other) | drawingFuse(a, b) |
drawing.fillet(r) | drawingFillet(d, r) |
Sketching
| Replicad | brepjs |
|---|---|
sketchCircle(r) | sketchCircle(r) |
sketchRectangle(w, h) | sketchRectangle(w, h) |
new Sketcher('XY').movePointerTo(...) | new Sketcher('XY').movePointerTo(...) |
sketcher.lineTo([x, y]) | sketcher.lineTo([x, y]) |
sketcher.tangentArcTo([x, y]) | sketcher.tangentArcTo([x, y]) |
sketcher.close() | sketcher.close() |
sketcher.extrude(h) | sketcher.extrude(h) |
drawing.sketchOnPlane('XY') | drawingToSketchOnPlane(d, 'XY') |
Booleans
| Replicad | brepjs |
|---|---|
a.fuse(b) (throws on error) | unwrap(fuse(a, b)) or shape(a).fuse(b).val |
a.cut(b) | unwrap(cut(a, b)) or shape(a).cut(b).val |
a.intersect(b) | unwrap(intersect(a, b)) or shape(a).intersect(b).val |
Refinement
| Replicad | brepjs |
|---|---|
shape.fillet(r, edgeFinder) | shape(s).fillet((e) => e.inDirection('Z'), r).val |
shape.chamfer(d, edgeFinder) | shape(s).chamfer((e) => e.inDirection('Z'), d).val |
shape.shell(t, faceFinder) | shape(s).shell((f) => f.inDirection('Z'), t).val |
Finders
| Replicad | brepjs |
|---|---|
new EdgeFinder().inDirection('Z') | edgeFinder().inDirection('Z') |
new FaceFinder().ofSurfaceType('PLANE') | faceFinder().ofSurfaceType('PLANE') |
.find(shape) | .findAll(shape) (returns array) |
Export / import
| Replicad | brepjs |
|---|---|
shape.toSTEP() (returns Promise) | unwrap(exportSTEP(s)) (returns Result<Blob>) |
shape.toSTL() | unwrap(exportSTL(s)) |
importSTEP(blob) | unwrap(await importSTEP(blob)) |
Two pattern shifts to internalize
Shift 1: Result instead of throws
Replicad:
import { makeBox, makeCylinder } from 'replicad';
try {
const part = makeBox(20, 20, 20).cut(makeCylinder(5, 25));
// …
} catch (e) {
console.error('Cut failed', e);
}brepjs:
import { box, cylinder, cut, isOk } from 'brepjs/quick';
const result = cut(box(20, 20, 20), cylinder(5, 25));
if (isOk(result)) {
console.log('Cut succeeded');
} else {
console.error('Cut failed:', result.error.code, result.error.suggestion);
}Or use the wrapper to keep the throwing style:
import { shape, box, cylinder, BrepWrapperError } from 'brepjs/quick';
try {
const part = shape(box(20, 20, 20)).cut(cylinder(5, 25)).val;
void part;
} catch (e) {
if (e instanceof BrepWrapperError) {
console.error('Cut failed:', e.code, e.suggestion);
}
}Shift 2: type guards on imported shapes
Replicad:
import { Sketcher } from 'replicad';
const wire = new Sketcher('XY').movePointerTo([0, 0]).lineTo([10, 0]).lineTo([10, 10]).close();
const face = wire.face();
const solid = face.extrude(5);brepjs:
import { Sketcher } from 'brepjs/quick';
const part = new Sketcher('XY')
.movePointerTo([0, 0])
.lineTo([10, 0])
.lineTo([10, 10])
.close()
.extrude(5);
void part;The chain is similar but each step's input type is checked at compile time. extrude requires OrientedFace, which close() guarantees by construction. If you build a wire and try to skip close(), the compiler catches it.
For shapes from outside (STEP imports, deserialized data), use the type guards:
import { isClosedWire, face, unwrap } from 'brepjs/quick';
declare const someWire: import('brepjs').Wire;
if (isClosedWire(someWire)) {
const f = unwrap(face(someWire)); // Now type-safe.
void f;
}What you'll miss (that we'll add)
- Replicad's workbench. brepjs has a similar playground. Same idea, slightly different UI.
- Some operation names.
revolution→revolve.loftis the same.pipe→sweep. - Replicad's higher-order helpers. Some niche helpers (e.g.
genericSweep) don't have direct brepjs equivalents — usually composing two operations covers the case.
When to keep Replicad
If you're not running into Replicad's pain points (silent failures, runtime topology errors), there's no urgent reason to switch. brepjs's pitch is: type safety catches a class of bugs Replicad ships to runtime. If your codebase has had several "this should not have happened" issues from boolean failures or invalid wires reaching extrude, brepjs's branded types will catch them.
Migration approach
For a moderate codebase:
- Add brepjs alongside Replicad. They can coexist — different imports.
- Pick one module (one part / one feature) and migrate it. Use the function map above.
- Embrace
Result— change error-handling sites to useisOkor the wrapper. - Add validity-type assertions at boundaries (where shapes enter/leave your code).
- Repeat for the next module.
A search-and-replace (replacing Replicad's class methods with brepjs's functional forms) gets you most of the way; the type system catches the rest.
Next steps
- Cheat Sheet — the brepjs API at a glance
- Result and Errors — handling fallible operations
- Types That Prove Geometry Is Valid — the differentiator