@metamesh/cli 0.17.0 → 0.19.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +93 -51
  3. package/dist/authoring/artifacts.d.ts +2 -2
  4. package/dist/authoring/artifacts.d.ts.map +1 -1
  5. package/dist/authoring/artifacts.js +52 -40
  6. package/dist/authoring/artifacts.js.map +1 -1
  7. package/dist/authoring/check.d.ts.map +1 -1
  8. package/dist/authoring/check.js +9 -3
  9. package/dist/authoring/check.js.map +1 -1
  10. package/dist/authoring/documentation-manifest.d.ts +3 -0
  11. package/dist/authoring/documentation-manifest.d.ts.map +1 -0
  12. package/dist/authoring/documentation-manifest.js +23 -0
  13. package/dist/authoring/documentation-manifest.js.map +1 -0
  14. package/dist/authoring/documentation.d.ts +20 -0
  15. package/dist/authoring/documentation.d.ts.map +1 -0
  16. package/dist/authoring/documentation.js +94 -0
  17. package/dist/authoring/documentation.js.map +1 -0
  18. package/dist/authoring/environment.d.ts +1 -19
  19. package/dist/authoring/environment.d.ts.map +1 -1
  20. package/dist/authoring/environment.js +4 -110
  21. package/dist/authoring/environment.js.map +1 -1
  22. package/dist/authoring/{worker.d.ts → execution-process.d.ts} +3 -4
  23. package/dist/authoring/execution-process.d.ts.map +1 -0
  24. package/dist/authoring/{worker.js → execution-process.js} +18 -19
  25. package/dist/authoring/execution-process.js.map +1 -0
  26. package/dist/authoring/export-capture.d.ts +22 -0
  27. package/dist/authoring/export-capture.d.ts.map +1 -0
  28. package/dist/authoring/export-capture.js +76 -0
  29. package/dist/authoring/export-capture.js.map +1 -0
  30. package/dist/authoring/{capture/render-client.js → render/client-bundle.js} +196 -196
  31. package/dist/authoring/{capture/render-client.js.map → render/client-bundle.js.map} +4 -4
  32. package/dist/authoring/{render-client.d.ts → render/client.d.ts} +2 -2
  33. package/dist/authoring/render/client.d.ts.map +1 -0
  34. package/dist/authoring/{render-client.js → render/client.js} +2 -2
  35. package/dist/authoring/render/client.js.map +1 -0
  36. package/dist/authoring/{render.d.ts → render/render-capture.d.ts} +2 -2
  37. package/dist/authoring/render/render-capture.d.ts.map +1 -0
  38. package/dist/authoring/{render.js → render/render-capture.js} +4 -4
  39. package/dist/authoring/render/render-capture.js.map +1 -0
  40. package/dist/authoring/{render-scene.d.ts → render/scene.d.ts} +2 -2
  41. package/dist/authoring/render/scene.d.ts.map +1 -0
  42. package/dist/authoring/{render-scene.js → render/scene.js} +6 -10
  43. package/dist/authoring/render/scene.js.map +1 -0
  44. package/dist/authoring/{render-types.d.ts → render/types.d.ts} +3 -3
  45. package/dist/authoring/render/types.d.ts.map +1 -0
  46. package/dist/authoring/render/types.js +2 -0
  47. package/dist/authoring/render/types.js.map +1 -0
  48. package/dist/authoring/run.d.ts.map +1 -1
  49. package/dist/authoring/run.js +7 -4
  50. package/dist/authoring/run.js.map +1 -1
  51. package/dist/authoring/types.d.ts +21 -18
  52. package/dist/authoring/types.d.ts.map +1 -1
  53. package/dist/authoring/types.js.map +1 -1
  54. package/dist/authoring.d.ts +7 -4
  55. package/dist/authoring.d.ts.map +1 -1
  56. package/dist/authoring.js +4 -2
  57. package/dist/authoring.js.map +1 -1
  58. package/dist/docs/guides/agent-authoring.md +76 -54
  59. package/dist/docs/guides/package.md +93 -51
  60. package/dist/docs/manifest.json +57 -1
  61. package/dist/docs/reference/authoring/AUTHORING_PROTOCOL_VERSION.md +1 -1
  62. package/dist/docs/reference/authoring/AuthoringDiagnostic.md +1 -1
  63. package/dist/docs/reference/authoring/AuthoringEnvironment.md +1 -1
  64. package/dist/docs/reference/authoring/AuthoringError.md +1 -1
  65. package/dist/docs/reference/authoring/AuthoringFailure-type.md +1 -1
  66. package/dist/docs/reference/authoring/AuthoringKind.md +1 -1
  67. package/dist/docs/reference/authoring/AuthoringPhase.md +2 -2
  68. package/dist/docs/reference/authoring/CaptureExportFormat.md +10 -0
  69. package/dist/docs/reference/authoring/CaptureExportOptions.md +17 -0
  70. package/dist/docs/reference/authoring/CaptureExportResult.md +15 -0
  71. package/dist/docs/reference/authoring/CapturedBuffer.md +1 -1
  72. package/dist/docs/reference/authoring/CapturedGeometry.md +1 -1
  73. package/dist/docs/reference/authoring/CapturedPart.md +4 -6
  74. package/dist/docs/reference/authoring/CapturedResource.md +2 -2
  75. package/dist/docs/reference/authoring/CapturedRun.md +1 -1
  76. package/dist/docs/reference/authoring/CapturedRunManifest.md +3 -4
  77. package/dist/docs/reference/authoring/CheckOptions.md +1 -1
  78. package/dist/docs/reference/authoring/CheckSuccess.md +1 -1
  79. package/dist/docs/reference/authoring/DocumentationEntry.md +1 -1
  80. package/dist/docs/reference/authoring/DocumentationManifest.md +1 -1
  81. package/dist/docs/reference/authoring/DocumentationSearchResult.md +1 -1
  82. package/dist/docs/reference/authoring/InstalledAuthoringPackage.md +1 -1
  83. package/dist/docs/reference/authoring/RenderCamera.md +1 -1
  84. package/dist/docs/reference/authoring/RenderFormat.md +1 -1
  85. package/dist/docs/reference/authoring/RenderOptions.md +1 -1
  86. package/dist/docs/reference/authoring/RenderResult.md +1 -1
  87. package/dist/docs/reference/authoring/RenderView.md +1 -1
  88. package/dist/docs/reference/authoring/RenderedImage.md +1 -1
  89. package/dist/docs/reference/authoring/RunOptions.md +13 -7
  90. package/dist/docs/reference/authoring/RunSuccess.md +1 -1
  91. package/dist/docs/reference/authoring/authoringFailure-value.md +1 -1
  92. package/dist/docs/reference/authoring/checkAuthoringSource.md +1 -1
  93. package/dist/docs/reference/authoring/discoverAuthoringEnvironment.md +1 -1
  94. package/dist/docs/reference/authoring/exportCapturedRun.md +10 -0
  95. package/dist/docs/reference/authoring/inspectCapturedRun.md +1 -1
  96. package/dist/docs/reference/authoring/installAuthoringBrowser.md +1 -1
  97. package/dist/docs/reference/authoring/readCapturedRun.md +1 -1
  98. package/dist/docs/reference/authoring/renderCapturedRun.md +1 -1
  99. package/dist/docs/reference/authoring/runAuthoringSource.md +1 -1
  100. package/dist/docs/reference/authoring/searchAuthoringDocumentation.md +1 -1
  101. package/dist/docs/reference/authoring/showAuthoringDocumentation.md +1 -1
  102. package/dist/metamesh.js +35 -14
  103. package/dist/metamesh.js.map +1 -1
  104. package/package.json +9 -6
  105. package/dist/authoring/render-client.d.ts.map +0 -1
  106. package/dist/authoring/render-client.js.map +0 -1
  107. package/dist/authoring/render-scene.d.ts.map +0 -1
  108. package/dist/authoring/render-scene.js.map +0 -1
  109. package/dist/authoring/render-types.d.ts.map +0 -1
  110. package/dist/authoring/render-types.js +0 -2
  111. package/dist/authoring/render-types.js.map +0 -1
  112. package/dist/authoring/render.d.ts.map +0 -1
  113. package/dist/authoring/render.js.map +0 -1
  114. package/dist/authoring/worker.d.ts.map +0 -1
  115. package/dist/authoring/worker.js.map +0 -1
  116. /package/dist/authoring/{capture/render-client.js.LEGAL.txt → render/client-bundle.js.LEGAL.txt} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,206 @@
