@instruktlabs/kiln 1.2.0 → 1.2.2
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 +27 -0
- package/README.md +11 -0
- package/dist/agent-run.mjs +5 -2
- package/dist/build.json +22 -22
- package/dist/cli.mjs +5 -2
- package/dist/mcp-engine.mjs +3 -4
- package/dist/mcp-server.mjs +6 -6
- package/dist/viewer/app.js +180 -180
- package/dist/viewer/chat.html +183 -183
- package/docs/install.md +7 -6
- package/docs/migration.md +16 -0
- package/docs/runtime.md +3 -3
- package/docs/sdk.md +2 -2
- package/lib/asset-widget-uri.d.ts +1 -1
- package/lib/asset-widget-uri.js +1 -1
- package/lib/engine-identity.d.ts +1 -1
- package/lib/engine-identity.js +1 -1
- package/lib/mcp-core.d.ts +1 -1
- package/lib/tools/registry.d.ts +1 -2
- package/lib/tools/registry.js +2 -1
- package/package.json +1 -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 +10 -0
- package/src/asset-widget-uri.ts +1 -1
- package/src/engine-identity.ts +1 -1
- package/src/generated/mcp-manifest.json +4 -4
- package/src/tools/registry.ts +2 -1
- package/src/viewer/chat-app.ts +6 -2
- package/src/viewer/glb-loader.ts +78 -0
- package/src/viewer/scene.ts +9 -26
package/docs/install.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Install Kiln for your coding agent
|
|
2
2
|
|
|
3
|
-
This guide
|
|
3
|
+
This guide covers the stable `@instruktlabs/kiln` package. Feature-specific
|
|
4
|
+
minimum versions are noted where they matter.
|
|
4
5
|
Use a version available in the [npm registry](https://www.npmjs.com/package/@instruktlabs/kiln),
|
|
5
6
|
or build a [local package](#install-a-local-package) to test an unpublished checkout.
|
|
6
7
|
[CHANGELOG.md](../CHANGELOG.md) lists changes; read the
|
|
@@ -154,7 +155,7 @@ For agent-assisted installation, give your agent the repository URL and ask:
|
|
|
154
155
|
|
|
155
156
|
## Release compatibility
|
|
156
157
|
|
|
157
|
-
The
|
|
158
|
+
The packaged local MCP server lists the fourteen base tools in the
|
|
158
159
|
[generated tool reference](tools.md), plus `kiln_project`, `kiln_material` and
|
|
159
160
|
`kiln_review` from the packaged local host: seventeen in all. When installing an
|
|
160
161
|
older release, use the documentation and receipt attached to that release; newer
|
|
@@ -163,7 +164,7 @@ tools and migration changes are not retroactively available there.
|
|
|
163
164
|
Package publication requires per-platform receipts (Linux, Windows, macOS arm64,
|
|
164
165
|
macOS x64) and `SHA256SUMS.txt` for the exact tarball. Check the artifacts actually
|
|
165
166
|
attached to the selected release; do not treat an older receipt or a source archive
|
|
166
|
-
as qualification of
|
|
167
|
+
as qualification of a different package version.
|
|
167
168
|
|
|
168
169
|
A successful local build or an unreleased fix does not update the GitHub release: a
|
|
169
170
|
checkout can be ahead of it. Registry and GitHub release receipts each identify the
|
|
@@ -483,7 +484,7 @@ The v1 local plugin below uses a smaller bundle and creates an explicit workspac
|
|
|
483
484
|
|
|
484
485
|
## Local Claude Code and Codex plugins
|
|
485
486
|
|
|
486
|
-
The commands below select the 1.1
|
|
487
|
+
The commands below select the 1.2.1 plugin, which supports new workspaces and
|
|
487
488
|
existing-project adoption. Use them once the matching GitHub tag and npm version
|
|
488
489
|
are available. To qualify an unpublished candidate, select its exact source ref
|
|
489
490
|
and supply the matching local engine archive as described in its bundled setup
|
|
@@ -498,14 +499,14 @@ does not start a second global Kiln MCP server.
|
|
|
498
499
|
For Claude Code:
|
|
499
500
|
|
|
500
501
|
```sh
|
|
501
|
-
claude plugin marketplace add instruktlabs/kiln#v1.1
|
|
502
|
+
claude plugin marketplace add instruktlabs/kiln#v1.2.1 --sparse .claude-plugin plugins/kiln-engine
|
|
502
503
|
claude plugin install kiln-engine@instruktlabs
|
|
503
504
|
```
|
|
504
505
|
|
|
505
506
|
For Codex:
|
|
506
507
|
|
|
507
508
|
```sh
|
|
508
|
-
codex plugin marketplace add instruktlabs/kiln --ref v1.1
|
|
509
|
+
codex plugin marketplace add instruktlabs/kiln --ref v1.2.1 --sparse .agents/plugins --sparse plugins/kiln-engine
|
|
509
510
|
codex plugin add kiln-engine@instruktlabs
|
|
510
511
|
```
|
|
511
512
|
|
package/docs/migration.md
CHANGED
|
@@ -4,6 +4,22 @@ Existing inline-code transport and the legacy capture format remain supported. T
|
|
|
4
4
|
Discovery and authoring-helper changes below require explicit migration; retired
|
|
5
5
|
names have no callable compatibility aliases.
|
|
6
6
|
|
|
7
|
+
## Changes in 1.2
|
|
8
|
+
|
|
9
|
+
Version 1.2.0 adds an opt-in named-operation presentation derived from the shared
|
|
10
|
+
tool registry. The default local MCP interface retains its seventeen tools;
|
|
11
|
+
ordinary local callers need no migration. Embedding hosts can adopt the
|
|
12
|
+
[named-operation presentation](sdk.md#named-operation-presentation), including
|
|
13
|
+
consolidated asset search and material operations. Host capabilities determine
|
|
14
|
+
which tools are available; do not maintain a separate schema copy.
|
|
15
|
+
|
|
16
|
+
Version 1.2.1 fixes embedded texture loading in the shared presentation viewer
|
|
17
|
+
under restrictive MCP Apps content security policies, reports failed texture
|
|
18
|
+
decoding, and advances the widget resource URI to refresh cached components.
|
|
19
|
+
Upgrade the installed package and rebuild embedding hosts to use those fixes.
|
|
20
|
+
No tool-count change or saved-asset migration is required. Package publication
|
|
21
|
+
does not establish that a particular hosted deployment has been upgraded.
|
|
22
|
+
|
|
7
23
|
## Changes in 1.1
|
|
8
24
|
|
|
9
25
|
Version 1.1 adds existing-project setup
|
package/docs/runtime.md
CHANGED
|
@@ -133,9 +133,9 @@ Advanced geometry callbacks also have operation-specific input limits. Those che
|
|
|
133
133
|
|
|
134
134
|
### Bake defaults
|
|
135
135
|
|
|
136
|
-
The rigid-group `full` contract and `rigid-v1` rebuild policy below are
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
The rigid-group `full` contract and `rigid-v1` rebuild policy below are supported
|
|
137
|
+
in stable Kiln releases from 1.0 onward. When upgrading a 0.10.0 integration,
|
|
138
|
+
see the [migration notes](migration.md#changes-in-10).
|
|
139
139
|
|
|
140
140
|
`KILN_BAKE_OPTIMIZE` and `KILN_BAKE_INSTANCE` choose the bake passes for a render that does not pass `optimize` or `instance`; a value the call passes always wins.
|
|
141
141
|
|
package/docs/sdk.md
CHANGED
|
@@ -22,7 +22,7 @@ const result = await renderGLB(code);
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
The complete authoring and host APIs remain available through documented subpaths.
|
|
25
|
-
The original 55 export paths are retained; version 1.1
|
|
25
|
+
The original 55 export paths are retained; version 1.1 added four
|
|
26
26
|
host entrypoints below. The root exposes rendering, validation,
|
|
27
27
|
Discovery and engine identity; it does not re-export every name from every module.
|
|
28
28
|
CommonJS is not part of this release's contract.
|
|
@@ -53,7 +53,7 @@ entrypoint list. Files inside `lib/`, including declaration dependencies under
|
|
|
53
53
|
|
|
54
54
|
## Stability in 1.x
|
|
55
55
|
|
|
56
|
-
The table classifies all 59 public entrypoints
|
|
56
|
+
The table classifies all 59 public entrypoints. The root and every listed subpath
|
|
57
57
|
except `implicit` are stable entrypoints for v1: their declared public signatures,
|
|
58
58
|
record shapes and documented behavior receive normal semantic-versioning
|
|
59
59
|
compatibility. Optional dependencies do not make the two agent APIs experimental.
|
package/lib/asset-widget-uri.js
CHANGED
package/lib/engine-identity.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `src/__tests__/package-contents.test.ts` fails if it disagrees with the manifest
|
|
27
27
|
* or with any plugin manifest, which is what keeps the literal honest.
|
|
28
28
|
*/
|
|
29
|
-
export declare const ENGINE_VERSION = "1.2.
|
|
29
|
+
export declare const ENGINE_VERSION = "1.2.2";
|
|
30
30
|
/**
|
|
31
31
|
* Where this engine was loaded from, as a `file://` URL of the package root.
|
|
32
32
|
*
|
package/lib/engine-identity.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `src/__tests__/package-contents.test.ts` fails if it disagrees with the manifest
|
|
27
27
|
* or with any plugin manifest, which is what keeps the literal honest.
|
|
28
28
|
*/
|
|
29
|
-
export const ENGINE_VERSION = '1.2.
|
|
29
|
+
export const ENGINE_VERSION = '1.2.2';
|
|
30
30
|
/**
|
|
31
31
|
* Where this engine was loaded from, as a `file://` URL of the package root.
|
|
32
32
|
*
|
package/lib/mcp-core.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { type ServeStdioOptions, type StdioServerHandle } from '@modelcontextpro
|
|
|
23
23
|
import type { AssetLink } from "./assets-resources.js";
|
|
24
24
|
/** Server identity reported in the handshake and stamped into every 2026-07-28 result. */
|
|
25
25
|
export declare const MCP_SERVER_NAME = "kiln";
|
|
26
|
-
export declare const MCP_SERVER_VERSION = "1.2.
|
|
26
|
+
export declare const MCP_SERVER_VERSION = "1.2.2";
|
|
27
27
|
/**
|
|
28
28
|
* The spec's optional `instructions` field, returned by `initialize` and
|
|
29
29
|
* `server/discover`.
|
package/lib/tools/registry.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { type ViewRenderTimeoutContextProvider, type ViewRenderTimeoutResolver }
|
|
|
17
17
|
import { ViewEvidenceHistoryStore } from "../views/evidence-history.js";
|
|
18
18
|
import { type BackdropId } from "../views/background.js";
|
|
19
19
|
import type { TextureUsage } from "../textures.js";
|
|
20
|
-
export
|
|
20
|
+
export { KILN_ASSET_WIDGET_URI } from "../asset-widget-uri.js";
|
|
21
21
|
export interface KilnToolDef {
|
|
22
22
|
/** Shared action ownership and requirements; never hand-maintained by transports. */
|
|
23
23
|
actionContract?: Readonly<Record<string, import("./operation-contract.js").KilnActionContract>>;
|
|
@@ -793,4 +793,3 @@ export declare function newestRevision(target: {
|
|
|
793
793
|
export declare function buildProgramAssetDraft(code: string, context: KilnToolContext, backdrop?: BackdropId): Promise<Pick<import("../assets.js").AssetDraft, 'code' | 'glb' | 'preview' | 'previewInfo' | 'build'>>;
|
|
794
794
|
/** All asset operation schemas live here, alongside the existing tool definitions. */
|
|
795
795
|
export declare function createKilnAssetDefs(context: KilnToolContext, presentation?: 'grouped' | 'operations'): KilnToolDef[];
|
|
796
|
-
export {};
|
package/lib/tools/registry.js
CHANGED
|
@@ -35,7 +35,8 @@ import { persistedPreviewFidelity } from "./preview-fidelity.js";
|
|
|
35
35
|
// =============================================================================
|
|
36
36
|
// Tool definition contract
|
|
37
37
|
// =============================================================================
|
|
38
|
-
export
|
|
38
|
+
export { KILN_ASSET_WIDGET_URI } from "../asset-widget-uri.js";
|
|
39
|
+
import { KILN_ASSET_WIDGET_URI } from "../asset-widget-uri.js";
|
|
39
40
|
function proceduralTextureMaterialContract(rendered, context) {
|
|
40
41
|
const required = [...new Set(context.requiredProceduralTextureUsages ?? [])];
|
|
41
42
|
if (required.length === 0)
|
package/package.json
CHANGED
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.2",
|
|
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.2, maintained by Instrukt Labs under the MIT license.
|
|
4
|
+
This plugin pins engine 1.2.2. 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.2` 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
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"kind": "kiln-local-plugin",
|
|
4
|
-
"pluginVersion": "1.2.
|
|
5
|
-
"engineVersion": "1.2.
|
|
4
|
+
"pluginVersion": "1.2.2",
|
|
5
|
+
"engineVersion": "1.2.2",
|
|
6
6
|
"executables": [
|
|
7
7
|
"bin/kiln-setup-workspace.mjs"
|
|
8
8
|
],
|
|
9
9
|
"files": {
|
|
10
10
|
".claude-plugin/icon.png": "sha256:b4b651c402793b7e27cab780f04b14f80daec6d0cd5b7e4e90313930581a54e9",
|
|
11
|
-
".claude-plugin/plugin.json": "sha256:
|
|
11
|
+
".claude-plugin/plugin.json": "sha256:8f4586ed437f4a0478de900547cdf5ace59f0e125a0f0b8d7e717e44061509e9",
|
|
12
12
|
"bin/kiln-setup-workspace.mjs": "sha256:3d9a30236919a6ca91acb6b26f808cec442e6edc3dd88fe010645b010d76e838",
|
|
13
13
|
"LICENSE": "sha256:cdba278ebf1d6de618869936226bf03be4887179c128efc7aaa13d22034826a5",
|
|
14
|
-
"plugin.json": "sha256:
|
|
14
|
+
"plugin.json": "sha256:937211437e673cef141599c5ec5e99902596a9bedf6d772694f50a39f9d8e6dd",
|
|
15
15
|
"PRIVACY.md": "sha256:5f58d981667e40441e21c2dec411f269a9a67a6141cec61976d27088fb12573b",
|
|
16
|
-
"README.md": "sha256:
|
|
17
|
-
"runtime.json": "sha256:
|
|
16
|
+
"README.md": "sha256:2224bb16ed6645a1861fce4fbcf6801e4b81a0cb918970ad95c21e75be511eb7",
|
|
17
|
+
"runtime.json": "sha256:cbb2367a7435583074f589e44162739db5f972d8e6656f6f1fb4b790ad6eddcc",
|
|
18
18
|
"skills/kiln-setup-workspace/references/plugin-install.md": "sha256:c338fb792e7fad0364c3e1a13c94c506afa1395489ec1b6e83b36d04728935fb",
|
|
19
19
|
"skills/kiln-setup-workspace/references/renderer-setup.md": "sha256:b743b1b2827fa814385990e1e5b849391cb427413e9c016d273aa19cebd46031",
|
|
20
20
|
"skills/kiln-setup-workspace/SKILL.md": "sha256:65ed926b3503a33f4fe793e84e4a045ce36e3967f0e6b340e456a15dd010ce54"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "kiln-engine",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.2",
|
|
5
5
|
"description": "Set up a local Kiln workspace to create, inspect and revise editable 3D assets with Claude Code or Codex.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Instrukt Labs",
|
|
@@ -97,7 +97,7 @@ node kiln.mjs render RETURNED_REF --out asset-v1.glb --views asset-v1.png
|
|
|
97
97
|
|
|
98
98
|
Replace `RETURNED_REF` with the final reference returned by Kiln. Keep `.kiln/programs`, including its mappings, while using saved references. Pass `--out` whenever you want a GLB: `render` with neither `--out` nor `--views` writes `out.glb` in the current directory.
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Use `render --json` for the source reference, requirements, paths, fidelity and compact review (`--detail full` only for a closer look). Read images separately. Check `ok` and `files` on every export, even when redirected; a later command can hide failure. Deliver source, GLB and bundles from the same final reference or revision.
|
|
101
101
|
|
|
102
102
|
To save a chosen camera view, write the `capture` object to `cameras.json` and run `node kiln.mjs render RETURNED_REF --capture cameras.json --views hero.png`, the same camera schema and pipeline as MCP; the CLI is the route that writes review PNGs to disk. With `"output": "separate"` it writes `hero.shot-01.png`, `hero.shot-02.png` and so on and lists each in `files`; add `--backdrop light` or `dark` when the grey hides the silhouette. Do not copy image base64 into shell commands.
|
|
103
103
|
|
|
@@ -31,3 +31,16 @@ shots share the grid. With `"output": "separate"` the CLI writes one file per sh
|
|
|
31
31
|
beside the `--views` path (`hero.shot-01.png`, `hero.shot-02.png`, ...) and lists each
|
|
32
32
|
in `files` with its shot name. MCP delivers the same shots as separate image blocks. `--backdrop neutral|dark|light` sets the backdrop of
|
|
33
33
|
any `--views` sheet, with or without `--capture`, and wins over the recipe's own `backdrop`.
|
|
34
|
+
|
|
35
|
+
## Verify exported files
|
|
36
|
+
|
|
37
|
+
Read each command's JSON receipt, including output redirected to a file. Check
|
|
38
|
+
`ok`, the returned reference and each entry in `files`; a GLB can exist even when
|
|
39
|
+
an image failed. A subsequent successful shell command does not establish that
|
|
40
|
+
an earlier export succeeded. Source export refuses to overwrite an existing file;
|
|
41
|
+
choose a new path instead of assuming that the old file was refreshed.
|
|
42
|
+
|
|
43
|
+
After an edit, export from the final reference or exact saved revision. Any source,
|
|
44
|
+
GLB and ZIP you hand over must describe that same revision. Regenerate a bundle
|
|
45
|
+
when its contents changed, or clearly identify it as an older revision rather than
|
|
46
|
+
listing it as the latest delivery.
|
|
@@ -69,6 +69,16 @@ four transformed corners against the intended frame plane and opening, then
|
|
|
69
69
|
inspect an oblique view where an out-of-plane pane is visible. Matching centers
|
|
70
70
|
or bounding boxes alone cannot establish this fit.
|
|
71
71
|
|
|
72
|
+
For blocks or panels around a cylinder, separate their radial positions from
|
|
73
|
+
their orientation. A `boxGeo(length, height, thickness)` has its long axis along
|
|
74
|
+
local X. At `[radius * Math.cos(theta), y, radius * Math.sin(theta)]`, Y rotation
|
|
75
|
+
`-thetaDeg` points that axis radially outward; `90 - thetaDeg` makes it tangent
|
|
76
|
+
to the ring (`theta` is radians, `thetaDeg` is degrees). Derive the orientation
|
|
77
|
+
again when the geometry's long axis or parent frame differs. Check one part at
|
|
78
|
+
each quarter turn before repeating it. For a continuous wall, inspect neighboring
|
|
79
|
+
edges on both its inside and outside, including staggered courses: correct centers
|
|
80
|
+
and tangency alone do not guarantee closure. Preserve intentional openings.
|
|
81
|
+
|
|
72
82
|
For attached veins, ribs or seams, discover `recipe:surface-detail-v1`. Its strip
|
|
73
83
|
shares the carrier mesh's sampled boundary vertices and rises along local normals.
|
|
74
84
|
A shared equation followed by a separately interpolated curve does not establish
|
package/src/asset-widget-uri.ts
CHANGED
package/src/engine-identity.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* `src/__tests__/package-contents.test.ts` fails if it disagrees with the manifest
|
|
28
28
|
* or with any plugin manifest, which is what keeps the literal honest.
|
|
29
29
|
*/
|
|
30
|
-
export const ENGINE_VERSION = '1.2.
|
|
30
|
+
export const ENGINE_VERSION = '1.2.2';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Where this engine was loaded from, as a `file://` URL of the package root.
|
|
@@ -1733,13 +1733,13 @@
|
|
|
1733
1733
|
},
|
|
1734
1734
|
"_meta": {
|
|
1735
1735
|
"ui": {
|
|
1736
|
-
"resourceUri": "ui://kiln/asset-
|
|
1736
|
+
"resourceUri": "ui://kiln/asset-v6.html",
|
|
1737
1737
|
"visibility": [
|
|
1738
1738
|
"model"
|
|
1739
1739
|
]
|
|
1740
1740
|
},
|
|
1741
|
-
"openai/outputTemplate": "ui://kiln/asset-
|
|
1742
|
-
"ui/resourceUri": "ui://kiln/asset-
|
|
1741
|
+
"openai/outputTemplate": "ui://kiln/asset-v6.html",
|
|
1742
|
+
"ui/resourceUri": "ui://kiln/asset-v6.html"
|
|
1743
1743
|
},
|
|
1744
1744
|
"outputSchema": {
|
|
1745
1745
|
"type": "object",
|
|
@@ -2018,7 +2018,7 @@
|
|
|
2018
2018
|
],
|
|
2019
2019
|
"resources": [
|
|
2020
2020
|
{
|
|
2021
|
-
"uri": "ui://kiln/asset-
|
|
2021
|
+
"uri": "ui://kiln/asset-v6.html",
|
|
2022
2022
|
"name": "kiln-asset-viewer",
|
|
2023
2023
|
"mimeType": "text/html;profile=mcp-app",
|
|
2024
2024
|
"description": "Interactive Kiln asset viewer and downloads",
|
package/src/tools/registry.ts
CHANGED
|
@@ -94,7 +94,8 @@ import type { TextureUsage } from '../textures';
|
|
|
94
94
|
// Tool definition contract
|
|
95
95
|
// =============================================================================
|
|
96
96
|
|
|
97
|
-
export
|
|
97
|
+
export { KILN_ASSET_WIDGET_URI } from '../asset-widget-uri';
|
|
98
|
+
import { KILN_ASSET_WIDGET_URI } from '../asset-widget-uri';
|
|
98
99
|
export interface KilnToolDef {
|
|
99
100
|
/** Shared action ownership and requirements; never hand-maintained by transports. */
|
|
100
101
|
actionContract?: Readonly<Record<string, import('./operation-contract').KilnActionContract>>;
|
package/src/viewer/chat-app.ts
CHANGED
|
@@ -51,6 +51,8 @@ function encode(bytes: Uint8Array) {
|
|
|
51
51
|
async function show(input: unknown) {
|
|
52
52
|
const revision = ++presentationRevision;
|
|
53
53
|
record = undefined;
|
|
54
|
+
element('stats').textContent = '';
|
|
55
|
+
element<HTMLSelectElement>('clips').hidden = true;
|
|
54
56
|
for (const button of document.querySelectorAll<HTMLButtonElement>('button[data-download]'))
|
|
55
57
|
button.disabled = true;
|
|
56
58
|
try {
|
|
@@ -116,14 +118,16 @@ async function showResult(
|
|
|
116
118
|
status.textContent = 'Drag to orbit · Scroll to zoom';
|
|
117
119
|
} catch (error) {
|
|
118
120
|
if (revision !== presentationRevision) return;
|
|
121
|
+
stage?.dispose();
|
|
122
|
+
stage = undefined;
|
|
119
123
|
const preview = record.files['preview.png'];
|
|
120
124
|
if (preview) {
|
|
121
125
|
const image = new Image();
|
|
122
|
-
image.alt = record.manifest.name
|
|
126
|
+
image.alt = `${record.manifest.name} · saved preview image`;
|
|
123
127
|
image.src = `data:image/png;base64,${encode(preview)}`;
|
|
124
128
|
element('stage').replaceChildren(image);
|
|
125
129
|
}
|
|
126
|
-
status.textContent =
|
|
130
|
+
status.textContent = `${preview ? 'Showing saved preview. ' : ''}3D preview unavailable: ${String(error)}`;
|
|
127
131
|
}
|
|
128
132
|
for (const button of document.querySelectorAll<HTMLButtonElement>('button[data-download]'))
|
|
129
133
|
button.disabled = false;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LoadingManager,
|
|
3
|
+
TextureLoader,
|
|
4
|
+
Texture,
|
|
5
|
+
type Object3D,
|
|
6
|
+
type Mesh,
|
|
7
|
+
type BufferGeometry,
|
|
8
|
+
type Material,
|
|
9
|
+
} from 'three';
|
|
10
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
11
|
+
import { validateAssetGlb } from '../assets';
|
|
12
|
+
|
|
13
|
+
export function disposeAssetModel(model: Object3D) {
|
|
14
|
+
const geometries = new Set<BufferGeometry>();
|
|
15
|
+
const materials = new Set<Material>();
|
|
16
|
+
const textures = new Set<Texture>();
|
|
17
|
+
model.traverse((object) => {
|
|
18
|
+
const mesh = object as Mesh;
|
|
19
|
+
if (!mesh.isMesh) return;
|
|
20
|
+
geometries.add(mesh.geometry);
|
|
21
|
+
for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
|
|
22
|
+
materials.add(material);
|
|
23
|
+
for (const value of Object.values(material))
|
|
24
|
+
if (value instanceof Texture) textures.add(value);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
for (const value of [...geometries, ...materials, ...textures]) value.dispose();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse self-contained GLBs under MCP Apps' img-src data: / connect-src none policy.
|
|
32
|
+
* ImageBitmapLoader fetches even blob/data URLs. Use the documented image handler
|
|
33
|
+
* instead, preserving GLTFLoader's sampler, UV, color-space and extension logic.
|
|
34
|
+
* Only the parser's private JSON copy changes; downloaded GLB bytes remain exact.
|
|
35
|
+
*/
|
|
36
|
+
export async function loadAssetGlb(bytes: Uint8Array) {
|
|
37
|
+
validateAssetGlb(bytes);
|
|
38
|
+
const manager = new LoadingManager();
|
|
39
|
+
let imageFailed = false;
|
|
40
|
+
manager.onError = () => {
|
|
41
|
+
imageFailed = true;
|
|
42
|
+
};
|
|
43
|
+
manager.addHandler(/^data:image\//, new TextureLoader(manager));
|
|
44
|
+
manager.setURLModifier((url) => {
|
|
45
|
+
if (!url.startsWith('blob:') && !url.startsWith('data:'))
|
|
46
|
+
throw new Error('External model resources are not loaded');
|
|
47
|
+
return url;
|
|
48
|
+
});
|
|
49
|
+
const loader = new GLTFLoader(manager);
|
|
50
|
+
loader.register((parser) => ({
|
|
51
|
+
name: 'KILN_embedded_images',
|
|
52
|
+
async beforeRoot() {
|
|
53
|
+
for (const image of parser.json.images ?? []) {
|
|
54
|
+
if (image.bufferView === undefined) continue;
|
|
55
|
+
const buffer = new Uint8Array(await parser.getDependency('bufferView', image.bufferView));
|
|
56
|
+
let binary = '';
|
|
57
|
+
for (let i = 0; i < buffer.length; i += 8192)
|
|
58
|
+
binary += String.fromCharCode(...buffer.subarray(i, i + 8192));
|
|
59
|
+
if (!/^image\/(png|jpeg|webp|avif)$/.test(image.mimeType ?? ''))
|
|
60
|
+
throw new Error('Unsupported embedded image type');
|
|
61
|
+
image.uri = `data:${image.mimeType};base64,${btoa(binary)}`;
|
|
62
|
+
delete image.bufferView;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
const gltf = await loader.parseAsync(Uint8Array.from(bytes).buffer, '');
|
|
67
|
+
if (imageFailed) {
|
|
68
|
+
for (const scene of gltf.scenes) disposeAssetModel(scene);
|
|
69
|
+
throw new Error('An embedded texture could not be decoded.');
|
|
70
|
+
}
|
|
71
|
+
// GLTFLoader deliberately converts failed images to null maps. Never commit a
|
|
72
|
+
// material-degraded scene, including resources loaded later for detached LODs.
|
|
73
|
+
return Object.assign(gltf, {
|
|
74
|
+
assertTexturesLoaded() {
|
|
75
|
+
if (imageFailed) throw new Error('An embedded texture could not be decoded.');
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
package/src/viewer/scene.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
import * as THREE from 'three';
|
|
3
|
-
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
4
3
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
5
4
|
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
|
|
6
|
-
import {
|
|
5
|
+
import { loadAssetGlb, disposeAssetModel as disposeModel } from './glb-loader';
|
|
7
6
|
import { frameAssetBounds } from './framing';
|
|
8
7
|
import {
|
|
9
8
|
captureCameraView,
|
|
@@ -156,22 +155,6 @@ export function createAssetStage(container: HTMLElement) {
|
|
|
156
155
|
],
|
|
157
156
|
};
|
|
158
157
|
};
|
|
159
|
-
const disposeModel = (model: THREE.Object3D) => {
|
|
160
|
-
const geometries = new Set<THREE.BufferGeometry>();
|
|
161
|
-
const materials = new Set<THREE.Material>();
|
|
162
|
-
const textures = new Set<THREE.Texture>();
|
|
163
|
-
model.traverse((object) => {
|
|
164
|
-
const mesh = object as THREE.Mesh;
|
|
165
|
-
if (!mesh.isMesh) return;
|
|
166
|
-
geometries.add(mesh.geometry);
|
|
167
|
-
for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
|
|
168
|
-
materials.add(material);
|
|
169
|
-
for (const value of Object.values(material))
|
|
170
|
-
if (value instanceof THREE.Texture) textures.add(value);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
for (const value of [...geometries, ...materials, ...textures]) value.dispose();
|
|
174
|
-
};
|
|
175
158
|
const reset = () => {
|
|
176
159
|
if (!root) return;
|
|
177
160
|
const box = drawnBounds(root);
|
|
@@ -263,17 +246,17 @@ export function createAssetStage(container: HTMLElement) {
|
|
|
263
246
|
const loadStarted = performance.now();
|
|
264
247
|
const current = ++generation;
|
|
265
248
|
cancelMeasurement('The artifact changed during measurement. Measure the new revision again.');
|
|
266
|
-
|
|
267
|
-
const manager = new THREE.LoadingManager();
|
|
268
|
-
manager.setURLModifier((url) => {
|
|
269
|
-
if (!url.startsWith('blob:') && !url.startsWith('data:'))
|
|
270
|
-
throw new Error('External model resources are not loaded');
|
|
271
|
-
return url;
|
|
272
|
-
});
|
|
273
|
-
const parsed = await new GLTFLoader(manager).parseAsync(Uint8Array.from(bytes).buffer, '');
|
|
249
|
+
const parsed = await loadAssetGlb(bytes);
|
|
274
250
|
// three's loader draws every node; hide what KHR_node_visibility hides before counting.
|
|
275
251
|
applyNodeVisibility(parsed, [parsed.scene]);
|
|
276
252
|
const parsedLevels = await loadViewerLevels(parsed);
|
|
253
|
+
try {
|
|
254
|
+
parsed.assertTexturesLoaded();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
disposeModel(parsed.scene);
|
|
257
|
+
for (const level of detachedLevels(parsedLevels)) disposeModel(level);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
277
260
|
const gltf = acceptLoadedResource(
|
|
278
261
|
parsed,
|
|
279
262
|
() => !disposed && current === generation && options.isCurrent?.() !== false,
|