forma-dsl-mcp 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -18
- package/package.json +3 -3
- package/src/bin.js +1 -1
- package/src/catalogue.js +15 -15
- package/src/document.js +8 -38
- package/src/examples.js +1 -1
- package/src/guide.js +24 -24
- package/src/reference/README.md +15 -5
- package/src/reference/api/describe-parameters.md +4 -4
- package/src/reference/api/evaluation-context.md +13 -13
- package/src/reference/api/export.md +7 -7
- package/src/reference/api/geometry-node.md +9 -9
- package/src/reference/api/kernel.md +8 -8
- package/src/reference/api/low-level.md +9 -9
- package/src/reference/api/render.md +8 -8
- package/src/reference/api/values.md +12 -12
- package/src/reference/cli.md +146 -0
- package/src/reference/errors.md +14 -14
- package/src/reference/language/control-flow.md +3 -3
- package/src/reference/language/expressions.md +10 -10
- package/src/reference/language/overview.md +11 -11
- package/src/reference/language/scope.md +13 -13
- package/src/reference/language/syntax.md +4 -4
- package/src/reference/reference/align.md +9 -9
- package/src/reference/reference/booleans.md +5 -5
- package/src/reference/reference/component.md +12 -12
- package/src/reference/reference/conversions.md +8 -8
- package/src/reference/reference/functions.md +8 -8
- package/src/reference/reference/local.md +4 -4
- package/src/reference/reference/model.md +10 -10
- package/src/reference/reference/param.md +8 -8
- package/src/reference/reference/part.md +12 -12
- package/src/reference/reference/refinement.md +10 -10
- package/src/reference/reference/shapes-2d.md +15 -15
- package/src/reference/reference/shapes-3d.md +13 -13
- package/src/reference/reference/transforms.md +10 -10
- package/src/reference.js +4 -3
- package/src/roots.js +2 -2
- package/src/server.js +2 -2
- package/src/tools.js +15 -15
- package/src/workspace.js +7 -7
package/src/reference/errors.md
CHANGED
|
@@ -25,7 +25,7 @@ Raised while turning characters into tokens. Always a typo.
|
|
|
25
25
|
|
|
26
26
|
| Message | Cause |
|
|
27
27
|
| --- | --- |
|
|
28
|
-
| `unexpected character "…"` | A character that cannot begin a token
|
|
28
|
+
| `unexpected character "…"` | A character that cannot begin a token - a smart quote, a stray backtick, a non-ASCII dash |
|
|
29
29
|
| `unterminated string` | A `"` with no closing `"`, or a literal newline inside one |
|
|
30
30
|
| `unterminated block comment` | A `/*` with no `*/`. Block comments do not nest |
|
|
31
31
|
| `unknown escape \q` | Only `\n` `\t` `\r` `\"` `\\` `\$` exist |
|
|
@@ -42,7 +42,7 @@ Raised while turning tokens into a tree. Always a grammar mistake.
|
|
|
42
42
|
| `expected a block, found "…"` | Something at the top level that is not a block |
|
|
43
43
|
| `unknown top-level block "shape"` | Only `param`, `local`, `component` and `model` may appear at the top level |
|
|
44
44
|
| `expected an attribute or block, found "…"` | Inside a body, an entry that does not start with a name |
|
|
45
|
-
| `expected {, found "…"` | A `param` with no `=` and no body
|
|
45
|
+
| `expected {, found "…"` | A `param` with no `=` and no body - write `param s { }`. Also a `component` or `model` with no body |
|
|
46
46
|
| `expected =, found "…"` | A `local` with no value |
|
|
47
47
|
| `expected ident, found "…"` | A keyword where a name was required |
|
|
48
48
|
| `"for" cannot be used as a block type` | One of the eleven [keywords](language/syntax.md#keywords) used as a block name |
|
|
@@ -93,7 +93,7 @@ Raised while evaluating.
|
|
|
93
93
|
| Message | Cause |
|
|
94
94
|
| --- | --- |
|
|
95
95
|
| `unknown name "x"` | Nothing in scope binds it. See [Scope](language/scope.md) |
|
|
96
|
-
| `no attribute "x"` | A missing key on an object
|
|
96
|
+
| `no attribute "x"` | A missing key on an object - including `var.x` for something that is not a param |
|
|
97
97
|
| `cannot read "x" of nothing` | A property read on `null` |
|
|
98
98
|
| `cannot index this value` | Indexing something that is not a list, string or object |
|
|
99
99
|
| `cannot negate a non-number` | Unary `-` on a string, list or bool |
|
|
@@ -115,7 +115,7 @@ operator: `outer - inner`. See [Identifiers](language/syntax.md#identifiers).
|
|
|
115
115
|
### `unknown name "var"`
|
|
116
116
|
|
|
117
117
|
A param default referred to `var`, which is assembled only after every param has resolved.
|
|
118
|
-
Use the earlier param's bare name instead
|
|
118
|
+
Use the earlier param's bare name instead - see
|
|
119
119
|
[the root scope](language/scope.md#the-root-scope).
|
|
120
120
|
|
|
121
121
|
---
|
|
@@ -135,11 +135,11 @@ Raised while building geometry.
|
|
|
135
135
|
|
|
136
136
|
| Message | Cause |
|
|
137
137
|
| --- | --- |
|
|
138
|
-
| `unknown block "bloop"
|
|
138
|
+
| `unknown block "bloop" - the blocks are …` | Not a builtin and not a declared component. The message lists every block there is, this document's own components first |
|
|
139
139
|
| `"box" does not take a label` | Only [`part`](reference/part.md) takes a meaningful label |
|
|
140
140
|
| `"box" is a shape and cannot contain other blocks` | A shape is a leaf. Usually a missing `}` swallowed the next block |
|
|
141
141
|
| `"translate" needs at least one shape inside it` | An operation with nothing to act on |
|
|
142
|
-
| `box: unknown attribute "colour"
|
|
142
|
+
| `box: unknown attribute "colour" - box reads …` | Every block rejects attributes it did not read, so a typo fails loudly, and names what it would have read |
|
|
143
143
|
| `attribute "size" is set twice` | The same attribute written twice in one block |
|
|
144
144
|
| `box: "size" must be a number or an array of 3 numbers` | Wrong shape of value |
|
|
145
145
|
| `circle: "radius" must be a finite number` | A `NaN` or `Infinity` reached the block |
|
|
@@ -149,7 +149,7 @@ Raised while building geometry.
|
|
|
149
149
|
| `cylinder: give "radius"/"diameter", or both "bottom_radius" and "top_radius"` | One end radius on its own is ambiguous |
|
|
150
150
|
| `offset: "join" must be one of "Square", "Round", "Miter"` | An enum attribute with an unrecognised value |
|
|
151
151
|
|
|
152
|
-
### `box: unknown attribute "colour"
|
|
152
|
+
### `box: unknown attribute "colour" - box reads "center", "size"`
|
|
153
153
|
|
|
154
154
|
Worth dwelling on, because it is the error that saves the most time. A misspelled attribute
|
|
155
155
|
would otherwise be silent, and the model would render subtly wrong with nothing to point at.
|
|
@@ -158,8 +158,8 @@ anything through.
|
|
|
158
158
|
|
|
159
159
|
Both this and `unknown block` carry the alternatives, because the reader is often a program
|
|
160
160
|
with no copy of this manual: an error that says only what is wrong leaves it guessing at
|
|
161
|
-
spellings. A block that branches on which of two spellings it was given
|
|
162
|
-
`diameter`
|
|
161
|
+
spellings. A block that branches on which of two spellings it was given - `radius` against
|
|
162
|
+
`diameter` - reads only one of them, so the list is what that call would accept rather than
|
|
163
163
|
always the whole set.
|
|
164
164
|
|
|
165
165
|
---
|
|
@@ -170,7 +170,7 @@ always the whole set.
|
|
|
170
170
|
| --- | --- |
|
|
171
171
|
| `"union" cannot mix 2D and 3D shapes` | Children of different dimensionality in one block |
|
|
172
172
|
| `"extrude" takes 2D geometry, got 3D` | A block that fixes what it takes got the other thing |
|
|
173
|
-
| `part "body" is 2D
|
|
173
|
+
| `part "body" is 2D - extrude or revolve it before rendering` | A cross-section reached the renderer |
|
|
174
174
|
| `cannot combine 2D and 3D geometry in a union` | The same mistake, from the [node API](api/geometry-node.md) rather than a document |
|
|
175
175
|
|
|
176
176
|
See [Dimensionality](language/overview.md#dimensionality) for the three rules, and
|
|
@@ -186,7 +186,7 @@ See [Dimensionality](language/overview.md#dimensionality) for the three rules, a
|
|
|
186
186
|
| `c: unknown param "t"` | Components have no pass-through: every value must be declared |
|
|
187
187
|
| `component "c" produced no geometry` | An empty body, a loop over an empty list, or an `if` that took no branch |
|
|
188
188
|
| `a component cannot declare parts` | Scene structure belongs in the model |
|
|
189
|
-
| `nesting deeper than 64 blocks
|
|
189
|
+
| `nesting deeper than 64 blocks - is a component using itself?` | Direct or mutual recursion. Rewrite as a `for` loop |
|
|
190
190
|
|
|
191
191
|
---
|
|
192
192
|
|
|
@@ -200,7 +200,7 @@ See [Dimensionality](language/overview.md#dimensionality) for the three rules, a
|
|
|
200
200
|
| `the document declares no model` | Add a `model` block |
|
|
201
201
|
| `no model named "x"` | `render(source, { model })` named one that does not exist |
|
|
202
202
|
|
|
203
|
-
A [`part`](reference/part.md) nested inside an operation raises **nothing**
|
|
203
|
+
A [`part`](reference/part.md) nested inside an operation raises **nothing** - it is silently
|
|
204
204
|
dropped. If a part is missing from a render, check it is written directly in the model.
|
|
205
205
|
|
|
206
206
|
---
|
|
@@ -217,7 +217,7 @@ dropped. If a part is missing from a render, check it is written directly in the
|
|
|
217
217
|
|
|
218
218
|
| Message | Cause |
|
|
219
219
|
| --- | --- |
|
|
220
|
-
| `align needs a kernel context
|
|
220
|
+
| `align needs a kernel context - render the model rather than building it` | An `Evaluator` built with no context. [`render`](api/render.md) always supplies one |
|
|
221
221
|
| `align needs at least one shape inside it` | Nothing to measure |
|
|
222
222
|
| `align: "x" must be "min", "center", "max" or a number` | The spelling is `"center"` |
|
|
223
223
|
|
|
@@ -239,7 +239,7 @@ Raised by the JavaScript API rather than by a document.
|
|
|
239
239
|
|
|
240
240
|
## Silent behaviours
|
|
241
241
|
|
|
242
|
-
Not errors, and each is deliberate
|
|
242
|
+
Not errors, and each is deliberate - but each has surprised someone. A parameter reaching the
|
|
243
243
|
end of its range should not break a model, so these degenerate quietly:
|
|
244
244
|
|
|
245
245
|
| What | Result |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Control flow
|
|
2
2
|
|
|
3
3
|
Two constructs, both of which appear where a block appears and produce geometry rather than
|
|
4
|
-
a value. Neither is an expression
|
|
4
|
+
a value. Neither is an expression - for a value that depends on a condition, use
|
|
5
5
|
[`cond ? a : b`](expressions.md#conditional).
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -38,7 +38,7 @@ difference {
|
|
|
38
38
|
| **Sequence** | Any expression that evaluates to a list. Iterating anything else is an error: `for: expected a list to iterate` |
|
|
39
39
|
| **Names** | One name binds the value. Two names bind the index and then the value |
|
|
40
40
|
| **Scope** | Each iteration gets a fresh child scope holding just those names |
|
|
41
|
-
| **Result** | The concatenation of what every iteration produced
|
|
41
|
+
| **Result** | The concatenation of what every iteration produced - geometry, and any `part`s |
|
|
42
42
|
|
|
43
43
|
#### Caveats
|
|
44
44
|
|
|
@@ -47,7 +47,7 @@ difference {
|
|
|
47
47
|
[`if`](#if) when a parameter can legitimately reach zero.
|
|
48
48
|
- `range(a, b)` **excludes** `b`, and produces an empty list rather than looping forever when
|
|
49
49
|
the range runs backwards. See [`range`](../reference/functions.md#range).
|
|
50
|
-
- Iterating a string does not work
|
|
50
|
+
- Iterating a string does not work - strings are not lists here. Use
|
|
51
51
|
[`len`](../reference/functions.md#len) and index into it if you need the characters.
|
|
52
52
|
- There is no `break`, no `continue` and no accumulator. A loop that needs to skip items
|
|
53
53
|
should filter the list it iterates, or wrap its body in an `if`.
|
|
@@ -17,7 +17,7 @@ their type, and a mismatch is reported where it is used.
|
|
|
17
17
|
| **object** | `{ w = 3, h = 4 }`, `{ w: 3 }` | `=` and `:` are interchangeable. Read with `.key` |
|
|
18
18
|
| **null** | `null` | Falsy; renders as the empty string in interpolation |
|
|
19
19
|
|
|
20
|
-
There is no separate vector type in the language
|
|
20
|
+
There is no separate vector type in the language - a vector is a list of numbers, and blocks
|
|
21
21
|
say how many components they want. Where a block wants a vector, a **single number is
|
|
22
22
|
accepted as shorthand for every component**, so `scale { factor = 2 }` and
|
|
23
23
|
`scale { factor = [2, 2, 2] }` are the same.
|
|
@@ -69,7 +69,7 @@ What each accepts:
|
|
|
69
69
|
Two lists of different lengths cannot be combined:
|
|
70
70
|
`cannot combine vectors of length 2 and 3`.
|
|
71
71
|
|
|
72
|
-
**Division and modulo by zero are errors**, not `Infinity` or `NaN`
|
|
72
|
+
**Division and modulo by zero are errors**, not `Infinity` or `NaN` - a NaN would otherwise
|
|
73
73
|
travel all the way into the kernel and surface as a degenerate vertex a long way from its
|
|
74
74
|
cause.
|
|
75
75
|
|
|
@@ -89,7 +89,7 @@ as [interpolation](#string-interpolation):
|
|
|
89
89
|
|
|
90
90
|
```hcl
|
|
91
91
|
[1, 2] == [1, 2] // true
|
|
92
|
-
1 == "1" // false
|
|
92
|
+
1 == "1" // false - different types are never equal
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
`<`, `<=`, `>` and `>=` require both sides to be the same type, and compare numbers
|
|
@@ -99,7 +99,7 @@ numerically and strings lexicographically. Mixing them is an error:
|
|
|
99
99
|
### Logic
|
|
100
100
|
|
|
101
101
|
`&&` and `||` short-circuit, so the right side is not evaluated when the left decides the
|
|
102
|
-
answer
|
|
102
|
+
answer - `count > 0 && list[0] > 1` is safe on an empty list. Both always return a bool, even
|
|
103
103
|
when given non-bool operands: `1 || false` is `true`, not `1`.
|
|
104
104
|
|
|
105
105
|
`!x` negates the [truthiness](#truthiness) of any value. Unary `-` requires a number:
|
|
@@ -118,7 +118,7 @@ right-associative, so `a ? b : c ? d : e` chains as `a ? b : (c ? d : e)`.
|
|
|
118
118
|
|
|
119
119
|
## Truthiness
|
|
120
120
|
|
|
121
|
-
Conditions
|
|
121
|
+
Conditions - in `? :`, `&&`, `||`, `!` and [`if`](control-flow.md#if) - accept any value.
|
|
122
122
|
|
|
123
123
|
| Falsy | Truthy |
|
|
124
124
|
| --- | --- |
|
|
@@ -147,7 +147,7 @@ opts.wall // 2
|
|
|
147
147
|
opts["wall"] // 2
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
`.x`, `.y` and `.z` on a **list** are sugar for indices 0, 1 and 2
|
|
150
|
+
`.x`, `.y` and `.z` on a **list** are sugar for indices 0, 1 and 2 - they are not a vector
|
|
151
151
|
type. They are read on a list of any length, so `[1, 2].z` yields nothing rather than
|
|
152
152
|
raising; the failure surfaces later, wherever that nothing is used.
|
|
153
153
|
|
|
@@ -165,7 +165,7 @@ an error: `cannot index this value`.
|
|
|
165
165
|
```hcl
|
|
166
166
|
max(sizes) // variadic, flattens its arguments
|
|
167
167
|
pow(2, 10)
|
|
168
|
-
sin(30) // 0.5
|
|
168
|
+
sin(30) // 0.5 - every angle is in degrees
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
Functions take **positional arguments only**. Named arguments are an error:
|
|
@@ -194,12 +194,12 @@ How each value renders:
|
|
|
194
194
|
|
|
195
195
|
| Value | Rendered as |
|
|
196
196
|
| --- | --- |
|
|
197
|
-
| number | `3`, `0.5`
|
|
197
|
+
| number | `3`, `0.5` - JavaScript's default number formatting |
|
|
198
198
|
| string | itself |
|
|
199
199
|
| bool | `true` / `false` |
|
|
200
|
-
| list | `[1, 2, 3]`
|
|
200
|
+
| list | `[1, 2, 3]` - elements joined by `, ` inside brackets, recursively |
|
|
201
201
|
| `null` | the empty string |
|
|
202
|
-
| object | `{w: 3, h: 4}`
|
|
202
|
+
| object | `{w: 3, h: 4}` - each entry rendered by these same rules. Interpolating one is usually a mistake, but it at least names which object |
|
|
203
203
|
|
|
204
204
|
A splice must contain exactly one expression; anything left over is an error:
|
|
205
205
|
`trailing input in ${ }`. Errors raised inside a splice are reported at the position of the
|
|
@@ -21,7 +21,7 @@ model "stack" { // something to render
|
|
|
21
21
|
}
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Order does not matter between declarations of different kinds
|
|
24
|
+
Order does not matter between declarations of different kinds - a `model` may use a
|
|
25
25
|
`component` declared below it. Order does matter among `local`s, which are evaluated top to
|
|
26
26
|
bottom. See [Scope and names](scope.md).
|
|
27
27
|
|
|
@@ -41,11 +41,11 @@ mistake it was.
|
|
|
41
41
|
1. **Lexer** turns characters into tokens. It rejects unknown characters, unterminated
|
|
42
42
|
strings and unterminated block comments.
|
|
43
43
|
2. **Parser** turns tokens into an abstract syntax tree. It rejects anything that is not
|
|
44
|
-
grammatical
|
|
44
|
+
grammatical - a missing brace, a keyword used as a block type, a `param` block containing
|
|
45
45
|
nested blocks.
|
|
46
46
|
3. **Evaluator** resolves params and locals, then walks the models. It rejects unknown names,
|
|
47
47
|
bad operands, unknown blocks, unknown attributes and dimensionality mismatches. Its output
|
|
48
|
-
is a tree of [geometry nodes](../api/geometry-node.md)
|
|
48
|
+
is a tree of [geometry nodes](../api/geometry-node.md) - it never calls the kernel itself.
|
|
49
49
|
4. **Kernel** solves the node tree into concrete solids, memoized by node digest in an
|
|
50
50
|
[`EvaluationContext`](../api/evaluation-context.md).
|
|
51
51
|
|
|
@@ -95,9 +95,9 @@ An operation with no children is an error; a shape with children is an error.
|
|
|
95
95
|
|
|
96
96
|
Every piece of geometry is either 2D or 3D, and the language tracks which.
|
|
97
97
|
|
|
98
|
-
- **2D shapes**
|
|
99
|
-
`polygon`
|
|
100
|
-
- **3D shapes**
|
|
98
|
+
- **2D shapes** - `rect`, `rounded_rect`, `circle`, `ellipse`, `regular_polygon`, `stadium`,
|
|
99
|
+
`polygon` - produce cross-sections.
|
|
100
|
+
- **3D shapes** - `box`, `sphere`, `cylinder`, `cone`, `torus` - produce solids.
|
|
101
101
|
- **Operations** either fix what they accept (`extrude` takes 2D, `refine` takes 3D) or pass
|
|
102
102
|
their children's dimensionality through (`translate`, `rotate`, `hull`, the booleans).
|
|
103
103
|
|
|
@@ -107,7 +107,7 @@ Three rules follow, and each has its own error message:
|
|
|
107
107
|
| --- | --- |
|
|
108
108
|
| The children of one block must all be the same dimensionality | `"union" cannot mix 2D and 3D shapes` |
|
|
109
109
|
| A block that fixes what it takes must get it | `"extrude" takes 2D geometry, got 3D` |
|
|
110
|
-
| Anything rendered must be 3D | `part "body" is 2D
|
|
110
|
+
| Anything rendered must be 3D | `part "body" is 2D - extrude or revolve it before rendering` |
|
|
111
111
|
|
|
112
112
|
To get from 2D to 3D use [`extrude`](../reference/conversions.md#extrude) or
|
|
113
113
|
[`revolve`](../reference/conversions.md#revolve); to go back use
|
|
@@ -118,8 +118,8 @@ To get from 2D to 3D use [`extrude`](../reference/conversions.md#extrude) or
|
|
|
118
118
|
|
|
119
119
|
## Units and orientation
|
|
120
120
|
|
|
121
|
-
- **Lengths are unitless.** Every consumer treats them as millimetres
|
|
122
|
-
either
|
|
121
|
+
- **Lengths are unitless.** Every consumer treats them as millimetres - STL has no unit
|
|
122
|
+
either - but nothing in the language enforces that.
|
|
123
123
|
- **Angles are always in degrees.** Everywhere: `rotate { z = 90 }`, `revolve { angle = 180 }`,
|
|
124
124
|
`sin(30)`, `param a { type = angle }`. There is no radian form in the language. The
|
|
125
125
|
JavaScript [`Angle`](../api/values.md#angle) class stores radians internally, and that is
|
|
@@ -134,7 +134,7 @@ To get from 2D to 3D use [`extrude`](../reference/conversions.md#extrude) or
|
|
|
134
134
|
|
|
135
135
|
## Identity is content
|
|
136
136
|
|
|
137
|
-
The evaluator does not produce a call graph
|
|
137
|
+
The evaluator does not produce a call graph - it produces a tree of immutable nodes, each
|
|
138
138
|
carrying a 128-bit digest of its own content and its children's digests. Two structurally
|
|
139
139
|
identical subtrees have the same digest and therefore *are* the same node.
|
|
140
140
|
|
|
@@ -142,7 +142,7 @@ That has consequences worth designing around:
|
|
|
142
142
|
|
|
143
143
|
- A component used twenty times is evaluated once. Eight spheres in a loop cost one sphere
|
|
144
144
|
and eight transforms.
|
|
145
|
-
- Two shapes written with their attributes in a different order still share a cache entry
|
|
145
|
+
- Two shapes written with their attributes in a different order still share a cache entry -
|
|
146
146
|
object keys are sorted before they are digested.
|
|
147
147
|
- The members of a `union` are sorted by subtree size, so two unions written in a different
|
|
148
148
|
order also share a digest. `difference` is never reordered, because its first child means
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Scope and names
|
|
2
2
|
|
|
3
3
|
Every name in a document resolves through a chain of scopes. This page says exactly what is
|
|
4
|
-
in each one, and in what order they are built
|
|
4
|
+
in each one, and in what order they are built - which is what determines whether a
|
|
5
5
|
declaration may refer to another.
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -10,12 +10,12 @@ declaration may refer to another.
|
|
|
10
10
|
|
|
11
11
|
Built once, before any geometry, in this order:
|
|
12
12
|
|
|
13
|
-
1. **Constants**
|
|
14
|
-
2. **Type names**
|
|
13
|
+
1. **Constants** - `pi` and `e`.
|
|
14
|
+
2. **Type names** - `number`, `string`, `bool`, `vector`, `list`, `angle`, each bound to
|
|
15
15
|
itself as a string, so `type = number` in a param block resolves with no quoting.
|
|
16
16
|
3. **Params**, in source order. Each is bound under its bare name as it is resolved, so a
|
|
17
17
|
later param's default may refer to an earlier one.
|
|
18
|
-
4. **`var`**
|
|
18
|
+
4. **`var`** - an object holding every resolved param.
|
|
19
19
|
5. **Locals**, in source order. Each is bound as it is resolved, so a later local may refer
|
|
20
20
|
to an earlier one.
|
|
21
21
|
|
|
@@ -24,10 +24,10 @@ The order has two consequences worth knowing:
|
|
|
24
24
|
```hcl
|
|
25
25
|
param a = 3
|
|
26
26
|
param b = a * 2 // ✓ an earlier param, by its bare name
|
|
27
|
-
param c = var.a * 2 // ✗ unknown name "var"
|
|
27
|
+
param c = var.a * 2 // ✗ unknown name "var" - var does not exist yet
|
|
28
28
|
|
|
29
29
|
local d = var.a + 1 // ✓ locals are built after var
|
|
30
|
-
local e = f // ✗ unknown name "f"
|
|
30
|
+
local e = f // ✗ unknown name "f" - a later local
|
|
31
31
|
local f = 2
|
|
32
32
|
```
|
|
33
33
|
|
|
@@ -45,8 +45,8 @@ model "m" {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
They differ when something shadows the bare name. `var` holds **params only**
|
|
49
|
-
locals, never component params, never loop variables
|
|
48
|
+
They differ when something shadows the bare name. `var` holds **params only** - never
|
|
49
|
+
locals, never component params, never loop variables - so `var.NAME` is the unambiguous way
|
|
50
50
|
to reach an input:
|
|
51
51
|
|
|
52
52
|
```hcl
|
|
@@ -68,8 +68,8 @@ of use.
|
|
|
68
68
|
|
|
69
69
|
## Body scopes
|
|
70
70
|
|
|
71
|
-
Every block body
|
|
72
|
-
branch
|
|
71
|
+
Every block body - a `model`, a `component`, a builtin block, a `for` iteration, an `if`
|
|
72
|
+
branch - evaluates in a fresh scope nested inside its parent.
|
|
73
73
|
|
|
74
74
|
**Locals declared in a body are visible to the whole body**, not only to what follows them,
|
|
75
75
|
because every local in a body is resolved before any of its blocks are built:
|
|
@@ -82,7 +82,7 @@ model "m" {
|
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
That is the opposite of the top-level rule, where locals are strictly ordered. Within one
|
|
85
|
-
body, ordering between locals still applies
|
|
85
|
+
body, ordering between locals still applies - a body local may not refer to a later body
|
|
86
86
|
local.
|
|
87
87
|
|
|
88
88
|
---
|
|
@@ -110,7 +110,7 @@ model "m" {
|
|
|
110
110
|
Inside a component body, these are in scope:
|
|
111
111
|
|
|
112
112
|
- its own `param`s, bound from the attributes at the call site
|
|
113
|
-
- everything in the root scope
|
|
113
|
+
- everything in the root scope - constants, type names, document params (bare and via `var.`)
|
|
114
114
|
and document locals
|
|
115
115
|
- its own body locals
|
|
116
116
|
|
|
@@ -123,7 +123,7 @@ Nothing from the caller. Values travel in through params and no other way.
|
|
|
123
123
|
A `model` may declare its own `param` blocks. They behave like locals with a declaration
|
|
124
124
|
syntax, not like document params:
|
|
125
125
|
|
|
126
|
-
- Each **must** have a default
|
|
126
|
+
- Each **must** have a default - `model "m": param "s" needs a default`.
|
|
127
127
|
- They are **not** settable from outside. `render(source, { params: { s: 9 } })` sets
|
|
128
128
|
document params only; a model param keeps its default.
|
|
129
129
|
- They are bound in the model's scope under the bare name, and are not added to `var`.
|
|
@@ -31,7 +31,7 @@ Decimal, with an optional fraction and an optional exponent.
|
|
|
31
31
|
There is no hexadecimal, no separator, and no unit suffix. A leading `-` is the unary minus
|
|
32
32
|
operator, not part of the literal.
|
|
33
33
|
|
|
34
|
-
`2e` and `2em` are **not** numbers with exponents
|
|
34
|
+
`2e` and `2em` are **not** numbers with exponents - the lexer backs out of a bare `e` and
|
|
35
35
|
lets it start an identifier, so `2e` lexes as the number `2` followed by the name `e`.
|
|
36
36
|
|
|
37
37
|
---
|
|
@@ -46,7 +46,7 @@ Double quotes only. A string may not contain a literal newline.
|
|
|
46
46
|
"a tab\there, a quote\", a literal \${ not a splice"
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
**Escapes.** Exactly six: `\n`, `\t`, `\r`, `\"`, `\\` and `\$`. Anything else is an error
|
|
49
|
+
**Escapes.** Exactly six: `\n`, `\t`, `\r`, `\"`, `\\` and `\$`. Anything else is an error -
|
|
50
50
|
`unknown escape \q`. `\$` exists so a literal `${` can be written.
|
|
51
51
|
|
|
52
52
|
**Interpolation.** `${ ... }` splices in any expression. Braces inside the splice are
|
|
@@ -65,7 +65,7 @@ height wall_thickness _private min-width part2
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
> **Hyphens are part of a name.** `a-b` is one identifier, not `a` minus `b`. Write
|
|
68
|
-
> subtraction with spaces: `a - b`. This is the single most common surprise in the lexer
|
|
68
|
+
> subtraction with spaces: `a - b`. This is the single most common surprise in the lexer -
|
|
69
69
|
> `local gap = outer-inner` fails with `unknown name "outer-inner"`.
|
|
70
70
|
|
|
71
71
|
### Keywords
|
|
@@ -111,7 +111,7 @@ Labels come in two flavours:
|
|
|
111
111
|
| `component "name"`, `model "name"` | Static | The name must be a plain string or identifier. `${ }` is rejected: `a label cannot contain ${ }` |
|
|
112
112
|
| `part "name"` | An expression | Evaluated at build time, so a part in a loop can name itself: `part "leg_${i}"` |
|
|
113
113
|
|
|
114
|
-
Blocks that take no label reject one
|
|
114
|
+
Blocks that take no label reject one - `"box" does not take a label`. Two blocks accept a
|
|
115
115
|
label and quietly ignore it: [`align`](../reference/align.md) and
|
|
116
116
|
[component calls](../reference/component.md).
|
|
117
117
|
|
|
@@ -46,13 +46,13 @@ the *minimum* face at that coordinate. `z = 5` means "sit this on a surface at h
|
|
|
46
46
|
|
|
47
47
|
#### Returns
|
|
48
48
|
|
|
49
|
-
The children, unioned and translated. Dimensionality passes through
|
|
49
|
+
The children, unioned and translated. Dimensionality passes through - a 2D child accepts
|
|
50
50
|
`x` and `y` only.
|
|
51
51
|
|
|
52
52
|
#### Caveats
|
|
53
53
|
|
|
54
54
|
- **`align` needs a kernel context.** It is the one block that cannot be evaluated without
|
|
55
|
-
one: `align needs a kernel context
|
|
55
|
+
one: `align needs a kernel context - render the model rather than building it`. Building a
|
|
56
56
|
node tree with a bare `Evaluator` and no context will fail on it.
|
|
57
57
|
- **It costs a kernel round-trip.** The child has to be solved before the alignment can be
|
|
58
58
|
computed, so the subtree is evaluated at build time rather than lazily. The digest cache
|
|
@@ -60,7 +60,7 @@ The children, unioned and translated. Dimensionality passes through — a 2D chi
|
|
|
60
60
|
- It measures the **bounding box**, not the geometry. For anything that is not
|
|
61
61
|
axis-aligned, the box is larger than the shape.
|
|
62
62
|
- Children are unioned first, so several blocks inside one `align` move together.
|
|
63
|
-
- A label
|
|
63
|
+
- A label - `align "name" { }` - is accepted and silently ignored.
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
@@ -81,7 +81,7 @@ align {
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Most shapes take a `center = true` attribute, which is cheaper
|
|
84
|
+
Most shapes take a `center = true` attribute, which is cheaper - it changes how the shape is
|
|
85
85
|
built rather than measuring it afterwards. Reach for `align` when the thing being centred is
|
|
86
86
|
a **composition** rather than a single shape, and there is no one attribute to set.
|
|
87
87
|
|
|
@@ -131,9 +131,9 @@ align {
|
|
|
131
131
|
|
|
132
132
|
| Approach | When |
|
|
133
133
|
| --- | --- |
|
|
134
|
-
| `center = true` on the shape | A single primitive. Free
|
|
134
|
+
| `center = true` on the shape | A single primitive. Free - it changes how the shape is built |
|
|
135
135
|
| `translate` by a known amount | You already know the dimensions, usually because they are params |
|
|
136
|
-
| `align` | The extent is **computed**
|
|
136
|
+
| `align` | The extent is **computed** - a hull, a composition, an offset outline - and writing the arithmetic would mean duplicating it |
|
|
137
137
|
|
|
138
138
|
A model that knows its own dimensions should use them:
|
|
139
139
|
|
|
@@ -154,7 +154,7 @@ kernel round-trip. For anything small, prefer clarity.
|
|
|
154
154
|
|
|
155
155
|
## Troubleshooting
|
|
156
156
|
|
|
157
|
-
### `align needs a kernel context
|
|
157
|
+
### `align needs a kernel context - render the model rather than building it`
|
|
158
158
|
|
|
159
159
|
An `Evaluator` was constructed without a `context`, and the document contains an `align`.
|
|
160
160
|
[`render`](../api/render.md) always supplies one, so this only comes up when driving the
|
|
@@ -170,12 +170,12 @@ unaffected.
|
|
|
170
170
|
|
|
171
171
|
### `align: "x" must be "min", "center", "max" or a number`
|
|
172
172
|
|
|
173
|
-
The value is a string that is not one of the three keywords
|
|
173
|
+
The value is a string that is not one of the three keywords - `"middle"` and `"centre"` are
|
|
174
174
|
the usual culprits. The spelling is `"center"`.
|
|
175
175
|
|
|
176
176
|
### `align needs at least one shape inside it`
|
|
177
177
|
|
|
178
|
-
The body produced nothing
|
|
178
|
+
The body produced nothing - an `if` that took no branch, or a `for` over an empty list.
|
|
179
179
|
|
|
180
180
|
### `align: unknown attribute "z"` on a 2D shape
|
|
181
181
|
|
|
@@ -27,7 +27,7 @@ None.
|
|
|
27
27
|
|
|
28
28
|
- **Usually unnecessary.** Sibling blocks are already implicitly unioned, so
|
|
29
29
|
`part "p" { a b }` and `part "p" { union { a b } }` are the same. Write `union`
|
|
30
|
-
explicitly when it makes the grouping clearer
|
|
30
|
+
explicitly when it makes the grouping clearer - typically as one operand of a
|
|
31
31
|
`difference`.
|
|
32
32
|
- The order of the children does not affect the result, and does not affect the digest
|
|
33
33
|
either: union members are sorted by subtree size before hashing, so two unions written in a
|
|
@@ -86,7 +86,7 @@ None.
|
|
|
86
86
|
|
|
87
87
|
#### Caveats
|
|
88
88
|
|
|
89
|
-
- With one child, it is that child. With none, it is an error
|
|
89
|
+
- With one child, it is that child. With none, it is an error -
|
|
90
90
|
`"intersection" needs at least one shape inside it`.
|
|
91
91
|
- Order does not affect the result.
|
|
92
92
|
|
|
@@ -149,7 +149,7 @@ intersection {
|
|
|
149
149
|
}
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
For a single flat cut, [`trim`](refinement.md#trim) is cheaper
|
|
152
|
+
For a single flat cut, [`trim`](refinement.md#trim) is cheaper - it cuts with a half-space
|
|
153
153
|
and needs no second solid.
|
|
154
154
|
|
|
155
155
|
### Grouping so a transform applies to several shapes
|
|
@@ -164,7 +164,7 @@ translate {
|
|
|
164
164
|
}
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
Inside a `difference`, though, each child is separate
|
|
167
|
+
Inside a `difference`, though, each child is separate - so to subtract two shapes *as one
|
|
168
168
|
body*, group them explicitly:
|
|
169
169
|
|
|
170
170
|
```hcl
|
|
@@ -178,7 +178,7 @@ difference {
|
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
Here the `union` is not decorative: without it the two cylinders would be two separate
|
|
181
|
-
cutters, which happens to give the same result for a difference
|
|
181
|
+
cutters, which happens to give the same result for a difference - but not for an
|
|
182
182
|
`intersection`, where it changes the answer entirely.
|
|
183
183
|
|
|
184
184
|
---
|
|
@@ -27,7 +27,7 @@ model "m" {
|
|
|
27
27
|
Declares a block named `NAME`. Its body is ordinary geometry; its `param` declarations are
|
|
28
28
|
the attributes a call site may pass.
|
|
29
29
|
|
|
30
|
-
Once declared, it is called like any other block
|
|
30
|
+
Once declared, it is called like any other block - the call site cannot tell a component
|
|
31
31
|
from a builtin, which is the point.
|
|
32
32
|
|
|
33
33
|
#### Body
|
|
@@ -41,7 +41,7 @@ from a builtin, which is the point.
|
|
|
41
41
|
|
|
42
42
|
#### Returns
|
|
43
43
|
|
|
44
|
-
One geometry node
|
|
44
|
+
One geometry node - everything in the body, unioned. The dimensionality is whatever the body
|
|
45
45
|
produced, so a component may return 2D or 3D geometry and be used wherever that fits.
|
|
46
46
|
|
|
47
47
|
#### Caveats
|
|
@@ -60,8 +60,8 @@ produced, so a component may return 2D or 3D geometry and be used wherever that
|
|
|
60
60
|
branch, fails with `component "c" produced no geometry`.
|
|
61
61
|
- **Recursion is not supported.** A component that calls itself has no base case the
|
|
62
62
|
evaluator can see, and hits the nesting limit:
|
|
63
|
-
`nesting deeper than 64 blocks
|
|
64
|
-
- A label on a call
|
|
63
|
+
`nesting deeper than 64 blocks - is a component using itself?`
|
|
64
|
+
- A label on a call - `my_component "label" { }` - is accepted and silently ignored.
|
|
65
65
|
Do not rely on it.
|
|
66
66
|
|
|
67
67
|
---
|
|
@@ -88,7 +88,7 @@ component "tray" {
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
Note the empty braces on `lid`. `param lid` on its own is a **syntax error**
|
|
91
|
+
Note the empty braces on `lid`. `param lid` on its own is a **syntax error** - a param needs
|
|
92
92
|
either `= expression` or a braced body, even an empty one.
|
|
93
93
|
|
|
94
94
|
Inside the body, a param is read by its bare name. There is no `var.` for component params;
|
|
@@ -172,7 +172,7 @@ model "m" {
|
|
|
172
172
|
### Reuse is free
|
|
173
173
|
|
|
174
174
|
Identity in the geometry tree is content identity, so a component called twenty times with
|
|
175
|
-
the same arguments is **evaluated once**
|
|
175
|
+
the same arguments is **evaluated once** - its digest is the same each time, and the kernel
|
|
176
176
|
sees one node. Calling it twenty times with twenty different sizes does cost twenty
|
|
177
177
|
evaluations, because the content genuinely differs.
|
|
178
178
|
|
|
@@ -190,7 +190,7 @@ A `param` with no default and no body:
|
|
|
190
190
|
|
|
191
191
|
```hcl
|
|
192
192
|
component "c" {
|
|
193
|
-
param s // ✗ syntax error
|
|
193
|
+
param s // ✗ syntax error - the parser is looking for `=` or `{`
|
|
194
194
|
box { size = [s, s, s] }
|
|
195
195
|
}
|
|
196
196
|
|
|
@@ -207,19 +207,19 @@ the call site, or give the declaration a default.
|
|
|
207
207
|
|
|
208
208
|
### `c: unknown param "t"`
|
|
209
209
|
|
|
210
|
-
The call passed an attribute the component does not declare
|
|
210
|
+
The call passed an attribute the component does not declare - usually a typo, or an
|
|
211
211
|
attribute meant for a block inside the component. Components have no pass-through: every
|
|
212
212
|
value they need must be declared.
|
|
213
213
|
|
|
214
214
|
### `component "box" shadows a builtin block`
|
|
215
215
|
|
|
216
|
-
The name is already a builtin. Pick another
|
|
216
|
+
The name is already a builtin. Pick another - `rounded_box`, `my_box`, `case_body`. See the
|
|
217
217
|
[full block list](../README.md#shapes) for what is taken.
|
|
218
218
|
|
|
219
219
|
### `a component cannot declare parts`
|
|
220
220
|
|
|
221
221
|
A [`part`](part.md) is a piece of the scene, not geometry an enclosing block can operate on,
|
|
222
|
-
so it cannot come out of a component. Invert it
|
|
222
|
+
so it cannot come out of a component. Invert it - put the part in the model and call the
|
|
223
223
|
component inside it:
|
|
224
224
|
|
|
225
225
|
```hcl
|
|
@@ -231,7 +231,7 @@ component "c" { box { size = [1, 1, 1] } }
|
|
|
231
231
|
model "m" { part "p" { c { } } }
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
### `nesting deeper than 64 blocks
|
|
234
|
+
### `nesting deeper than 64 blocks - is a component using itself?`
|
|
235
235
|
|
|
236
236
|
Almost always direct or mutual recursion. There is no base case the evaluator can detect,
|
|
237
237
|
because a component's body is expanded before any condition inside it is known. Rewrite the
|
|
@@ -239,7 +239,7 @@ recursion as a [`for`](../language/control-flow.md#for) loop.
|
|
|
239
239
|
|
|
240
240
|
### `component "c" produced no geometry`
|
|
241
241
|
|
|
242
|
-
The body evaluated to nothing
|
|
242
|
+
The body evaluated to nothing - an empty body, a loop over an empty list, or an `if` with no
|
|
243
243
|
`else` whose condition was false. A component must return something; guard at the call site
|
|
244
244
|
instead:
|
|
245
245
|
|