1
1
  # @metamesh/cli
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c9de510: Extend straight extrusion end finishing to concave outlines, holes, and disconnected regions.
8
+ Add independent-solid `join` with a feature datum and a smooth (default) or straight width/height
9
+ transition against validated planar, angled, and curved sampled support patches. Both profiles use
10
+ the same support, collar, and whole-volume containment proofs.
11
+
12
+ Breaking: compiled Generators require Modeling runtime major 12. Rebuild packages for the new
13
+ host contract; old compiled runtimes are rejected. Joining preserves the supplied operands and
14
+ does not promise a circular fillet radius or perform automatic placement.
15
+
16
+ Replace named lazy `computed` readers with eager `memo(fn, ...args)` values. Stable pure helpers
17
+ reuse immutable arguments and tracked current params/context reads, with bounded recent histories
18
+ and native geometry retention. Update Studio diagnostics to warn about fresh function identities.
19
+ All authored and compiled callers must use the new API; there is no compatibility surface.
20
+
21
+ - de3d234: Add runtime-neutral STL, 3MF, and GLB export under `@metamesh/modeling/export`, with isolated
22
+ format entry points and backpressure-aware streaming writers. All formats consume captured local
23
+ geometry, placement, and appearance, with independent limits, cancellation, and structured
24
+ information-loss diagnostics. Standard 3MF color groups carry appearance, while base materials
25
+ and multiproperties retain explicit material identities independently of RGB values. These
26
+ properties do not assign slicer filaments or physical device trays. Add `mesh.render()` for
27
+ shared crease-aware display buffers that preserve exact triangle coordinates.
28
+
29
+ Breaking: remove `export3mf` and export types from `/engine`, remove SDK export request protocols
30
+ and `toWorldMeshes`, and return owned `Uint8Array` bytes with format metadata from the shared
31
+ exporters. Studio uses one export worker for every format. CLI captures use format version 3;
32
+ replace `run --export-3mf` with `metamesh export RUN_DIRECTORY --format 3mf --output model.3mf`.
33
+ Captured-run export also supports STL and GLB without rerunning source. No legacy export paths
34
+ or capture readers are retained.
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies [c9de510]
39
+ - Updated dependencies [de3d234]
40
+ - @metamesh/modeling@0.19.0
41
+ - @metamesh/generator-sdk@0.19.0
42
+ - @metamesh/testkit@0.19.0
43
+
44
+ ## 0.18.0
45
+
46
+ ### Minor Changes
47
+
48
+ - 2eea694: Consolidate model authoring, execution, and testing around fluent shapes and durable immutable
49
+ results. Upgrade all four packages together; these Preview API changes have no compatibility
50
+ aliases. Rebuild compiled Generators for module format 5 and Modeling runtime major 11. Older
51
+ module formats and incompatible runtime versions are rejected before execution.
52
+
53
+ ### Modeling imports and operations
54
+
55
+ | Previous API | 0.18 API |
56
+ | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
57
+ | `body.subtract([cutter])` for a single operand | `body.subtract(cutter)`; fluent `union`, `intersect`, and `subtract` accept one shape or a readonly array |
58
+ | `compose(shapes)` | `union(shapes)` |
59
+ | Free shape inspection, emptiness, component, and anchor queries | `shape.inspect()`, `shape.isEmpty()`, `shape.components()`, and `shape.anchor(name)` |
60
+ | Free Solid distance/segment queries and splitting functions | `solid.distance(other)`, `solid.castSegment(...)`, `solid.split(cutter)`, and `solid.splitByPlane(plane)` |
61
+ | `solid.toTriangleMesh()` | `solid.toMesh()` |
62
+ | Named `mesh` import from `/geometry`, or standalone triangle-mesh functions | `import * as mesh from "@metamesh/modeling/geometry/mesh"` |
63
+ | Standalone `immutableMesh(...)` | `mesh.immutable(...)` |
64
+ | `helicalThreadRidgeMesh`, `helicalThreadSolidMesh`, and `preflightHelicalThreadSolidMesh` | `mesh.threadRidge`, `mesh.threadSolid`, and `mesh.preflightThreadSolid` |
65
+ | `HelicalThreadRidgeMeshOptions`, `HelicalThreadSolidMeshOptions`, and `HelicalThreadSolidMeshPreflight` | `ThreadRidgeOptions`, `ThreadSolidOptions`, and `ThreadSolidPreflight`, exported from the mesh module |
66
+ | Mesh `connectedComponentCount` | `shellCount`; native inspection `componentCount` still counts material components |
67
+ | Inspection extrema on the inspection result | Immutable `inspection.bounds.min` and `inspection.bounds.max` |
68
+ | `starContour(outerRadius, points, innerRadius, limits)` | `starContour({ outerRadius, innerRadius, points, limits })` |
69
+
70
+ Free `union(shapes)` and `intersect(shapes)` remain for collections of peers and reject empty
71
+ collections. Fluent Boolean methods with an empty array return the receiver.
72
+
73
+ Use `section.extrude(options)` in fluent modeling, `extrude(profile, options)` for generic
74
+ `Section | CorrespondingProfile` inputs, and `mesh.extrude(contour, options)` for pure triangle
75
+ buffers. The same fluent/generic distinction applies to revolution and sweeping. Pure extrusion
76
+ uses `ExtrudeMeshOptions` and rejects `ends`; use Section extrusion for native end treatments.
77
+ Construction, transforms, and splitting remain deferred recipes; observations and `toMesh()`
78
+ require active model execution.
79
+
80
+ Point-loop and arbitrary Section lofts default to direct correspondence when the method is
81
+ omitted; known primitive landmarks remain preserved. Dimension inputs accept fluent vectors.
82
+ Tube sizing accepts complete inner/outer radius or diameter pairs. Both star factories require
83
+ an explicit inner radius and support degree rotation; use `innerRadius: outerRadius / 2` to retain
84
+ the contour helper's former default.
85
+
86
+ Rod and internal-cutter `bevelBottom`, `bevelTop`, and `bevelSize` become `bevel`. The shared
87
+ `Bevel` contract accepts a nonnegative size, `"auto"`, or a readonly `[bottom, top]` pair; zero
88
+ disables an end and omission preserves factory defaults. Complete-thread mesh builders use the
89
+ same contract, with thread termination under `ends`. Nuts replace bevel selection, `chamferSize`,
90
+ and `flushBuildPlateBottom` with independent `innerBevel` and `outerBevel`. For a flat outside
91
+ bottom that retains its inner opening bevel, use `outerBevel: [0, "auto"]`. Nut preflight exposes
92
+ resolved inner/outer pairs and checks face walls pairwise; each end's reservation and automatic
93
+ sizing are resolved independently, including internal-cutter axial extension.
94
+
95
+ Primitive contours and their points are immutable and can be explicit `computed` dependencies.
96
+ Copy the outer array and any points before editing. Geometry errors are available from the
97
+ Modeling root; operation option/result types are exported beside their owning operations.
98
+ Topology analysis with `includeTopology: true`, finalized parts, and local geometry resources
99
+ include required `shellCount` and `edgeCount`. Bounds construction options describe inputs only;
100
+ existing fluent bounds remain accepted.
101
+
102
+ ### Part placement and result ownership
103
+
104
+ Replace `part(id, { geometry, position, rotation })` with
105
+ `part(id, { geometry, transform })`. The optional transform accepts a fluent `transform3()` value,
106
+ a row-major 16-number matrix, or a computed reader; omission means identity. Pattern and
107
+ attachment matrices pass directly to `part()`. Only rigid placement is supported: scale, shear,
108
+ reflection, perspective, and non-finite values are rejected. Geometry-only and geometry-or-options
109
+ union inputs remain accepted without casts.
110
+
111
+ Use `PartDeclaration` for authored parts. Replace `ModelSnapshot` / `FinalizedModel` with
112
+ `ModelResult`, and `SnapshotPart` / `FinalizedPart` with `ModelPart`. `engine.run()`, `session.run()`,
113
+ and Testkit's `runModel()` return durable immutable results. Dispose sessions and runners to release their
114
+ caches; completed results remain valid and need no disposal. `model.part(id)` returns a required
115
+ semantic part or throws with available IDs; `model.parts` preserves output order.
116
+
117
+ Finalized buffers expose stable, iterable `ImmutableBuffer` values. Use `.copy()` for mutable
118
+ storage. A `ModelPart`'s top-level positions and indices are already in world space; apply its
119
+ immutable `transform` only to the reusable local buffers under `geometry`. Worker deliveries also
120
+ nest local resources under `geometry`. Host integrations use `ModelDeliveryEncoder`,
121
+ `PartDeliveryDecoder`, and `toWorldMeshes` for owned transfer and manufacturing output.
122
+ Replace `engine.export3mf(...)` with standalone `export3mf(parts, options)` from `/engine`, which
123
+ accepts completed meshes with explicit resource limits, independently of engine initialization.
124
+
125
+ Statistics rename `volumeMm3` → `signedVolume`, `finalizationMs` → `finalizeMs`, and
126
+ `generationMs` → `generateMs`; `buildMs` measures declarations.
127
+ `ModelResult.stats.byteCount` means bytes occupied by finalized world-space position and index buffers,
128
+ counting shared buffers once. Worker statistics preserve engine `generateMs`; host elapsed time
129
+ is `timings.totalMs`, with `paramsMs` for parameter work. Other host timing stages are optional:
130
+ an omitted stage is unmeasured.
131
+
132
+ Model parameter roots must be object maps. Captured parameters and computed results expose
133
+ deeply readonly `ImmutableValue<T>` views; supported mutable caller input is still accepted and
134
+ recaptured on every revision, including mutable children inside frozen objects. Deeply immutable
135
+ data retains identity reuse. Assertions and warnings accept bounded immutable nested `path`
136
+ metadata. Replace `validateModel` with `checkModel` and session/runner `.validate()` with `.check()`.
137
+ These check declarations and reached assertions without finalizing geometry; unexpected execution
138
+ failures still throw.
139
+
140
+ ### Generator SDK and Testkit
141
+
142
+ | Previous API | 0.18 API |
143
+ | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
144
+ | Nested compiled Generator module | Flat format-5 model program; bind an existing program with `defineGeneratorModule({ definition, program })` |
145
+ | `isGeneratorModule(value)` | `parseGeneratorModule(value, definition)` |
146
+ | `executeGenerator({ ..., parsedParameters })` | `executeGenerator({ ..., parameters })`; current-definition validation always runs |
147
+ | Callback-scoped `withModel`, `ModelTestOptions`, or `runner.withModel` | `await runModel(programOrCallback, ...)` for one-off results; explicit sessions or Generator runners for repeated edits |
148
+ | Generator test `runner.execute(...)` | `runner.run(...)`, returning `GeneratorTestResult` |
149
+ | `expectSerializableDefinition` | `assertValidDefinition` |
150
+ | `expectValidDefaults`, `expectValidPresets`, and `expectValidParts` | `assertValidDefaults`, `assertValidPresets`, and `assertValidParts` |
151
+ | `expectMeshMeasurements` | `assertMeshMeasurements` |
152
+ | `canonicalizeParameters(definition, parameters)` from `/parameters` | `canonicalizeParameters(parameters)` from `/runtime` |
153
+ | `GeneratedPartsDecoder`, `parseGeneratedParts`, and `GeneratedPartsLimits` | `PartDeliveryDecoder`, `parseModelParts`, and `PartDeliveryLimits` |
154
+ | `GeneratedPart` and `GeneratedModel` | `ModelPartData` and `ModelData` |
155
+ | `GeneratedPartDelivery` and `GeneratedModelDelivery` | `PartDelivery` and `ModelDelivery` |
156
+
157
+ Testkit `runModel` accepts existing `ModelProgram` values and synchronous inline callbacks;
158
+ typed callbacks use `runModel<Parameters, Services>`. Its definition-bound Generator overload
159
+ validates inputs and prepares services. The SDK Modeling adapter adds definition-inferred
160
+ `GeneratorModel` and `parseGeneratorModel`. Ordinary model tests preserve definition-inferred
161
+ input types. Reusable Generator runners accept physical generation policy plus `cache` and
162
+ `verifyCache` for edit sequences; dispose the runner after use.
163
+
164
+ Generator execution and ordinary tests strictly require complete canonical parameter maps.
165
+ Use the parameter codec for strict input and `normalizeParameters` for forgiving recovery;
166
+ replace `prepareParameters`, `validateParameters`, and `PreparedParameters` with
167
+ `createParamCodec(definition).parse(...)` or `.safeParse(...)`. Schema adapters remain private.
168
+ Codec failures provide structured issues without a substitute parameter map. For deliberate
169
+ malformed-input assertion probes, replace `runner.validate(parameters, { normalize: false })`
170
+ with `runner.check(parameters, { validateInput: false })`.
171
+ Defaults are selected only by omission or `undefined` where supported; a `null` parameter map is
172
+ rejected. Parameterless model calls can omit the empty map. Definition and preset helpers catch
173
+ nested key typos and validate immediately while retaining literal inference.
174
+
175
+ `assertValidParts` checks the supplied world-space buffers, closed topology, and exact agreement
176
+ with local geometry under `transform`; it no longer hides corrupted output by reconstructing it.
177
+ Always await `assertValidParts(parts, engine)` (or omit the engine): failures reject, and supplying
178
+ an engine changes resource reuse only.
179
+ `assertValidDefinition` also checks the canonical JSON round trip. New `assertEqualBuffer` and
180
+ `assertEqualMesh` compare exact ordered contents without copying; equivalent shapes with different
181
+ triangulations can differ. `ContentMismatchError` identifies the first differing element or
182
+ length, while malformed input and reader failures retain their errors. Optional `contentMatchers`
183
+ support `expect.extend`, including negation, without a test-framework dependency.
184
+
185
+ ### CLI and package requirements
186
+
187
+ Rename the type-only CLI command `metamesh check` to `metamesh typecheck`; no alias is retained.
188
+ Node authoring APIs and Modeling/Testkit declaration-check methods retain their names. Generator
189
+ runs use `overrides` / `--overrides`, merged with defaults before strict parsing; standalone models
190
+ use `params` / `--params`. Supplying the other entry kind's input option is rejected.
191
+
192
+ Captured runs use format 2 with transform-only placement. The bundled capture renderer resolves
193
+ from the installed CLI package so published installations can render captures.
194
+ Public packages require Node.js 24 or newer, ESM imports, and TypeScript 6.0 or newer with strict
195
+ checking for TypeScript consumers.
196
+
197
+ ### Patch Changes
198
+
199
+ - Updated dependencies [2eea694]
200
+ - @metamesh/modeling@0.18.0
201
+ - @metamesh/generator-sdk@0.18.0
202
+ - @metamesh/testkit@0.18.0
203
+
3
204
  ## 0.17.0
