forma-dsl-mcp 0.1.4

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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/package.json +52 -0
  4. package/src/bin.js +30 -0
  5. package/src/catalogue.js +703 -0
  6. package/src/document.js +285 -0
  7. package/src/examples.js +212 -0
  8. package/src/guide.js +313 -0
  9. package/src/reference/README.md +153 -0
  10. package/src/reference/api/describe-parameters.md +167 -0
  11. package/src/reference/api/evaluation-context.md +221 -0
  12. package/src/reference/api/export.md +185 -0
  13. package/src/reference/api/geometry-node.md +215 -0
  14. package/src/reference/api/kernel.md +182 -0
  15. package/src/reference/api/low-level.md +239 -0
  16. package/src/reference/api/render.md +217 -0
  17. package/src/reference/api/values.md +223 -0
  18. package/src/reference/errors.md +253 -0
  19. package/src/reference/language/control-flow.md +160 -0
  20. package/src/reference/language/expressions.md +206 -0
  21. package/src/reference/language/overview.md +156 -0
  22. package/src/reference/language/scope.md +184 -0
  23. package/src/reference/language/syntax.md +210 -0
  24. package/src/reference/reference/align.md +189 -0
  25. package/src/reference/reference/booleans.md +231 -0
  26. package/src/reference/reference/component.md +248 -0
  27. package/src/reference/reference/conversions.md +269 -0
  28. package/src/reference/reference/functions.md +314 -0
  29. package/src/reference/reference/local.md +197 -0
  30. package/src/reference/reference/model.md +213 -0
  31. package/src/reference/reference/param.md +230 -0
  32. package/src/reference/reference/part.md +229 -0
  33. package/src/reference/reference/refinement.md +284 -0
  34. package/src/reference/reference/shapes-2d.md +309 -0
  35. package/src/reference/reference/shapes-3d.md +279 -0
  36. package/src/reference/reference/transforms.md +246 -0
  37. package/src/reference.js +160 -0
  38. package/src/server.js +219 -0
  39. package/src/tools.js +523 -0
  40. package/src/workspace.js +204 -0
