15. Drawing

A drawing reads a diagram as a dimensioned sheet. Its placement is layout: stack’s (SPEC 12) — every geometry child’s origin on one shared datum — and everything in this section is what a drawing adds on top: its links are annotations (dimensions, callouts, leaders) or mates that seat parts against each other, and it generates the chrome drafting always draws. One bet carries the design: because the engine has the geometry in numbers, a dimension’s smart label is its measured value — the numbers live once, in the geometry, and the annotations point at them. Drawings are the one layout that substantially extends the grammar — in the seven ways SPEC 22 enumerates; everything else is nodes, declarations, and links, and it lowers to primitives like any layout-owning engine (SPEC 11, seam 3). A drawing needs at least one geometry child (SPEC 21); its children split by role:

ChildIsDrawn
a box (|sketch|, |rect|, |oval|, |hole|, …)geometry — a part or a featureits outline and fill, at the shared datum
a link with a measuring op — (-) (o) (<) — or a leader op — <- *- >-an annotationextension lines, arrows, text (15.6, 15.7)
a link with ||a mate — or, with a sheet-content end, a seatnothing — it positions a part or an annotation (15.5)
any other two-ended link (->, <->, -->, -*, …)a straight annotation arrowone segment, markers per the op
"…", |note|, |balloon|, |table|, |surface-finish|, |feature-control|, |datum|, …sheet contentper its own type, sheet-space (15.1, 15.9)

Four properties of the model, each inherited from the core:

  • A drawing scope owns its links — the wiring strategy (SPEC 11): the router never sees them; every one lowers at layout time to dimension or leader primitives, or (for ||) to a position. routing: and along: have no role on them; clearance: reads as a dimension’s stand-off minimum (15.6).
  • No auto-create. Unlike a diagram (cat -> dog invents boxes), a drawing never invents an endpoint: an annotation must point at real geometry. An unknown endpoint is an error with suggestions (SPEC 21).
  • One placement model, whole scope. Every geometry child — and a part’s own [ ] features, recursively — places its origin on the parent’s datum, not by flow. A child that owns a layout (a \|table\|, a nested \|drawing\|, a \|row\|…) lays out its interior as usual and places as one box.
  • What you measure is a node — or a point or edge it names. Anything dimensioned, mated, or pointed at is a node with an id, or a :segment a \|sketch\| authors on its own profile (15.2, 15.3). Anonymous geometry is drawable but not addressable.

The geometry machinery is ordinary Lini, usable in any layout; only the annotation semantics need a drawing scope:

Global — works everywhereDrawing-scope only
|sketch| + draw: / mirror: / revolve: / thread: / break:; pattern:; scale:; hatch() fills; stroke-style: center / phantom; |note| / |balloon| / |hidden|; the |page| sheetthe measuring ops ((-) linear, (o) round, (<) angle), the leader ops, ||, tol:, dim side: / project:, auto-measure, unit:, datum placement, the drafting-symbol types (|surface-finish| / |feature-control| / |control| / |datum|, 15.9), the chrome (centre marks, auto centerlines, dimension packing)

Outside a drawing a \|sketch\| is just a shape; its authored :segments are declared but dormant (a routed link landing on one is deferred — SPEC 24). A floorplan is this same engine under an architectural vocabulary — 15.11, the one subsection that is a dialect rather than a mechanism.

15.1 The container, the datum & the scale

|drawing| is |block| + layout: drawing — frameless, padding 0 (the geometry and its annotations are the content). { layout: drawing } on the root makes the whole file one drawing, exactly as a root sequence works; the root’s padding then frames the sheet.

Datum & ground. The datum is the container’s own origin. Every child’s origin lands on it — not its bbox centre: a symmetric primitive’s origin is its centre, so primitives stack concentric by default; a |sketch|’s origin is its pen origin (15.3), so two sketches drawn at different pen offsets keep their drawn relationship. translate: x y offsets a child from the datum — the universal nudge, unchanged. Children paint in source order (later on top), so overlaps, punched holes, and cutaways compose with no boolean operations. The ground is the first-declared geometry child: mates resolve by walking outward from it (15.5); to reground, reorder the declarations.

Scale — three settings, one derived number. Numbers in a drawing are drawing units; three settings turn them into pixels and paper:

  • scale: — the drafting ratio, per view. Default 1; scale: 2 reads 2 : 1, scale: 0.5 reads 1 : 2, and the composed section / detail / view titles read it directly (15.8). Magnitude is scale:’s job — a 5 m beam on an A4 is scale: 0.02 (1 : 50), never a resolution fudge.
  • unit: — the physical size of one drawing unit: mm (a drawing’s default), cm, m, in, or px — one unit is one pixel, the non-physical case, and a plain stack’s default (SPEC 12). Inherits nearest-wins (state it once, on the page); read in any datum scope — a |sketch| in a flow diagram stays pixel-space (right(300) is 300 px). Displaying a unit suffix on measured values is presentation — format:’s territory (15.6).
  • density — pixels per millimetre: density: N on the root only, default 4. Non-semantic — it sets screen/raster resolution and nothing else: print stays true-scale regardless (SPEC 18), and no measured value, mate, or title reads it. Under unit: px there are no millimetres to convert, so only the identity density: 1 applies and any other value warns (SPEC 21).

The engine’s pixels-per-unit is always derived — ratio × unit-in-mm × density — never authored. Desugar folds the two it owns, unit: and density:, into the scope’s internal px-per-unit: — its pixels per unit at ratio 1, so lini desugar shows it (SPEC 19); the ratio is an ordinary cascading property and multiplies it once, at layout, so a scale: from any tier (or from an ancestor) reaches the view. Draw a 300 mm bar as right(300) at the defaults and it renders 1200 px wide while every dimension still reads 300 — measured values are always pre-scale; an absurd rendered extent draws a hint naming the likely scale: fix (SPEC 21).

scale: is an ordinary node property, nearest ancestor wins: on the drawing it is the view’s ratio (a 2 : 1 detail is a sibling drawing at scale: 2, 15.8); on any node it overrides — scale: 1 opts a node out. One split makes it behave: a node’s position (translate:) scales by its parent’s scale, its own shape (draw:, points:, width / height, pattern: offsets) by its own — so a balloon in a 2:1 view stays beside its part at true size. What never scales, at any setting: text (font-size is compile-measured, per core), stroke-width, markers, hatch pitch, every dimension / leader constant (SPEC 10.5), and a pinned overlay’s translate: — a pin-relative nudge is chrome anatomy (a badge’s offset, the title’s gap), not a position in the drawing. The |note| / |balloon| / |table| / |surface-finish| / |feature-control| / |datum| templates carry scale: 1 (SPEC 8) — annotations are sheet chrome — and a define inherits its base’s side (|steel::sketch| scales, |finish::note| doesn’t).

Sizing & measurement. A drawing’s bbox is the union of its children’s paint bboxes and its annotations (dimensions stack outside the geometry and count), plus padding; an explicit width / height is a floor, per core. Measurement, by contrast, uses each node’s geometry bbox — the drawn path, stroke excluded — so line weight never leaks into a value or a mate. Geometry keeps the core stroke-width: 2; a drawing’s links take the thinner sheet constants (SPEC 10.5) — drawing-scope link defaults (like the scope’s clearance / routing), below every user rule, so a plain |-| { stroke-width: … } restyles them — the drafting 2 : 1 line-weight contrast. Pen geometry, holes, shoulder lines, and the dimension/leader linework paint the full drafting tone (--stroke-dark — black on white); support lines the translucent --stroke-light (SPEC 10.1). gap, align, justify, and direction have no role on a drawing container and are ignored.

