forma-dsl-mcp 0.3.8 → 0.4.0
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 +22 -18
- package/package.json +2 -2
- 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 +199 -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
|
@@ -29,7 +29,7 @@ the node's digest.
|
|
|
29
29
|
| | |
|
|
30
30
|
| --- | --- |
|
|
31
31
|
| **Parameter** | `node: GeometryNode` |
|
|
32
|
-
| **Returns** | `Promise<Solid \| Shape>`
|
|
32
|
+
| **Returns** | `Promise<Solid \| Shape>` - a `Manifold` for a 3D node, a `CrossSection` for a 2D one |
|
|
33
33
|
| **Throws** | If the context has been disposed |
|
|
34
34
|
|
|
35
35
|
Concurrent requests for the same node share one evaluation, so a shape used twice in a tree
|
|
@@ -41,12 +41,12 @@ Frees every cached object the given roots can no longer reach.
|
|
|
41
41
|
|
|
42
42
|
| | |
|
|
43
43
|
| --- | --- |
|
|
44
|
-
| **`roots`** | `readonly GeometryNode[]`
|
|
44
|
+
| **`roots`** | `readonly GeometryNode[]` - the trees still in use |
|
|
45
45
|
| **`options.keep`** | `number`, default `512`. How many unreachable entries to keep as a most-recently-used tail |
|
|
46
|
-
| **Returns** | `number`
|
|
46
|
+
| **Returns** | `number` - how many objects were freed |
|
|
47
47
|
|
|
48
48
|
The retained tail matters more than it looks: editing churns the tree, so the entries that
|
|
49
|
-
just fell out of it are the ones most likely to come straight back
|
|
49
|
+
just fell out of it are the ones most likely to come straight back - on an undo, or on the
|
|
50
50
|
next character of a number being typed.
|
|
51
51
|
|
|
52
52
|
### `context.dispose()`
|
|
@@ -68,7 +68,7 @@ evaluations still in flight free their own results as they land. Disposing twice
|
|
|
68
68
|
## Why a context exists
|
|
69
69
|
|
|
70
70
|
WebAssembly objects are not garbage-collected. Something has to own them, and something has
|
|
71
|
-
to decide when they are no longer needed
|
|
71
|
+
to decide when they are no longer needed - that is this class.
|
|
72
72
|
|
|
73
73
|
It is also the cache. Nothing is freed as evaluation proceeds, because an intermediate result
|
|
74
74
|
is also a cache entry and a later node may still need it. The two ways out are `collect`,
|
|
@@ -115,7 +115,7 @@ async function rerender(source, params) {
|
|
|
115
115
|
}
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
There is no `dispose` here, and that is correct
|
|
118
|
+
There is no `dispose` here, and that is correct - the page unloading frees the whole heap.
|
|
119
119
|
|
|
120
120
|
### Tuning what is kept
|
|
121
121
|
|
|
@@ -141,7 +141,7 @@ console.log(`${evaluated} solved, ${hits} reused`);
|
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
On a document where one dimension changed, `evaluated` should be a handful and `hits` most of
|
|
144
|
-
the tree. If `evaluated` is the whole document every time, something is defeating the cache
|
|
144
|
+
the tree. If `evaluated` is the whole document every time, something is defeating the cache -
|
|
145
145
|
see [Troubleshooting](#every-render-re-evaluates-everything).
|
|
146
146
|
|
|
147
147
|
### Evaluating a node directly
|
|
@@ -167,7 +167,7 @@ context.dispose();
|
|
|
167
167
|
1. **Everything a context returns belongs to the context.** Never call `delete()` on a
|
|
168
168
|
`Solid`, a `Shape` or anything hanging off `part.concrete`.
|
|
169
169
|
2. **A result is valid only while its context is alive.** After `dispose()`, every object it
|
|
170
|
-
produced is gone. Copy out what you need first
|
|
170
|
+
produced is gone. Copy out what you need first - `toRenderMesh` and `toBinarySTL` both
|
|
171
171
|
produce plain typed arrays that outlive the context.
|
|
172
172
|
3. **`render` does not dispose a context you supplied.** It disposes only one it created
|
|
173
173
|
itself, and only on failure.
|
|
@@ -179,7 +179,7 @@ context.dispose();
|
|
|
179
179
|
### `EvaluationContext has been disposed`
|
|
180
180
|
|
|
181
181
|
An `evaluate` or a `render` used a context after `dispose()`. Usually an async render that
|
|
182
|
-
was still in flight when a page or a test tore the context down
|
|
182
|
+
was still in flight when a page or a test tore the context down - await it first.
|
|
183
183
|
|
|
184
184
|
### `EvaluationContext used before create() resolved`
|
|
185
185
|
|
|
@@ -198,19 +198,19 @@ Add a `collect` after each render, passing the roots that are still in use:
|
|
|
198
198
|
context.collect(result.parts.map((part) => part.node));
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
Without it, every intermediate solid from every render is retained
|
|
201
|
+
Without it, every intermediate solid from every render is retained - which is the correct
|
|
202
202
|
default, because the context cannot know whether you still hold a reference.
|
|
203
203
|
|
|
204
204
|
### `collect` freed nothing
|
|
205
205
|
|
|
206
|
-
It returns `0` when the number of unreachable entries is at or below `keep`
|
|
206
|
+
It returns `0` when the number of unreachable entries is at or below `keep` - the default 512
|
|
207
207
|
tail. That is expected on a small document. Pass `{ keep: 0 }` to see whether anything is
|
|
208
208
|
genuinely unreachable.
|
|
209
209
|
|
|
210
210
|
### Every render re-evaluates everything
|
|
211
211
|
|
|
212
|
-
Either a fresh context is being created per render
|
|
213
|
-
in
|
|
212
|
+
Either a fresh context is being created per render - check that the same one is passed back
|
|
213
|
+
in - or the document's content genuinely changes on every render. A param that feeds a
|
|
214
214
|
dimension changes the digest of everything downstream of it, which is correct; a param that
|
|
215
215
|
feeds only one wall should not.
|
|
216
216
|
|
|
@@ -6,8 +6,8 @@ Turning a solved solid into buffers a renderer can upload, or a file a slicer ca
|
|
|
6
6
|
import { toRenderMesh, toBinarySTL } from 'forma-dsl';
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Both take a `Solid`
|
|
10
|
-
[rendered part](render.md#returns)
|
|
9
|
+
Both take a `Solid` - the `concrete` field of a
|
|
10
|
+
[rendered part](render.md#returns) - and both return plain typed arrays that **outlive the
|
|
11
11
|
context**, so they are what to copy out before disposing.
|
|
12
12
|
|
|
13
13
|
---
|
|
@@ -25,7 +25,7 @@ Converts a solid into flat arrays for a renderer.
|
|
|
25
25
|
|
|
26
26
|
| Field | Type | Description |
|
|
27
27
|
| --- | --- | --- |
|
|
28
|
-
| `positions` | `Float32Array` | Three vertices per triangle, expanded
|
|
28
|
+
| `positions` | `Float32Array` | Three vertices per triangle, expanded - `triangleCount * 9` floats |
|
|
29
29
|
| `normals` | `Float32Array` | One face normal, repeated for all three corners |
|
|
30
30
|
| `triangleCount` | `number` | Triangles |
|
|
31
31
|
| `vertexCount` | `number` | Vertices **before** expansion, as the kernel counted them |
|
|
@@ -44,7 +44,7 @@ The cost is that `positions.length / 3` is `triangleCount * 3` rather than `vert
|
|
|
44
44
|
|
|
45
45
|
- A degenerate triangle keeps a zero normal rather than dividing by zero and poisoning the
|
|
46
46
|
buffer with NaN.
|
|
47
|
-
- [`render`](render.md) already calls this for every part
|
|
47
|
+
- [`render`](render.md) already calls this for every part - `part.mesh` is the result. Call
|
|
48
48
|
it yourself only for a solid you built directly.
|
|
49
49
|
|
|
50
50
|
---
|
|
@@ -59,7 +59,7 @@ Encodes a solid as binary STL.
|
|
|
59
59
|
| --- | --- |
|
|
60
60
|
| **`solid`** | `Solid` |
|
|
61
61
|
| **`header`** | `string`, default `'forma-dsl'`. Free text for the 80-byte header, truncated to fit |
|
|
62
|
-
| **Returns** | `Uint8Array`
|
|
62
|
+
| **Returns** | `Uint8Array` - the file |
|
|
63
63
|
|
|
64
64
|
Binary rather than ASCII: a model of any size is several times smaller and every slicer reads
|
|
65
65
|
it.
|
|
@@ -141,7 +141,7 @@ function toThree(part) {
|
|
|
141
141
|
|
|
142
142
|
### Keeping geometry past the context
|
|
143
143
|
|
|
144
|
-
Typed arrays are plain memory, so they survive disposal
|
|
144
|
+
Typed arrays are plain memory, so they survive disposal - unlike `part.concrete`:
|
|
145
145
|
|
|
146
146
|
```js
|
|
147
147
|
const result = await render(source);
|
|
@@ -159,7 +159,7 @@ result.context.dispose(); // …so this is safe
|
|
|
159
159
|
|
|
160
160
|
### `EvaluationContext has been disposed`
|
|
161
161
|
|
|
162
|
-
The solid was freed before it was encoded. Export first, dispose after
|
|
162
|
+
The solid was freed before it was encoded. Export first, dispose after - or copy the arrays
|
|
163
163
|
out, which is what the example above does.
|
|
164
164
|
|
|
165
165
|
### The STL is empty, or has zero triangles
|
|
@@ -7,7 +7,7 @@ the kernel consumes them.
|
|
|
7
7
|
import { GeometryNode } from 'forma-dsl';
|
|
8
8
|
|
|
9
9
|
const box = GeometryNode.shape(3, 'box', { size: [10, 10, 10], center: true });
|
|
10
|
-
box.digest; // '3f2a…'
|
|
10
|
+
box.digest; // '3f2a…' - 32 hex characters
|
|
11
11
|
box.subtreeSize; // 1
|
|
12
12
|
```
|
|
13
13
|
|
|
@@ -36,7 +36,7 @@ process, so it is identical in every browser and every run.
|
|
|
36
36
|
| Property | Type | Description |
|
|
37
37
|
| --- | --- | --- |
|
|
38
38
|
| `dim` | `2 \| 3` | Whether this node is a cross-section or a solid |
|
|
39
|
-
| `kind` | `string` | What the node does
|
|
39
|
+
| `kind` | `string` | What the node does - `shape3d`, `boolean`, `transform`, `extrude`, … |
|
|
40
40
|
| `props` | `Record<string, unknown>` | The node's own parameters |
|
|
41
41
|
| `children` | `readonly GeometryNode[]` | The operands |
|
|
42
42
|
| `digest` | `string` | 128-bit content digest, 32 lowercase hex characters |
|
|
@@ -47,13 +47,13 @@ Nodes are frozen. Every factory returns a new node rather than mutating one.
|
|
|
47
47
|
|
|
48
48
|
### Factories
|
|
49
49
|
|
|
50
|
-
Prefer these to the constructor
|
|
50
|
+
Prefer these to the constructor - they apply the algebraic simplifications that keep the tree
|
|
51
51
|
small.
|
|
52
52
|
|
|
53
53
|
| Factory | Description |
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| `GeometryNode.empty(dim)` | A node standing for no geometry |
|
|
56
|
-
| `GeometryNode.shape(dim, shape, params)` | A leaf primitive
|
|
56
|
+
| `GeometryNode.shape(dim, shape, params)` | A leaf primitive - `'box'`, `'circle'`, `'cylinder'`, … |
|
|
57
57
|
| `GeometryNode.union(children)` | Everything added together |
|
|
58
58
|
| `GeometryNode.difference(children)` | The first child with the rest removed |
|
|
59
59
|
| `GeometryNode.intersection(children)` | Only what every child shares |
|
|
@@ -82,7 +82,7 @@ call and a cache entry:
|
|
|
82
82
|
- **`dim` must be 2 or 3.** Anything else throws `bad dimensionality`.
|
|
83
83
|
- **Booleans cannot mix dimensionality**: `cannot combine 2D and 3D geometry in a union`.
|
|
84
84
|
- `GeometryNode.unary` defaults its result dimensionality to the child's, which is wrong for
|
|
85
|
-
the operations that change it
|
|
85
|
+
the operations that change it - pass `dim` explicitly for anything extrude-like.
|
|
86
86
|
- Two nodes with the same digest are interchangeable. Nothing stops you building the same
|
|
87
87
|
node twice; the cache will treat both as one.
|
|
88
88
|
|
|
@@ -108,7 +108,7 @@ for (const node of walk(result.parts[0].node)) {
|
|
|
108
108
|
}
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
Deduplicating on `digest` is what makes the walk reflect what the kernel will actually do
|
|
111
|
+
Deduplicating on `digest` is what makes the walk reflect what the kernel will actually do -
|
|
112
112
|
the tree is a DAG, not a tree, once sharing is taken into account.
|
|
113
113
|
|
|
114
114
|
### Measuring how much a model shares
|
|
@@ -123,7 +123,7 @@ for (const node of walk(root)) unique.add(node.digest);
|
|
|
123
123
|
console.log(`${root.subtreeSize} nodes written, ${unique.size} distinct`);
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
A large gap is the digest cache doing its job
|
|
126
|
+
A large gap is the digest cache doing its job - eight spheres in a loop are one sphere node
|
|
127
127
|
and eight transforms.
|
|
128
128
|
|
|
129
129
|
### Building geometry without the language
|
|
@@ -151,7 +151,7 @@ await writeFile('ring.stl', toBinarySTL(solid));
|
|
|
151
151
|
context.dispose();
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
Note that `pin` is one node used six times
|
|
154
|
+
Note that `pin` is one node used six times - the kernel builds one cylinder.
|
|
155
155
|
|
|
156
156
|
### Checking whether an edit changed anything
|
|
157
157
|
|
|
@@ -211,5 +211,5 @@ GeometryNode.unary('extrude', profile, { height: 10, /* … */ }, 3);
|
|
|
211
211
|
|
|
212
212
|
Digests cover **all** props. A `segments` of `0` and a `segments` of `32` are different
|
|
213
213
|
shapes even when they look alike, and a float that differs in the last bit is a different
|
|
214
|
-
number. Digest equality is exact, by design
|
|
214
|
+
number. Digest equality is exact, by design - the alternative is a cache that occasionally
|
|
215
215
|
returns the wrong solid.
|
|
@@ -21,8 +21,8 @@ Loads and sets up the Manifold module, once per page or process.
|
|
|
21
21
|
|
|
22
22
|
| | |
|
|
23
23
|
| --- | --- |
|
|
24
|
-
| **`options.locateFile`** | `() => string`
|
|
25
|
-
| **Returns** | `Promise<Kernel>`
|
|
24
|
+
| **`options.locateFile`** | `() => string` - returns the URL of the `.wasm` file |
|
|
25
|
+
| **Returns** | `Promise<Kernel>` - the `manifold-3d` toplevel module |
|
|
26
26
|
|
|
27
27
|
#### Caveats
|
|
28
28
|
|
|
@@ -30,7 +30,7 @@ Loads and sets up the Manifold module, once per page or process.
|
|
|
30
30
|
share one instantiation rather than each starting their own.
|
|
31
31
|
- **Only the first call's options take effect.** Later calls get the memoized promise, so
|
|
32
32
|
`locateFile` has to be right the first time.
|
|
33
|
-
- A rejected load is *not* memoized
|
|
33
|
+
- A rejected load is *not* memoized - a WASM fetch that lost the network can be retried
|
|
34
34
|
rather than being permanently broken for the page.
|
|
35
35
|
|
|
36
36
|
### Bundlers and `locateFile`
|
|
@@ -48,7 +48,7 @@ await loadKernel({ locateFile: () => wasmUrl });
|
|
|
48
48
|
Do this **before** the first `EvaluationContext.create()`, or the default path is what gets
|
|
49
49
|
memoized.
|
|
50
50
|
|
|
51
|
-
In Node, nothing is needed
|
|
51
|
+
In Node, nothing is needed - the module resolves the file itself.
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -75,7 +75,7 @@ left alone when omitted.
|
|
|
75
75
|
solid built at draft quality will be *reused* at final quality unless the cache is cleared.
|
|
76
76
|
See [Usage](#a-draft-pass-and-a-final-pass).
|
|
77
77
|
- A shape's own `segments` attribute overrides all of this for that shape. Prefer it whenever
|
|
78
|
-
a specific facet count matters
|
|
78
|
+
a specific facet count matters - a hole that must clear a printed bolt should not depend on
|
|
79
79
|
a global.
|
|
80
80
|
|
|
81
81
|
## `resetQuality`
|
|
@@ -91,7 +91,7 @@ Restores the kernel's own defaults. Returns `Promise<void>`.
|
|
|
91
91
|
### A draft pass and a final pass
|
|
92
92
|
|
|
93
93
|
Because quality is global and digests do not include it, the cache has to be dropped between
|
|
94
|
-
passes
|
|
94
|
+
passes - otherwise the final render happily reuses the draft's geometry:
|
|
95
95
|
|
|
96
96
|
```js
|
|
97
97
|
import { setQuality, EvaluationContext, render } from 'forma-dsl';
|
|
@@ -149,7 +149,7 @@ const context = await EvaluationContext.create();
|
|
|
149
149
|
const { Manifold, CrossSection } = context.wasm;
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
Anything you construct this way is **yours** to delete
|
|
152
|
+
Anything you construct this way is **yours** to delete - the context only owns what it built
|
|
153
153
|
itself.
|
|
154
154
|
|
|
155
155
|
---
|
|
@@ -167,7 +167,7 @@ await loadKernel({ locateFile: () => wasmUrl });
|
|
|
167
167
|
|
|
168
168
|
### `locateFile` is being ignored
|
|
169
169
|
|
|
170
|
-
The kernel was already loaded
|
|
170
|
+
The kernel was already loaded - the promise is memoized, so only the first call's options
|
|
171
171
|
count. Move the `loadKernel` call earlier, before anything creates a context.
|
|
172
172
|
|
|
173
173
|
### `setQuality` had no effect on an existing model
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Compiler stages
|
|
2
2
|
|
|
3
3
|
The pipeline, stage by stage, plus the registries the evaluator reads. These are exported so
|
|
4
|
-
that editors, linters and tools can reach into the compiler without re-implementing it
|
|
4
|
+
that editors, linters and tools can reach into the compiler without re-implementing it - the
|
|
5
5
|
[editor](../../../editor) uses them for syntax
|
|
6
6
|
highlighting and inline diagnostics.
|
|
7
7
|
|
|
@@ -63,7 +63,7 @@ duplicate names and a component that shadows a builtin.
|
|
|
63
63
|
### `program.parameterDescriptors(evaluator?)`
|
|
64
64
|
|
|
65
65
|
The declared params as [`ParameterDescriptor`](describe-parameters.md#returns)s. Without an
|
|
66
|
-
evaluator, every field but the name comes back `undefined`
|
|
66
|
+
evaluator, every field but the name comes back `undefined` -
|
|
67
67
|
[`describeParameters`](describe-parameters.md) is this with one supplied.
|
|
68
68
|
|
|
69
69
|
#### Listing a document's models without rendering
|
|
@@ -104,10 +104,10 @@ everything unioned.
|
|
|
104
104
|
|
|
105
105
|
#### Caveats
|
|
106
106
|
|
|
107
|
-
- **The evaluator does not call the kernel**
|
|
107
|
+
- **The evaluator does not call the kernel** - except for `align`. Its output is a node tree;
|
|
108
108
|
solving it is [`EvaluationContext`](evaluation-context.md)'s job.
|
|
109
109
|
- Without a `context`, a document containing `align` fails with
|
|
110
|
-
`align needs a kernel context
|
|
110
|
+
`align needs a kernel context - render the model rather than building it`.
|
|
111
111
|
- `requireParams: false` is for inspection only. A param with no value is simply absent from
|
|
112
112
|
scope, so evaluating geometry that reads it will fail.
|
|
113
113
|
|
|
@@ -152,7 +152,7 @@ try {
|
|
|
152
152
|
}
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
Not every failure is a `FormaError`
|
|
155
|
+
Not every failure is a `FormaError` - a few come from the kernel, and `render` raises a plain
|
|
156
156
|
`Error` for a 2D part. Check `error.loc` rather than the class when all you need is a
|
|
157
157
|
position. See [Errors](../errors.md) for the full catalogue.
|
|
158
158
|
|
|
@@ -172,7 +172,7 @@ Every block the language understands, by name, as `Record<string, BlockDefinitio
|
|
|
172
172
|
| `combine` | `'list'` receives every child; anything else receives them unioned |
|
|
173
173
|
| `build(args, children?)` | Produces the node |
|
|
174
174
|
|
|
175
|
-
**[`part`](../reference/part.md) and [`align`](../reference/align.md) are not in it**
|
|
175
|
+
**[`part`](../reference/part.md) and [`align`](../reference/align.md) are not in it** - the
|
|
176
176
|
evaluator handles both itself, because neither is an ordinary geometry operation.
|
|
177
177
|
|
|
178
178
|
```js
|
|
@@ -184,8 +184,8 @@ Object.keys(BLOCKS).filter((k) => BLOCKS[k].leaf); // just the shapes
|
|
|
184
184
|
|
|
185
185
|
### `FUNCTIONS`
|
|
186
186
|
|
|
187
|
-
Every builtin function, as `Record<string, FunctionDefinition>` with `arity`
|
|
188
|
-
variadic
|
|
187
|
+
Every builtin function, as `Record<string, FunctionDefinition>` with `arity` - `null` for
|
|
188
|
+
variadic - and `call(args)`.
|
|
189
189
|
|
|
190
190
|
### `CONSTANTS`
|
|
191
191
|
|
|
@@ -213,7 +213,7 @@ cannot be added without the documentation's code samples learning about it.
|
|
|
213
213
|
|
|
214
214
|
## Troubleshooting
|
|
215
215
|
|
|
216
|
-
### `align needs a kernel context
|
|
216
|
+
### `align needs a kernel context - render the model rather than building it`
|
|
217
217
|
|
|
218
218
|
Pass a context, or avoid `align` in documents you intend to evaluate without one:
|
|
219
219
|
|
|
@@ -33,7 +33,7 @@ A `Promise<RenderResult>`:
|
|
|
33
33
|
| Field | Type | Description |
|
|
34
34
|
| --- | --- | --- |
|
|
35
35
|
| `name` | `string` | The model's name |
|
|
36
|
-
| `parts` | `RenderedPart[]` | One per [`part`](../reference/part.md)
|
|
36
|
+
| `parts` | `RenderedPart[]` | One per [`part`](../reference/part.md) - see below |
|
|
37
37
|
| `program` | [`Program`](low-level.md#program) | The parsed document, for inspecting params |
|
|
38
38
|
| `context` | `EvaluationContext` | The context that owns every solid in `parts` |
|
|
39
39
|
| `stats` | `RenderStats` | `nodes`, `evaluated`, `cacheHits`, `milliseconds` |
|
|
@@ -60,7 +60,7 @@ Each `RenderedPart`:
|
|
|
60
60
|
- Params are resolved before any geometry is built, so a missing required param fails
|
|
61
61
|
immediately rather than halfway through a model.
|
|
62
62
|
- `options.params` sets **top-level** params only. A param declared inside a `model` is a
|
|
63
|
-
constant
|
|
63
|
+
constant - see [model params](../reference/model.md#model-params).
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
@@ -129,7 +129,7 @@ async function rerender(source, params) {
|
|
|
129
129
|
}
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
Typing a digit into a dimension re-solves that wall and nothing else
|
|
132
|
+
Typing a digit into a dimension re-solves that wall and nothing else - `stats.cacheHits`
|
|
133
133
|
against `stats.evaluated` shows how much was skipped.
|
|
134
134
|
|
|
135
135
|
### Choosing a model
|
|
@@ -165,7 +165,7 @@ result.context.dispose();
|
|
|
165
165
|
```js
|
|
166
166
|
const { stats } = await render(source, { context });
|
|
167
167
|
|
|
168
|
-
stats.nodes; // nodes in the tree
|
|
168
|
+
stats.nodes; // nodes in the tree - a measure of the document's size
|
|
169
169
|
stats.evaluated; // kernel calls actually made, across the context's life
|
|
170
170
|
stats.cacheHits; // how many were avoided
|
|
171
171
|
stats.milliseconds; // wall clock for this render
|
|
@@ -181,7 +181,7 @@ them across calls rather than reading one in isolation.
|
|
|
181
181
|
### `EvaluationContext has been disposed`
|
|
182
182
|
|
|
183
183
|
Something held on to a result after disposing the context that owns it. `part.concrete` and
|
|
184
|
-
`part.mesh` are only valid while the context is alive
|
|
184
|
+
`part.mesh` are only valid while the context is alive - copy the mesh arrays out first if you
|
|
185
185
|
need them later.
|
|
186
186
|
|
|
187
187
|
### Memory grows across renders
|
|
@@ -193,18 +193,18 @@ Add the `collect` call. A reused context caches every solid it has ever built, b
|
|
|
193
193
|
context.collect(result.parts.map((part) => part.node));
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
### `part "body" is 2D
|
|
196
|
+
### `part "body" is 2D - extrude or revolve it before rendering`
|
|
197
197
|
|
|
198
198
|
The model produced a cross-section. See [2D ↔ 3D](../reference/conversions.md).
|
|
199
199
|
|
|
200
|
-
### `align needs a kernel context
|
|
200
|
+
### `align needs a kernel context - render the model rather than building it`
|
|
201
201
|
|
|
202
202
|
Only happens when driving [`Evaluator`](low-level.md#evaluator) directly without a context.
|
|
203
203
|
`render` always supplies one.
|
|
204
204
|
|
|
205
205
|
### The error has no line number
|
|
206
206
|
|
|
207
|
-
Most do
|
|
207
|
+
Most do - `FormaError` carries a `loc` with `line`, `column` and `offset`, and appends it to
|
|
208
208
|
the message. A few errors are raised before any position is known (`the document declares no
|
|
209
209
|
model`) or inside a value helper that has none (`no attribute "a"`).
|
|
210
210
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Values
|
|
2
2
|
|
|
3
3
|
Three small immutable classes the evaluator works in: [`Vector`](#vector),
|
|
4
|
-
[`Angle`](#angle) and [`Transform`](#transform). Documents never see them directly
|
|
5
|
-
document writes lists and bare numbers
|
|
4
|
+
[`Angle`](#angle) and [`Transform`](#transform). Documents never see them directly - a
|
|
5
|
+
document writes lists and bare numbers - but they are exported for anyone building geometry
|
|
6
6
|
through the [`GeometryNode`](geometry-node.md) API.
|
|
7
7
|
|
|
8
8
|
```js
|
|
@@ -26,7 +26,7 @@ separate 3D class would be the same code with one more letter.
|
|
|
26
26
|
| `Vector.of(value, dimensions)` | From anything vector-like |
|
|
27
27
|
| `Vector.zero(dimensions)` | The origin |
|
|
28
28
|
|
|
29
|
-
`Vector.of` accepts a `Vector`, a **number**
|
|
29
|
+
`Vector.of` accepts a `Vector`, a **number** - broadcast to every component - an array, or an
|
|
30
30
|
`{x, y, z}` object whose missing keys read as zero. This is what makes
|
|
31
31
|
`scale { factor = 2 }` work the same as `factor = [2, 2, 2]`.
|
|
32
32
|
|
|
@@ -89,7 +89,7 @@ wrong rather than an error.
|
|
|
89
89
|
| `plus(other)` `minus(other)` | Other may be an `Angle` or a number of degrees |
|
|
90
90
|
| `times(factor)` | A plain multiplier, not an angle |
|
|
91
91
|
|
|
92
|
-
The unit names appear twice
|
|
92
|
+
The unit names appear twice - `Angle.degrees(90)` builds one, `angle.degrees` reads one back.
|
|
93
93
|
They are different namespaces and do not collide.
|
|
94
94
|
|
|
95
95
|
---
|
|
@@ -121,9 +121,9 @@ renders.
|
|
|
121
121
|
| --- | --- |
|
|
122
122
|
| `dim` | 2 or 3 |
|
|
123
123
|
| `m` | The matrix, column-major and frozen |
|
|
124
|
-
| `order` | Side length
|
|
124
|
+
| `order` | Side length - one more than `dim` |
|
|
125
125
|
| `at(row, col)` | One entry, read out of column-major storage |
|
|
126
|
-
| `concat(other)` | `this ∘ other`
|
|
126
|
+
| `concat(other)` | `this ∘ other` - **`other` runs first** |
|
|
127
127
|
| `toArray()` | A copy, safe to keep |
|
|
128
128
|
|
|
129
129
|
#### Caveats
|
|
@@ -145,7 +145,7 @@ renders.
|
|
|
145
145
|
```js
|
|
146
146
|
import { Transform, GeometryNode } from 'forma-dsl';
|
|
147
147
|
|
|
148
|
-
// Rotate, then move
|
|
148
|
+
// Rotate, then move - the argument to concat runs first.
|
|
149
149
|
const placement = Transform.translation([30, 0, 0], 3)
|
|
150
150
|
.concat(Transform.rotation3D([0, 0, 45]));
|
|
151
151
|
|
|
@@ -176,7 +176,7 @@ import { Angle } from 'forma-dsl';
|
|
|
176
176
|
const a = Angle.degrees(30);
|
|
177
177
|
a.sin; // 0.5
|
|
178
178
|
a.radians; // 0.5235987755982988
|
|
179
|
-
a.plus(60).degrees; // 90
|
|
179
|
+
a.plus(60).degrees; // 90 - a bare number is read as degrees
|
|
180
180
|
```
|
|
181
181
|
|
|
182
182
|
### Vector arithmetic
|
|
@@ -190,8 +190,8 @@ const wall = 2;
|
|
|
190
190
|
const inner = size.minus(new Vector([wall * 2, wall * 2, 0]));
|
|
191
191
|
inner.toArray(); // [36, 26, 12]
|
|
192
192
|
|
|
193
|
-
Vector.of(3, 3).toArray(); // [3, 3, 3]
|
|
194
|
-
Vector.of({ x: 1, z: 5 }, 3).toArray(); // [1, 0, 5]
|
|
193
|
+
Vector.of(3, 3).toArray(); // [3, 3, 3] - a scalar broadcasts
|
|
194
|
+
Vector.of({ x: 1, z: 5 }, 3).toArray(); // [1, 0, 5] - missing keys are zero
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
---
|
|
@@ -205,7 +205,7 @@ surfaces more helpfully as `translate: "offset" must be a number or an array of
|
|
|
205
205
|
|
|
206
206
|
### `cannot read an angle from string`
|
|
207
207
|
|
|
208
|
-
`Angle.of` takes an `Angle` or a number. A string is not coerced
|
|
208
|
+
`Angle.of` takes an `Angle` or a number. A string is not coerced - that would let `"90deg"`
|
|
209
209
|
silently become `NaN`.
|
|
210
210
|
|
|
211
211
|
### My composed transform is applied in the wrong order
|
|
@@ -219,5 +219,5 @@ Every transform is about the origin. Centre the geometry, rotate, then translate
|
|
|
219
219
|
|
|
220
220
|
### Non-uniform scaling made my circle an ellipse
|
|
221
221
|
|
|
222
|
-
Expected
|
|
222
|
+
Expected - that is how [`ellipse`](../reference/shapes-2d.md#ellipse) is built internally, and
|
|
223
223
|
it is the cheapest route to an ellipsoid from a sphere.
|