@@ -0,0 +1,248 @@
1
+ # `component`
2
+
3
+ A reusable, parameterised piece of geometry, callable exactly like a builtin block.
4
+
5
+ ```hcl
6
+ component "rounded_box" {
7
+ param size = [20, 20, 10]
8
+ param radius = 3
9
+
10
+ extrude {
11
+ height = size.z
12
+ rounded_rect { size = [size.x, size.y] radius = radius center = true }
13
+ }
14
+ }
15
+
16
+ model "m" {
17
+ rounded_box { size = [40, 30, 12] radius = 4 }
18
+ }
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Reference
24
+
25
+ ### `component "NAME" { ... }`
26
+
27
+ Declares a block named `NAME`. Its body is ordinary geometry; its `param` declarations are
28
+ the attributes a call site may pass.
29
+
30
+ Once declared, it is called like any other block — the call site cannot tell a component
31
+ from a builtin, which is the point.
32
+
33
+ #### Body
34
+
35
+ | Entry | Meaning |
36
+ | --- | --- |
37
+ | `param NAME = expr` or `param NAME { default = expr }` | An input. Without a default, the call site must supply it |
38
+ | `local NAME = expr` | A working value, scoped to the component |
39
+ | geometry blocks | The component's output. Several are implicitly unioned |
40
+ | `for` / `if` | Ordinary control flow |
41
+
42
+ #### Returns
43
+
44
+ One geometry node — everything in the body, unioned. The dimensionality is whatever the body
45
+ produced, so a component may return 2D or 3D geometry and be used wherever that fits.
46
+
47
+ #### Caveats
48
+
49
+ - **A component cannot declare [`part`](part.md)s.** Parts are pieces of the finished scene,
50
+ not operands: `a component cannot declare parts`. Call the component *inside* a part
51
+ instead.
52
+ - **A component cannot shadow a builtin.** `component "box"` is an error:
53
+ `component "box" shadows a builtin block`. Components and builtins share one namespace.
54
+ - **A component body cannot see the call site.** It is evaluated in a fresh scope rooted at
55
+ the document, so a local where it is used does not leak in. See
56
+ [Scope → Component isolation](../language/scope.md#component-isolation).
57
+ - **Only declared params may be passed.** An extra attribute is an error:
58
+ `c: unknown param "t"`. There is no catch-all.
59
+ - **A component must produce geometry.** An empty body, or one whose only `if` took no
60
+ branch, fails with `component "c" produced no geometry`.
61
+ - **Recursion is not supported.** A component that calls itself has no base case the
62
+ evaluator can see, and hits the nesting limit:
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
+ Do not rely on it.
66
+
67
+ ---
68
+
69
+ ## Usage
70
+
71
+ ### Declaring inputs
72
+
73
+ Both `param` forms work, and a param with no default is required at every call site:
74
+
75
+ ```hcl
76
+ component "tray" {
77
+ param size = [40, 30, 12] // has a default; optional at the call site
78
+ param wall = 2
79
+ param lid { } // no default; every call must pass it
80
+
81
+ difference {
82
+ rounded_box { size = size radius = 4 }
83
+ translate {
84
+ offset = [0, 0, wall]
85
+ rounded_box { size = [size.x - wall * 2, size.y - wall * 2, size.z] radius = 3 }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ Note the empty braces on `lid`. `param lid` on its own is a **syntax error** — a param needs
92
+ either `= expression` or a braced body, even an empty one.
93
+
94
+ Inside the body, a param is read by its bare name. There is no `var.` for component params;
95
+ `var` holds document params only, and those are in scope too.
96
+
97
+ ### Composing components
98
+
99
+ Components can call other components, which is how a model is built up in layers:
100
+
101
+ ```hcl
102
+ component "rounded_box" {
103
+ param size = [20, 20, 10]
104
+ param radius = 3
105
+ extrude { height = size.z rounded_rect { size = [size.x, size.y] radius = radius center = true } }
106
+ }
107
+
108
+ component "tray" {
109
+ param size = [40, 30, 12]
110
+ param wall = 2
111
+ difference {
112
+ rounded_box { size = size radius = 4 }
113
+ translate { offset = [0, 0, wall] rounded_box { size = size - [wall * 2, wall * 2, 0] radius = 3 } }
114
+ }
115
+ }
116
+
117
+ model "stacked_trays" {
118
+ for i in range(0, var.rows) {
119
+ part "tray_${i}" {
120
+ translate { offset = [0, 0, i * 13] tray { size = [40, 30, 12] wall = 2 } }
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ Nesting may go 64 levels deep, which is far more than any real model needs.
127
+
128
+ ### Naming a repeated cut
129
+
130
+ A component does not have to be a whole object. Naming the *negative* of a feature is often
131
+ what makes a model readable:
132
+
133
+ ```hcl
134
+ component "counterbore" {
135
+ param depth = 4
136
+ param bolt = 3
137
+ param head = 5.5
138
+
139
+ union {
140
+ cylinder { radius = bolt height = 40 center = true }
141
+ translate { offset = [0, 0, -depth] cylinder { radius = head height = depth + 40 } }
142
+ }
143
+ }
144
+
145
+ model "plate" {
146
+ difference {
147
+ box { size = [60, 60, 8] }
148
+ for p in [[12, 12], [48, 12], [12, 48], [48, 48]] {
149
+ translate { offset = [p.x, p.y, 8] counterbore { depth = 3 } }
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ### 2D components
156
+
157
+ A component that produces a 2D shape is used wherever a 2D shape is:
158
+
159
+ ```hcl
160
+ component "slot_profile" {
161
+ param length = 30
162
+ param width = 6
163
+
164
+ stadium { size = [length, width] center = true }
165
+ }
166
+
167
+ model "m" {
168
+ extrude { height = 5 slot_profile { length = 40 } }
169
+ }
170
+ ```
171
+
172
+ ### Reuse is free
173
+
174
+ Identity in the geometry tree is content identity, so a component called twenty times with
175
+ the same arguments is **evaluated once** — its digest is the same each time, and the kernel
176
+ sees one node. Calling it twenty times with twenty different sizes does cost twenty
177
+ evaluations, because the content genuinely differs.
178
+
179
+ That makes it worth parameterising a component on what varies and letting position be a
180
+ `translate` on the outside: twenty copies of one shape in twenty places cost one shape and
181
+ twenty transforms.
182
+
183
+ ---
184
+
185
+ ## Troubleshooting
186
+
187
+ ### `expected {, found "box"`
188
+
189
+ A `param` with no default and no body:
190
+
191
+ ```hcl
192
+ component "c" {
193
+ param s // ✗ syntax error — the parser is looking for `=` or `{`
194
+ box { size = [s, s, s] }
195
+ }
196
+
197
+ component "c" {
198
+ param s { } // ✓ declared, required at the call site
199
+ box { size = [s, s, s] }
200
+ }
201
+ ```
202
+
203
+ ### `c: "s" has no default and was not given`
204
+
205
+ The component declares `s` with no default, and the call passed nothing. Either supply it at
206
+ the call site, or give the declaration a default.
207
+
208
+ ### `c: unknown param "t"`
209
+
210
+ The call passed an attribute the component does not declare — usually a typo, or an
211
+ attribute meant for a block inside the component. Components have no pass-through: every
212
+ value they need must be declared.
213
+
214
+ ### `component "box" shadows a builtin block`
215
+
216
+ The name is already a builtin. Pick another — `rounded_box`, `my_box`, `case_body`. See the
217
+ [full block list](../README.md#shapes) for what is taken.
218
+
219
+ ### `a component cannot declare parts`
220
+
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 — put the part in the model and call the
223
+ component inside it:
224
+
225
+ ```hcl
226
+ // ✗
227
+ component "c" { part "p" { box { size = [1, 1, 1] } } }
228
+
229
+ // ✓
230
+ component "c" { box { size = [1, 1, 1] } }
231
+ model "m" { part "p" { c { } } }
232
+ ```
233
+
234
+ ### `nesting deeper than 64 blocks — is a component using itself?`
235
+
236
+ Almost always direct or mutual recursion. There is no base case the evaluator can detect,
237
+ because a component's body is expanded before any condition inside it is known. Rewrite the
238
+ recursion as a [`for`](../language/control-flow.md#for) loop.
239
+
240
+ ### `component "c" produced no geometry`
241
+
242
+ The body evaluated to nothing — an empty body, a loop over an empty list, or an `if` with no
243
+ `else` whose condition was false. A component must return something; guard at the call site
244
+ instead:
245
+
246
+ ```hcl
247
+ if var.show_ribs { ribs { } }
248
+ ```
@@ -0,0 +1,269 @@
1
+ # 2D ↔ 3D
2
+
3
+ Four operations that change dimensionality. Two give a profile thickness; two take a
4
+ cross-section of a solid.
5
+
6
+ | Block | Direction | Description |
7
+ | --- | --- | --- |
8
+ | [`extrude`](#extrude) | 2D → 3D | Sweeps a profile along Z |
9
+ | [`revolve`](#revolve) | 2D → 3D | Spins a profile about the Y axis |
10
+ | [`project`](#project) | 3D → 2D | The shadow of a solid on the XY plane |
11
+ | [`slice`](#slice) | 3D → 2D | The cross-section at one Z |
12
+
13
+ Each unions its children before acting on them, and each insists on the dimensionality it
14
+ takes: `"extrude" takes 2D geometry, got 3D`.
15
+
16
+ ---
17
+
18
+ ## `extrude`
19
+
20
+ Sweeps a 2D profile along Z, optionally twisting and tapering as it goes.
21
+
22
+ ```hcl
23
+ extrude { height = 20 circle { radius = 10 } }
24
+
25
+ extrude {
26
+ height = 40
27
+ twist = 90
28
+ scale_top = [0.5, 0.5]
29
+ divisions = 24
30
+ rect { size = [20, 20] center = true }
31
+ }
32
+ ```
33
+
34
+ #### Attributes
35
+
36
+ | Attribute | Type | Default | Description |
37
+ | --- | --- | --- | --- |
38
+ | `height` | number | **required** | How far to sweep, along +Z |
39
+ | `twist` | angle | `0` | Degrees of rotation from bottom to top |
40
+ | `scale_top` | 2-vector | `1` | Size of the top face relative to the bottom. A single number scales both axes |
41
+ | `divisions` | integer | `0` | Intermediate layers. Needed for a smooth twist or taper |
42
+ | `center` | bool | `false` | `true` centres the result on Z = 0; otherwise it stands on the XY plane |
43
+
44
+ #### Caveats
45
+
46
+ - **`divisions` matters whenever `twist` or `scale_top` is set.** With the default `0` the
47
+ sides are straight lines between the two end faces, so a twist becomes a single sheared
48
+ step rather than a helix. Set it to the number of intermediate layers you want.
49
+ - A `height` of `0` or less yields empty geometry rather than an error, so a parameter
50
+ sweeping to zero does not break the model.
51
+ - `scale_top = [0, 0]` gives a point, which is one way to build a pyramid from any profile.
52
+ - The profile is swept as drawn — `extrude` does not centre it. A `rect` that is not centred
53
+ produces a box in the positive quadrant.
54
+
55
+ ---
56
+
57
+ ## `revolve`
58
+
59
+ Spins a 2D profile about the **Y axis** into a solid of revolution.
60
+
61
+ ```hcl
62
+ revolve { segments = 96 polygon { points = profile } }
63
+ revolve { angle = 180 segments = 64 polygon { points = profile } }
64
+ ```
65
+
66
+ #### Attributes
67
+
68
+ | Attribute | Type | Default | Description |
69
+ | --- | --- | --- | --- |
70
+ | `angle` | angle | `360` | How far round to sweep, in degrees |
71
+ | `segments` | integer | `0` | Segments around; `0` lets the kernel choose |
72
+
73
+ #### Caveats
74
+
75
+ - **The profile is written in the XY plane, and its Y becomes the result's Z.** X is the
76
+ radius. A profile drawn from `[0, 0]` upwards produces a solid standing on the XY plane.
77
+ - **Every X must be ≥ 0.** A profile that crosses the axis revolves through itself and the
78
+ result is not what you drew.
79
+ - The profile does not have to touch the axis. One that does not produces a tube — which is
80
+ how [`torus`](shapes-3d.md#torus) is built internally.
81
+ - An `angle` of `0` or less yields empty geometry rather than an error.
82
+
83
+ ---
84
+
85
+ ## `project`
86
+
87
+ The shadow a solid casts on the XY plane — its full outline, from any Z.
88
+
89
+ ```hcl
90
+ project { bracket { } }
91
+ ```
92
+
93
+ #### Attributes
94
+
95
+ None.
96
+
97
+ #### Caveats
98
+
99
+ - Takes 3D geometry and produces 2D: `"project" takes 3D geometry, got 2D`.
100
+ - The result is the union of everything, seen from above. Internal voids that are covered
101
+ from above do not appear.
102
+
103
+ ---
104
+
105
+ ## `slice`
106
+
107
+ The cross-section of a solid at one height.
108
+
109
+ ```hcl
110
+ slice { z = 12 bracket { } }
111
+ ```
112
+
113
+ #### Attributes
114
+
115
+ | Attribute | Type | Default | Description |
116
+ | --- | --- | --- | --- |
117
+ | `z` | number | `0` | The plane to cut at |
118
+
119
+ #### Caveats
120
+
121
+ - Takes 3D geometry and produces 2D.
122
+ - A plane that misses the solid gives an empty cross-section, not an error.
123
+ - A plane exactly on a flat face is ambiguous — offset it slightly.
124
+
125
+ ---
126
+
127
+ ## Usage
128
+
129
+ ### Profiles are where the modelling happens
130
+
131
+ Most shapes are easier to describe in 2D and then given thickness. A rounded box, for
132
+ instance, has no primitive — it is a rounded rectangle with a height:
133
+
134
+ ```hcl
135
+ extrude {
136
+ height = 12
137
+ rounded_rect { size = [40, 30] radius = 4 center = true }
138
+ }
139
+ ```
140
+
141
+ ### Turning a bottle, a vase, a knob
142
+
143
+ `revolve` on a hand-written profile is how anything with rotational symmetry is built. Read
144
+ the points as (radius, height):
145
+
146
+ ```hcl
147
+ local profile = [
148
+ [0, 0], [26, 0], [24, 6], [16, 26],
149
+ [14, 48], [18, 66], [22, 74], [0, 74],
150
+ ]
151
+
152
+ // The same silhouette walked inwards by one wall, so the difference leaves a shell.
153
+ local cavity = [
154
+ [0, 3], [21, 3], [20, 8], [13, 26],
155
+ [11, 48], [15, 66], [19, 72], [0, 72],
156
+ ]
157
+
158
+ model "vase" {
159
+ part "vase" {
160
+ difference {
161
+ revolve { segments = 96 polygon { points = profile } }
162
+ translate {
163
+ offset = [0, 0, 3]
164
+ revolve { segments = 96 polygon { points = cavity } }
165
+ }
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ The hollowing is the same trick as anywhere else: the solid minus a slightly smaller copy of
172
+ itself.
173
+
174
+ ### Twisted extrusions
175
+
176
+ A twist needs divisions, or there is nothing to twist between:
177
+
178
+ ```hcl
179
+ extrude {
180
+ height = 60
181
+ twist = 180
182
+ divisions = 40 // without this the sides are two straight lines
183
+ regular_polygon { sides = 6 radius = 12 }
184
+ }
185
+ ```
186
+
187
+ ### Tapers and pyramids
188
+
189
+ ```hcl
190
+ extrude { height = 30 scale_top = 0.4 rect { size = [40, 40] center = true } } // frustum
191
+ extrude { height = 30 scale_top = 0 rect { size = [40, 40] center = true } } // pyramid
192
+ ```
193
+
194
+ ### Deriving a base plate from a part
195
+
196
+ `project` turns a finished solid back into an outline, which is useful for a footprint, a
197
+ gasket or a support pad:
198
+
199
+ ```hcl
200
+ model "m" {
201
+ part "body" { bracket { } }
202
+
203
+ part "pad" {
204
+ color = "#c8842f"
205
+ translate {
206
+ offset = [0, 0, -3]
207
+ extrude {
208
+ height = 3
209
+ offset { amount = 2 project { bracket { } } } // footprint, grown 2 mm
210
+ }
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ ### Inspecting a cross-section
217
+
218
+ `slice` is a debugging tool as much as a modelling one — extrude the slice thinly to see
219
+ where the walls actually fell:
220
+
221
+ ```hcl
222
+ part "section" {
223
+ color = "#ff6b7f"
224
+ extrude { height = 0.5 slice { z = 40 bottle { } } }
225
+ }
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Troubleshooting
231
+
232
+ ### `"extrude" takes 2D geometry, got 3D`
233
+
234
+ The child is already a solid. Extrusion is for profiles:
235
+
236
+ ```hcl
237
+ extrude { height = 10 box { size = [1, 1, 1] } } // ✗
238
+ extrude { height = 10 rect { size = [1, 1] } } // ✓
239
+ ```
240
+
241
+ ### `"extrude" cannot mix 2D and 3D shapes`
242
+
243
+ Two children of different dimensionality. Extrude each profile separately, or union the
244
+ profiles before extruding.
245
+
246
+ ### `part "body" is 2D — extrude or revolve it before rendering`
247
+
248
+ A profile reached the renderer. Somewhere the `extrude` or `revolve` is missing, or is
249
+ wrapped around the wrong block.
250
+
251
+ ### My twist looks like a single shear
252
+
253
+ `divisions` defaults to `0`, which means the extrusion has no intermediate layers. Set it to
254
+ 20–60 for a smooth twist.
255
+
256
+ ### My revolved solid is inside out, or has a spike through the middle
257
+
258
+ Part of the profile has a negative X, so it crossed the axis of revolution. Every point must
259
+ have X ≥ 0.
260
+
261
+ ### My revolved solid is lying down
262
+
263
+ `revolve` maps the profile's **Y** to the result's **Z**. A profile written as (x, z) with
264
+ the height in the first component produces a solid on its side — swap the components.
265
+
266
+ ### `revolve` produced nothing
267
+
268
+ Either the `angle` reached `0`, or the profile has zero area. Both yield empty geometry
269
+ silently, by design, so that a parameter at the end of its range does not break a model.