15.2 Anchors

The endpoint form is the core one (SPEC 9) with a wider point set, valid only in a drawing scope:

anchor = id { "." id } [ "." index ] [ ":" point ]
index  = a 1-based pattern-copy number                      (15.4)
point  = center                                            (the default)
       | top | bottom | left | right                       (side midpoints)
       | top-left | top-right | bottom-left | bottom-right  (corners)
       | segment                                            (authored in draw:, 15.3)
  • Points sit on the node’s geometry bbox (15.1): a side is that side’s midpoint, a corner the bbox corner, center its centre. Corners glue vertical word first, matching pin’s vocabulary (pin: top left → :top-left); the reversed order errors with a did-you-mean. Corners and :center are drawing-scope only — elsewhere the core four sides stand, with one exception: a sheet’s projection link (15.8).
  • A |sketch| authors its own segments with the point sigil in draw: (15.3) — declared in the pen, selected on an endpoint, the same declare / select symmetry as #id. Built-in names win (:left cannot be authored); an unknown segment errors with suggestions; mirror: copies of a segment are not addressable (SPEC 24) — a pattern: copy is addressed by its index (15.4).
  • For measurement every anchor reduces to a representative point — a point is itself, an edge or arc its midpoint, a bbox name its bbox point — and a named edge additionally carries its direction, which sets a dimension’s axis and feeds the angular op (15.6).
  • Dot-paths walk into children as everywhere (pump.body:right), resolve in the statement’s scope, and never search (SPEC 9). A patterned node’s position is its seed copy (grid) or ring centre (radial), its other anchors read one copy’s geometry about that datum, and a numeric segment picks a copy outright — plate.bolt.2 (15.4).
  • The anchor aims; the outline lands. A leader’s tip is a ray from its text toward the anchor’s representative point, stopped at the ray’s first crossing of the drawn path — aiming at the bbox corner of a filleted plate touches the fillet arc itself. Dimension extension lines, by contrast, spring exactly from the anchor points — except an edge anchor’s, which springs from the edge’s end nearest the dimension line (the drafting convention: the witness line leaves the corner, never travels the face — so it also never crosses a wall opening, 15.11). Measurement is untouched — the representative point stands.

15.3 The sketch pen

|sketch| is a closed primitive (SPEC 7): a pen that folds to a path. It requires draw: (as |poly| requires points:), paints like any closed primitive (defaults --fill / --stroke / stroke-width: 2), and derives its bbox from the geometry.

draw: is a left-to-right list of bare calls — ordinary value-position calls, no new value grammar beyond the :segment suffix; the value runs to its ; and may span lines. An argument is an expression — a number, a bound value, a call, or math inside the call’s own parens (right(w / 2), up(5 * r), SPEC 10.7).

CallDoes
move(x, y)set the start / begin a new subpath — absolute, in the sketch’s own frame
left(n) / right(n) / up(n) / down(n)an orthogonal run; the verb is visual (up goes up on screen)
line(dx, dy)a relative straight segment
angle(deg, n)a run of length n at a bearing — 0 = up, clockwise (90 right, 180 down, 270 left)
arc(dx, dy, r)the minor arc to a relative point; r > 0 sweeps clockwise, r < 0 counter-clockwise; |r| ≥ half the chord or it errors
arc(r, deg)a tangent arc: continue the current heading, sweeping deg on radius r > 0 — deg > 0 turns clockwise; the heading updates by deg
curve(dx1, dy1, dx2, dy2, dx, dy)a relative cubic bézier
fillet(r) / chamfer(c)corner modifiers between two segments — a line or an arc on either side — trim both legs (chamfer cuts c back along each, by arclength on a curved leg; on a square corner, the 45° bevel) and join with a tangent arc / a straight bevel. They draw nothing alone and error anywhere but at a corner.
circle(r)a circle subpath centred on the current point; the point and heading are unchanged
point()record the pen’s current point under its attached :segment — a station; draws nothing, changes nothing
close()close the current subpath. A closed path is cyclic: a modifier may sit on either side of close() — fillet(3) close() rounds the corner where the last segment meets the seam, close() fillet(3) the one where the seam meets the first segment.

Coordinates. The pen’s frame keeps the core orientation — y grows down, like points: and translate: everywhere in Lini — but the verbs and bearings are visual, so a profile written with up / right / arc never types a signed y; only move(), line(), and curve() expose raw coordinates. Heading state: each drawing call leaves the pen heading along its own direction; angle() and the tangent arc() read and update it.

Subpaths & holes. A second move() starts a new subpath; fill is even-odd, so an inner subpath reads as a hole — an outline with a bore is one shape, composite parts are overlapping nodes, and no boolean operations exist or are needed. An open path (no close(), no mirror:) is legal; fill paints it as if closed (SVG semantics).

:segment — the point sigil in the pen

Anything the pen draws can carry a segment name, written with the point sigil (15.2) glued to its call — one rule, two readings:

OnNamesExample
a drawing callthat call’s drawn segment: an edge, an arc, a bevel, a circle, a close() seamright(50):neck, fillet(3):r1
point()the pen’s current pointright(38):thread point():m1 right(32) — a station with no drawn edge

The names are yours, not vocabulary. A :segment always glues to a call — a floating :name is an error. point() draws nothing and changes nothing; beside a fillet / chamfer (either order) it records the theoretical sharp corner — the point drafting measures (the arc itself is named on the modifier). move() takes no segment — name its landing with point() (move(-90, 0) point():origin). A duplicate segment in one draw: is an error.

mirror: — draw half, get the whole

mirror: reflects everything the node holds — the path the pen drew and its features — and unions the copy. The value is a list, applied left to right, each item reflecting the union so far — two items give a 4-fold part:

ItemAxis (through the node’s origin)Gives
x-axisthe horizontal axis (y = 0)top ↔ bottom symmetry
y-axisthe vertical axis (x = 0)left ↔ right symmetry
a number 45the line at that bearing (angle()’s convention)angled symmetry

What mirroring does is decided per subpath, and both intents fall out of one rule each: an open subpath is fused — the copy joins end-to-end, the edge on the axis the invisible seam (draw the half, get the whole); a closed subpath is duplicated — a reflected second copy (draw one ear, get both). So leave a half-profile open (a close() there would draw a visible spine down the axis — the cue you meant the other form), and close a shape you want twice. A fused mirror also generates its axis |centerline| — auto chrome, 15.7; a duplicated subpath generates none. mirror: runs before pattern: and before placement: it builds the node’s geometry, so anchors, dimensions, and mates all see the whole part.

A feature takes the same split, read on its position: one on the axis reflects onto itself and is drawn once; one off it becomes a reflected second copy — a carrier addressed and counted exactly like pattern:’s (15.4). A reflected copy is one whose coordinates are reflected, never a node wearing a flip: its labels read forward, its anchors stay handedness-free, and a silhouette the renderer draws from a box (a |slant|’s lean, a |cyl|‘s rim) rides upright with them. A node declines with mirror: none, and its subtree with it: none means no reflection touches it, its own axis and its ancestors’ alike. The auto default reflects iff an ancestor does. Only the pen folds a path, so on any other primitive mirror: reflects the features and leaves the node’s own shape; |path| and |image| read none outright — a raw d and a raster have no reflection to take — and naming an axis on either errors (SPEC 21).