4
205
 
5
206
  ### Patch Changes
package/README.md CHANGED
@@ -1,71 +1,107 @@
1
1
  # `@metamesh/cli`
2
2
 
3
- Check, execute, inspect, and render trusted local MetaMesh source. The `metamesh` executable and
4
- Node-only `/authoring` functions use the production engine and version-matched installed documentation.
3
+ Check and run MetaMesh models from your terminal. Save generated geometry, inspect its dimensions,
4
+ and render previews from the same result. The `metamesh` command also reads documentation for
5
+ your installed packages, and its tools are available through a Node.js API.
5
6
 
6
7
  > **Preview:** `0.x` minor releases may break compatibility. Patches remain compatible within the
7
8
  > same minor line.
8
9
 
9
10
  ## Installation
10
11
 
11
- Install the same exact release of each MetaMesh package in the project where you author models:
12
+ Install matching Modeling and CLI releases in your authoring project:
12
13
 
13
14
  ```bash
14
15
  pnpm add @metamesh/modeling
15
16
  pnpm add -D @metamesh/cli
16
17
  ```
17
18
 
18
- Add `@metamesh/generator-sdk` when your source defines Generators. Add `@metamesh/testkit` as a
19
- development dependency when importing testing helpers. CLI internally reuses Testkit font fixtures
20
- and its SDK contracts; neither package needs to be directly declared for standalone model authoring.
21
- The compiler, bundler, Three renderer, and Playwright belong to CLI; Testkit does not install them.
22
- Chromium is installed separately and never downloaded by checking or generation.
19
+ The executable and Node API require Node.js 24 or newer. The API publishes ES modules only.
20
+ TypeScript source and API consumers need TypeScript 6.0 or newer with `strict` checking and
21
+ `NodeNext` or `Bundler` module resolution. The CLI includes its source-checking compiler.
22
+
23
+ Add the [Generator SDK](https://github.com/JasonKoolman/metamesh/blob/main/packages/generator-sdk/README.md)
24
+ to author Generators, or [Testkit](https://github.com/JasonKoolman/metamesh/blob/main/packages/testkit/README.md)
25
+ to import testing helpers directly. Standalone models need neither as a direct dependency.
26
+ Only rendering needs Chromium; checking, generation, and inspection work without a browser.
23
27
 
24
28
  ## Local workflow
25
29
 
30
+ Save this plate with a through hole as `model.ts`:
31
+
32
+ ```ts
33
+ import { box, cylinder, generate } from "@metamesh/modeling";
34
+
35
+ export default generate(({ part }) => {
36
+ const plate = box({ size: [30, 20, 6] });
37
+ const bore = cylinder({ height: 8, radius: 3, sizing: "face" });
38
+ return part("plate", plate.subtract(bore));
39
+ });
40
+ ```
41
+
42
+ From the same project, discover the installed packages, check the source, and capture a run:
43
+
26
44
  ```bash
27
45
  pnpm exec metamesh environment --json
46
+ pnpm exec metamesh docs search "sweep"
28
47
  pnpm exec metamesh docs show modeling/guide/concepts
29
- pnpm exec metamesh docs search "extrude"
30
- pnpm exec metamesh docs show TOPIC_ID
31
- pnpm exec metamesh check model.ts --kind model
48
+ pnpm exec metamesh typecheck model.ts --kind model
32
49
  pnpm exec metamesh run model.ts --kind model
33
50
  pnpm exec metamesh inspect RUN_DIRECTORY
51
+ pnpm exec metamesh export RUN_DIRECTORY --format 3mf --output plate.3mf
52
+ ```
53
+
54
+ Replace `RUN_DIRECTORY` with the path reported by `run`. Export that capture as 3MF, STL, or GLB.
55
+ Commands resolve packages and paths from the current project; use `--project PATH`
56
+ to select another. `environment` reports package versions, local documentation, and the supported
57
+ authoring protocol. Version mismatches fail before generation.
58
+
59
+ Local model entries default-export a `ModelProgram` created with Modeling's `generate` helper.
60
+ For Generators, use `--kind generator` with a named `definition` export and a default Generator
61
+ module. Local entries import from the installed packages; Studio's virtual `@metamesh/project`
62
+ module is unavailable here.
63
+
64
+ Use `--params values.json` for a standalone model's complete input map, or
65
+ `--overrides values.json` for a Generator. Generator overrides merge with defaults and undergo
66
+ strict validation. Standalone model parameters pass through directly. The Node API uses the
67
+ matching `params` and `overrides` fields, selected by `kind`.
68
+
69
+ ## Authoring tools
70
+
71
+ | Command | Purpose |
72
+ | --------------------------- | -------------------------------------------------------------------------- |
73
+ | `environment` | Discover project-local packages, capabilities, and documentation. |
74
+ | `docs search` / `docs show` | Find and read exact-version guides and API references offline. |
75
+ | `typecheck` | Check TypeScript and entry-point export types without executing the model. |
76
+ | `run` | Check and execute source, then capture geometry and diagnostics. |
77
+ | `export` | Save a capture as STL, 3MF, or GLB. |
78
+ | `inspect` | Read captured parts, bounds, measurements, and diagnostics. |
79
+ | `render` | Create standard views from a capture. |
80
+ | `setup` | Print skill installation and project-instruction guidance. |
81
+
82
+ `run` executes source in a disposable child process with a 30-second default deadline and saves
83
+ attempts under `.metamesh/runs`. Each capture records the source identity, effective parameters,
84
+ and geometry hashes. `inspect`, `export`, and `render` validate the saved buffers and read that same result
85
+ without rerunning the source.
86
+
87
+ Source runs with your account's permissions. The child process is not a security sandbox; run
88
+ source you trust. Use `--json` for structured stdout; author logs are bounded and sent to stderr.
89
+ Exit codes are 0 for success, 1 for source or generation failure, and 2 for invocation, environment,
90
+ artifact, or tooling failure. Each command's `--help` lists its options and limits.
91
+
92
+ ## Render a capture
93
+
94
+ Install the CLI's pinned Chromium version, then choose views of the captured model:
95
+
96
+ ```bash
34
97
  pnpm exec metamesh browser install
35
98
  pnpm exec metamesh render RUN_DIRECTORY --views isometric,front,top,right
36
99
  ```
37
100
 
38
- `environment` reports `authoringProtocolVersion: 1`, installed package versions, capabilities,
39
- and documentation manifest paths. Mixed package versions or stale documentation fail before
40
- generation. Commands resolve packages and relative paths from the current project; pass
41
- `--project PATH` to select another project explicitly. `TOPIC_ID` comes from documentation search;
42
- `RUN_DIRECTORY` comes from a successful run.
43
-
44
- The concepts guide starts with a compact construction map. Search matches distinct words in
45
- titles, guide headings, keywords, and summaries. Exact titles, topic IDs, and import paths take
46
- priority; equally complete intent matches favor guides over reference entries.
47
-
48
- `run` includes semantic TypeScript checking and executes in a disposable child process with a
49
- 30-second default deadline. It saves each attempt beneath `.metamesh/runs` with source identity,
50
- parameters, diagnostics, measurements, and hashed geometry buffers. `inspect` and `render` validate
51
- those captured buffers without importing the original source. Use `--json` for one structured stdout
52
- result; bounded author logs go to stderr. Exit codes are 0 for success, 1 for source or generation
53
- failure, and 2 for invocation, environment, artifact, or tooling failure. This executes trusted local
54
- code and is not a security sandbox.
55
-
56
- For Generators, use `--kind generator`. Entries named-export `definition` and default-export the
57
- Generator module envelope. Defaults merge with `--params parameters.json`, then strict validation
58
- rejects unknown or invalid overrides. Standalone model entries default-export a `ModelProgram` and
59
- receive no injected services. Both support normal local imports and installed dependencies; Studio's
60
- virtual `@metamesh/project` module is unavailable.
61
-
62
- Use `--help` on each command for parameters, fonts, resource limits, export, and image options.
63
- `metamesh setup` prints installation guidance for `metamesh-developer` from the canonical
64
- `metamesh-studio/metamesh-skills` repository and a project-instruction suggestion without modifying
65
- files. Its printed GitHub installation command becomes usable when that repository is published;
66
- the skill supplies the workflow, while installed package documentation supplies exact API facts.
67
-
68
- ## Programmatic API and documentation
101
+ Rendering supports PNG and lossless WebP, transparent backgrounds, and standard orthographic views.
102
+ Checking and generation never download a browser.
103
+
104
+ ## Node API and agent workflows
69
105
 
70
106
  ```ts
71
107
  import { inspectCapturedRun, runAuthoringSource } from "@metamesh/cli/authoring";
@@ -76,16 +112,22 @@ if (result.success) {
76
112
  }
77
113
  ```
78
114
 
79
- Resolve `@metamesh/cli/docs/manifest.json` for the installed Markdown reference, or run
80
- `pnpm exec metamesh docs show cli/guide/agent-authoring` for the complete authoring workflow.
81
- The [authoring guide source](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/docs/develop/agent-authoring.mdx)
82
- also covers local font fixtures, artifacts, rendering, and agent integration. Until the public
83
- documentation site launches, use installed documentation, repository sources, or
84
- [run the docs locally](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/README.md#local-use).
115
+ `@metamesh/cli/authoring` also exposes environment discovery, documentation lookup, source checking,
116
+ export, and rendering.
117
+
118
+ For coding agents, `metamesh setup` prints skill installation and project-instruction guidance
119
+ without changing files. The portable `metamesh-developer` skill describes the authoring workflow
120
+ and uses installed package documentation for API details.
121
+ Install a reviewed skill snapshot from the canonical `metamesh-studio/metamesh-skills` repository
122
+ or its local checkout when public installation is unavailable.
123
+
124
+ ## Documentation
85
125
 
86
- The reusable authoring functions and command implementation stay in this package; geometry and
87
- Generator contracts retain their existing owners. Testing assertions and font-fixture adapters
88
- remain in Testkit.
126
+ Resolve `@metamesh/cli/docs/manifest.json` for exact-version Markdown guidance and API references,
127
+ or run `pnpm exec metamesh docs show cli/guide/agent-authoring`. The
128
+ [authoring guide](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/docs/develop/agent-authoring.mdx)
129
+ covers entry contracts, parameters, fonts, captures, and rendering. See
130
+ [Contributing](https://github.com/JasonKoolman/metamesh/blob/main/CONTRIBUTING.md) for package development.
89
131
 
90
132
  ## License
91
133
 
@@ -1,8 +1,8 @@
1
- import type { ModelSnapshot } from "@metamesh/modeling/engine";
1
+ import { type ModelResult } from "@metamesh/modeling/engine";
2
2
  import type { CapturedPart, CapturedRun, CapturedRunManifest } from "./types.js";
3
3
  export declare function sha256(value: Uint8Array | string): string;
4
4
  export declare function writeAtomicJson(file: string, value: unknown): Promise<void>;
5
- export declare function captureSnapshot(directory: string, snapshot: ModelSnapshot, metadata: Omit<CapturedRunManifest, "resources" | "parts" | "bounds" | "stats" | "diagnostics" | "dependencies" | "export3mf">, export3mf?: ArrayBuffer): Promise<CapturedRunManifest>;
5
+ export declare function captureSnapshot(directory: string, snapshot: ModelResult, metadata: Omit<CapturedRunManifest, "resources" | "parts" | "bounds" | "stats" | "diagnostics" | "dependencies">): Promise<CapturedRunManifest>;
6
6
  /** Reads only immutable captured files. It never imports or executes original author source. */
7
7
  export declare function readCapturedRun(directory: string): Promise<CapturedRun>;
8
8
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../src/authoring/artifacts.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAa/D,OAAO,KAAK,EAGV,YAAY,EAEZ,WAAW,EACX,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,IAAI,CACZ,mBAAmB,EACnB,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,GAAG,cAAc,GAAG,WAAW,CAC1F,EACD,SAAS,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAqD9B;AAiQD,gGAAgG;AAChG,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAsF7E;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC,CAWpF"}
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../src/authoring/artifacts.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAYnC,OAAO,KAAK,EAGV,YAAY,EAEZ,WAAW,EACX,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,IAAI,CACZ,mBAAmB,EACnB,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,GAAG,cAAc,CAC5E,GACA,OAAO,CAAC,mBAAmB,CAAC,CAsD9B;AAyQD,gGAAgG;AAChG,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+E7E;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC,CAWpF"}
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
3
3
  import { join, resolve } from "node:path";
4
- import { defaultModelingLimits } from "@metamesh/modeling/engine";
5
- import { analyzeTriangleMesh, composeTransform3, eulerTransform3, transformTriangleMesh, translationTransform3, } from "@metamesh/modeling/geometry";
4
+ import { defaultModelingLimits, } from "@metamesh/modeling/engine";
5
+ import { IDENTITY_TRANSFORM3, validateRigidTransform3, } from "@metamesh/modeling/geometry";
6
+ import * as mesh from "@metamesh/modeling/geometry/mesh";
6
7
  import { authoringPackageNames, confinedFile } from "./environment.js";
7
8
  import { AuthoringError, isRecord } from "./errors.js";
8
9
  export function sha256(value) {
@@ -13,37 +14,41 @@ export async function writeAtomicJson(file, value) {
13
14
  await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: "wx" });
14
15
  await rename(temporary, file);
15
16
  }
16
- export async function captureSnapshot(directory, snapshot, metadata, export3mf) {
17
+ export async function captureSnapshot(directory, snapshot, metadata) {
17
18
  await mkdir(join(directory, "geometry"), { recursive: true });
18
19
  const resources = [];
19
20
  const parts = [];
20
21
  const seen = new Map();
22
+ const capturedResources = new Map();
21
23
  const store = async (path, bytes) => {
22
24
  await writeFile(join(directory, path), bytes, { flag: "wx" });
23
25
  return { path, bytes: bytes.byteLength, sha256: sha256(bytes) };
24
26
  };
25
27
  for (const part of snapshot.parts) {
26
- const positions = part.geometry.positions;
27
- const indices = part.geometry.indices;
28
- const positionBytes = new Uint8Array(positions.buffer, positions.byteOffset, positions.byteLength);
29
- const indexBytes = new Uint8Array(indices.buffer, indices.byteOffset, indices.byteLength);
30
- const key = `${sha256(positionBytes)}:${sha256(indexBytes)}`;
31
- let resourceId = seen.get(key);
28
+ let resourceId = capturedResources.get(part.geometry);
32
29
  if (resourceId === undefined) {
33
- resourceId = `geometry-${resources.length}`;
34
- seen.set(key, resourceId);
35
- resources.push({
36
- id: resourceId,
37
- positions: await store(`geometry/${resourceId}.positions.f32`, positionBytes),
38
- indices: await store(`geometry/${resourceId}.indices.u32`, indexBytes),
39
- measurements: part.geometry.measurements,
40
- });
30
+ const positions = part.geometry.positions.copy();
31
+ const indices = part.geometry.indices.copy();
32
+ const positionBytes = new Uint8Array(positions.buffer, positions.byteOffset, positions.byteLength);
33
+ const indexBytes = new Uint8Array(indices.buffer, indices.byteOffset, indices.byteLength);
34
+ const key = `${sha256(positionBytes)}:${sha256(indexBytes)}`;
35
+ resourceId = seen.get(key);
36
+ if (resourceId === undefined) {
37
+ resourceId = `geometry-${resources.length}`;
38
+ seen.set(key, resourceId);
39
+ resources.push({
40
+ id: resourceId,
41
+ positions: await store(`geometry/${resourceId}.positions.f32`, positionBytes),
42
+ indices: await store(`geometry/${resourceId}.indices.u32`, indexBytes),
43
+ measurements: part.geometry.measurements,
44
+ });
45
+ }
46
+ capturedResources.set(part.geometry, resourceId);
41
47
  }
42
48
  parts.push({
43
49
  id: part.id,
44
50
  resourceId,
45
- position: part.position,
46
- rotation: part.rotation,
51
+ transform: part.transform,
47
52
  measurements: part.measurements,
48
53
  ...(part.color === undefined ? {} : { color: part.color }),
49
54
  });
@@ -56,9 +61,6 @@ export async function captureSnapshot(directory, snapshot, metadata, export3mf)
56
61
  dependencies: snapshot.dependencies,
57
62
  resources,
58
63
  parts,
59
- ...(export3mf === undefined
60
- ? {}
61
- : { export3mf: await store("model.3mf", new Uint8Array(export3mf)) }),
62
64
  };
63
65
  await writeAtomicJson(join(directory, "manifest.json"), manifest);
64
66
  return manifest;
@@ -69,6 +71,16 @@ function invalid(message) {
69
71
  const vector = (value) => Array.isArray(value) &&
70
72
  value.length === 3 &&
71
73
  value.every((number) => typeof number === "number" && Number.isFinite(number));
74
+ function rigidTransform(value) {
75
+ try {
76
+ validateRigidTransform3(value);
77
+ const matrix = value;
78
+ return matrix[12] === 0 && matrix[13] === 0 && matrix[14] === 0 && matrix[15] === 1;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
72
84
  const bounds = (value) => isRecord(value) &&
73
85
  vector(value.min) &&
74
86
  vector(value.max) &&
@@ -111,8 +123,7 @@ function measurements(value) {
111
123
  ["signedVolume", "surfaceArea"].every((key) => typeof value[key] === "number" && Number.isFinite(value[key])) &&
112
124
  positiveInteger(value.triangleCount) &&
113
125
  positiveInteger(value.vertexCount) &&
114
- (value.connectedComponentCount === undefined ||
115
- positiveInteger(value.connectedComponentCount)) &&
126
+ (value.shellCount === undefined || positiveInteger(value.shellCount)) &&
116
127
  (value.edgeCount === undefined || positiveInteger(value.edgeCount)));
117
128
  }
118
129
  function sameBounds(actual, recorded) {
@@ -124,7 +135,7 @@ function sameMeasurements(actual, recorded) {
124
135
  actual.triangleCount === recorded.triangleCount &&
125
136
  actual.signedVolume === recorded.signedVolume &&
126
137
  actual.surfaceArea === recorded.surfaceArea &&
127
- actual.connectedComponentCount === recorded.connectedComponentCount &&
138
+ actual.shellCount === recorded.shellCount &&
128
139
  actual.edgeCount === recorded.edgeCount &&
129
140
  sameBounds(actual.bounds, recorded.bounds));
130
141
  }
@@ -134,7 +145,7 @@ function manifestFrom(value) {
134
145
  const limits = policy !== undefined && isRecord(policy.limits) ? policy.limits : undefined;
135
146
  const dependencies = isRecord(value) && isRecord(value.dependencies) ? value.dependencies : undefined;
136
147
  if (!isRecord(value) ||
137
- value.formatVersion !== 1 ||
148
+ value.formatVersion !== 3 ||
138
149
  value.status !== "complete" ||
139
150
  !identifier(value.runId) ||
140
151
  typeof value.createdAt !== "string" ||
@@ -189,14 +200,14 @@ function manifestFrom(value) {
189
200
  "partCount",
190
201
  "vertexCount",
191
202
  "triangleCount",
192
- "volumeMm3",
203
+ "signedVolume",
193
204
  "byteCount",
194
205
  "buildMs",
195
- "finalizationMs",
196
- "generationMs",
206
+ "finalizeMs",
207
+ "generateMs",
197
208
  ].every((key) => typeof stats?.[key] === "number" && Number.isFinite(stats[key])) ||
198
209
  !["partCount", "vertexCount", "triangleCount", "byteCount"].every((key) => positiveInteger(stats?.[key])) ||
199
- !["buildMs", "finalizationMs", "generationMs"].every((key) => typeof stats?.[key] === "number" && stats[key] >= 0) ||
210
+ !["buildMs", "finalizeMs", "generateMs"].every((key) => typeof stats?.[key] === "number" && stats[key] >= 0) ||
200
211
  dependencies === undefined ||
201
212
  !strings(dependencies.modelParameters) ||
202
213
  !Array.isArray(dependencies.computations) ||
@@ -215,8 +226,7 @@ function manifestFrom(value) {
215
226
  value.parts.length === 0 ||
216
227
  stats?.partCount !== value.parts.length ||
217
228
  value.parts.length > limits.maxParts ||
218
- value.resources.length > limits.maxParts ||
219
- (value.export3mf !== undefined && !bufferDescriptor(value.export3mf)))
229
+ value.resources.length > limits.maxParts)
220
230
  invalid("Unsupported, incomplete, or malformed captured-run manifest.");
221
231
  const ids = new Set();
222
232
  for (const resource of value.resources) {
@@ -236,8 +246,9 @@ function manifestFrom(value) {
236
246
  partIds.has(part.id) ||
237
247
  typeof part.resourceId !== "string" ||
238
248
  !ids.has(part.resourceId) ||
239
- !vector(part.position) ||
240
- !vector(part.rotation) ||
249
+ !rigidTransform(part.transform) ||
250
+ Object.hasOwn(part, "position") ||
251
+ Object.hasOwn(part, "rotation") ||
241
252
  !measurements(part.measurements) ||
242
253
  (part.color !== undefined &&
243
254
  (typeof part.color !== "string" || !/^#[a-f0-9]{6}$/i.test(part.color))))
@@ -288,7 +299,7 @@ export async function readCapturedRun(directory) {
288
299
  positions: new Float32Array(positions.buffer),
289
300
  indices: new Uint32Array(indices.buffer),
290
301
  };
291
- const facts = analyzeTriangleMesh(geometry, { requireClosed: true, includeTopology: true });
302
+ const facts = mesh.analyze(geometry, { requireClosed: true, includeTopology: true });
292
303
  if (!sameMeasurements(facts, resource.measurements))
293
304
  invalid(`Captured measurements do not match geometry ${resource.id}.`);
294
305
  resources.set(resource.id, { geometry, facts });
@@ -302,9 +313,12 @@ export async function readCapturedRun(directory) {
302
313
  };
303
314
  for (const part of manifest.parts) {
304
315
  const resource = resources.get(part.resourceId);
305
- const facts = [...part.position, ...part.rotation].every((number) => number === 0)
316
+ const facts = part.transform.every((number, index) => number === IDENTITY_TRANSFORM3[index])
306
317
  ? resource.facts
307
- : analyzeTriangleMesh(transformTriangleMesh(resource.geometry, composeTransform3(translationTransform3(part.position), eulerTransform3(part.rotation))), { requireClosed: true, includeTopology: true });
318
+ : mesh.analyze(mesh.transform(resource.geometry, part.transform), {
319
+ requireClosed: true,
320
+ includeTopology: true,
321
+ });
308
322
  if (!sameMeasurements(facts, part.measurements))
309
323
  invalid(`Captured placement measurements do not match part ${part.id}.`);
310
324
  vertices += facts.vertexCount;
@@ -317,15 +331,13 @@ export async function readCapturedRun(directory) {
317
331
  }
318
332
  if (vertices !== manifest.stats.vertexCount ||
319
333
  triangles !== manifest.stats.triangleCount ||
320
- volume !== manifest.stats.volumeMm3 ||
334
+ volume !== manifest.stats.signedVolume ||
321
335
  manifest.stats.byteCount < totalBytes ||
322
336
  manifest.stats.byteCount > manifest.policy.limits.maxBytes ||
323
337
  vertices > manifest.policy.limits.maxVertices ||
324
338
  triangles > manifest.policy.limits.maxTriangles ||
325
339
  !sameBounds(aggregateBounds, manifest.bounds))
326
340
  invalid("Captured aggregate facts do not match finalized part geometry.");
327
- if (manifest.export3mf !== undefined)
328
- await readBuffer(root, manifest.export3mf);
329
341
  return {
330
342
  directory: root,
331
343
  manifest,