Documentation
CAD geometry
Drawing entities, constrained sketches, parametric histories, meshes, and exact solids in a shared geometry core.
Drawing entities
@cadcamfun/core exports CAD functions both directly and under the cad namespace. Schema-first 2D entities use an _tag discriminator: Line, Circle, Arc, Polyline, Rectangle, Ellipse, Spline, Text, Dimension, Hatch, and Block. Each entity has a stable identifier, layer, visibility, and lock state; selection belongs to editor state.
Entity geometry uses millimetres and radians. Arc angles are normalized to a full turn, and equal start/end angles represent a full circle. A local entity transform applies scale, rotation, then translation; Affine2D supports composed transforms that can include shear. The scene-element and parametric-history adapters have their own documented angular fields, including degree-valued placement and feature controls.
entitySchemaOf(tag) returns the schema for one entity kind. Decode imported data before calling geometry functions. Bounds, tessellation, intersections, offsets, and snapping are separate operations; rendering a tessellated curve does not replace its analytic entity.
Constrained sketches
The sketch model contains named points, lines between points, circles with a center and radius, and counter-clockwise arcs. Shared point identifiers express shared endpoints. Constraints include coincidence, horizontal and vertical alignment, distances, lengths, parallel and perpendicular lines, equal lengths and radii, tangency, symmetry, and geometric incidence.
The numerical solver minimizes constraint residuals with a Levenberg–Marquardt iteration and a central-difference Jacobian. It reports degrees of freedom, free points, redundant equations, and conflicting constraints. A result can be fully constrained, under-constrained, over-constrained, conflicting, or failed. An under-constrained solution can satisfy every dimension while still allowing motion; callers must preserve that distinction.
Sketch constraints receive numbers. Parametric histories resolve parameter expressions before invoking the solver. Sketch angle constraints are radians; use the feature's own input convention when converting a user-entered dimension.
Parametric histories
A parametric part stores its parameters and ordered feature history. Rebuilding evaluates expressions and features in sequence so a feature can reference sketches, planes, bodies, or tools created earlier. The core includes extrusions, revolutions, lofts and sweeps, booleans, holes, patterns, face edits, text, threads, sheet-metal features, and mating controls. Availability of an exact representation depends on the particular feature sequence.
rebuild produces mesh bodies and feature issues. rebuildExact replays the history through the boundary-representation kernel and records why a body lacks an exact representation. Keep the history as the editable source; generated bodies are derived output. A failed or unsupported feature must remain visible through its issue record.
Read the replay result
Mesh output can exist when exact replay cannot represent a feature. Check the body and issue records before making claims about exact geometry, volume, or export fidelity.
Mesh geometry and placement
Meshes use indexed triangle geometry. The CAD layer provides primitive construction, mesh booleans, sweep and loft helpers, topology and normal utilities, and geometry cleanup. The CAM model adapter converts supported scene elements to meshes, including nested components, so analysis and toolpath generation can use the same placed geometry.
Element position, rotation, scale, and translation affect the resulting part. Exact scene placement mirrors the viewport transform about the bounding-box center. Non-uniform scaling can remove an element's exact-model path; scene conversion reports a reason and may use a faceted representation during export.
Exact boundary representation
cad.brep exposes analytic and NURBS geometry with face, loop, edge-use, edge, and vertex topology. Its modules cover primitives, sweeps, lofts, booleans, face edits, blends, sewing, tessellation, classification, validation, mass properties, and STEP reading and writing. history-brep.ts and scene-step.ts connect this kernel to parametric parts and placed scene elements.
POST /api/cad/exact/analyze returns per-part validity issues, volume, area, centroid, bounds, topology counts, surface counts, recognized holes, and machining classification. It also returns skipped elements with reasons and aggregate totals. A prismatic machining result reports outlines, holes, pockets, and top/bottom levels; unsupported parts carry a reason.
POST /api/cad/exact/assembly reports interference, expected overlaps, touching and undetermined pairs, bill-of-material items, joints, and fastener plans. Undetermined geometry is a reported outcome, not proof of clearance. These analysis routes compute results without altering the drawing.
Import and export
The core has DXF and drawing-import helpers, a prismatic STEP interpretation path, and an exact STEP B-rep reader. The prismatic path reads a footprint and extrusion height for suitable plates, brackets, and flanges; it reports features that a prism cannot describe. Choose the exact reader when exact supported STEP topology is needed, and inspect diagnostics from either path.
sceneToStep writes a scene as STEP AP214. Exact supported solids retain exact geometry; mesh-only solids can be emitted as faceted solids. Non-solid elements are skipped with reasons. The faceted option controls that fallback. A response without step contains no exportable solid.
The HTTP exact routes accept either elements or drawingId; explicit elements take precedence. Snapshot arrays allow 1–2,000 elements and an optional selection of up to 200 identifiers. With a stored drawing, the sidecar reads the scene under drawing access rules. A missing source, empty drawing, unknown drawing, or denied drawing produces the corresponding request or access error.
Technical drawings
The drawing namespace provides projections, sections, dimensions, sheet composition, and SVG output from 3D models. This is a separate representation from the editable 2D entity drawing and from a machining program. Projection and section geometry should be regenerated after model changes; a sheet export captures the model state used to build it.
Where to extend geometry
Start with packages/core/src/cad/index.ts for the public CAD surface. Entity operations live beside entities.ts; constraint solving is in sketch.ts; mesh history is in history.ts; exact replay is in history-brep.ts; exact topology and exchange are under brep/. packages/core/src/drawing/ owns sheet output. Supporting modules in the same folder include expr.ts (parameter expressions such as width / 2 + 3, resolved in dependency order with cycles reported as errors), tolerance.ts (chord-sagitta segment counts for curves), gear.ts (ISO 53 involute spur-gear outlines), thread.ts, sheet-metal.ts, fasteners.ts, recipes.ts (generators that emit complete, fully constrained feature histories), dfm.ts (design-for-manufacture findings with measured and required values), and dxf.ts.
Geometry functions operate on values without opening application storage. Sidecar services handle access and persistence, and desktop adapters handle interaction and rendering. Keep conversions explicit at the boundaries so a display mesh, an exact solid, and a saved history retain their distinct meaning.
Build, validate, and export a solid
This complete core example constructs a 40 × 25 × 8 mm box with one corner at the world origin, checks its topology, measures it, and exports STEP text. It requires only the workspace core package and performs no application writes.
import { cad } from "@cadcamfun/core"
const solid = cad.brep.makeBox(40, 25, 8)
const report = cad.brep.validateBrep(solid)
if (!report.valid) throw new Error(JSON.stringify(report.issues))
const mass = cad.brep.massProperties(solid)
console.log(mass.volume.toFixed(3)) // "8000.000" cubic millimetres
console.log(mass.centroid) // approximately { x: 20, y: 12.5, z: 4 }
const step = cad.brep.writeStepBreps([{ name: "Bracket blank", brep: solid }], {
author: "Workshop",
timestamp: "2026-01-01T00:00:00.000Z"
})
console.log(step.startsWith("ISO-10303-21;")) // true The explicit timestamp makes repeated exchange output reproducible. Without it, STEP writing uses the current time. massProperties also returns area, per-face areas, and an inertia tensor about the centroid at unit density; volume is mm³ and area is mm². Convert density explicitly when calculating a physical mass.
Source: packages/core/src/cad/brep/primitives.ts, mass.ts, validate.ts, and step.ts.
Validation and conversion options
validateBrep returns valid, issue records, manifoldSolid, and optional genus/volume. manifoldSolid means closed shells with every edge used twice in opposite senses. The default geometric distance tolerance scales as 10⁻⁵ of model size with a 10⁻⁶ mm floor. A caller can supply tol, skip volume/orientation checking with skipVolume, or use a coarse tessellation for orientation with coarseVolume.
The exact-analysis API's machining flag defaults to true. STEP scene export's faceted fallback defaults to true, and assembly fastener planning's washers option defaults to true. Selection IDs refer to scene elements, including component children resolved by the scene traversal. These HTTP options affect analysis/export, not the stored drawing.
Scene export returns per-part exact, faces, valid, and an optional note. Check those fields alongside skipped: a valid faceted solid can be exportable without retaining analytic surfaces.
Source: packages/core/src/cad/brep/validate.ts, scene-analysis.ts, scene-step.ts, assembly-analysis.ts, and packages/api/src/cad-exact.ts.
Repair a failed rebuild or export
| Finding | Next inspection |
|---|---|
| Sketch remains under-constrained | Read degrees of freedom and free-point IDs. Add the missing position/orientation constraints; a dimensioned line with only one fixed endpoint can still rotate. |
| Sketch constraints conflict | Use named conflict IDs and the residual to locate incompatible dimensions. Repeating a compatible equation is redundancy; two different required lengths are a conflict. |
| History feature references an unknown body or plane | Check feature order and identifiers. Dependencies must exist earlier in the replay, and suppression/deletion can invalidate later references. |
| Exact model unavailable after scaling | Inspect non-uniform scale and the element's reported reason. Use supported uniform placement or accept and label a faceted export. |
| STEP result has skipped items or no text | Check whether the selection contains solids and whether faceted fallback is enabled. Empty or 2D-only selections cannot produce a solid STEP file. |
Primitive dimensions must be positive and finite. Direct geometry functions can throw range errors for invalid dimensions; transport and service layers must convert those failures into their own typed results. Rebuild issues are keyed to feature IDs so a failure can be repaired at its source rather than concealed by retaining an earlier mesh.
Source: packages/core/src/cad/sketch.test.ts, history.ts, scene-step.ts, and apps/sidecar/test/cad-exact.test.ts.
Solve a sketch step by step
A sketch is plain data: an array of entities (points, lines, circles, arcs) and an array of constraints. Lines, circles, and arcs refer to points by identifier, so two lines that share an endpoint share the same point ID. The solver moves only point coordinates and circle radii; it never adds or removes entities.
fully-constrained with dof = 0.import { cad } from "@cadcamfun/core"
const sketch = {
entities: [
{ id: "p1", kind: "point", x: 0, y: 0 },
{ id: "p2", kind: "point", x: 38, y: 1 }, // rough positions are fine:
{ id: "p3", kind: "point", x: 39, y: 26 }, // the solver moves them
{ id: "p4", kind: "point", x: 1, y: 24 },
{ id: "l1", kind: "line", p1: "p1", p2: "p2" },
{ id: "l2", kind: "line", p1: "p2", p2: "p3" },
{ id: "l3", kind: "line", p1: "p3", p2: "p4" },
{ id: "l4", kind: "line", p1: "p4", p2: "p1" },
],
constraints: [
{ type: "fixed", point: "p1", x: 0, y: 0 },
{ type: "horizontal", line: "l1" },
{ type: "horizontal", line: "l3" },
{ type: "vertical", line: "l2" },
{ type: "vertical", line: "l4" },
{ id: "width", type: "length", line: "l1", value: 40 },
{ id: "depth", type: "length", line: "l2", value: 25 },
],
}
const result = cad.solveSketch(sketch)
result.status // "fully-constrained"
result.dof // 0
result.iterations // 3
result.residual // < 1e-9
// points now at (0,0) (40,0) (40,25) (0,25) Run the same sketch without the fixed constraint and the result changes to under-constrained with dof = 2 and free = ["p1", "p2", "p3", "p4"]. The rectangle still has the right size, but it can slide in X and Y. Add a third length, { id: "w2", type: "length", line: "l3", value: 50 }, and the status becomes conflicting. conflicts then lists the named constraints that cannot be satisfied together, worst first (for example w2 and width). Name the constraints you care about with id; unnamed constraints are reported with a generated label such as vertical#4.
| Option | Default | Meaning |
|---|---|---|
maxIterations | 200 | Upper bound on Levenberg–Marquardt iterations before the solve is reported as failed. |
tolerance | 1e-9 | The largest absolute residual accepted as solved. |
| Result field | Content |
|---|---|
sketch | The input sketch with solved coordinates and radii. |
status | fully-constrained, under-constrained, over-constrained, conflicting, or failed. |
dof | Remaining degrees of freedom. |
free | Point IDs that can still move, and circle IDs whose radius can still change. |
conflicts | Constraint IDs that could not be satisfied, worst first. |
redundant | Number of redundant equations (rank deficiency). |
iterations, residual, message | Solver diagnostics. |
Malformed input throws
A sketch that refers to an unknown point or entity is malformed rather than unsolvable, and solveSketch throws a SketchError. Validate imported sketches before solving them in a loop.
Constraint reference
Every constraint is an object with a type and an optional id. Lengths are millimetres and angles are radians. In a parametric history, any value may also be a parameter expression string.
| Type | Fields | Effect |
|---|---|---|
fixed | point, optional x, y | Pins a point, optionally to explicit coordinates. |
coincident | a, b | Two points occupy the same position. |
horizontal / vertical | line | A line is parallel to X / Y. |
horizontalPoints / verticalPoints | a, b | Two points share a Y / X coordinate. |
distance | a, b, value | Euclidean distance between two points. |
distanceX / distanceY | a, b, value | Signed horizontal / vertical offset. |
length | line, value | Length of a line. |
parallel / perpendicular | a, b | Relationship between two lines. |
equalLength / equalRadius | a, b | Equal line lengths / circle or arc radii. |
radius / diameter | circle, value | Size of a circle or arc. |
angle | a, b, value | Counter-clockwise angle from line a to line b, in radians. |
pointOnLine / pointOnCircle | point and line / circle | Incidence. |
midpoint | point, line | A point sits at the middle of a line. |
symmetric | a, b, line | Two points mirror each other about a line. |
tangentLineCircle | line, circle | A line touches a circle or arc. |
tangentCircles | a, b, optional internal | Circles touch externally (distance = r1 + r2) or internally (distance = |r1 − r2|). |
concentric | a, b | Shared centre of circles or arcs. |
Arcs are counter-clockwise from start to end about center. A circle's radius is a solver variable, so a circle without a radius, diameter, equalRadius, or tangency constraint appears in free.
Build a parametric part
A History is a parameter table plus an ordered feature list. Parameters are numbers or expressions of other parameters. They are resolved in dependency order, and a cycle is an error that names the loop. Features refer to parameters by name in any dimension field.
import { cad } from "@cadcamfun/core"
const history = {
parameters: { width: 40, depth: 25, thick: 8, hole: "width / 8" },
features: [
// the rectangle sketch from the previous section, with dimension
// values replaced by parameter names: value: "width", value: "depth"
{ id: "s1", type: "sketch", plane: "XY", sketch: rectangleSketch },
{ id: "e1", type: "extrude", sketch: "s1", depth: "thick", operation: "new" },
{ id: "h1", type: "hole", plane: "XY", offset: "thick",
at: [20, 12.5], diameter: "hole", depth: "thick" },
],
}
const mesh = cad.rebuild(history)
mesh.parameters // { width: 40, depth: 25, thick: 8, hole: 5 }
mesh.bodies // one mesh body, id "e1"
mesh.issues // []
const exact = cad.rebuildExact(history, mesh)
exact.bodies[0].brep // present: the history has an exact representation
exact.warnings // []
cad.brep.massProperties(exact.bodies[0].brep).volume // 7842.92… mm³
cad.brep.validateBrep(exact.bodies[0].brep).valid // true rebuild returns bodies (triangle meshes keyed by the feature that created them), the resolved parameters, each sketch's plane, frame, and solve result, the flat patterns of sheet-metal features, and issues. A feature that cannot be built is reported in issues with its feature ID and severity, and it is skipped. Features that do not depend on it still build. rebuildExact(history, given?, options?) can reuse a mesh rebuild you already have, and accepts a budgetMs time budget.
Base planes are right-handed: XY has u = X, v = Y, normal +Z; YZ has u = Y, v = Z, normal +X; ZX has u = Z, v = X, normal +Y. A sketch, hole, or text can also be placed on the ID of an earlier plane feature, with an offset along the normal.
| Feature | Key fields and defaults |
|---|---|
sketch | plane, optional offset, sketch (entities, constraints, optional parametric shapes: rectangle, circle, polygon, ellipse, slot, polyline). |
extrude | sketch, depth, direction up/down/symmetric, operation new/union/subtract/intersect, optional targets (default: every body so far), edge chamfer/fillet of end faces (ends default both), corner treatment, and taper draft in degrees. Loops inside loops are holes (even-odd). |
revolve | sketch, axis (x, y, or a sketch line), angle in degrees (default 360), operation. |
hole | plane, offset, at [u, v], diameter, depth; kind simple (default), counterbore, or countersink with headDiameter, headDepth, and angle (default 90°). Optional cosmetic thread note (for example M4), tolerance, and ISO 286 fit such as H7. A cosmetic thread does not change the geometry. |
pattern, mirror | Copies of an earlier feature's tool, applied the same way. |
sweep, loft, coil | Profiles along a path, between sections, or along a helix. |
shell, fillet, draft, pushpull | Face and edge edits. |
move, scale, split, combine | Body operations. |
rib, sheet, thread, text | Ribs, sheet-metal bodies with flat patterns, modelled threads, and stroke-font text. |
plane, primitive, mate | Construction planes, primitive solids, and assembly mating. |
Edit the history, not the body
Change a parameter and rebuild: every sketch that uses it re-solves and every solid and Boolean after it is redone. Editing a generated mesh directly is lost at the next rebuild.
Exact primitives and placement
The B-rep kernel builds analytic solids directly. Each constructor takes an optional placement Frame; without one, the solid is placed in the world frame.
| Constructor | Arguments | World placement |
|---|---|---|
makeBox(dx, dy, dz, placement?) | Edge lengths in mm | Minimum corner at the origin. |
makeCylinder(radius, height, placement?) | Radius and height | A cone with equal radii; axis along Z. |
makeCone(r1, r2, height, placement?) | Base and top radii | Axis along Z. |
makeSphere(radius, placement?) | Radius | Centred on the origin. |
makeTorus(major, minor, placement?) | Ring and tube radii | Axis along Z. |
Dimensions must be positive and finite; otherwise the constructors throw a range error. Run validateBrep on anything you build from Booleans, sweeps, or imports before trusting volume or export results. A primitive straight from a constructor is valid by construction.
Modelling practices that rebuild reliably
- Fix one point and constrain orientation before adding dimensions. An under-constrained sketch can satisfy every dimension and still move at the next rebuild.
- Give every user-facing dimension an
idso a conflict report names something a person recognises. - Prefer parameters and expressions (
"width / 2 + 3","max(a, b) * 2") over repeated literals; one edit then drives the whole part. - Reference earlier features by ID only after they exist in the list. Reordering a history can turn a valid reference into an issue.
- Use
targetson Booleans when a part has several bodies. The default applies the operation to every body built so far. - Check
issuesafter everyrebuildand the per-bodybreppresence afterrebuildExactbefore relying on exact mass properties or STEP export. - Keep units in millimetres and radians inside sketches. Degree-valued fields are documented as such (revolve angle, taper, countersink angle).
Design recipes
cad.buildRecipe(name, input) turns a few engineering inputs into a full parametric history, plus a one-line summary, the derived numbers and the design rules that produced them. Seven recipes exist; parameters without a default are derived from the others when omitted.
| Name | Title | Parameters (defaults) |
|---|---|---|
mounting_plate | Mounting plate | width=100, depth=60, thickness=10, fastener=M8, fit=medium, counterbore=false, cornerRadius=0, edgeChamfer=0, edgeDistance |
flange | Bolted flange | outerDiameter=120, boreDiameter=40, thickness=14, boltCount=6, fastener=M10, fit=medium, boltCircleDiameter, hubDiameter=0, hubHeight=0, edgeChamfer=0 |
l_bracket | L bracket | baseLength=60, legHeight=50, thickness=5, width=40, fastener=M6, fit=medium, holesPerLeg=1, cornerRadius=0 |
stepped_shaft | Stepped shaft | sections, chamfer=1, keywaySection=-1, keyLength |
pipe_bend | Pipe elbow | outerDiameter=30, wall=3, legA=100, legB=100, bendAngle=90, bendRadius |
duct_transition | Duct transition | shape=rect, width1=80, height1=50, width2=40, height2=40, length=120, wall=2 |
enclosure | Enclosure | length=120, width=80, height=40, wall=3, floor, cornerRadius=6, fastener=M3, bosses=true |
import { cad } from "@cadcamfun/core"
const recipe = cad.buildRecipe("mounting_plate", { width: 100, depth: 60, thickness: 10, fastener: "M8" })
recipe.summary // "100 x 60 x 10 mm plate with four holes for M8 screws (Ø9 mm, medium fit)"
recipe.derived // { holeDiameter: 9, edgeDistance: 18, holeSpacingX: 64, holeSpacingY: 24 }
recipe.rules // ["hole diameter from ISO 273",
// "centre to edge >= 1.5 x hole diameter",
// "centre to centre >= 2.5 x hole diameter"]
recipe.history.parameters // { width: 100, depth: 60, thick: 10, hole: 9, inset: 18 }
// features: sketch "outline", extrude "plate", holes hole_sw, hole_se, hole_ne, hole_nw
// placed at ["inset","inset"], ["width - inset","inset"], …
const mesh = cad.rebuild(recipe.history) // issues: []
const exact = cad.rebuildExact(recipe.history, mesh)
cad.brep.massProperties(exact.bodies[0].brep).volume // 57455.31 mm³ = 60000 − 4·π·4.5²·10 The history is ordinary: you can open it in the editor, change a parameter and rebuild. The recipe only chose sensible numbers.
Involute gears
const spec = { moduleValue: 2, teeth: 18 } // pressureAngle defaults to 20°
cad.gearDimensions(spec)
// { pitchRadius: 18, baseRadius: 16.914, tipRadius: 20, rootRadius: 15.5 }
const outline = cad.gearOutline(spec, 6) // 324 points, 6 per flank
cad.gearOutline({ moduleValue: 2, teeth: 5 })
// throws: 'A gear needs a module > 0 and at least 6 whole teeth' | Dimension | Formula | m = 2, z = 18 |
|---|---|---|
| Pitch radius | m·z / 2 | 18 |
| Tip radius | pitch + m | 20 |
| Root radius | pitch − 1.25·m | 15.5 |
| Base radius | pitch · cos α | 16.914 |
MIN_GEAR_TEETH is 6: below that, the flanks undercut the base circle and neighbouring teeth overlap.
Fastener and fit data
| Size | Pitch | Tap drill | Clearance fine / medium / coarse | Head Ø × h | Counterbore Ø × depth | Countersink Ø |
|---|---|---|---|---|---|---|
| M3 | 0.5 | 2.5 | 3.2 / 3.4 / 3.6 | 5.5 × 3 | 6.5 × 3.4 | 6 |
| M5 | 0.8 | 4.2 | 5.3 / 5.5 / 5.8 | 8.5 × 5 | 10 × 5.7 | 10 |
| M6 | — | 5 | 6.4 / 6.6 / 7 | — | — | — |
| M8 | 1.25 | 6.8 | 8.4 / 9 / 10 | 13 × 8 | 15 × 9 | 16 |
| M12 | 1.75 | 10.2 | 13 / 13.5 / 14.5 | 18 × 12 | 20 × 13 | 24 |
Values come from cad.metricThread(size), clearanceHole(size, fit) and tapDrill(size); the M6 row was queried through the two helpers only. Related rules, with real results for an M6 screw and its Ø6.6 clearance hole:
| Helper | Result |
|---|---|
threadEngagement("M6", material) | steel 6, aluminium 9, cast 12, plastic 12 mm |
tappedHoleDepth("M6", 12) | 15.8 mm |
minEdgeDistance(6.6) / (6.6, "plastic") | 9.9 / 13.2 mm |
minHoleSpacing(6.6) | 16.5 mm (2.5 × diameter) |
keywayFor(25) | over 22 up to 30: b 8, h 7, t1 4, t2 3.3 |
toleranceGrade(7, 25) / (6, 25) | 21 / 13 µm |
| Fit | Kind | Use case |
|---|---|---|
| H7/h6 | location (sliding) | parts assembled by hand and located precisely, sliding spigots, guide bushings |
| H7/g6 | close running | precise guiding of moving parts, spindles, slide valves |
| H7/f7 | running | plain bearings, shafts in general machine building |
| H7/k6 | transition | gears, pulleys and bearing inner rings driven on with a light press |
| H7/n6 | interference (light press) | bushings and hubs pressed in; no key needed for small torque |
| H7/p6 | interference (press) | permanent press fit of bushings in housings |