@instruktlabs/kiln 1.2.1 → 1.2.3
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +86 -1
- package/README.md +18 -0
- package/dist/agent-run.mjs +6392 -1477
- package/dist/build.json +23 -23
- package/dist/cli.mjs +8453 -3407
- package/dist/evaluator-worker.mjs +16608 -12387
- package/dist/mcp-engine.mjs +6746 -1725
- package/dist/mcp-server.mjs +4 -4
- package/dist/viewer/app.js +203 -199
- package/dist/viewer/chat.html +209 -205
- package/dist/viewer/style.css +10 -0
- package/docs/atlas-provenance.md +23 -0
- package/docs/community-exporter.md +27 -0
- package/docs/export-profiles.md +20 -0
- package/docs/install.md +7 -6
- package/docs/material-library.md +9 -5
- package/docs/migration.md +16 -0
- package/docs/procedural-surfaces-v3.md +276 -0
- package/docs/projects-and-live-review.md +1 -1
- package/docs/rendering.md +41 -0
- package/docs/runtime.md +3 -3
- package/docs/sdk.md +55 -2
- package/docs/tools-operations.md +2 -2
- package/docs/tools.md +3 -3
- package/lib/asset-materials-node.d.ts +3 -3
- package/lib/asset-materials-node.js +29 -33
- package/lib/assets-node.d.ts +1 -1
- package/lib/assets-node.js +11 -4
- package/lib/assets.d.ts +4 -2
- package/lib/assets.js +61 -16
- package/lib/atlas-provenance.d.ts +86 -0
- package/lib/atlas-provenance.js +448 -0
- package/lib/character.d.ts +11 -2
- package/lib/character.js +90 -2
- package/lib/community-exporter.d.ts +3 -2
- package/lib/community-exporter.js +139 -3
- package/lib/discovery/helper-contracts.js +161 -7
- package/lib/discovery/helper-specs.js +49 -4
- package/lib/discovery/shapes.js +20 -7
- package/lib/engine-identity.d.ts +1 -1
- package/lib/engine-identity.js +1 -1
- package/lib/evaluator/authoring-diagnostic.d.ts +1 -1
- package/lib/evaluator/authoring-diagnostic.js +12 -0
- package/lib/evaluator/protocol.d.ts +2 -2
- package/lib/evaluator/protocol.js +16 -6
- package/lib/evaluator/subprocess.js +2 -2
- package/lib/exporter-node.js +9 -1
- package/lib/geometry-budget.d.ts +7 -0
- package/lib/geometry-budget.js +8 -0
- package/lib/gltf-texture-binding.d.ts +6 -0
- package/lib/gltf-texture-binding.js +54 -0
- package/lib/material-authoring-node.d.ts +6 -0
- package/lib/material-authoring-node.js +49 -0
- package/lib/material-authoring-schema.d.ts +871 -0
- package/lib/material-authoring-schema.js +310 -0
- package/lib/material-export.d.ts +5 -0
- package/lib/material-export.js +21 -0
- package/lib/material-library-node.d.ts +34 -8
- package/lib/material-library-node.js +691 -11
- package/lib/material-library.d.ts +3704 -4
- package/lib/material-library.js +272 -0
- package/lib/mcp-core.d.ts +1 -1
- package/lib/pinned-material-map.d.ts +33 -0
- package/lib/pinned-material-map.js +1 -0
- package/lib/portable-material-runtime.d.ts +5 -0
- package/lib/portable-material-runtime.js +160 -0
- package/lib/portable-sampler-runtime.d.ts +4 -0
- package/lib/portable-sampler-runtime.js +26 -0
- package/lib/portable-sampler.d.ts +11 -0
- package/lib/portable-sampler.js +42 -0
- package/lib/primitives.d.ts +15 -3
- package/lib/primitives.js +24 -4
- package/lib/procedural-material-v2.d.ts +2 -0
- package/lib/procedural-material-v2.js +2 -1
- package/lib/procedural-material-v3.d.ts +72 -0
- package/lib/procedural-material-v3.js +162 -0
- package/lib/procedural-texture-v2-compiler.d.ts +31 -0
- package/lib/procedural-texture-v2-compiler.js +172 -0
- package/lib/procedural-texture-v3-contract.d.ts +125 -0
- package/lib/procedural-texture-v3-contract.js +333 -0
- package/lib/procedural-texture-v3.d.ts +12 -0
- package/lib/procedural-texture-v3.js +165 -0
- package/lib/procedural-texture.d.ts +9 -31
- package/lib/procedural-texture.js +43 -170
- package/lib/project-bundle-node.d.ts +1 -1
- package/lib/project-bundle-node.js +70 -13
- package/lib/project-bundle.d.ts +91 -3
- package/lib/project-bundle.js +48 -7
- package/lib/qa/atlas.d.ts +21 -0
- package/lib/qa/atlas.js +474 -0
- package/lib/qa/index.d.ts +1 -0
- package/lib/qa/index.js +1 -0
- package/lib/qa/material.js +2 -0
- package/lib/qa/skin-integrity.d.ts +18 -0
- package/lib/qa/skin-integrity.js +221 -0
- package/lib/render-service-host.js +4 -7
- package/lib/render.d.ts +3 -3
- package/lib/render.js +108 -16
- package/lib/skin-binding.d.ts +20 -0
- package/lib/skin-binding.js +144 -0
- package/lib/texture-atlas-contract.d.ts +97 -0
- package/lib/texture-atlas-contract.js +414 -0
- package/lib/texture-atlas-pixels.d.ts +73 -0
- package/lib/texture-atlas-pixels.js +290 -0
- package/lib/texture-atlas.d.ts +10 -0
- package/lib/texture-atlas.js +143 -0
- package/lib/texture-bake.d.ts +3 -0
- package/lib/texture-bake.js +66 -11
- package/lib/texture-resolver.d.ts +22 -0
- package/lib/texture-resolver.js +24 -0
- package/lib/tools/materials.js +8 -7
- package/lib/tools/registry.js +5 -16
- package/lib/tools/review.js +1 -1
- package/lib/uv-data.d.ts +32 -0
- package/lib/uv-data.js +172 -0
- package/lib/uv-shapes.js +3 -0
- package/lib/uv.d.ts +9 -3
- package/lib/uv.js +53 -82
- package/lib/views/camera.d.ts +3 -3
- package/lib/views/camera.js +32 -28
- package/lib/views/face-policy.d.ts +9 -0
- package/lib/views/face-policy.js +16 -0
- package/lib/views/glb.js +218 -24
- package/lib/views/pose.d.ts +7 -1
- package/lib/views/pose.js +63 -12
- package/lib/views/raster.d.ts +6 -2
- package/lib/views/raster.js +49 -37
- package/lib/views/scene-copy.d.ts +6 -0
- package/lib/views/scene-copy.js +73 -0
- package/lib/views/scene-raster.js +2 -1
- package/lib/views/triangle-coverage.d.ts +8 -0
- package/lib/views/triangle-coverage.js +58 -0
- package/lib/workspace-node.js +7 -3
- package/lib/workspace.d.ts +2 -2
- package/package.json +3 -1
- package/plugin.json +1 -1
- package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
- package/plugins/kiln-engine/README.md +3 -3
- package/plugins/kiln-engine/package-provenance.json +6 -6
- package/plugins/kiln-engine/plugin.json +1 -1
- package/plugins/kiln-engine/runtime.json +1 -1
- package/skills/kiln-author-asset/SKILL.md +1 -1
- package/skills/kiln-author-asset/references/camera-cli.md +13 -0
- package/skills/kiln-author-asset/references/geometry-recipes.md +134 -0
- package/skills/kiln-author-asset/references/projects-and-materials.md +32 -2
- package/skills/kiln-compose-scene/references/runtime-scenes.md +19 -0
- package/skills/kiln-qa-asset/references/integration-checks.md +37 -2
- package/src/asset-materials-node.ts +38 -45
- package/src/assets-node.ts +22 -5
- package/src/assets.ts +61 -20
- package/src/atlas-provenance.ts +522 -0
- package/src/character.ts +107 -2
- package/src/community-exporter.ts +143 -3
- package/src/discovery/helper-contracts.ts +188 -7
- package/src/discovery/helper-specs.ts +69 -4
- package/src/discovery/shapes.ts +25 -9
- package/src/engine-identity.ts +1 -1
- package/src/evaluator/authoring-diagnostic.ts +19 -1
- package/src/evaluator/protocol.ts +19 -9
- package/src/evaluator/subprocess.ts +2 -2
- package/src/exporter-node.ts +12 -1
- package/src/generated/mcp-manifest.json +3 -3
- package/src/geometry-budget.ts +8 -0
- package/src/gltf-texture-binding.ts +60 -0
- package/src/material-authoring-node.ts +68 -0
- package/src/material-authoring-schema.ts +343 -0
- package/src/material-export.ts +25 -0
- package/src/material-library-node.ts +840 -16
- package/src/material-library.ts +348 -5
- package/src/pinned-material-map.ts +43 -0
- package/src/portable-material-runtime.ts +200 -0
- package/src/portable-sampler-runtime.ts +40 -0
- package/src/portable-sampler.ts +54 -0
- package/src/primitives.ts +98 -4
- package/src/procedural-material-v2.ts +2 -1
- package/src/procedural-material-v3.ts +224 -0
- package/src/procedural-texture-v2-compiler.ts +234 -0
- package/src/procedural-texture-v3-contract.ts +437 -0
- package/src/procedural-texture-v3.ts +220 -0
- package/src/procedural-texture.ts +61 -229
- package/src/project-bundle-node.ts +106 -14
- package/src/project-bundle.ts +61 -18
- package/src/project-cli.ts +29 -17
- package/src/qa/atlas.ts +597 -0
- package/src/qa/index.ts +1 -0
- package/src/qa/material.ts +2 -0
- package/src/qa/skin-integrity.ts +291 -0
- package/src/render-service-host.ts +4 -6
- package/src/render.ts +133 -15
- package/src/skin-binding.ts +225 -0
- package/src/texture-atlas-contract.ts +558 -0
- package/src/texture-atlas-pixels.ts +456 -0
- package/src/texture-atlas.ts +185 -0
- package/src/texture-bake.ts +88 -10
- package/src/texture-resolver.ts +55 -0
- package/src/tools/materials.ts +15 -11
- package/src/tools/registry.ts +5 -15
- package/src/tools/review.ts +1 -1
- package/src/uv-data.ts +238 -0
- package/src/uv-shapes.ts +3 -0
- package/src/uv.ts +73 -128
- package/src/viewer/app.ts +37 -0
- package/src/viewer/live-panel.ts +57 -1
- package/src/viewer/material-preview-panel.ts +140 -0
- package/src/viewer/material-preview.ts +138 -0
- package/src/viewer/materials-panel.ts +34 -5
- package/src/viewer/navigation.ts +2 -0
- package/src/views/camera.ts +37 -40
- package/src/views/face-policy.ts +20 -0
- package/src/views/glb.ts +294 -17
- package/src/views/pose.ts +71 -13
- package/src/views/raster.ts +73 -46
- package/src/views/scene-copy.ts +86 -0
- package/src/views/scene-raster.ts +2 -1
- package/src/views/triangle-coverage.ts +78 -0
- package/src/workspace-node.ts +11 -3
- package/src/workspace.ts +2 -2
package/lib/views/pose.js
CHANGED
|
@@ -35,11 +35,13 @@ export function clipNames(clips) {
|
|
|
35
35
|
* clip doesn't carry one.
|
|
36
36
|
*/
|
|
37
37
|
export function prepareClip(root, clip) {
|
|
38
|
-
const
|
|
38
|
+
const nodes = new Map();
|
|
39
39
|
root.traverse?.((obj) => {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
|
|
40
|
+
const node = obj;
|
|
41
|
+
if (node.name && !nodes.has(node.name))
|
|
42
|
+
nodes.set(node.name, node);
|
|
43
|
+
if (node.uuid)
|
|
44
|
+
nodes.set(node.uuid, node);
|
|
43
45
|
});
|
|
44
46
|
const tracks = [];
|
|
45
47
|
const unresolved = [];
|
|
@@ -52,8 +54,10 @@ export function prepareClip(root, clip) {
|
|
|
52
54
|
if (dot === -1)
|
|
53
55
|
continue;
|
|
54
56
|
const nodeName = raw.slice(0, dot);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
+
const suffix = raw.slice(dot + 1);
|
|
58
|
+
const morph = /^morphTargetInfluences(?:\[([^\]]+)\])?$/.exec(suffix);
|
|
59
|
+
const property = (morph ? 'morphTargetInfluences' : suffix);
|
|
60
|
+
if (property !== 'position' && property !== 'quaternion' && property !== 'scale' && !morph)
|
|
57
61
|
continue;
|
|
58
62
|
const times = track.times ? Array.from(track.times) : [];
|
|
59
63
|
const values = track.values ? Array.from(track.values) : [];
|
|
@@ -68,17 +72,48 @@ export function prepareClip(root, clip) {
|
|
|
68
72
|
throw new Error(`Unsupported animation interpolation on ${raw}; use LINEAR, STEP or CUBICSPLINE.`);
|
|
69
73
|
}
|
|
70
74
|
const interpolation = cubic ? 'CUBICSPLINE' : mode === 2300 ? 'STEP' : 'LINEAR';
|
|
71
|
-
|
|
75
|
+
const node = nodes.get(nodeName);
|
|
76
|
+
const indexed = morph?.[1];
|
|
77
|
+
const morphIndex = indexed === undefined
|
|
78
|
+
? undefined
|
|
79
|
+
: /^\d+$/.test(indexed)
|
|
80
|
+
? Number(indexed)
|
|
81
|
+
: node?.morphTargetDictionary?.[indexed];
|
|
82
|
+
let stride = morph
|
|
83
|
+
? indexed === undefined
|
|
84
|
+
? (node?.morphTargetInfluences?.length ?? 0)
|
|
85
|
+
: 1
|
|
86
|
+
: PROP_STRIDE[property];
|
|
87
|
+
let resolved = !!node;
|
|
88
|
+
if (morph) {
|
|
89
|
+
resolved =
|
|
90
|
+
!!node?.morphTargetInfluences?.length &&
|
|
91
|
+
(indexed === undefined ||
|
|
92
|
+
(Number.isInteger(morphIndex) &&
|
|
93
|
+
morphIndex >= 0 &&
|
|
94
|
+
morphIndex < node.morphTargetInfluences.length));
|
|
95
|
+
// Unresolved morph tracks remain visible diagnostics, but never get sampled.
|
|
96
|
+
if (!resolved)
|
|
97
|
+
stride = values.length / (times.length * (cubic ? 3 : 1));
|
|
98
|
+
if (!Number.isInteger(stride) ||
|
|
99
|
+
stride < 1 ||
|
|
100
|
+
values.length !== times.length * stride * (cubic ? 3 : 1) ||
|
|
101
|
+
!values.every(Number.isFinite) ||
|
|
102
|
+
!times.every((v, i) => Number.isFinite(v) && v >= 0 && (i === 0 || v > times[i - 1])))
|
|
103
|
+
throw new Error(`Invalid morph animation values on ${raw}.`);
|
|
104
|
+
}
|
|
105
|
+
if (!resolved)
|
|
72
106
|
unresolved.push(raw);
|
|
73
107
|
if (times[times.length - 1] > maxTime)
|
|
74
108
|
maxTime = times[times.length - 1];
|
|
75
109
|
tracks.push({
|
|
76
110
|
nodeName,
|
|
77
111
|
prop: property,
|
|
78
|
-
stride
|
|
112
|
+
stride,
|
|
79
113
|
times,
|
|
80
114
|
values,
|
|
81
115
|
interpolation,
|
|
116
|
+
...(morph ? { sourceName: raw, ...(morphIndex === undefined ? {} : { morphIndex }) } : {}),
|
|
82
117
|
});
|
|
83
118
|
}
|
|
84
119
|
const duration = clip.duration && clip.duration > 0 ? clip.duration : maxTime;
|
|
@@ -153,7 +188,7 @@ function sampleTrack(track, t, out) {
|
|
|
153
188
|
}
|
|
154
189
|
if (track.interpolation === 'CUBICSPLINE') {
|
|
155
190
|
cubicSegment(values, stride, i, times[i], times[i + 1], t, out);
|
|
156
|
-
if (
|
|
191
|
+
if (track.prop === 'quaternion')
|
|
157
192
|
normalizeQuaternion(out);
|
|
158
193
|
return;
|
|
159
194
|
}
|
|
@@ -168,7 +203,7 @@ function sampleTrack(track, t, out) {
|
|
|
168
203
|
const a = span > 0 ? (t - t0) / span : 0;
|
|
169
204
|
const o0 = i * stride;
|
|
170
205
|
const o1 = (i + 1) * stride;
|
|
171
|
-
if (
|
|
206
|
+
if (track.prop === 'quaternion') {
|
|
172
207
|
slerpFlat(quatTmp, values, o0, values, o1, a);
|
|
173
208
|
out[0] = quatTmp[0];
|
|
174
209
|
out[1] = quatTmp[1];
|
|
@@ -229,6 +264,12 @@ export function measureLoopClosure(root, clip) {
|
|
|
229
264
|
}
|
|
230
265
|
result.unassessedTracks = total - prepared.tracks.length;
|
|
231
266
|
for (const track of prepared.tracks) {
|
|
267
|
+
// This established evidence contract measures transforms only. Weight-loop
|
|
268
|
+
// continuity needs a separately versioned evidence field, not a scale label.
|
|
269
|
+
if (track.prop === 'morphTargetInfluences') {
|
|
270
|
+
result.unassessedTracks++;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
232
273
|
const name = `${track.nodeName}.${track.prop}`;
|
|
233
274
|
const valid = !prepared.unresolved.includes(name) &&
|
|
234
275
|
track.values.length ===
|
|
@@ -300,19 +341,29 @@ export function poseSceneAtTime(root, prepared, t) {
|
|
|
300
341
|
const node = obj;
|
|
301
342
|
if (node.name && !byName.has(node.name))
|
|
302
343
|
byName.set(node.name, node);
|
|
344
|
+
if (node.uuid)
|
|
345
|
+
byName.set(node.uuid, node);
|
|
303
346
|
});
|
|
304
347
|
const buf = [0, 0, 0, 0];
|
|
305
348
|
for (const track of prepared.tracks) {
|
|
306
349
|
const node = byName.get(track.nodeName);
|
|
307
|
-
if (!node
|
|
350
|
+
if (!node ||
|
|
351
|
+
prepared.unresolved.includes(track.sourceName ?? `${track.nodeName}.${track.prop}`))
|
|
308
352
|
continue;
|
|
309
353
|
sampleTrack(track, t, buf);
|
|
310
354
|
if (track.prop === 'position')
|
|
311
355
|
node.position?.set(buf[0], buf[1], buf[2]);
|
|
312
356
|
else if (track.prop === 'scale')
|
|
313
357
|
node.scale?.set(buf[0], buf[1], buf[2]);
|
|
314
|
-
else
|
|
358
|
+
else if (track.prop === 'quaternion')
|
|
315
359
|
node.quaternion?.set(buf[0], buf[1], buf[2], buf[3]);
|
|
360
|
+
else if (node.morphTargetInfluences) {
|
|
361
|
+
if (track.morphIndex !== undefined)
|
|
362
|
+
node.morphTargetInfluences[track.morphIndex] = buf[0];
|
|
363
|
+
else
|
|
364
|
+
for (let i = 0; i < track.stride; i++)
|
|
365
|
+
node.morphTargetInfluences[i] = buf[i];
|
|
366
|
+
}
|
|
316
367
|
}
|
|
317
368
|
root.updateMatrixWorld?.(true);
|
|
318
369
|
}
|
package/lib/views/raster.d.ts
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
* kiln-studio agent-runtime container ships without a browser
|
|
7
7
|
* (PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1) and Playwright is unsupported under
|
|
8
8
|
* Bun on Windows (see imposter/bake.ts). headless-gl is WebGL1-only while
|
|
9
|
-
* three >= r163 requires WebGL2. So: a
|
|
9
|
+
* three >= r163 requires WebGL2. So: a CPU scanline z-buffer
|
|
10
10
|
* rasterizer over the executed scene. Flat lambert, base color only — enough
|
|
11
11
|
* fidelity for silhouette / proportion / orientation / contact QA, which is
|
|
12
12
|
* what the vision loop checks.
|
|
13
13
|
*
|
|
14
14
|
* Scene access is strictly duck-typed (`.isMesh`, `matrixWorld.elements`,
|
|
15
|
-
* position
|
|
15
|
+
* vertex readers and position attributes) because sandbox-created objects live in a
|
|
16
16
|
* different module realm — see executeKilnCode in ../render.ts.
|
|
17
17
|
*
|
|
18
18
|
* Lighting matches scripts/visual-audit.ts: key directional light from
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* the offline audit grid) so inverted winding shows up as missing faces.
|
|
21
21
|
*/
|
|
22
22
|
import { type BackdropId } from "./background.js";
|
|
23
|
+
import { type SurfaceSide } from "./face-policy.js";
|
|
23
24
|
export interface ViewSpec {
|
|
24
25
|
name: string;
|
|
25
26
|
/** Direction from model center toward the camera (kiln frame: +X fwd, +Y up, +Z right). */
|
|
@@ -95,6 +96,7 @@ interface DuckObject3D {
|
|
|
95
96
|
visible?: boolean;
|
|
96
97
|
children?: DuckObject3D[];
|
|
97
98
|
updateMatrixWorld?(force?: boolean): void;
|
|
99
|
+
updateWorldMatrix?(updateParents: boolean, updateChildren: boolean): void;
|
|
98
100
|
traverse?(cb: (obj: unknown) => void): void;
|
|
99
101
|
}
|
|
100
102
|
interface Tri {
|
|
@@ -102,6 +104,8 @@ interface Tri {
|
|
|
102
104
|
color: [number, number, number];
|
|
103
105
|
alpha: number;
|
|
104
106
|
doubleSided: boolean;
|
|
107
|
+
side: SurfaceSide;
|
|
108
|
+
windingParity: 1 | -1;
|
|
105
109
|
}
|
|
106
110
|
/**
|
|
107
111
|
* Opaque triangles first in scene order, then translucent ones farthest-first,
|
package/lib/views/raster.js
CHANGED
|
@@ -6,20 +6,23 @@
|
|
|
6
6
|
* kiln-studio agent-runtime container ships without a browser
|
|
7
7
|
* (PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1) and Playwright is unsupported under
|
|
8
8
|
* Bun on Windows (see imposter/bake.ts). headless-gl is WebGL1-only while
|
|
9
|
-
* three >= r163 requires WebGL2. So: a
|
|
9
|
+
* three >= r163 requires WebGL2. So: a CPU scanline z-buffer
|
|
10
10
|
* rasterizer over the executed scene. Flat lambert, base color only — enough
|
|
11
11
|
* fidelity for silhouette / proportion / orientation / contact QA, which is
|
|
12
12
|
* what the vision loop checks.
|
|
13
13
|
*
|
|
14
14
|
* Scene access is strictly duck-typed (`.isMesh`, `matrixWorld.elements`,
|
|
15
|
-
* position
|
|
15
|
+
* vertex readers and position attributes) because sandbox-created objects live in a
|
|
16
16
|
* different module realm — see executeKilnCode in ../render.ts.
|
|
17
17
|
*
|
|
18
18
|
* Lighting matches scripts/visual-audit.ts: key directional light from
|
|
19
19
|
* [1.5, 2, 1] at 1.1 plus 0.25 ambient. Back-face culling is strict (like
|
|
20
20
|
* the offline audit grid) so inverted winding shows up as missing faces.
|
|
21
21
|
*/
|
|
22
|
+
import { Vector3 } from 'three';
|
|
22
23
|
import { resolveBackdrop } from "./background.js";
|
|
24
|
+
import { visibleNormalSign, worldWindingParity } from "./face-policy.js";
|
|
25
|
+
import { prepareTriangleCoverage } from "./triangle-coverage.js";
|
|
23
26
|
/** Same six views as the offline audit grid (scripts/visual-audit.ts). */
|
|
24
27
|
export const SIX_VIEWS = [
|
|
25
28
|
{ name: 'Front', dir: [1, 0, 0] },
|
|
@@ -98,6 +101,25 @@ function cross(a, b) {
|
|
|
98
101
|
function dot(a, b) {
|
|
99
102
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
100
103
|
}
|
|
104
|
+
function updateSurfaceMatrices(root) {
|
|
105
|
+
// Isolated part measurements may start below a changed parent. Keep the normal
|
|
106
|
+
// updateMatrixWorld dispatch too: SkinnedMesh refreshes its bind inverse there.
|
|
107
|
+
root.updateWorldMatrix?.(true, false);
|
|
108
|
+
root.updateMatrixWorld?.(true);
|
|
109
|
+
}
|
|
110
|
+
/** Three's own vertex reader applies morphs, then skinning, without changing the
|
|
111
|
+
* source buffers. A local Vector3 works across realms; no class identity test is
|
|
112
|
+
* needed. Plain GLB duck geometry retains its deterministic array fallback. */
|
|
113
|
+
function surfaceVertexReader(mesh, position) {
|
|
114
|
+
for (const bone of mesh.skeleton?.bones ?? [])
|
|
115
|
+
bone.updateWorldMatrix?.(true, false);
|
|
116
|
+
const vertex = new Vector3();
|
|
117
|
+
if (mesh.getVertexPosition)
|
|
118
|
+
return (index) => mesh.getVertexPosition(index, vertex);
|
|
119
|
+
if (position.getX && position.getY && position.getZ)
|
|
120
|
+
return (index) => vertex.set(position.getX(index), position.getY(index), position.getZ(index));
|
|
121
|
+
return (index) => vertex.set(position.array[index * 3], position.array[index * 3 + 1], position.array[index * 3 + 2]);
|
|
122
|
+
}
|
|
101
123
|
/**
|
|
102
124
|
* Opaque triangles first in scene order, then translucent ones farthest-first,
|
|
103
125
|
* as a GPU renderer composites them. Drawn in scene order, a translucent pane
|
|
@@ -147,7 +169,7 @@ function forEachDrawn(root, visit) {
|
|
|
147
169
|
}
|
|
148
170
|
/** Collect world-space triangles + base colors from a (possibly cross-realm) scene. */
|
|
149
171
|
export function collectTriangles(root) {
|
|
150
|
-
root
|
|
172
|
+
updateSurfaceMatrices(root);
|
|
151
173
|
const tris = [];
|
|
152
174
|
const min = [Infinity, Infinity, Infinity];
|
|
153
175
|
const max = [-Infinity, -Infinity, -Infinity];
|
|
@@ -183,12 +205,11 @@ export function collectTriangles(root) {
|
|
|
183
205
|
const m = mesh.matrixWorld?.elements;
|
|
184
206
|
if (!m)
|
|
185
207
|
return;
|
|
186
|
-
const
|
|
208
|
+
const windingParity = worldWindingParity(m);
|
|
209
|
+
const readVertex = surfaceVertexReader(mesh, pos);
|
|
187
210
|
const world = new Float64Array(pos.count * 3);
|
|
188
211
|
for (let i = 0; i < pos.count; i++) {
|
|
189
|
-
const x =
|
|
190
|
-
const y = arr[i * 3 + 1];
|
|
191
|
-
const z = arr[i * 3 + 2];
|
|
212
|
+
const { x, y, z } = readVertex(i);
|
|
192
213
|
const wx = m[0] * x + m[4] * y + m[8] * z + m[12];
|
|
193
214
|
const wy = m[1] * x + m[5] * y + m[9] * z + m[13];
|
|
194
215
|
const wz = m[2] * x + m[6] * y + m[10] * z + m[14];
|
|
@@ -219,11 +240,14 @@ export function collectTriangles(root) {
|
|
|
219
240
|
v[6] = world[c2 * 3];
|
|
220
241
|
v[7] = world[c2 * 3 + 1];
|
|
221
242
|
v[8] = world[c2 * 3 + 2];
|
|
243
|
+
const side = material?.doubleSided === true || material?.side === 2 ? 2 : material?.side === 1 ? 1 : 0;
|
|
222
244
|
tris.push({
|
|
223
245
|
v,
|
|
224
246
|
color: colorOf(material),
|
|
225
247
|
alpha: clamp01(material?.opacity ?? 1),
|
|
226
|
-
doubleSided:
|
|
248
|
+
doubleSided: side === 2,
|
|
249
|
+
side,
|
|
250
|
+
windingParity,
|
|
227
251
|
});
|
|
228
252
|
};
|
|
229
253
|
// Geometry groups address the index buffer for indexed geometry and the
|
|
@@ -307,6 +331,7 @@ export function rasterizeView(root, dir, opts = {}) {
|
|
|
307
331
|
const sx = new Float64Array(3);
|
|
308
332
|
const sy = new Float64Array(3);
|
|
309
333
|
const sz = new Float64Array(3);
|
|
334
|
+
const weights = new Float64Array(3);
|
|
310
335
|
// Orthographic: distance from the viewer runs against the view axis `z`.
|
|
311
336
|
const ordered = compositingOrder(tris, (px, py, pz) => -((px - center[0]) * z[0] + (py - center[1]) * z[1] + (pz - center[2]) * z[2]));
|
|
312
337
|
for (const tri of ordered) {
|
|
@@ -317,9 +342,14 @@ export function rasterizeView(root, dir, opts = {}) {
|
|
|
317
342
|
const nLen = Math.hypot(n[0], n[1], n[2]);
|
|
318
343
|
if (nLen < 1e-12)
|
|
319
344
|
continue;
|
|
320
|
-
const N = [
|
|
345
|
+
const N = [
|
|
346
|
+
(n[0] / nLen) * tri.windingParity,
|
|
347
|
+
(n[1] / nLen) * tri.windingParity,
|
|
348
|
+
(n[2] / nLen) * tri.windingParity,
|
|
349
|
+
];
|
|
321
350
|
const facing = dot(N, z);
|
|
322
|
-
|
|
351
|
+
const normalSign = visibleNormalSign(facing, tri.side, cull);
|
|
352
|
+
if (normalSign === 0)
|
|
323
353
|
continue;
|
|
324
354
|
if (tri.alpha <= 0)
|
|
325
355
|
continue;
|
|
@@ -335,38 +365,20 @@ export function rasterizeView(root, dir, opts = {}) {
|
|
|
335
365
|
}
|
|
336
366
|
// Flat lambert with the audit-grid light rig, in linear space, then the
|
|
337
367
|
// sRGB output transform (matching what a Three.js renderer would write).
|
|
338
|
-
const lambert = Math.max(0, dot(N, KEY_DIR));
|
|
368
|
+
const lambert = Math.max(0, dot(N, KEY_DIR) * normalSign);
|
|
339
369
|
const lit = Math.min(1, AMBIENT + KEY_INTENSITY * lambert);
|
|
340
370
|
const r = Math.round(linearToSrgb(tri.color[0] * lit) * 255);
|
|
341
371
|
const g = Math.round(linearToSrgb(tri.color[1] * lit) * 255);
|
|
342
372
|
const b = Math.round(linearToSrgb(tri.color[2] * lit) * 255);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const maxX = Math.min(size - 1, Math.ceil(Math.max(sx[0], sx[1], sx[2])));
|
|
346
|
-
const minY = Math.max(0, Math.floor(Math.min(sy[0], sy[1], sy[2])));
|
|
347
|
-
const maxY = Math.min(size - 1, Math.ceil(Math.max(sy[0], sy[1], sy[2])));
|
|
348
|
-
if (minX > maxX || minY > maxY)
|
|
349
|
-
continue;
|
|
350
|
-
const ax = sx[0], ay = sy[0], bx2 = sx[1], by = sy[1], cx = sx[2], cy = sy[2];
|
|
351
|
-
const area = (bx2 - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
352
|
-
if (Math.abs(area) < 1e-9)
|
|
373
|
+
const triangle = prepareTriangleCoverage(sx[0], sy[0], sx[1], sy[1], sx[2], sy[2], size);
|
|
374
|
+
if (!triangle)
|
|
353
375
|
continue;
|
|
354
|
-
const
|
|
376
|
+
const { minX, maxX, minY, maxY } = triangle;
|
|
355
377
|
for (let py = minY; py <= maxY; py++) {
|
|
356
378
|
for (let px = minX; px <= maxX; px++) {
|
|
357
|
-
|
|
358
|
-
const cyp = py + 0.5;
|
|
359
|
-
const w0 = ((bx2 - ax) * (cyp - ay) - (by - ay) * (cxp - ax)) * invArea;
|
|
360
|
-
const w1 = ((cx - bx2) * (cyp - by) - (cy - by) * (cxp - bx2)) * invArea;
|
|
361
|
-
const w2 = 1 - w0 - w1;
|
|
362
|
-
// Accept either winding on screen (back-face culling already happened
|
|
363
|
-
// in world space) — check all barycentrics share the area's sign space.
|
|
364
|
-
if (w0 < 0 || w1 < 0 || w2 < 0)
|
|
379
|
+
if (!triangle.sample(px + 0.5, py + 0.5, weights))
|
|
365
380
|
continue;
|
|
366
|
-
|
|
367
|
-
// for this edge-function arrangement; depth interpolation just needs a
|
|
368
|
-
// consistent convex combination, so order doesn't matter for QA views.
|
|
369
|
-
const depth = w1 * sz[0] + w2 * sz[1] + w0 * sz[2];
|
|
381
|
+
const depth = weights[0] * sz[0] + weights[1] * sz[1] + weights[2] * sz[2];
|
|
370
382
|
const pi = py * size + px;
|
|
371
383
|
if (depth <= zbuf[pi])
|
|
372
384
|
continue;
|
|
@@ -406,7 +418,7 @@ export function measurePartBounds(root) {
|
|
|
406
418
|
* triangle), but each mesh is transformed once however many requested ancestors contain it.
|
|
407
419
|
*/
|
|
408
420
|
export function createPartBoundsReader(root) {
|
|
409
|
-
root
|
|
421
|
+
updateSurfaceMatrices(root);
|
|
410
422
|
const own = new Map();
|
|
411
423
|
const meshBounds = (obj) => {
|
|
412
424
|
if (own.has(obj))
|
|
@@ -418,9 +430,9 @@ export function createPartBoundsReader(root) {
|
|
|
418
430
|
if (pos && pos.itemSize === 3 && m) {
|
|
419
431
|
const min = [Infinity, Infinity, Infinity];
|
|
420
432
|
const max = [-Infinity, -Infinity, -Infinity];
|
|
421
|
-
const
|
|
433
|
+
const readVertex = surfaceVertexReader(mesh, pos);
|
|
422
434
|
for (let i = 0; i < pos.count; i++) {
|
|
423
|
-
const x
|
|
435
|
+
const { x, y, z } = readVertex(i);
|
|
424
436
|
const w = [
|
|
425
437
|
m[0] * x + m[4] * y + m[8] * z + m[12],
|
|
426
438
|
m[1] * x + m[5] * y + m[9] * z + m[13],
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { DoubleSide, Group, Skeleton, } from 'three';
|
|
2
|
+
/** Owned review hierarchy and skeletons; immutable geometry/textures stay shared. */
|
|
3
|
+
export function cloneReviewScene(root, options = {}) {
|
|
4
|
+
const visible = new Set();
|
|
5
|
+
if (options.omitHidden)
|
|
6
|
+
root.traverseVisible((node) => visible.add(node));
|
|
7
|
+
else
|
|
8
|
+
root.traverse((node) => visible.add(node));
|
|
9
|
+
const retained = new Set(visible);
|
|
10
|
+
const hierarchy = new Set();
|
|
11
|
+
root.traverse((node) => hierarchy.add(node));
|
|
12
|
+
// Visibility hides drawing, not the transforms used by another mesh's skeleton.
|
|
13
|
+
for (const node of visible) {
|
|
14
|
+
const mesh = node;
|
|
15
|
+
if (!mesh.isSkinnedMesh)
|
|
16
|
+
continue;
|
|
17
|
+
if (!mesh.skeleton)
|
|
18
|
+
throw new Error(`Review skin ${mesh.name} has no skeleton.`);
|
|
19
|
+
for (const bone of mesh.skeleton.bones) {
|
|
20
|
+
if (!hierarchy.has(bone)) {
|
|
21
|
+
throw new Error(`Skin bone ${bone?.name ?? '(missing)'} is outside the review hierarchy. Include its ancestors in the review root.`);
|
|
22
|
+
}
|
|
23
|
+
for (let ancestor = bone; ancestor; ancestor = ancestor.parent) {
|
|
24
|
+
retained.add(ancestor);
|
|
25
|
+
if (ancestor === root)
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const copies = new Map();
|
|
31
|
+
const copyNode = (node) => {
|
|
32
|
+
// A hidden mesh/light can be a joint ancestor. Keep its transform as a group,
|
|
33
|
+
// not its drawing payload. Referenced bones must retain their Bone identity.
|
|
34
|
+
const copy = !visible.has(node) && !node.isBone
|
|
35
|
+
? new Group().copy(node, false)
|
|
36
|
+
: node.clone(false);
|
|
37
|
+
copies.set(node, copy);
|
|
38
|
+
if (options.omitHidden)
|
|
39
|
+
copy.visible = true;
|
|
40
|
+
for (const child of node.children)
|
|
41
|
+
if (retained.has(child))
|
|
42
|
+
copy.add(copyNode(child));
|
|
43
|
+
return copy;
|
|
44
|
+
};
|
|
45
|
+
if (!retained.has(root)) {
|
|
46
|
+
const empty = new Group();
|
|
47
|
+
empty.name = root.name;
|
|
48
|
+
return empty;
|
|
49
|
+
}
|
|
50
|
+
const copy = copyNode(root);
|
|
51
|
+
for (const [source, target] of copies) {
|
|
52
|
+
const sourceSkin = source;
|
|
53
|
+
const targetSkin = target;
|
|
54
|
+
if (targetSkin.isSkinnedMesh) {
|
|
55
|
+
// SkinnedMesh.copy already preserves bind mode/matrices and morph weights.
|
|
56
|
+
// Reconnect only to copied bones, including detached skeletons. Keep inverse
|
|
57
|
+
// matrices independent too, so later edits cannot mutate the retained source.
|
|
58
|
+
targetSkin.skeleton = new Skeleton(sourceSkin.skeleton.bones.map((bone) => copies.get(bone)), sourceSkin.skeleton.boneInverses.map((matrix) => matrix.clone()));
|
|
59
|
+
}
|
|
60
|
+
const mesh = target;
|
|
61
|
+
if (options.doubleSided && mesh.isMesh) {
|
|
62
|
+
const prepare = (material) => {
|
|
63
|
+
const owned = material.clone();
|
|
64
|
+
owned.side = DoubleSide;
|
|
65
|
+
return owned;
|
|
66
|
+
};
|
|
67
|
+
mesh.material = Array.isArray(mesh.material)
|
|
68
|
+
? mesh.material.map(prepare)
|
|
69
|
+
: prepare(mesh.material);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return copy;
|
|
73
|
+
}
|
|
@@ -18,6 +18,7 @@ import { resolveEvaluatorPortV2, } from "../evaluator/index.js";
|
|
|
18
18
|
import { loadGlbReviewScene } from "./glb.js";
|
|
19
19
|
import { encodePng } from "./png.js";
|
|
20
20
|
import { rasterizeView } from "./raster.js";
|
|
21
|
+
import { cloneReviewScene } from "./scene-copy.js";
|
|
21
22
|
const DEG = Math.PI / 180;
|
|
22
23
|
const PAD = 4;
|
|
23
24
|
const PAD_COLOR = [10, 11, 13];
|
|
@@ -51,7 +52,7 @@ export async function rasterizeComposedScene(parts, opts = {}) {
|
|
|
51
52
|
base = (await loadGlbReviewScene(glb)).root;
|
|
52
53
|
baseByInput.set(key, base);
|
|
53
54
|
}
|
|
54
|
-
const inst = base
|
|
55
|
+
const inst = cloneReviewScene(base);
|
|
55
56
|
inst.position.set(part.pos[0], part.pos[1], part.pos[2]);
|
|
56
57
|
inst.rotation.set(0, part.rotYDeg * DEG, 0);
|
|
57
58
|
inst.scale.setScalar(part.scale);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function prepareTriangleCoverage(ax: number, ay: number, bx: number, by: number, cx: number, cy: number, size: number): {
|
|
2
|
+
minX: number;
|
|
3
|
+
maxX: number;
|
|
4
|
+
minY: number;
|
|
5
|
+
maxY: number;
|
|
6
|
+
/** Writes A/B/C weights into caller-owned storage; no per-pixel allocations. */
|
|
7
|
+
sample(x: number, y: number, weights: Float64Array): boolean;
|
|
8
|
+
} | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function edge(ax, ay, bx, by, orientation) {
|
|
2
|
+
const dx = (bx - ax) * orientation;
|
|
3
|
+
const dy = (by - ay) * orientation;
|
|
4
|
+
// Screen Y grows downward. For positive area, top/left edges own exact ties.
|
|
5
|
+
const inclusive = dy < 0 || (dy === 0 && dx > 0);
|
|
6
|
+
// Both triangles evaluate a shared edge from the SAME endpoint. Merely reversing
|
|
7
|
+
// (B-A)x(P-A) can round differently from (A-B)x(P-B), causing cracks/overlap.
|
|
8
|
+
const forward = ax < bx || (ax === bx && ay <= by);
|
|
9
|
+
return forward
|
|
10
|
+
? { x: ax, y: ay, dx: bx - ax, dy: by - ay, sign: orientation, inclusive }
|
|
11
|
+
: { x: bx, y: by, dx: ax - bx, dy: ay - by, sign: -orientation, inclusive };
|
|
12
|
+
}
|
|
13
|
+
function value(e, x, y) {
|
|
14
|
+
return (e.dx * (y - e.y) - e.dy * (x - e.x)) * e.sign;
|
|
15
|
+
}
|
|
16
|
+
export function prepareTriangleCoverage(ax, ay, bx, by, cx, cy, size) {
|
|
17
|
+
if (![ax, ay, bx, by, cx, cy].every(Number.isFinite))
|
|
18
|
+
return null;
|
|
19
|
+
const area = value(edge(ax, ay, bx, by, 1), cx, cy);
|
|
20
|
+
if (!Number.isFinite(area) || area === 0)
|
|
21
|
+
return null;
|
|
22
|
+
const orientation = area < 0 ? -1 : 1;
|
|
23
|
+
const a = edge(bx, by, cx, cy, orientation);
|
|
24
|
+
const b = edge(cx, cy, ax, ay, orientation);
|
|
25
|
+
const c = edge(ax, ay, bx, by, orientation);
|
|
26
|
+
const minX = Math.max(0, Math.floor(Math.min(ax, bx, cx)));
|
|
27
|
+
const maxX = Math.min(size - 1, Math.ceil(Math.max(ax, bx, cx)));
|
|
28
|
+
const minY = Math.max(0, Math.floor(Math.min(ay, by, cy)));
|
|
29
|
+
const maxY = Math.min(size - 1, Math.ceil(Math.max(ay, by, cy)));
|
|
30
|
+
if (minX > maxX || minY > maxY)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
minX,
|
|
34
|
+
maxX,
|
|
35
|
+
minY,
|
|
36
|
+
maxY,
|
|
37
|
+
/** Writes A/B/C weights into caller-owned storage; no per-pixel allocations. */
|
|
38
|
+
sample(x, y, weights) {
|
|
39
|
+
const wa = value(a, x, y), wb = value(b, x, y), wc = value(c, x, y);
|
|
40
|
+
if (wa < 0 ||
|
|
41
|
+
wb < 0 ||
|
|
42
|
+
wc < 0 ||
|
|
43
|
+
(wa === 0 && !a.inclusive) ||
|
|
44
|
+
(wb === 0 && !b.inclusive) ||
|
|
45
|
+
(wc === 0 && !c.inclusive))
|
|
46
|
+
return false;
|
|
47
|
+
// Evaluate all three edges directly. Deriving one as 1-wa-wb was the source
|
|
48
|
+
// of false negative coverage. Tie ownership never biases interpolation.
|
|
49
|
+
const sum = wa + wb + wc;
|
|
50
|
+
if (!(sum > 0) || !Number.isFinite(sum))
|
|
51
|
+
return false;
|
|
52
|
+
weights[0] = wa / sum;
|
|
53
|
+
weights[1] = wb / sum;
|
|
54
|
+
weights[2] = wc / sum;
|
|
55
|
+
return true;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
package/lib/workspace-node.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { FileProjectStore } from "./projects-node.js";
|
|
4
|
-
import { FileMaterialLibrary, createMaterialLibraryPayload } from "./material-library-node.js";
|
|
4
|
+
import { FileMaterialLibrary, createMaterialLibraryPayload, resolveMaterialRecordClosure, } from "./material-library-node.js";
|
|
5
5
|
import { workspaceSelectionSchema, } from "./workspace.js";
|
|
6
6
|
import { materialDependenciesSchema } from "./projects.js";
|
|
7
7
|
import { MATERIAL_LIBRARY_LIMITS } from "./material-library.js";
|
|
@@ -58,16 +58,20 @@ export class FileWorkspace {
|
|
|
58
58
|
const materialDependencies = materialDependenciesSchema.parse([...pins.values()].sort((a, b) => a.resourceId.localeCompare(b.resourceId)));
|
|
59
59
|
if (materialDependencies.length > MATERIAL_LIBRARY_LIMITS.maxPayloadRecords)
|
|
60
60
|
throw new Error(`An evaluation may resolve at most ${MATERIAL_LIBRARY_LIMITS.maxPayloadRecords} material resources; select a smaller dependency closure.`);
|
|
61
|
-
const
|
|
61
|
+
const manifests = await Promise.all(materialDependencies.map(async (dependency) => {
|
|
62
62
|
if (dependency.sha256 !== dependency.revisionId)
|
|
63
63
|
throw new Error(`Material lock hash mismatch: ${dependency.resourceId}`);
|
|
64
64
|
try {
|
|
65
|
-
return await this.materials.
|
|
65
|
+
return await this.materials.readManifest(dependency.resourceId, dependency.revisionId);
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
68
|
throw new Error(`Locked material unavailable: ${dependency.resourceId} at ${dependency.revisionId}`, { cause: error });
|
|
69
69
|
}
|
|
70
70
|
}));
|
|
71
|
+
const materials = await resolveMaterialRecordClosure(manifests, {
|
|
72
|
+
readManifest: (id, revision) => this.materials.readManifest(id, revision),
|
|
73
|
+
read: (id, revision) => this.materials.readRecordFiles(id, revision),
|
|
74
|
+
});
|
|
71
75
|
const materialResources = await createMaterialLibraryPayload(materials);
|
|
72
76
|
return this.active.run({ ...(project ? { project } : {}), materialDependencies, materialResources }, execute);
|
|
73
77
|
}
|
package/lib/workspace.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { type MaterialDependency, type ProjectRevision } from "./projects.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { MaterialLibraryPayload } from "./material-library.js";
|
|
4
4
|
export interface WorkspaceBinding {
|
|
5
5
|
project?: ProjectRevision;
|
|
6
6
|
materialDependencies: MaterialDependency[];
|
|
7
|
-
materialResources:
|
|
7
|
+
materialResources: MaterialLibraryPayload;
|
|
8
8
|
}
|
|
9
9
|
export declare const workspaceSelectionSchema: z.ZodObject<{
|
|
10
10
|
projectId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instruktlabs/kiln",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Create and refine procedural 3D assets with JavaScript, image feedback, and reusable source revisions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Matthew Kissinger",
|
|
@@ -293,6 +293,8 @@
|
|
|
293
293
|
"docs/install.md",
|
|
294
294
|
"docs/material-library.md",
|
|
295
295
|
"docs/migration.md",
|
|
296
|
+
"docs/procedural-surfaces-v3.md",
|
|
297
|
+
"docs/atlas-provenance.md",
|
|
296
298
|
"docs/programs.md",
|
|
297
299
|
"docs/projects-and-live-review.md",
|
|
298
300
|
"docs/rendering.md",
|
package/plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "kiln",
|
|
3
3
|
"displayName": "Kiln",
|
|
4
4
|
"description": "Build and revise 3D assets with your coding agent. Write JavaScript, inspect selected views, edit saved source revisions, and export GLB files locally.",
|
|
5
|
-
"version": "1.2.
|
|
5
|
+
"version": "1.2.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/instruktlabs/kiln",
|
|
8
8
|
"keywords": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Kiln local plugin
|
|
2
2
|
|
|
3
|
-
Plugin version 1.2.
|
|
4
|
-
This plugin pins engine 1.2.
|
|
3
|
+
Plugin version 1.2.3, maintained by Instrukt Labs under the MIT license.
|
|
4
|
+
This plugin pins engine 1.2.3. Registry setup requires that exact version
|
|
5
5
|
to be published. Use a published release tag for normal installation or a reviewed
|
|
6
6
|
local archive for development qualification. The installation ID remains
|
|
7
7
|
`kiln-engine@instruktlabs`.
|
|
@@ -31,7 +31,7 @@ codex plugin add kiln-engine@instruktlabs
|
|
|
31
31
|
Restart or reload your coding agent's plugins as its installation message directs.
|
|
32
32
|
Then ask it to set up a Kiln workspace. The maintained setup skill runs this
|
|
33
33
|
plugin's `bin/kiln-setup-workspace.mjs` with Node, installs
|
|
34
|
-
`@instruktlabs/kiln@1.2.
|
|
34
|
+
`@instruktlabs/kiln@1.2.3` outside the project and configures your chosen
|
|
35
35
|
workspace. The same initializer supports existing projects and new workspaces.
|
|
36
36
|
|
|
37
37
|
The plugin registers setup only. The workspace supplies authoring skills and one
|