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:
| Child | Is | Drawn |
|---|---|---|
a box (|sketch|, |rect|, |oval|, |hole|, …) | geometry — a part or a feature | its outline and fill, at the shared datum |
a link with a measuring op — (-) (o) (<) — or a leader op — <- *- >- | an annotation | extension lines, arrows, text (15.6, 15.7) |
a link with || | a mate — or, with a sheet-content end, a seat | nothing — it positions a part or an annotation (15.5) |
any other two-ended link (->, <->, -->, -*, …) | a straight annotation arrow | one segment, markers per the op |
"…", |note|, |balloon|, |table|, |surface-finish|, |feature-control|, |datum|, … | sheet content | per 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:andalong:have no role on them;clearance:reads as a dimension’s stand-off minimum (15.6). - No auto-create. Unlike a diagram (
cat -> doginvents 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
:segmenta\|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 everywhere | Drawing-scope only |
|---|---|
|sketch| + draw: / mirror: / revolve: / thread: / break:; pattern:; scale:; hatch() fills; stroke-style: center / phantom; |note| / |balloon| / |hidden|; the |page| sheet | the 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: 2reads 2 : 1,scale: 0.5reads 1 : 2, and the composed section / detail / view titles read it directly (15.8). Magnitude isscale:’s job — a 5 m beam on an A4 isscale: 0.02(1 : 50), never a resolution fudge.unit:— the physical size of one drawing unit:mm(a drawing’s default),cm,m,in, orpx— one unit is one pixel, the non-physical case, and a plainstack’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: Non 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. Underunit: pxthere are no millimetres to convert, so only the identitydensity: 1applies 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,
centerits centre. Corners glue vertical word first, matchingpin’s vocabulary (pin: top left→:top-left); the reversed order errors with a did-you-mean. Corners and:centerare 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 indraw:(15.3) — declared in the pen, selected on an endpoint, the same declare / select symmetry as#id. Built-in names win (:leftcannot be authored); an unknown segment errors with suggestions;mirror:copies of a segment are not addressable (SPEC 24) — apattern: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).
| Call | Does |
|---|---|
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:
| On | Names | Example |
|---|---|---|
| a drawing call | that call’s drawn segment: an edge, an arc, a bevel, a circle, a close() seam | right(50):neck, fillet(3):r1 |
point() | the pen’s current point | right(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:
| Item | Axis (through the node’s origin) | Gives |
|---|---|---|
x-axis | the horizontal axis (y = 0) | top ↔ bottom symmetry |
y-axis | the vertical axis (x = 0) | left ↔ right symmetry |
a number 45 | the 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 adjoiningchamfer()’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:
| Form | Copies |
|---|---|
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 || bandb || aare 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:toperrors), 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 placegapgoes below zero). Point anchors (center, a freestanding name) make the points coincide — the barea || bis the origin-to-origin case — and have no normal, sogap: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 owntranslate:applies after — the universal post-placement nudge, here a lateral slide along the face. Apin: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:
| Ends | Reads | Who moves |
|---|---|---|
geometry || geometry | a mate | the grounding walk above |
annotation || geometry | a seat | the annotation, always — either operand order |
annotation || annotation | error | seat 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, sorotate: -90stands 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.
| Annotation | Default seat anchor |
|---|---|
|surface-finish| | the symbol’s tip — the vee stands on the face (15.9) |
everything else — |feature-control|, |datum|, |note|, |balloon|, a bundle | the 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).
| Write | Reads | Renders |
|---|---|---|
a:left (-) b:right | a linear span | extension lines, arrows, 25 |
a:left (-) b (-) c | a chain | each hop its own dim, one shared row |
pin (o) | a round feature | the ⌀ line across the circle — both arrows on the rims — 2× ⌀10 |
hole:top (o) | a round feature, side-anchored | the diametral line through the circle |
bore:top (o) | any node, side-anchored | the span to the opposite side, ⌀-read — ⌀16 |
body:neck (o) | a revolved-profile segment | the station’s span across the axis — ⌀28 |
body:r1 (o) | a named arc | a leader — R3 |
body:flank (<) body:base | two line-like anchors | the angle arc — 40° |
body:taper (<) | a mirrored- / revolved-profile segment | the 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:
| Source | Owns | Example |
|---|---|---|
| the op | the glyph | (o) → ⌀ / R · (<) → ° · tol: → ± (linear (-) adds none — a plain length) |
| the geometry | the number | 10 |
| the label | the words | two-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, appended | tol: 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 | 2× · 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
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:
| Op | Tip on the feature | For |
|---|---|---|
<- | arrow | an edge or outline |
*- | dot | a leader landing within an outline — a face, a region |
>- | datum triangle | a 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 gainsdatum(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|’sdatums: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 standsnote-offsetclear 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’stranslatenudges 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:
| Producer | Generates |
|---|---|
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 geometry | its |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: Nplaces it: the plane runs perpendicular to an axis at stationN, 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 | downturns the arrows — defaultrightfor a vertical plane,downfor 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 withtranslate: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 },aa|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 ownscale:read directly (1:1at the default,2:1enlarged,1:1.5reduced, ≤ 2 dp — 15.1). - The detail view —
|drawing#det| { of: c },ca|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 magnifyingscale: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: | Draws | Means |
|---|---|---|
basic (default) | the bare vee | any process |
machined | vee + bar | material removal required |
prohibited | vee + circle | removal 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):
| Group | Characteristics | datums: |
|---|---|---|
| form | straightness · flatness · circularity · cylindricity | forbidden |
| profile | profile-line · profile-surface | optional |
| orientation | angularity · perpendicularity · parallelism | required |
| location | position (optional) · concentricity · symmetry (required) | — |
| runout | circular-runout · total-runout | required |
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-datummaximum/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:
- Geometry per child, bottom-up: fold
draw:to a path (corner modifiers applied cyclically throughclose()), collect its:segments, applymirror:/revolve:(+ the edge lines andthread:dressing), expandpattern:, buildbreak:’s view map; nested drawings lower first, becoming rigid subtrees. Compute each node’s geometry bbox (stroke excluded) and paint bbox (core). - Place children: origins on the datum,
translate:applied. - Mates: walk from the ground; rotate first, seat, the child’s own translate after; flag cycles and over-constraints.
- Measure every annotation’s anchors against the seated, unbroken geometry;
compose the texts (glyph + number / label +
tol:+ count). - Annotate: assign dims to sides and pack the rows in source order; auto-place callout texts outward; ray-cast leader tips; land the elbow.
- 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>. - 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:
| Type | symbol: | Body (mm) |
|---|---|---|
|bed| | queen (default) · king · double · single | 1500 × 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 · round | the 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 · dishwasher | 600 × 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).