revolve: — a turned part

revolve: x-axis (or y-axis) declares the profile a solid of revolution about that axis through the pen origin. It folds exactly as a fused mirror: on the same axis — draw the half, get the whole, plus the axis |centerline| — and adds the edge lines a lathe part’s side view draws: at every profile vertex where two segments meet with a tangent break, off the axis, a generated |shoulder| line (geometry weight — real visible edges, SPEC 8) runs perpendicular to the axis to the vertex’s reflected twin; a span the profile already draws whole is skipped, and vertices sharing a station draw once, at the widest span. So a fillet() joins tangent-continuously and generates nothing, a chamfer() keeps two sharp vertices and generates its two lines, a step completes itself — drafting’s rule falls out of the geometry, with no per-call cases. Edge lines live in the sketch’s frame, so they ride break: like features. A sketch takes revolve: or mirror:, never both; revolve: folds the profile alone, a turned part’s features being drilled, not turned. The unary ⌀ readings require a revolved profile (15.6).

break: — cut the boring middle

break: a b; removes the span between two stations from the view — the model stays whole. a < b (error otherwise) are coordinates in the node’s own frame on the break axis: the node’s longer axis by default, or named per group — break: -40 40 y-axis; reads the stations sit on the y-axis. Several breaks are a comma list, each group defaulting to the longer axis: break: -90 -30, 30 90;.

  • The far piece slides toward the near one, leaving a sheet-space break-gap; the cut edges draw as generated |breakline| children — the standards’ thin line with a sharp jog mid-span — styled or removed by the cascade like all chrome (15.7).
  • The break is a black hole for position. Everything placed in the broken node’s frame rides the compression — its features, their sub-features, a pattern:’s copies: a far-side hole slides with the far piece. (A descendant’s own shape never clips — only the profile cuts.)
  • Dimensions stay true. Anchors and extension lines land at displayed positions; measured values always read the unbroken model — the same law as scale:.

thread: — dress a threaded surface

thread: seg pitch; marks an authored segment as an ISO 6410 thread — comma groups for several (thread: left 1.5, right 1.5;, a double-end stud). The segment name reads bare — a value has no id to separate it from, the same way a chart band’s axis: t names its axis (SPEC 14.5) — and must name a straight run parallel to the revolve: axis, on a revolved profile. The pitch is in drawing units, and the numbers live once — the surface gives the major ⌀, thread: the pitch, and the chrome follows:

  • the thin line — --stroke-light — offset into the material, running the segment and stopping at an adjoining chamfer()’s trim point. The subpath sets the sense: on an outer profile the run is the major and the line marks the minor, in by the ISO 60° depth, 0.6134 × pitch; on an inner (even-odd hole) subpath the thread is internal — the run is the drilled minor and the line marks the major, out by 0.5413 × pitch (the round view’s numbers, 15.4);
  • the thread-end line — geometry weight, across the full diameter — at an end where the surface continues collinearly past the run (a thread stopping mid-surface); where the profile turns instead — a chamfer, a face, a step — the geometry already ends the thread and no line is drawn;
  • both doubled about the axis by the revolve.

A bare leader on a threaded segment composes its spec — bar:m20 <- reads M20×1.5 (major ⌀ × pitch, the metric form; an internal run composes from its major the same way) — re-cut the bar and the callout follows. An authored text follows the composed spec, per the one-ended label law (15.6): bar:m20 <- "LH" reads M20×1.5 LH. On a round node — a threaded hole’s top view, a stud’s end view — thread: takes the pitch alone (15.4).

15.4 Features, holes & patterns

A part’s features ride in its [ ] — placed at the part’s datum and rigid with it: mate or translate the part and its holes travel along.

|rect#plate| { width: 120; height: 70 } [
  |hole#pin| { width: 10; translate: -35 20; pattern: grid(2, 1, 70, 0) }
]
plate:left (-) plate.pin { side: top }        // dot-path to the feature → 25

|hole| (SPEC 8) is round: width: — required — is its diameter. It punches by paint order (fill: --bg over a filled or hatched part reads as a through-hole, hatch-exempt with no special case) and draws its own dash-dot centre marks, overhanging by a sheet-space constant — a hole without marks is a plain |oval|. pin (o) reads its diameter (15.6); pattern: prefixes the count (2× ⌀10).

thread: pitch dresses a round feature’s view with the ISO 6410 ¾ arc — a thin (--stroke-light) circle broken over its upper-right quadrant. The type carries the sense: on a |hole| the thread is internal — the drawn circle stays the drilled bore and the arc sits outside it at the major ⌀ (width + 1.0825 × pitch, the ISO internal thread height); on plain round geometry (|oval| lineage) it is external — the outline is the major and the arc sits inside at the minor (width − 1.2269 × pitch). Centre marks are unchanged and pin (o) still reads the drawn width. Counterbores and countersinks stay deferred (SPEC 24).

pattern: replicates a node about its own position — a node property, legal in any layout, though its chrome belongs to drawings:

FormCopies
pattern: grid(cols, rows, dx, dy)cols × rows copies at offsets (i·dx, j·dy); the seed is copy one and keeps the node’s position
pattern: radial(count, radius)count copies on the circle, first at bearing 0, clockwise; the node’s position is the ring centre and no copy is drawn there

The two datums match drafting practice — you locate a grid by its first hole and a bolt circle by its centre. The node’s bbox becomes the union of the copies; each copy repeats the full lowering (a patterned |hole| punches and centre-marks per copy); a radial pattern generates its |pitch-circle| (15.7). Counts ≥ 1 (grid) / ≥ 2 (radial), radius > 0; offsets are drawing units.

Copies are addressable by a numeric path segment — plate.bolt.2: 1-based, grid copies row-major from the seed, radial copies clockwise from bearing 0, a mirror:’s reflections after their originals, item by item. The index extends the carrier’s dot-path only — copies leak no ids (bolt.2 alone is an unknown endpoint); an index past the count errors with it (SPEC 21). A copy is the feature at its own position: every anchor — bbox points and authored :segments — reads that copy’s geometry; a dimension on it measures the true model position (displayed anchors still ride break:’s compression — 15.3); a leader lands on the displayed copy. The bare carrier keeps its seed / ring-centre reading and its N× count prefix (15.2, 15.6).

Composition is the geometry model — there is no CSG. A part is one |sketch|, its surfaces and corners named where dimensions will land, or composed from overlapping nodes in paint order: a bore in a section view is a --bg-filled |rect| — it punches the hatch and its edges anchor a (o). The escape hatches are core (|poly|, |path|, |image|). A parts library is plain defines — no engine support, just bundled geometry and paint:

{
  |steel::sketch| { fill: hatch(45, 6) }
  |brass::sketch| { fill: hatch(-45, 4) }
}

15.5 Mates & seating

a:anchor || b:anchor seats one node against another — ||, the parallel bars of GD&T: it moves a part — or, with a sheet-content end, an annotation (seating, below) — and draws nothing, so it can never be confused with an annotation line. Grammatically one more link op (SPEC 22); chains and fans parse as usual; a mate takes no label and no markers.

