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,230 @@
1
+ # `param`
2
+
3
+ A declared input. Params are what the editor turns into controls and what
4
+ [`render`](../api/render.md) accepts values for.
5
+
6
+ ```hcl
7
+ param height { type = number default = 20 min = 8 max = 40 step = 1 }
8
+ param wall = 2.5
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Reference
14
+
15
+ ### `param NAME { ... }`
16
+
17
+ Declares an input at the top level of a document. Read it as `var.NAME`, or by its bare name.
18
+
19
+ ```hcl
20
+ param bolts {
21
+ type = number
22
+ default = 6
23
+ min = 3
24
+ max = 16
25
+ step = 1
26
+ description = "How many holes on the bolt circle"
27
+ }
28
+ ```
29
+
30
+ #### Attributes
31
+
32
+ Every attribute is optional. **Only `default` affects geometry** — the rest describe the
33
+ control the editor should build, and are returned verbatim by
34
+ [`describeParameters`](../api/describe-parameters.md).
35
+
36
+ | Attribute | Type | Description |
37
+ | --- | --- | --- |
38
+ | `default` | any | The value used when the caller supplies none. Without it the param is *required* |
39
+ | `type` | `number` `string` `bool` `vector` `list` `angle` | Which control to build. Written bare, without quotes. Inferred from `default` when omitted |
40
+ | `min` | number | Lower bound of the control |
41
+ | `max` | number | Upper bound of the control |
42
+ | `step` | number | Granularity of the control |
43
+ | `description` | string | Help text |
44
+ | `options` | list | A fixed set of choices, for a dropdown rather than a free field |
45
+
46
+ Unknown attributes are **not** rejected here — a param block is metadata, and anything in it
47
+ is passed through to the descriptor. This is the one block in the language that does not
48
+ reject attributes it does not understand.
49
+
50
+ #### Returns
51
+
52
+ Nothing. A param binds a name; it produces no geometry.
53
+
54
+ #### Caveats
55
+
56
+ - **`min`, `max`, `step` and `options` are not enforced.** They shape the editor's control
57
+ and nothing else. `render(source, { params: { bolts: 9999 } })` succeeds on a param
58
+ declared `max = 16`. Validate in the model if a bound matters —
59
+ `local n = min(max(var.bolts, 3), 16)`.
60
+ - A param block may contain **attributes only**. Nested blocks inside it are an error:
61
+ `a param block holds attributes, not nested blocks`.
62
+ - Declaring the same param twice is an error: `param "a" is declared twice`.
63
+ - A param's `default` may refer to params declared **above** it by their bare name, but not
64
+ to `var` — `var` is assembled after every param has resolved. See
65
+ [Scope](../language/scope.md#the-root-scope).
66
+ - A param declared inside a `model` is a different thing: it must have a default and cannot
67
+ be set from outside. See [`model`](model.md#model-params).
68
+
69
+ ---
70
+
71
+ ### `param NAME = expression`
72
+
73
+ Shorthand for a param whose only attribute is `default`.
74
+
75
+ ```hcl
76
+ param thickness = 8
77
+ param sizes = [1.5, 2, 2.5, 3]
78
+ ```
79
+
80
+ Identical to `param thickness { default = 8 }`. Use it when the editor needs no bounds and
81
+ no description.
82
+
83
+ ---
84
+
85
+ ## Usage
86
+
87
+ ### Reading a param
88
+
89
+ Both spellings work; `var.` is the convention because it marks the value as an input at the
90
+ point of use.
91
+
92
+ ```hcl
93
+ param height = 20
94
+
95
+ model "m" {
96
+ extrude { height = var.height circle { radius = 10 } }
97
+ // ^^^^^^^^^^ or just `height`, same value
98
+ }
99
+ ```
100
+
101
+ `var` holds **params only**. A [`local`](local.md) with the same name shadows the bare name
102
+ but never appears under `var`, which is what makes `var.` unambiguous:
103
+
104
+ ```hcl
105
+ param a = 1
106
+ local a = 2
107
+
108
+ model "m" { box { size = [a, var.a, 1] } } // [2, 1, 1]
109
+ ```
110
+
111
+ ### Choosing a type
112
+
113
+ `type` is what the editor reads to decide which control to draw.
114
+
115
+ ```hcl
116
+ param count { type = number default = 6 min = 1 max = 20 step = 1 } // slider
117
+ param label { type = string default = "A" } // text field
118
+ param hollow { type = bool default = true } // checkbox
119
+ param size { type = vector default = [40, 30, 12] } // three fields
120
+ param sweep { type = angle default = 45 min = 0 max = 360 } // degrees
121
+ ```
122
+
123
+ When `type` is omitted it is inferred from the default: a number gives `number`, a boolean
124
+ `bool`, a string `string`, a list `vector`. A param with neither a `type` nor a `default`
125
+ falls back to `number`.
126
+
127
+ An `angle` param is an ordinary number as far as the language is concerned — every angle in
128
+ forma is in degrees, and the type only tells the editor to label the control that way.
129
+
130
+ ### Declaring a required input
131
+
132
+ A param with no `default` has no value until one is supplied. Rendering without it fails:
133
+
134
+ ```hcl
135
+ param serial { type = string }
136
+ ```
137
+
138
+ ```
139
+ param "serial" has no default and no value was supplied
140
+ ```
141
+
142
+ [`describeParameters`](../api/describe-parameters.md) reports it as `required: true`, so a
143
+ front end can insist on a value before rendering. This is how a document says "there is no
144
+ sensible default for this".
145
+
146
+ ### Deriving one input from another
147
+
148
+ Params are resolved in source order, so a later default may be written in terms of an
149
+ earlier one:
150
+
151
+ ```hcl
152
+ param diameter = 72
153
+ param wall = 1.6
154
+ param bore = diameter / 2 - wall * 4 // ✓ bare names of earlier params
155
+ ```
156
+
157
+ For a value that is always derived and should never be a control, use a
158
+ [`local`](local.md) instead — locals are computed after every param and may use `var.`.
159
+
160
+ ### Offering a fixed set of choices
161
+
162
+ ```hcl
163
+ param finish {
164
+ type = string
165
+ default = "matte"
166
+ options = ["matte", "gloss", "raw"]
167
+ }
168
+
169
+ model "m" {
170
+ part "body" {
171
+ color = var.finish == "gloss" ? "#e8eef4" : "#8f9ba6"
172
+ box { size = [20, 20, 20] }
173
+ }
174
+ }
175
+ ```
176
+
177
+ `options` is carried through to the descriptor for the editor to render as a dropdown.
178
+ Nothing checks that a supplied value is one of them, so branch with a final `else` that is
179
+ safe.
180
+
181
+ ---
182
+
183
+ ## Troubleshooting
184
+
185
+ ### `param "height" has no default and no value was supplied`
186
+
187
+ The document declares the param with no `default`, and the caller passed no value. Either
188
+ give it a default, or supply one:
189
+
190
+ ```js
191
+ await render(source, { params: { height: 20 } });
192
+ ```
193
+
194
+ [`describeParameters`](../api/describe-parameters.md) works on such a document — it is built
195
+ to be callable before any value exists, so a front end can discover what to ask for.
196
+
197
+ ### `param "a" is declared twice`
198
+
199
+ Two top-level `param` blocks share a name. Unlike locals, params are rejected rather than
200
+ overwritten, because a duplicated input would make the editor's control ambiguous.
201
+
202
+ ### `a param block holds attributes, not nested blocks`
203
+
204
+ Something inside the braces was parsed as a block rather than an attribute — usually a
205
+ missing `=`:
206
+
207
+ ```hcl
208
+ param height { default 20 } // ✗ parsed as a block named `default`
209
+ param height { default = 20 } // ✓
210
+ ```
211
+
212
+ ### `unknown name "var"`
213
+
214
+ A param default referred to `var`. `var` is assembled only after every param has been
215
+ resolved, so it does not exist yet:
216
+
217
+ ```hcl
218
+ param a = 3
219
+ param b = var.a * 2 // ✗ unknown name "var"
220
+ param b = a * 2 // ✓ earlier params are in scope by their bare name
221
+ ```
222
+
223
+ ### The editor ignores my `min` and `max`
224
+
225
+ They are advisory. The control respects them, but a value supplied through the JavaScript
226
+ API does not, and the geometry never sees them. Clamp in the model if it matters:
227
+
228
+ ```hcl
229
+ local bolts = max(3, min(16, var.bolts))
230
+ ```
@@ -0,0 +1,229 @@
1
+ # `part`
2
+
3
+ A separately coloured piece of the rendered scene.
4
+
5
+ ```hcl
6
+ model "assembly" {
7
+ part "frame" { color = "#6f7d8c" box { size = [40, 40, 4] } }
8
+ part "pin" { color = "#c8842f" translate { offset = [0, 0, 4] cylinder { radius = 3 height = 10 } } }
9
+ }
10
+ ```
11
+
12
+ ---
13
+
14
+ ## Reference
15
+
16
+ ### `part "NAME" { ... }`
17
+
18
+ Groups geometry into one named, coloured piece of the scene. Parts are solved independently
19
+ and drawn in their own colour.
20
+
21
+ A part is **not an operand**. It does not contribute geometry to the block around it, and an
22
+ enclosing `translate` will not move it — which is why a part belongs directly in a
23
+ [`model`](model.md), or inside a `for` or `if` in one.
24
+
25
+ #### Label
26
+
27
+ Optional, and an **expression** rather than a fixed string, so a part built in a loop can
28
+ name itself:
29
+
30
+ ```hcl
31
+ part "tray_${i}" { ... }
32
+ ```
33
+
34
+ Without a label, the part is named `part_<line>` after the line it was written on.
35
+
36
+ #### Attributes
37
+
38
+ | Attribute | Type | Default | Description |
39
+ | --- | --- | --- | --- |
40
+ | `color` | string | `"#b8c4d0"` | Any CSS colour the viewer understands. Usually `#rrggbb` |
41
+ | `opacity` | number | `1` | `0` is invisible, `1` is opaque. Useful for showing contents through a shell |
42
+
43
+ #### Returns
44
+
45
+ Nothing, to the enclosing block. The part travels separately and lands in the scene.
46
+
47
+ #### Caveats
48
+
49
+ - **A part cannot contain another part**: `a part cannot contain another part`. Parts are a
50
+ flat list, not a tree.
51
+ - **A part nested inside an operation is silently dropped.** `translate { part "p" { … } }`
52
+ discards the part with no error, because an operation collects geometry from its children
53
+ and a part contributes none. Declare parts directly in the model. See
54
+ [Troubleshooting](#a-part-inside-an-operation-disappears).
55
+ - **A part cannot be empty**: `a part needs at least one shape inside it`. To make a part
56
+ conditional, put the [`if`](../language/control-flow.md#if) around it, not inside it.
57
+ - **A part cannot come out of a [`component`](component.md)**:
58
+ `a component cannot declare parts`. Call the component inside a part instead.
59
+ - **Every part must be 3D** by the time it renders:
60
+ `part "body" is 2D — extrude or revolve it before rendering`.
61
+ - Several blocks inside a part are implicitly unioned, exactly as anywhere else.
62
+ - Nothing rejects two parts with the same name; the viewer will simply list two entries
63
+ called the same thing.
64
+
65
+ ---
66
+
67
+ ## Usage
68
+
69
+ ### Colouring an assembly
70
+
71
+ The common case. Each physical piece gets a part, and the colours make the render readable:
72
+
73
+ ```hcl
74
+ model "water_bottle" {
75
+ part "bottle" { color = "#8fd3e8" bottle_shell { } }
76
+ part "cap" { color = "#e2593c" translate { offset = [0, 0, 170] cap { } } }
77
+ part "label" { color = "#f4f1e4" label_band { } }
78
+ }
79
+ ```
80
+
81
+ ### Showing what is inside
82
+
83
+ `opacity` makes a shell see-through without changing the geometry:
84
+
85
+ ```hcl
86
+ model "assembly" {
87
+ part "case" {
88
+ color = "#8fa8bd"
89
+ opacity = 0.35 // the board below shows through
90
+ case_shell { }
91
+ }
92
+
93
+ part "board" { color = "#2f7d4f" translate { offset = [0, 0, 3] board { } } }
94
+ }
95
+ ```
96
+
97
+ Opacity is a display property. It does not affect the solid, the digest, or an exported STL.
98
+
99
+ ### Parts in a loop
100
+
101
+ A part label is an expression, so the loop variable can name each one:
102
+
103
+ ```hcl
104
+ model "stack" {
105
+ for i in range(0, var.rows) {
106
+ part "tray_${i}" {
107
+ color = i % 2 == 0 ? "#3f7fbf" : "#bf6b3f"
108
+ translate { offset = [0, 0, i * 13] tray { } }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Making a part optional
115
+
116
+ Put the condition **outside** the part:
117
+
118
+ ```hcl
119
+ // ✓
120
+ if var.show_label {
121
+ part "label" { color = "#f4f1e4" label_band { } }
122
+ }
123
+
124
+ // ✗ — when show_label is false this is an empty part, which is an error
125
+ part "label" {
126
+ if var.show_label { label_band { } }
127
+ }
128
+ ```
129
+
130
+ ### One part, several pieces of geometry
131
+
132
+ Everything in a part is unioned, so a part may hold as many blocks as it needs:
133
+
134
+ ```hcl
135
+ part "bottle" {
136
+ color = "#8fd3e8"
137
+
138
+ difference { // the shell…
139
+ bottle_solid { }
140
+ bottle_void { }
141
+ }
142
+
143
+ for i in range(0, 3) { // …plus the thread ridges, same part
144
+ translate { offset = [0, 0, 174 + i * 4.5] torus { radius = 15.6 tube_radius = 1.1 } }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ---
150
+
151
+ ## How parts reach the renderer
152
+
153
+ [`render`](../api/render.md) returns one entry per part:
154
+
155
+ ```js
156
+ const result = await render(source);
157
+
158
+ for (const part of result.parts) {
159
+ part.name; // "bottle"
160
+ part.color; // "#8fd3e8"
161
+ part.opacity; // 1
162
+ part.node; // the GeometryNode — what context.collect() wants
163
+ part.concrete; // the solved Manifold solid, owned by the context
164
+ part.mesh; // positions and normals, ready to upload
165
+ }
166
+ ```
167
+
168
+ Parts are solved independently, so two parts that share a subtree still solve it once — the
169
+ digest cache is keyed on content, not on which part asked.
170
+
171
+ To export one part, pass its `concrete` to [`toBinarySTL`](../api/export.md#tobinarystl).
172
+
173
+ ---
174
+
175
+ ## Troubleshooting
176
+
177
+ ### `a part needs at least one shape inside it`
178
+
179
+ The part's body produced nothing — usually an `if` inside it that took no branch, or a `for`
180
+ over an empty list. Move the condition outside the part.
181
+
182
+ ### `a part cannot contain another part`
183
+
184
+ Parts are flat. To group several coloured pieces, declare them as siblings in the model. If
185
+ the grouping matters for positioning, apply the same `translate` inside each one, or hoist
186
+ the shared placement into a [`component`](component.md).
187
+
188
+ ### `a component cannot declare parts`
189
+
190
+ Geometry comes out of a component; scene structure does not. Invert it:
191
+
192
+ ```hcl
193
+ component "shell" { ... } // geometry only
194
+ model "m" { part "shell" { shell { } } } // the part lives here
195
+ ```
196
+
197
+ ### `part "body" is 2D — extrude or revolve it before rendering`
198
+
199
+ The part holds a cross-section. Extrude or revolve it first — see
200
+ [2D ↔ 3D](conversions.md).
201
+
202
+ ### A part inside an operation disappears
203
+
204
+ A part is not an operand, so an operation collects no geometry from it. What happens next
205
+ depends on whether the operation had anything else to work with:
206
+
207
+ ```hcl
208
+ // ✗ fails: "translate" needs at least one shape inside it
209
+ translate { offset = [0, 0, 10] part "p" { box { size = [1, 1, 1] } } }
210
+
211
+ // ✗ worse — renders, but part "p" is silently gone from the scene
212
+ translate {
213
+ offset = [0, 0, 10]
214
+ box { size = [1, 1, 1] }
215
+ part "p" { sphere { radius = 1 } }
216
+ }
217
+
218
+ // ✓ the transform belongs inside the part
219
+ part "p" { translate { offset = [0, 0, 10] box { size = [1, 1, 1] } } }
220
+ ```
221
+
222
+ Nothing warns about the second case. If a part you declared is missing from the render,
223
+ check that it is written directly in the [`model`](model.md) — or inside a `for` or `if` in
224
+ one — and not nested inside a transform or a boolean.
225
+
226
+ ### Two parts have the same name
227
+
228
+ Nothing rejects it. Part names come from an expression, so a loop that computes the same
229
+ label twice produces duplicates — include the loop index: `part "leg_${i}"`.