Reuse and extension
Three ways to add your own vocabulary, each narrow on purpose, and none of them a way to run code.
Every format eventually meets something it does not know: your chair, your finish, your shingle grain. bimtex has three places to put that, and it is deliberately three narrow ones rather than one escape hatch.
| adds | where it shows | |
|---|---|---|
module + instance | repetition | everywhere, as real entities |
symboldef | a plan symbol | the drawings |
lookdef + look | a 3D appearance | only the 3D |
New compile behaviour still belongs in code. None of these is a plugin.
Repetition stays repetition
{"t":"module","id":"cluster","entities":[{"t":"furniture","id":"desk-nw","type":"desk","x":0,"y":0}]}
{"t":"instance","id":"cluster-w1","module":"cluster","level":"l1","x":5.2,"y":11.2}Edit the module once and every instance recompiles. The point is not typing less — it is that the file still says these desks are the same desk, so a change cannot land on fifteen of the sixteen.
Instances namespace what they contain: that stamp produces cluster-w1/desk-nw.
Local symbols and materials
A symboldef may use only rect, line, circle, arc and polygon, in normalised
coordinates. A materialdef picks a category, a value step, a hatch and a
procedural texture, and its enums are exact — a wrong one is a named error, not
a silent drop.
Narrow on purpose: expressive enough to name a local chair or finish, tight enough to validate before the renderer ever sees it.
The appearance layer
symboldef says what a thing looks like on a plan. lookdef says what it looks
like in 3D, and it is much the largest of the three: a node tree in Three.js's
own vocabulary, carried as data inside the same NDJSON file.
One rule decides what may go in it:
If a drawing must show it, it is semantic. If only the 3D shows it, it is appearance.
Window muntins appear on real elevations, so they are schema. Shingle grain does not, so it is a lookdef.
That rule is enforced, not asserted. The acceptance suite compiles a dressed model and its undressed twin and compares every sheet; they come out identical.
So a lookdef cannot fix a single line of any drawing — and if you are reaching for appearance to solve a drawing problem, you have diagnosed the wrong thing.
Three properties follow, and they are why this is safe to leave unchecked:
- Interpreted, never executed. The renderer walks the graph through a whitelist. No eval, no function field, no escape hatch.
- The envelope law. A body is affine-fitted into the compiled envelope of whatever it dresses, so it may subdivide that box infinitely and can never outgrow it. The drawings drew the box; the look lives inside it.
- Appearance never blocks. A malformed body, an unknown node, an
over-budget definition — each falls back to the plain semantic volumes. A
model that declares no appearance is still a complete deliverable, and one
is: of the twelve shipped examples,
15-courtyard-estatecarries nolookdefat all and its drawings are no less complete for it.
A look line binds a definition's open slots and chooses between definitions
claiming the same selector. Only materials are slottable. A slot carrying a
dimension would let the plan and the model disagree about the same bed, which is
the one failure this layer exists to prevent. Size is already parameterised by
the envelope.
Packs are files, and the library carries their build
A pack remains a plain NDJSON file and the authoring source for reusable elements. Shipped packs are also built into the bimtex library. A model can name one without pasting its body:
{"t":"look","id":"oak-look","for":"tree:oak","use":"broadleaf-oak-2"}The package compiler resolves that id against the catalogue bundled with the
installed library version. This is pinned like \usepackage{tikz} is pinned by
a TeX distribution: no server, network request, filesystem search or ambient
registry participates. An unknown id simply keeps the plain semantic volume.
Inlining is still available when the file must carry the whole look, and a local definition with the same id always wins:
cat packs/trees.ndjson model.ndjson > dressed.ndjsonConcatenation is therefore an import mechanism, not the only import mechanism. Every shipped pack is browsable at /elements, built by the same interpreter a model gets.
The grammar, the node whitelist, the budgets and the extension nodes are in
10 · Appearance;
packs/AUTHORING.md
is the brief to hand a model that is writing one.