nozzle:left || barrel:right              // abut those faces, flush
cap || barrel                            // no anchors — concentric (origins coincide)
nozzle:left || barrel:right { gap: 4 }   // 4 units of daylight along the normal
piston:left || bore:left { gap: -6 }     // negative gap — inserted 6 deep
  • Resolution. Mates resolve after datum placement, walking outward from the ground (the first-declared child, 15.1): each mate moves the side not yet connected to the ground, translating that whole scope-level child, rigid, features and all. a || b and b || a are the same mate — grounding, not operator order, decides who moves. A mate whose ends are both already grounded is over-constrained — an error naming the cycle; an unconnected island grounds its own first-declared node. Deterministic, source-ordered.
  • Directed vs point anchors. Sides and named edges are directed: a mate between them aligns the faces flush along the shared normal (the other axis stays where the datum put it — translate: slides it), the two directions must be parallel (a:left || b:top errors), and a named edge seats a part against an interior face (ring:right || housing:shoulder). A named edge faces the left of the pen’s travel — draw the profile with the material on the pen’s right (axis → up → across → down, the natural half) and every face points out, interior shoulders included. gap: offsets along the normal and may be negative (overlap — the one place gap goes below zero). Point anchors (center, a freestanding name) make the points coincide — the bare a || b is the origin-to-origin case — and have no normal, so gap: there errors.
  • Rotate, then mate; translate after. A part’s rotate: turns its geometry first and the mate aligns the rotated anchor; the mated child’s own translate: applies after — the universal post-placement nudge, here a lateral slide along the face. A pin: on a mated child is ignored with a warning.
  • A mate between two features of one part errors — a part is rigid. Mates are valid only where children datum-place: inside a layout-owning child the flow already decided every position, the same over-constraint error. Dot-paths reach into parts (pump.shaft:right || frame:left), moving the scope-level child that contains the moving anchor.

|| with a sheet-content end is a seat. The operator generalizes to annotation seating — same syntax, split by what the ends are:

EndsReadsWho moves
geometry || geometrya matethe grounding walk above
annotation || geometrya seatthe annotation, always — either operand order
annotation || annotationerrorseat annotations on geometry (SPEC 21)
  • Seats run after mates, outside the grounding graph: every part is already seated when annotations place, and a seat never grounds, moves geometry, or over-constrains anything. One seat per annotation — a second errors.
  • The target supplies the face: the geometry anchor must be directed — a side or a named edge; a point target errors (SPEC 21).
  • A seat places; a mate aligns. The annotation’s seat anchor — its own endpoint anchor, or the type’s default (the table) — lands on the target anchor’s representative point, both axes: flush contact (the annotation had no position of its own worth keeping). gap: offsets along the target’s outward normal, positive = daylight (the mate’s signed law); rotate: turns the annotation before the seat — the rotated anchor aligns, so rotate: -90 stands a symbol on a vertical face; translate: nudges after, the lateral slide along the face.
  • Bundles seat as one. A wrapper (a |column| of finish symbol over frame) is sheet content like its children: it seats whole — interior laid out as usual — and reports one painted extent to the dimension packer (15.6), so rows stand off the bundle, never thread it.
AnnotationDefault seat anchor
|surface-finish|the symbol’s tip — the vee stands on the face (15.9)
everything else — |feature-control|, |datum|, |note|, |balloon|, a bundlethe facing side — the bbox side whose outward opposes the target’s normal, read after rotate:

15.6 Dimensions

A dimension is a link; the operator carries the kind and supplies the glyph you can’t type. The statement is the core link statement, with one relaxation: the measuring and leader ops may stand one-ended (SPEC 22).

WriteReadsRenders
a:left (-) b:righta linear spanextension lines, arrows, 25
a:left (-) b (-) ca chaineach hop its own dim, one shared row
pin (o)a round featurethe ⌀ line across the circle — both arrows on the rims — 2× ⌀10
hole:top (o)a round feature, side-anchoredthe diametral line through the circle
bore:top (o)any node, side-anchoredthe span to the opposite side, ⌀-read — ⌀16
body:neck (o)a revolved-profile segmentthe station’s span across the axis — ⌀28
body:r1 (o)a named arca leader — R3
body:flank (<) body:basetwo line-like anchorsthe angle arc — 40°
body:taper (<)a mirrored- / revolved-profile segmentthe included angle vs its own twin

Each glyph is a picture of what it measures: the dash (-) is a length, the circle (o) a diameter, the wedge (<) an angle. Arity disambiguates — (-) is always binary, (o) always unary / side-anchored, (<) either.

(-) — the linear measure. The dash pictures a length: (-) spans two anchors and reads the distance between them, projected on its axis. It is always binary — a (-) b, or a chain a (-) b (-) c sharing one row; a unary a (-) errors (“a linear dimension measures two anchors”, SPEC 21). Extension lines spring from the anchors and the value rides the line (Placement & stacking, below).

(o) — the round measure. The circle pictures a diameter: (o) is unary / side-anchored — hole (o), bore:top (o); a binary a (o) b errors (“(o) measures one round feature”, SPEC 21). The feature picks the symbol, per the standards: a named arc (a fillet, an arc() product) reads its radius — R — and everything else reads as a diameter, ⌀, across whatever span its anchor gives. Roundness is by construction (|hole| / |oval| lineage, a circle() product, |pitch-circle|, a revolved profile), never guessed from coordinates. A bare (o) needs an inferable axis — a round node (symmetric, any) or a revolved sketch (across its axis, the full span); otherwise the error asks for an anchor. The ⌀ station and full-span readings require revolve: — a merely mirrored profile’s span is a width, not a diameter, and errors asking for the revolve (SPEC 21). R on a full circle has no auto form (the standards say ⌀) — type a leader (pin <- "SR5"), the universal fallback for anything auto-measure can’t read.

The diametral line. On a round node, a side anchor draws the dimension through the circle, arrows out against the rims: :top / :bottom vertical, :left / :right horizontal, a corner the 45° diagonal. The value sits on the line when it fits inside; otherwise the line overruns the anchored rim and carries the text there — hole:top (o) spills upward, packing along that ray (Placement & stacking, below). Deterministic, no solver.

(<) — the angle. Binary, between two line-like anchors — a named edge, a |line| / |centerline|, a bbox side: the angle between their directions, the arc drawn at their (extended) intersection, the value riding the arc. Unary, on a named edge of a mirrored or revolved sketch: the included angle of a taper against its own reflection. Point anchors have no direction and error. (>) is reserved — an error with a did-you-mean, kept for a future reading.

Auto-measure — the smart label. A dimension with no label renders its measured value: the anchor distance projected on its axis, in drawing units, measured after mates resolve and on the unbroken model. The number renders through format: — the inherited presentation property (SPEC 17): the auto default rounds to at most 2 decimals, trailing zeros trimmed — a bare number: drafting states units once, in the title block, and a per-value suffix is format:’s job. format: shapes the number only, never the measurement — the pieces compose around the formatted number as count → glyph → number → label words → tol: (2× ⌀10 H7); a fraction D stack rides the same raised / lowered machinery as tol: deviations. The text composes from sources that each own one thing:

