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 derivedratio × 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 300measured 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 > 0deg > 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 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 binarya (-) 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-anchoredhole (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 multiply · a mirrored pair of holes

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 (ae; a portrait, be 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 propertiestitle, 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: 115.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).