A Smith model records operations. See CAD concepts for the geometry vocabulary. Every modeling operation returns a new recipe, so feature functions can accept a body, add a feature, and return the next body. Smith.evaluate/1 executes the ordered operations and returns a native shape.
Primitives and placement
Boxes, cylinders, and cones accept the same placement options:
at: {x, y, z}anchors the primitive in world coordinates; default{0, 0, 0}.align: {x, y, z}selects:min,:center, or:maxindependently for each axis. The selected point of the primitive's bounds lands atat:.
| Primitive | Default alignment | Meaning of dimensions |
|---|---|---|
Smith.box(width, depth, height, opts) | {:min, :min, :min} | Positive X, Y, Z extents |
Smith.cylinder(radius, height, opts) | {:center, :center, :min} | Radius and positive Z height |
Smith.cone(bottom_radius, top_radius, height, opts) | {:center, :center, :min} | Bottom/top radii and positive Z height |
All dimensions are millimeters. Dimensions must exceed the native length tolerance of 1.0e-7 mm; a cone may have one zero end radius.
Alignment uses bounds before any subsequent rotations. A cone's X/Y bounds include
the larger radius, and its centered Z is halfway up its height, regardless of its
center of mass. Unsupported options, malformed points, and invalid alignment
return a Smith.Error with reason :invalid_options during evaluation.
Invalid dimensions retain the native :invalid_argument error.
boss = Smith.cylinder(5, 8, at: {15, 0, 4})
plate = Smith.box(40, 20, 4, align: {:center, :center, :min})
model = plate |> Smith.fuse(boss)Interactive preview available in HexDocs.
To describe a centered footprint between two elevations, keep the height explicit:
bottom = 6
top = 14
body = Smith.box(30, 20, top - bottom,
at: {12, -8, bottom}, align: {:center, :center, :min})Interactive preview available in HexDocs.
Use sketches for local 2D outlines and arbitrary planes; their at: uses two local coordinates, while
solid primitive at: uses three world coordinates.
Smith.rotate(model, axis, degrees, origin) uses a world axis vector and optional world origin (default zero). Rotation is right-handed. Transform order matters: translating then rotating moves the translated position around the rotation axis. Use Smith.Plane and local sketches for side-mounted profiles rather than manually rotating every point.
Spheres and rings
Smith.sphere(radius) and Smith.torus(major_radius, minor_radius) default to centered bounds on all three axes. The torus lies around world Z; its major radius reaches the tube center, and its minor radius is the tube radius. Both support the standard world at: and three-axis align: options. Rotate the result for a different orientation. Tori must have major radius greater than minor radius by more than 1.0e-7 mm.
ring = Smith.torus(10, 2, align: {:center, :center, :min})
ball = Smith.sphere(3, at: {0, 0, 3})Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
Boolean composition
Write ordinary Smith.cut/2 and Smith.fuse/2 recipes, including lists of tools.
The evaluator automatically groups compatible consecutive operations to reduce
repeated intersection and cleanup work. Tools containing selectors or callbacks,
retained results, and operations that inspect or transform the current body
remain evaluation boundaries. Failed groups are retried in recipe order so
errors identify the original failing step. Older OCEx versions use sequential
operations automatically.
Hole features also avoid repeatedly measuring the whole part. Through-holes
reuse an enclosing box within a consecutive run, while on: :top placement
still follows the current face centroid after each cut. Runs of explicit-plane
holes are batched when their complete cutter envelopes are disjoint. Overlapping
cutters and body-dependent placement keep sequential behavior, including the
checks that pilots and recesses remove material. No optimization options or
recipe rewrites are needed.
Smith.cut_many/2 and Smith.fuse_many/2 explicitly request a single native
batch, including tools that automatic planning treats as boundaries. These
advanced operations require OCEx batch support and can report different
failure steps. Ordinary recipes do not need them for automatic optimization.
Repeated, self-contained subrecipes are automatically evaluated once within an
evaluation, including parts that differ only by translation, rotation or mirror.
Native reuse is bounded and ends when evaluation returns or raises. Recipes containing
callbacks or retained snapshots are not memoized; nested evaluations have their
own scope. Smith.from_result/1 remains useful for explicitly retaining geometry
across separate evaluations, with revision checks intact.
The evaluator also remembers expensive pure prefixes across edits in a bounded snapshot cache: at most 128 entries, 32 MiB of serialized keys and geometry, 4 MiB per geometry snapshot, expiring after two minutes without use. Hits restore independent, validated shapes; the cache holds no native resources. Changed operations invalidate their suffix, and callbacks, external assets, retained results and unknown operations stop prefix caching. Cache misses, expiry or unavailability simply evaluate the ordinary recipe. Consecutive translations, rotations and mirrors are automatically composed into one native transform.
A Boolean combines the material occupied by shapes. A boss is a raised pad, often used around a fastener; a bore is a cylindrical opening.
fuse/2 adds material, cut/2 subtracts a tool, and common/2 retains the intersection. Fuse and cut also accept ordered lists. An empty list leaves the recipe alone. The evaluator resolves tool recipes and cleans same-domain topology at evaluation boundaries.
bores = for x <- [-12, 12], do: Smith.cylinder(2, 6, at: {x, 0, -1})
model = plate |> Smith.cut(bores)Interactive preview available in HexDocs.
Extend cutting tools past the surface when the intended feature is through-all; this avoids relying on coincident faces. Alternatively use Smith.hole/2 with through: :all, which sizes its cutter across the complete body.
A missed Boolean tool can be a no-op; a missed hole/2 fails explicitly. Disjoint unions can contain multiple solids. Smith.compound/1 groups shapes without fusing and preserves separate boundaries. For separately named and printable parts, use assemblies.
Fillets and chamfers
model =
Smith.box(60, 40, 5)
|> Smith.fillet(edges: {:parallel, :z}, radius: 2, count: 4)
|> Smith.hole(on: :top, diameter: 8, through: :all)Interactive preview available in HexDocs.
Fillet/chamfer selectors run against the body at that evaluation step. {:parallel, axis} selects straight edges parallel to the world axis; curved edges are not included. edges: :all selects all edges. A predicate receives native edge information together with its world bounds and a point at the middle of its parameter interval. For a spline, that point need not divide its length in half:
round_vertical = fn edge ->
edge.type == :line and abs(elem(edge.direction, 2)) > 0.99
end
model = Smith.box(20, 10, 4) |> Smith.fillet(edges: round_vertical, radius: 1, count: 4)Interactive preview available in HexDocs.
For reusable selections, compose Smith.Selector filters such as Selector.type(:line) |> Selector.parallel(:z). The same selector machinery supports face queries and shell openings. Smith.edges(result, selector) and Smith.faces(result, selector) inspect evaluated geometry.
Use count: when the design expects a particular number of edges. Unexpected counts fail rather than rounding unintended geometry. Avoid relying on enumeration order; topology can change after booleans and cleanup. Predicates must return booleans, and exceptions in your own callbacks propagate.
The order of finishing operations affects the result. Filleting an outside edge before drilling can differ from filleting all edges after drilling. Use named functions to make that order clear.
Hole placement
on: :top selects the highest outward +Z planar face. Its default hole
position is the area centroid of that face, which can move after a cut.
Two successive top holes with the same offset need not share the same XY
position. If the design specifies fixed hole coordinates, use a plane:
model =
Smith.box(60, 40, 5)
|> Smith.hole(on: Smith.Plane.xy(), at: {10, 20}, diameter: 4, through: :all)
|> Smith.hole(on: Smith.Plane.xy(), at: {50, 20}, diameter: 4, through: :all)Interactive preview available in HexDocs.
The plane supplies a frame and drill direction; it does not need to coincide
with a face. The cutter spans the body's projected bounds. If several top
faces share the highest elevation, on: :top fails with
:ambiguous_top_face instead of choosing one.
Curves and solid generation
Smith.line/2, arc/6, and spline/2 describe world-coordinate edges. Smith.profile/1 joins ordered edges into a closed planar face. Smith.polygon/1 creates a planar face from world-coordinate points. Extrude a face recipe with a world vector.
Prefer Smith.Sketch for local 2D outlines. Its signed scalar extrusion, full/partial revolve, and loft are covered in Sketches and planes. See paths, lofts, and shells for open paths, smooth interpolation, sweeps, and hollowing.
Evaluation and branching
base = Smith.box(20, 10, 4)
rounded = base |> Smith.fillet(edges: {:parallel, :z}, radius: 1)
drilled = base |> Smith.hole(on: :top, diameter: 3, through: :all)Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
All three recipes remain independent. For a costly stage, evaluate once and branch
from Smith.from_result(result) to avoid rebuilding it for each variant:
{:ok, evaluated_base} = Smith.evaluate(base)
snapshot = Smith.from_result(evaluated_base)
rounded_snapshot = snapshot |> Smith.fillet(edges: {:parallel, :z}, radius: 1, count: 4)Interactive preview available in HexDocs.
A snapshot contains the geometry from one evaluation. Reevaluating the source
produces a new snapshot; it does not change the old one. Keep the Elixir recipe
for editing. The evaluated revision is a SHA-256 hash of serialized BREP, used
to identify exports and detect stale results. The hash can differ across kernel
versions or platforms.
For measured filters, sorting, topology metadata, mirrored parts, blind holes, and recessed fasteners, see mechanical parts.