SourceOwnsExample
the opthe glyph(o) → ⌀ / R · (<) → ° · tol: → ± (linear (-) adds none — a plain length)
the geometrythe number10
the labelthe wordstwo-ended: replaces the number and its glyph (a (-) b "180" — the honest override for schematic or nominal figures); one-ended: follows the value (pin (o) "H7" → 2× ⌀10 H7)
tol:the tolerance, appendedtol: 0.1 → ±0.1 · tol: +0.2 -0.05 → stacked deviations, 0.7 × font, raised / lowered · tol: H7 → a fit class
pattern: · mirror:the count prefix — stacked replications multiply2× · a mirrored pair of holes 4×

Axis — inference & project:. The anchors pick the axis. A directed anchor sets it — a side name (left / right → horizontal, top / bottom → vertical) or a named edge (a vertical shoulder → a horizontal dim across it); two directed anchors must be parallel — a perpendicular pair has no shared normal and errors, pointing at (<) (SPEC 21). Two point anchors read the true aligned distance — the dim line parallel to the span, extension lines perpendicular to it. project: horizontal | vertical | aligned overrides the point readings; against a directed anchor it must agree — a conflict errors (SPEC 21).

Placement & stacking. A dimension sits outside the geometry, on a side: — a horizontal dim defaults to bottom, a vertical one to right; anchors both on one edge pull it there; side: must suit the axis. An aligned dim sits on the side of its span facing away from the geometry centre — the bbox centre of the scope’s geometry union; its side: left | right overrides, read along the span, first anchor → second (left is the walker’s left). Dims sharing a side pack into rows: each dim, in source order, takes the innermost row where its span — text included — overlaps nothing already placed, so a chain shares one row and dims over different stations share too. Row offsets derive from painted bounds: a row stands clearance off everything already painted on its side — geometry, text, callouts, frames, earlier rows — never at a fixed pitch. clearance is a minimum, not a coordinate (SPEC 17); a per-dim value widens that dim’s own stand-off independently, and the packer may still go farther out to clear obstacles. A statement that leaves along a ray instead of seating on a side — a leader’s text, a spilled diametral value — packs the same way along its exit. translate stays the exact nudge; a dimension takes no gap: (SPEC 21). The anatomy is baked sheet constants (SPEC 10.5): extension lines spring from the anchors (an edge anchor’s from its dimension-side end — 15.2) with a small gap and overshoot past the dim line — painted the light support tone (--stroke-light, SPEC 10.1) unless the statement recolours, so the geometry reads first; arrows are drafting-slender (≈ 3 : 1, filled), sized by the dim’s stroke-width; the value rides above the line, ISO-aligned — it rotates with the line and reads from the bottom or from the right, overridable like any text (the styled-label form + rotate:). A span too narrow for text + arrows flips its arrows outside the extension lines; the value stays centred inside while it still fits there, and only a span too tight even for the bare text slides it past the nearer one. A packed row also clears every callout’s text — leaders, angles, and every seated or carried annotation node register as obstacles before dims seat, each statement one painted box, a bundle’s the union of its children (15.5, 15.9). Dimensions are links, styled per core (SPEC 9) at the drawing scope’s link defaults (15.1).

{ layout: drawing }                            // ratio 1, unit mm, density 4 — the defaults

|sketch#body| {
  draw: move(-80, 0)
        up(14) right(50):neck fillet(3):r1 up(8) right(60):mid fillet(3) down(8) right(50) down(14);
  revolve: x-axis;                             // a turned part: half → whole, axis + edge lines
}

body:left (-) body:right { side: bottom }      // → 160
body:neck (o) { side: left; tol: h6 }          // → ⌀28 h6 — the surface, doubled about the axis
body:r1 (o)                                    // → R3 — the fillet knows its radius
160 ⌀28 h6 R3

15.7 Leaders, notes & line conventions

A callout is a one-ended link, written tip-first: the glyph hugs the feature, the line runs toward the text — which is formally the link’s label, so everything core says about labels (the [ ] form, styling, one inline label) applies verbatim:

OpTip on the featureFor
<-arrowan edge or outline
*-dota leader landing within an outline — a face, a region
>-datum trianglea datum feature (>- is the crow op elsewhere — the scope reinterprets it, as a sequence reinterprets ->)
bolt <- "THRU"                              // arrow lands on the hole's rim
face *- "Ra 1.6"                            // a dot — a surface note
body:seat >- "A"                            // datum A on that face
bolt <- [ "R3 TYP" { translate: 30 -24 } ]  // a styled / nudged text — the core form
  • A callout has one tip, so the singular marker: overrides it; the marker set gains datum (SPEC 7). One arrowhead style per sheet (ISO 129): a word leader’s <- tips with the same drafting-slender arrow as every dimension; *-’s dot and the datum triangle keep their own shapes. A one-ended callout with no text is an error; a one-ended -> / -* errors the other way — a leader points back at its feature (a schematic scope reads that same statement shape as a label wire — SPEC 16.5). A label-terminated statement is single-hop — chain before the text (SPEC 21).
  • A fan shares one note. a & b <- "2× R5" — & on a one-ended leader op keeps one text and one landing (the first endpoint steers the auto placement; side: overrides), each endpoint its own ray-cast leg, sharing what trunk the geometry permits; a leg that cannot land is an error, never a silent drop. & on a two-ended op stays the core fan of links (SPEC 9); on a measuring op or mate it errors (SPEC 21).
  • A datum’s letter is an identity. body:seat >- "A" seats the letter in the standard framed box, riding the leader’s text seat at the landing — sheet-space and obstacle-registered like any callout text (15.6). Letters collect per drawing scope — a duplicate errors (SPEC 21); referenced elsewhere the letter is written bare — a |feature-control|’s datums: validates against the set, and the |datum| node states the same identity in node form (15.9).
  • Text placement. The text auto-places outward: a directed feature’s leader leaves straight off its face — along the surface normal — while a point feature’s runs along the ray from the drawing’s datum through it; either way just past the geometry union, horizontal — and the leader ends in a short horizontal landing (note-landing) before its text (note-text-gap), the drafting elbow. The stand-off is measured on the ink, not the elbow: what stands note-offset clear of the geometry is the statement’s whole painted block — its text, a datum’s frame, a carried annotation node (15.9) — so a leader leaving downward keeps the same air as one leaving sideways, whose text the exit ray already carried clear. side: picks the direction instead (a side or a corner); a styled label’s translate nudges from there; the block then packs along its exit (15.6). The tip ray-casts onto the drawn outline (15.2).
  • The leader makes the note. A callout’s text lowers to a bare leaf — drafting callouts are unboxed. A boxed note is the |note| template (SPEC 8) wired with an ordinary two-ended link; a balloon is |balloon| plus a leader (b1 -* nozzle); bare "…" stays plain sheet text (“SECTION A-A”). Any other two-ended op between two nodes draws a straight annotation line, markers per the op — a flow direction, an exploded-view path.

Line & material conventions. hatch() fills section cuts (SPEC 10.3); stroke-style: center / phantom are the drafting dash conventions and dashed the hidden-edge one, each on its own child — one node has one stroke style (SPEC 7). The |hidden| template (SPEC 8) is that child ready-made — a dashed, unfilled pen profile for interior geometry (a socket, a bore): a feature in the part’s [ ], rigid under mates, riding break:, its :segments dimensionable like any sketch’s. Besides the section |plane| (15.8), two chrome types carry the centerline pattern in the part frame (SPEC 8): |centerline| (a |line| — an axis, a symmetry line, a spoke) and |pitch-circle| (an |oval|, width: its diameter — the bolt circle; being round, bc (o) reads its PCD). A manual |pitch-circle| covers what pattern: can’t — unequally spaced holes still share one drawn circle.

Crossing halos. Annotation linework — dimension, extension, and leader lines — breaks where it crosses geometry: a sheet-space knockout, halo-margin wide each side (SPEC 10.5), mask-based so the break holds over hatching and in every theme. Never over arrowheads, text, frames, or the contact region (a tip, a landing) — the crossing alone. The generated |halo| chrome rule restyles or removes them scope-wide (|halo| { … }), like all chrome.

Auto chrome — one mechanism, twelve producers. The lines drafting always draws are generated children, so the cascade styles or removes them with no dedicated knobs (|sketch| |centerline| { stroke: none }). Removal is literal: chrome whose stroke and fill both resolve to none puts no ink on the page, so it takes no geometry — it draws nothing and reserves no space, its overhang included. The same stroke: none that deletes an SVG line deletes the chrome that would have drawn it; there is no separate opt-out:

ProducerGenerates
a fused mirror: (15.3)the axis |centerline|, overhanging the profile
a revolve: (15.3)the axis |centerline| + the |shoulder| edge lines at every sharp diameter change
a thread: (15.3, 15.4)the thin minor line + the thread-end line; on a round view, the ¾ thread arc
pattern: radial (15.4)the |pitch-circle| through the copies
a |hole|its centre-mark crosshair
a |plane| (15.8)its thick end strokes, the viewing-direction arrows, and the paired section letter
a break: (15.3)the |breakline| pair — thin, sharply jogged mid-span
a |page| (15.8)the sheet chrome — the |frame|, the |zone| references, the |tick| dividers and centring marks
annotation linework crossing geometryits |halo| knockouts — the understroke break, above
a sheet’s projection link (15.8)its straight |projection| construction line
a |door| / |window| (15.11)the leaf + quarter swing arc / the sill lines
a |stairs| (15.11)its tread lines + the up arrow

15.8 Assemblies, views, sheets & titles

There is no |assembly| type: an assembly is a drawing whose children mate — and drawings nest. A child |drawing| is one rigid body from outside (the core sealed-body law): its internal mates, dims, and features stay in its [ ], its geometry bbox is its parts’ union, and it grounds, mates, and anchors like any part. Build sub-assemblies in isolation, then seat them — the same vocabulary at every level; reach in where both ends are visible (motor.shaft:right || pump.rotor:left). A project that wants the word writes |assembly::drawing| { } — a define, not a language feature. Item balloons are |balloon| + a leader; the parts list is a core |table| beside the drawing; auto-numbering and auto-BOM are deferred (SPEC 24).

A multi-view sheet is ordinary layout: drawings in a |row| / |grid|, each view its own scope and scale: (a 2 : 1 detail still dims true, 15.1). There is no |view| type and no projection engine; views share their axes with align: origin (SPEC 12) — a drawing’s origin is its datum, so a row of views lines up datum-to-datum however their dimensions stack, and a grid with align: origin; justify: origin is the first- / third-angle arrangement. A drawing’s smart label is its title, placed below — it lowers to a |footnote| (the bottom-centred caption template), because drafting titles sit under the view: |drawing| "SECTION A-A"; style every title with |drawing| |footnote| { … }. An authored label always wins; a view sourced from a marker with of: composes one instead (Sections & details, below).

Projection construction links. The thin lines tying a feature across views are authored correspondences, never inferred (no projection engine): in the sheet’s scope — outside every drawing, where both views are visible — the unmarked - op between two anchors that dot-path into different views draws one straight thin line: side.screw:head - end.od:top. On such a link — and only there — the full drawing anchor vocabulary (15.2) is legal outside a drawing scope, the one exception to sealed bodies. It lowers at layout, after align: origin and every seat have placed the views — never routed, never a packing obstacle — as generated |projection| chrome (15.7): |projection| { … } restyles or removes projection lines scope-wide. Everything else stands: a marked op, a dimension, or a mate across views errors (SPEC 21) — a construction line relates views; it never measures or seats. View-letter arrows (of: an arrow marker) are beyond 1.0 (SPEC 24).

Sections & details. Lini is 2D: a section’s cut face is authored — drawn with the pen and filled with hatch(), as the bushing is (15.4) — but a detail needs no concession, being a 2D re-render, and the engine is re-entrant. Either way the view is a plain |drawing| { of: <marker> } — one property, one view type. of: names a marker on the source view by id (like a chart’s axis:); the marker’s kind decides what the view captures:

  • The cutting plane — |plane#a| "A" { at: N }, a chrome child of the view it cuts (a |line|), its smart label the section letter. at: N places it: the plane runs perpendicular to an axis at station N, the axis defaulting to the model’s longer one or named — at: 40 y-axis (break:’s convention). It lowers to the ISO plane: a thin dash-dot line (stroke-style: center) across the geometry and its overhang, thick end strokes just past each end, a viewing-direction arrow (the slender dimension arrow) at each, and the letter beside them. facing: left | right | up | down turns the arrows — default right for a vertical plane, down for a horizontal one. The cascade styles or removes the whole marker.
  • The magnifier — |magnifier#c| "C" { width: … }, ringing a region: a thin outlined circle (|oval|, --stroke-light), width: its diameter, positioned with translate: like any feature; its smart label the letter, set just outside the rim at 45°. An ordinary part-frame child, like a |balloon| — not generated chrome, and the single source of truth for the region it names.
  • The section view — |drawing#sec| { of: a }, a a |plane|. The face is authored (the hatched cut you draw); of: composes the title from the plane’s letter — doubled, A-A — plus the drafting ratio: the view’s own scale: read directly (1:1 at the default, 2:1 enlarged, 1:1.5 reduced, ≤ 2 dp — 15.1).
  • The detail view — |drawing#det| { of: c }, c a |magnifier|. The view takes its centre and diameter from the marker and its letter titles it (C (1:1), composed as above), so only the magnifying scale: is yours. The engine re-lays the marker’s host view at the detail’s scale — a plain 2D re-render, no projection — keeping the geometry, dropping the source’s annotations, shifted to centre the region and clipped to the circle, with the circle drawn as its boundary — the marker’s own thin chrome paint, since the rim is the marker’s other half: both wear .lini-magnifier, so restyling the marker (|magnifier| { … }, or the instance’s block) carries the rim with it and neither inlines a thing (SPEC 18). The detail’s own [ ] annotations dimension the re-laid copies (by the ids the clones carry from the source); only the detail’s own links may reach them. A detail re-renders a base view — of: can’t name a marker inside another sourced view.

The sheet. |page| gives the multi-view story its walls: the trimmed ISO 5457 sheet as a template container, not a layout — inside its frame it is an ordinary container (default flow; layout: / columns: / direction: free), hosting drawings, tables, and notes as normal children in sheet space (a page is never a drawing scope). sheet: names the trimmed size — sheet: a3, sheet: a4 landscape — pure sugar for width / height in millimetres (the orientation keyword swaps the pair; ISO defaults — A4 and A5 portrait, A3–A0 landscape; a bare |page| is a4), so an explicit width: / height: overrides through the ordinary slot and a custom sheet still derives its zones. The ANSI/ASME Y14.1 letters ride the same sugar in their own millimetres — sheet: b (a…e; a portrait, b–e landscape) — nothing else differs. A page’s direction defaults by orientation — landscape → row, portrait → column — so views flow with the paper; set it to override. A page carries no scale: of its own: the root’s density: sets pixels per millimetre (default 4, screen-only — 15.1), a drawing’s scale: is its drafting ratio directly, and so a default drawing on any page draws 1 : 1 true (a 2 : 1 detail is scale: 2).

The ISO furniture is generated chrome (15.7): the thick |frame| 10 mm in from every trimmed edge; the zone grid — divisions of ≈ 50 mm, rounded to the nearest even count per edge (A4 4 × 6, A3 8 × 6, A0 24 × 16) — numbered 1… left-to-right along top and bottom and lettered A… top-to-bottom along both sides, drawn as |zone| labels and |tick| dividers in the reference band, the margin beside the frame; and the four centring marks, each crossing the frame at an edge’s midpoint (the middle divider, which would coincide, is not drawn). The content area is the frame inset by 5 mm (padding: adds to it). A |title-block| child (ISO 7200 — a |table|, SPEC 8) is seated by type, flush inside the frame’s bottom-right corner; content stays centred on the sheet, and only a view whose box would run under the block lifts the page’s flow content clear of it, as far as the content area’s top allows. String-valued field properties — title, drawing-number, revision, date, sheet-number, author, approved, department, reference, document-type, status — desugar (like sheet:) into the fixed ISO grid: each a caption in the muted footer tone over its value, and absent fields collapse their cells, so the default block is minimal (Title / DWG No. / Rev / Sheet). The block’s smart label is its title field — |title-block| "Socket cap screw" lowers to the same generated spanning cell; a label or any field property selects the structured-field mode, and a |title-block| with neither keeps the plain-table form — its cells fully authored. In field mode, authored children remain ordinary cells after the generated ones, in the same grid — cell: / span: honoured; an authored cell landing on a generated field’s slot errors, naming the field (SPEC 21). There is no logo: — a logo is an |image| in a cell (SPEC 7), or anywhere on the page. A file whose drawn content is only pages hugs them — the paper is the margin, so the root’s padding defaults to 0 (your own { padding: … } still wins) and the sheet runs edge to edge of the SVG. That same predicate makes the sheet true-scale in print (SPEC 18).

|page| { sheet: a4 } [
  |drawing#side| "DIN 912 — M8 × 40" [ … ]         // 1 : 1 on the sheet
  |drawing#detail| "DETAIL A" { scale: 2 } [ … ]    // a 2 : 1 view
  |title-block| { columns: 60 auto } [
    "Title" "Socket cap screw"
    "Scale" "1:1"
  ]
]

15.9 Drafting symbols & annotation composition

GD&T rides three node templates over one shared drafting-glyph set — the characteristic symbols, the modifier circles (Ⓜ Ⓛ Ⓕ Ⓣ Ⓟ), the finish vees — drawn as paths like icons but sized in natural units, never fit to a box: a glyph’s height follows the annotation font-size, its line weight the statement’s stroke-width, so every symbol reads at dimension-linework weight beside every value, at every view scale. All three types are sheet content (scale: 1 — 15.1) and drawing-scope only (SPEC 21). Place one with translate:, or attach it three ways, all ordinary: seat it on a face with || (15.5), wire it with a leader (body:seat <- sf — the same node either way), or carry it in an annotation’s [ ] (below).

|surface-finish| — the ISO 1302 surface-texture symbol. Its smart label is the textual indication (|surface-finish| "Ra 1.6"), riding the symbol’s long leg; symbol: picks the variant:

symbol:DrawsMeans
basic (default)the bare veeany process
machinedvee + barmaterial removal required
prohibitedvee + circleremoval prohibited

Seated, the vee’s tip stands on the face (the type’s seat anchor, 15.5); rotate: turns it for a vertical face.

|feature-control| — the GD&T frame. The common single frame carries its properties directly; a composite / combined frame holds |control| children, one row each (mixing the two forms errors). The smart label names the characteristic — the frame’s in one-row form, each |control|’s otherwise; longhand characteristic:; setting both errors. The set is ISO 1101’s fourteen (ASME Y14.5-2018 dropped concentricity / symmetry; lini’s drafting lineage is ISO and both validate):

GroupCharacteristicsdatums:
formstraightness · flatness · circularity · cylindricityforbidden
profileprofile-line · profile-surfaceoptional
orientationangularity · perpendicularity · parallelismrequired
locationposition (optional) · concentricity · symmetry (required)—
runoutcircular-runout · total-runoutrequired

The row properties, each owning one compartment slot:

  • tol: — the tolerance zone width, required: a number > 0 (the deviation / fit forms are a dimension’s — 15.6).
  • zone: diameter | spherical — the ⌀ / S⌀ zone prefix; legal only where the zone is axial — position, straightness, perpendicularity, parallelism, angularity, concentricity.
  • material: maximum | least — Ⓜ / Ⓛ after the value; legal on the feature-of-size controls — position, the orientation three, straightness.
  • datums: A, B maximum, C — primary → tertiary, at most three; each a bare letter declared in the scope (>- or |datum|, 15.7) with an optional per-datum maximum / least; an unknown letter errors naming the declared set.
  • modifiers: — ordered extras after the material modifier: projected N (Ⓟ plus the projection length), free-state (Ⓕ), tangent-plane (Ⓣ).

Any combination outside these rules — the table’s forbidden / required cells, an unknown characteristic — is an error with a correction (SPEC 21): a frame renders semantically valid or not at all, never plausible-looking and wrong. Adjacent |control| rows sharing one characteristic merge its symbol compartment — the composite frame; rows with different characteristics stack as a combined frame, in source order.

|datum| — the framed datum letter as a node: its smart label the letter, joining the scope’s identity set exactly as >- does (15.7 — a duplicate errors across both forms), one frame anatomy shared with the leader’s box. Standalone it seats or wires like any annotation; carried in a dimension’s [ ] it states the feature-of-size axis datum — the measured feature’s axis is the datum feature.

Annotation nodes on a dimension or leader. A drawing link’s [ ] may carry these nodes beside its text labels: each stacks at the statement’s text seat — under the dim value or callout lines, in source order — rides the row like the text does, and registers its painted bounds with the packer (15.6), so no row overlaps a carried frame. Strings keep their label semantics (replace / follows, 15.6); a node is never a label. Core routed links stay text-only — the grammar is scope-blind (SPEC 22), and a node in a [ ] outside a drawing scope errors at resolve (SPEC 21).

{ layout: drawing }

|sketch#body| { draw: …; revolve: x-axis }
body:seat >- "A"                                     // datum A, the leader form

|surface-finish#sf| "Ra 1.6" { symbol: machined }
sf || body:top                                       // the vee stands on the face

body:mid (o) { tol: h6 } [                           // a frame rides the dimension
  |feature-control| "circular-runout" { tol: 0.05; datums: A }
]

15.10 Lowering

layout: drawing resolves in the layout phase (SPEC 19) — geometry must exist before it can be measured:

  1. Geometry per child, bottom-up: fold draw: to a path (corner modifiers applied cyclically through close()), collect its :segments, apply mirror: / revolve: (+ the edge lines and thread: dressing), expand pattern:, build break:’s view map; nested drawings lower first, becoming rigid subtrees. Compute each node’s geometry bbox (stroke excluded) and paint bbox (core).
  2. Place children: origins on the datum, translate: applied.
  3. Mates: walk from the ground; rotate first, seat, the child’s own translate after; flag cycles and over-constraints.
  4. Measure every annotation’s anchors against the seated, unbroken geometry; compose the texts (glyph + number / label + tol: + count).
  5. Annotate: assign dims to sides and pack the rows in source order; auto-place callout texts outward; ray-cast leader tips; land the elbow.
  6. Lower to primitives at baked coordinates: sketch → |path|; hole → |oval| + centre marks; the auto chrome → generated children; dim → extension |line|s + a marker-tipped dimension |line| + text; an angle → its arc |path| + text; leader → |line| + marker + text; hatch → one deduplicated <defs> <pattern>.
  7. Scale geometry per the effective per-node scale; chrome stays sheet-space. Emit geometry in source order and annotations above all of it (the drawing’s one draw-order override, like a chart’s semantic order; layer: still wins).

The output is an ordinary primitive subtree (SPEC 11, seam 3). The parser is scope-blind: the ops and forms parse everywhere and mean drawing only in a drawing scope — elsewhere they error at resolve (SPEC 21).

The drawing property index — owners, value shapes, defaults — is the Property Ledger; each property’s law lives in its subsection above.

15.11 Floorplan — the architectural dialect

layout: floorplan — and the |floorplan| template (|drawing|-based, so |drawing|-scoped rules dress a floorplan too) — is the drawing engine under another vocabulary: everything in this section applies unchanged (the datum, scale: / unit:, anchors, the pen, pattern:, dimensions, leaders, mates, sheets, hatch), and every “drawing-scope only” rule reads a floorplan scope as a drawing scope — the ops, tol:, the drafting symbols, and the SPEC 21 gates hold unchanged. What the dialect adds is a vocabulary with its own gate: the floorplan types are legal only in a floorplan scope — a |wall| in a plain drawing errors like any scope type (SPEC 21) — so each drafting language keeps its own completion surface, while every drawing-global mechanism (|sketch|, |hole|, |note|, |page|, …) stays welcome here. No new role variables.

True-size defaults. A floorplan type’s intrinsic sizes — a wall’s thickness:, an opening’s width:, every fixture body — are physical millimetres, converted to drawing units through the scope’s unit: where each is read (15.1): a bed is 1500 × 2000 mm whether the file drafts in m or mm. An authored value is drawing units like everything else — at unit: m, a 100 mm partition reads thickness: 0.1.

Walls. A |wall| is a |sketch| whose draw: traces the wall’s centreline — named :segments and all — and thickness: (inherits nearest-wins, like unit:; default 200 mm) grows it into the wall outline: each run offset ± thickness ∕ 2, corners mitred (an acute spike bevels at miter limit 4), an arc offset to its concentric pair (an arc radius under thickness ∕ 2 errors), an open end butt-capped at its endpoint, a close() seam mitred like any corner; curve() in a wall’s draw: errors (SPEC 21). The outline is the wall’s shape: it takes the paint — solid --stroke-dark, the poché read ({ fill: --bg; stroke: --stroke-dark } is the hollow double-line look, fill: hatch(45) a section convention — both show their junctions, where the solid default merges by paint order) — and it is the geometry bbox (15.1). In 15.10 step 1 the offset runs after the draw: fold and before the bboxes, and an opening clips it there — resolving against its already-folded parent, the one place a child reads down from its part. Anchors: :segments read the centreline, bbox points the outline — and every named segment derives its two face anchors, name-in / name-out: the segment’s own offset edges. On a closed run -in is the enclosed side; on an open one it is the left of the pen’s travel (the named-edge convention, 15.5). So a clear room span — what a listing plan dimensions — is face to face, outer:north-in (-) bedwall:top, measured like everything else; an authored segment name ending -in / -out errors as colliding with its derived twin (SPEC 21). |partition| is the built-in 100 mm interior define (SPEC 8).

Openings. A |door| / |window| rides in its wall’s [ ], stationed on a straight named segment: on: the segment, bare (the thread: shape, 15.3); at: the near jamb’s distance from the segment’s start; width: the clear opening. The gap clips the wall outline at the two jambs — a profile clip, not break: (15.3): the wall keeps its length, nothing compresses, no |breakline| draws, and each jamb closes flat across the thickness. An opening’s own geometry is that jamb-to-jamb box — width × thickness, seated on the segment — so an id’d opening anchors a dimension at its centre (outer:west (-) outer.entry (-) outer:east, the location chain along a wall — a dot-path, the sealed-body rule as everywhere, SPEC 9); it is placed by on: / at: alone, so translate: on an opening errors. hinge: start | end picks the jamb by the segment’s draw direction; swing: left | right the side the leaf opens toward — left is the left of the pen’s travel, the named-edge convention (15.5). The chrome is generated children in the thin tone (15.7): a door’s leaf — a line of length width from the hinge jamb, drawn at 90° open — and its quarter swing arc, radius width, sweeping leaf to closed; symbol: double splits two half-width leaves + arcs mirrored about the gap’s centre; symbol: sliding draws two overlapping half-length panel lines offset to either face, no arc (hinge: / swing: on it error). A window draws two sill lines across the gap at the thickness’s thirds — the double-glazing read. An opening past its segment, on a curved one, or overlapping another errors (SPEC 21).

Fixtures. Six symbol-bodied types — the discretes’ pattern (SPEC 16.3), their smart label below the body like a discrete’s value — except an |appliance|’s, which centres in its body: the labelled-box convention ("F", "DW", "W/D"). Fixture and opening labels stay readable like dimension text — ISO-aligned, from the bottom or the right, never upside-down (15.6’s rule, shared). (An opening’s is its schedule tag beside the gap; a |floorplan|’s is the drawing title it inherits, 15.8; a |wall|’s keeps the sketch’s centred read.) width / height are floors as everywhere (SPEC 5) and the body stretches to the resolved box; symbol: picks the variant:

Typesymbol:Body (mm)
|bed|queen (default) · king · double · single1500 × 2000 · 1800 × 2000 · 1350 × 1900 · 900 × 2000
|sofa|three (default) · two · one (the armchair) · corner · stool (the bar stool — a plain round seat)2200 × 900 · 1600 × 900 · 900 × 900 · 2400 × 2400 L · ⌀350
|dining|six (default) · four · roundthe tabletop — 1800 × 900 · 1200 × 800 · ⌀1000 — its chairs (450 × 450, drawn a small pull-back off the edge; six 3 + 3 on the long sides, four 2 + 2, round 4 at the quadrants) extending the bbox
|bath|tub (default) · shower · toilet · sink · double-sink (one unit, two square basins — the kitchen run’s)1700 × 750 · 900 × 900 · 700 × 400 · 500 × 400 · 800 × 450
|appliance|stove (default) · fridge · washer · dishwasher600 × 600 each
|stairs|— (steps: N required, ≥ 2)900 wide × N × 250 run; treads across the flight, the up arrow from the first tread past the last

A counter, island, desk, or coffee table is a plain |rect|; anything else is a |sketch| define — the parts-library escape (15.4). A room name is plain sheet text ("KITCHEN", an authored area beside it); computed room areas, curved-segment openings, and a north arrow are deferred (SPEC 24).