sceneview-mcp 4.0.11 → 4.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/analyze-project.js +9 -2
- package/dist/android-docs.js +206 -0
- package/dist/artifact.js +1 -1
- package/dist/debug-issue.js +35 -10
- package/dist/examples.js +136 -0
- package/dist/extra-guides.js +4 -3
- package/dist/generate-scene.js +1 -9
- package/dist/generated/llms-txt.js +1 -1
- package/dist/generated/version.js +7 -4
- package/dist/guides.js +9 -8
- package/dist/index.js +32 -3
- package/dist/issues.js +54 -3
- package/dist/migration.js +5 -4
- package/dist/platform-setup.js +26 -11
- package/dist/samples.js +65 -64
- package/dist/telemetry.js +178 -2
- package/dist/tiers.js +2 -0
- package/dist/tools/definitions.js +38 -0
- package/dist/tools/handler.js +54 -14
- package/dist/validator.js +9 -0
- package/package.json +15 -4
- package/dist/auth.js +0 -84
- package/dist/billing.js +0 -137
- package/dist/convert-platform.js +0 -302
- package/dist/explain-api.js +0 -246
- package/dist/generate-animation.js +0 -576
- package/dist/generate-environment.js +0 -483
- package/dist/generate-gesture.js +0 -532
- package/dist/generate-physics.js +0 -570
- package/dist/optimize-scene.js +0 -173
- package/llms.txt +0 -3326
package/README.md
CHANGED
|
@@ -45,6 +45,17 @@ Restart Claude Desktop after saving.
|
|
|
45
45
|
|
|
46
46
|
### Claude Code
|
|
47
47
|
|
|
48
|
+
Two options.
|
|
49
|
+
|
|
50
|
+
**Recommended — install the [SceneView Claude Code plugin](https://github.com/sceneview/claude-marketplace)** to get this MCP server **plus** 11 namespaced contributor commands and cross-platform reminder hooks in one shot:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
/plugin marketplace add sceneview/claude-marketplace
|
|
54
|
+
/plugin install sceneview@sceneview
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Or — just the MCP server** (lighter, no commands or hooks):
|
|
58
|
+
|
|
48
59
|
```bash
|
|
49
60
|
claude mcp add sceneview -- npx -y sceneview-mcp
|
|
50
61
|
```
|
|
@@ -100,7 +111,7 @@ Every developer tool is **free**: setup guides for every platform, code samples,
|
|
|
100
111
|
|
|
101
112
|
| Tool | What it does |
|
|
102
113
|
|---|---|
|
|
103
|
-
| `get_node_reference` | Full API reference for any of
|
|
114
|
+
| `get_node_reference` | Full API reference for any of 42+ node types — exact signatures, defaults, examples |
|
|
104
115
|
| `list_platforms` | Supported platforms with their status, renderer, and framework |
|
|
105
116
|
| `get_platform_roadmap` | Multi-platform status and timeline |
|
|
106
117
|
|
|
@@ -114,6 +125,8 @@ Every developer tool is **free**: setup guides for every platform, code samples,
|
|
|
114
125
|
|---|---|
|
|
115
126
|
| `search_models` | Searches Sketchfab for free 3D models (BYOK — set `SKETCHFAB_API_KEY`) |
|
|
116
127
|
| `analyze_project` | Scans a local SceneView project on disk — detects platform, extracts version, flags outdated deps and known anti-patterns |
|
|
128
|
+
| `search_android_docs` | Searches Google's stock Android docs knowledge base (needs the `android` CLI on PATH) |
|
|
129
|
+
| `fetch_android_doc` | Fetches a full Android docs entry by its `kb://...` URI (needs the `android` CLI on PATH) |
|
|
117
130
|
|
|
118
131
|
### 2 resources
|
|
119
132
|
|
|
@@ -195,7 +208,7 @@ The assistant calls `validate_code` with the generated snippet and checks it aga
|
|
|
195
208
|
- Always use the current SceneView 4.0.x API surface
|
|
196
209
|
- Generate correct **Compose-native** 3D/AR code for Android
|
|
197
210
|
- Generate correct **SwiftUI-native** code for iOS/macOS/visionOS
|
|
198
|
-
- Know about all
|
|
211
|
+
- Know about all 42+ node types and their exact parameters
|
|
199
212
|
- Validate code against 15+ rules before presenting it
|
|
200
213
|
- Provide working, tested sample code for 33 scenarios
|
|
201
214
|
|
package/dist/analyze-project.js
CHANGED
|
@@ -21,9 +21,16 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { promises as fs } from "node:fs";
|
|
23
23
|
import path from "node:path";
|
|
24
|
+
import { LATEST_SCENEVIEW_RELEASE } from "./generated/version.js";
|
|
24
25
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
25
|
-
/**
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The latest SceneView release known to this build of the MCP, snapshotted
|
|
28
|
+
* from the root `gradle.properties:VERSION_NAME` at build time via
|
|
29
|
+
* `scripts/generate-version.js`. See #941 — pre-fix this was hardcoded to
|
|
30
|
+
* "4.0.0" and never bumped, so every install of every MCP version told the
|
|
31
|
+
* LLM that "4.0.0" was current even when the real SDK was at 4.0.9.
|
|
32
|
+
*/
|
|
33
|
+
export const LATEST_SCENEVIEW_VERSION = LATEST_SCENEVIEW_RELEASE;
|
|
27
34
|
/** Hard cap on the number of source files inspected per call. */
|
|
28
35
|
export const MAX_FILES_SCANNED = 30;
|
|
29
36
|
/** Hard cap on total bytes read across all source files per call (500 KB). */
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* android-docs — `search_android_docs` / `fetch_android_doc` MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Google's `android` CLI (https://developer.android.com/tools/agents/android-cli)
|
|
5
|
+
* ships a `docs` subcommand backed by a knowledge base of ~4 800 stock Android
|
|
6
|
+
* documentation entries: Jetpack Compose, Camera2, ARCore SDK, Kotlin APIs,
|
|
7
|
+
* platform guides, and more. Wrapping it as MCP tools lets any MCP-aware
|
|
8
|
+
* assistant cross-reference stock Android docs with SceneView code without
|
|
9
|
+
* leaving the SceneView chat (issue #1083).
|
|
10
|
+
*
|
|
11
|
+
* - `search_android_docs(query)` → `android docs search <query>`
|
|
12
|
+
* - `fetch_android_doc(uri)` → `android docs fetch kb://<path>`
|
|
13
|
+
*
|
|
14
|
+
* Runtime dependency: the `android` CLI must be on the consumer's PATH. It is
|
|
15
|
+
* NOT a hard dependency of `sceneview-mcp` — most MCP hosts won't have it
|
|
16
|
+
* installed. Every code path here detects the binary up front and returns a
|
|
17
|
+
* structured, friendly error (never throws / crashes the MCP server) when it
|
|
18
|
+
* is absent.
|
|
19
|
+
*
|
|
20
|
+
* Hygiene ported from `.claude/scripts/lib/android-cli.sh`:
|
|
21
|
+
* - `--no-metrics` is passed on every invocation (keeps telemetry off and
|
|
22
|
+
* output clean).
|
|
23
|
+
* - The CLI prints a one-time terms-of-service blurb to stderr on its first
|
|
24
|
+
* invocation; we run a throwaway `--version` once per process to consume
|
|
25
|
+
* it so the real `docs` call returns clean stdout.
|
|
26
|
+
*/
|
|
27
|
+
import { execFile } from "node:child_process";
|
|
28
|
+
// ─── Configuration ──────────────────────────────────────────────────────────
|
|
29
|
+
/** Global flags applied to every `android` invocation (telemetry off). */
|
|
30
|
+
const ANDROID_CLI_GLOBAL_FLAGS = ["--no-metrics"];
|
|
31
|
+
/** Hard cap on a single `android docs` call so a hung CLI can't wedge MCP. */
|
|
32
|
+
const ANDROID_CLI_TIMEOUT_MS = 20_000;
|
|
33
|
+
/** Cap the captured stdout/stderr so a pathological response can't blow memory. */
|
|
34
|
+
const ANDROID_CLI_MAX_BUFFER = 4 * 1024 * 1024; // 4 MB
|
|
35
|
+
const INSTALL_URL = "https://developer.android.com/tools/agents/android-cli";
|
|
36
|
+
// ─── CLI detection ───────────────────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* Whether the first-run ToS blurb has already been consumed for this process.
|
|
39
|
+
* The `android` CLI prints its terms-of-service notice to stderr exactly once
|
|
40
|
+
* per host; running any command absorbs it. We do it lazily, once.
|
|
41
|
+
*/
|
|
42
|
+
let tosConsumed = false;
|
|
43
|
+
/** Cached binary-presence result for the lifetime of the MCP process. */
|
|
44
|
+
let cliPresenceCache;
|
|
45
|
+
/**
|
|
46
|
+
* Promisified `execFile` returning stdout + stderr, never rejecting.
|
|
47
|
+
* `error` is non-null when the process exits non-zero, is killed, or cannot
|
|
48
|
+
* be spawned (`ENOENT` when the binary is absent).
|
|
49
|
+
*/
|
|
50
|
+
function run(file, args) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
execFile(file, args, { timeout: ANDROID_CLI_TIMEOUT_MS, maxBuffer: ANDROID_CLI_MAX_BUFFER, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
53
|
+
resolve({
|
|
54
|
+
error: error ?? null,
|
|
55
|
+
stdout: stdout ?? "",
|
|
56
|
+
stderr: stderr ?? "",
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detect the `android` CLI on PATH. Result is cached for the process lifetime
|
|
63
|
+
* and also consumes the one-time ToS blurb on first success.
|
|
64
|
+
*
|
|
65
|
+
* Exported for tests; production callers go through `runAndroidDocs`.
|
|
66
|
+
*/
|
|
67
|
+
export async function isAndroidCliAvailable() {
|
|
68
|
+
if (cliPresenceCache !== undefined)
|
|
69
|
+
return cliPresenceCache;
|
|
70
|
+
const { error } = await run("android", [...ANDROID_CLI_GLOBAL_FLAGS, "--version"]);
|
|
71
|
+
// ENOENT (binary not found) ⇒ unavailable. Any other exit still means the
|
|
72
|
+
// binary spawned, so it IS present — and that --version run has now consumed
|
|
73
|
+
// the first-run ToS notice.
|
|
74
|
+
const spawnFailed = error?.code === "ENOENT";
|
|
75
|
+
cliPresenceCache = !spawnFailed;
|
|
76
|
+
if (cliPresenceCache)
|
|
77
|
+
tosConsumed = true;
|
|
78
|
+
return cliPresenceCache;
|
|
79
|
+
}
|
|
80
|
+
/** Test-only: reset the process-scoped CLI detection / ToS caches. */
|
|
81
|
+
export function __resetAndroidCliCache() {
|
|
82
|
+
cliPresenceCache = undefined;
|
|
83
|
+
tosConsumed = false;
|
|
84
|
+
}
|
|
85
|
+
function cliMissingError() {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
code: "cli_missing",
|
|
89
|
+
message: [
|
|
90
|
+
"This tool needs Google's `android` CLI, which is not installed on this MCP host.",
|
|
91
|
+
"",
|
|
92
|
+
"`android docs` is an optional runtime dependency — `sceneview-mcp` works fine without it,",
|
|
93
|
+
"but `search_android_docs` / `fetch_android_doc` cannot run until the CLI is on PATH.",
|
|
94
|
+
"",
|
|
95
|
+
`Install it from ${INSTALL_URL} (or run \`bash .claude/scripts/android-env-check.sh --fix\``,
|
|
96
|
+
"from a SceneView checkout), then retry.",
|
|
97
|
+
].join("\n"),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// ─── Core runner ─────────────────────────────────────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Run an `android docs <subcommand> <arg>` invocation and return its stdout
|
|
103
|
+
* as a structured result. Never throws.
|
|
104
|
+
*/
|
|
105
|
+
async function runAndroidDocs(subcommand, arg) {
|
|
106
|
+
if (!(await isAndroidCliAvailable())) {
|
|
107
|
+
return cliMissingError();
|
|
108
|
+
}
|
|
109
|
+
// Belt-and-braces: ensure the first-run ToS notice is consumed. Normally
|
|
110
|
+
// `isAndroidCliAvailable()` already did this via its `--version` probe, but
|
|
111
|
+
// a caller could have populated `cliPresenceCache` some other way.
|
|
112
|
+
if (!tosConsumed) {
|
|
113
|
+
await run("android", [...ANDROID_CLI_GLOBAL_FLAGS, "--version"]);
|
|
114
|
+
tosConsumed = true;
|
|
115
|
+
}
|
|
116
|
+
const { error, stdout, stderr } = await run("android", [
|
|
117
|
+
...ANDROID_CLI_GLOBAL_FLAGS,
|
|
118
|
+
"docs",
|
|
119
|
+
subcommand,
|
|
120
|
+
arg,
|
|
121
|
+
]);
|
|
122
|
+
if (error) {
|
|
123
|
+
// `execFile` sets `killed` + `signal` on a timeout kill.
|
|
124
|
+
const killedByTimeout = error.killed === true ||
|
|
125
|
+
error.signal === "SIGTERM";
|
|
126
|
+
if (killedByTimeout) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
code: "timeout",
|
|
130
|
+
message: `\`android docs ${subcommand}\` did not finish within ${ANDROID_CLI_TIMEOUT_MS / 1000}s.`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const detail = (stderr || stdout || error.message).trim();
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
code: "cli_error",
|
|
137
|
+
message: `\`android docs ${subcommand}\` failed: ${detail}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return { ok: true, output: stdout.trim() };
|
|
141
|
+
}
|
|
142
|
+
// ─── Public API: search ──────────────────────────────────────────────────────
|
|
143
|
+
/**
|
|
144
|
+
* Search the stock Android documentation knowledge base.
|
|
145
|
+
*
|
|
146
|
+
* @param query Free-text query, e.g. `"LazyColumn paging"`.
|
|
147
|
+
*/
|
|
148
|
+
export async function searchAndroidDocs(query) {
|
|
149
|
+
if (typeof query !== "string" || query.trim().length === 0) {
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
code: "invalid_input",
|
|
153
|
+
message: "Missing required parameter: `query` must be a non-empty string.",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return runAndroidDocs("search", query.trim());
|
|
157
|
+
}
|
|
158
|
+
// ─── Public API: fetch ───────────────────────────────────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Fetch a single Android documentation entry by its knowledge-base URI.
|
|
161
|
+
*
|
|
162
|
+
* @param uri A `kb://...` URI as returned by `search_android_docs`. A bare
|
|
163
|
+
* path (no scheme) is tolerated and normalised to `kb://`.
|
|
164
|
+
*/
|
|
165
|
+
export async function fetchAndroidDoc(uri) {
|
|
166
|
+
if (typeof uri !== "string" || uri.trim().length === 0) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
code: "invalid_input",
|
|
170
|
+
message: "Missing required parameter: `uri` must be a non-empty `kb://...` string.",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
let normalised = uri.trim();
|
|
174
|
+
if (!normalised.startsWith("kb://")) {
|
|
175
|
+
// Tolerate a bare path or a leading slash — normalise to a kb:// URI so an
|
|
176
|
+
// assistant that drops the scheme still gets a result.
|
|
177
|
+
normalised = `kb://${normalised.replace(/^\/+/, "")}`;
|
|
178
|
+
}
|
|
179
|
+
return runAndroidDocs("fetch", normalised);
|
|
180
|
+
}
|
|
181
|
+
// ─── Formatting ──────────────────────────────────────────────────────────────
|
|
182
|
+
/** Render a search result as the markdown text block the MCP dispatcher returns. */
|
|
183
|
+
export function formatAndroidDocsSearch(query, result) {
|
|
184
|
+
if (!result.ok)
|
|
185
|
+
return result.message;
|
|
186
|
+
if (result.output.length === 0) {
|
|
187
|
+
return `No Android documentation entries found for "${query}". Try a broader query.`;
|
|
188
|
+
}
|
|
189
|
+
return [
|
|
190
|
+
`## Android docs — search results for "${query}"`,
|
|
191
|
+
"",
|
|
192
|
+
result.output,
|
|
193
|
+
"",
|
|
194
|
+
"---",
|
|
195
|
+
"Use `fetch_android_doc` with a `kb://...` URI above to read a full entry.",
|
|
196
|
+
].join("\n");
|
|
197
|
+
}
|
|
198
|
+
/** Render a fetch result as the markdown text block the MCP dispatcher returns. */
|
|
199
|
+
export function formatAndroidDocsFetch(uri, result) {
|
|
200
|
+
if (!result.ok)
|
|
201
|
+
return result.message;
|
|
202
|
+
if (result.output.length === 0) {
|
|
203
|
+
return `No Android documentation entry found at \`${uri}\`.`;
|
|
204
|
+
}
|
|
205
|
+
return [`## Android docs — \`${uri}\``, "", result.output].join("\n");
|
|
206
|
+
}
|
package/dist/artifact.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// - scene: multi-model 3D scene with lighting and environment (Filament.js)
|
|
13
13
|
// - product-360: product turntable with hotspot annotations (Filament.js)
|
|
14
14
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
15
|
-
const FILAMENT_CDN = "https://cdn.jsdelivr.net/npm/filament@1.70.
|
|
15
|
+
const FILAMENT_CDN = "https://cdn.jsdelivr.net/npm/filament@1.70.2/filament.js";
|
|
16
16
|
const DEFAULT_MODEL = "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/DamagedHelmet/glTF-Binary/DamagedHelmet.glb";
|
|
17
17
|
const DEFAULT_COLORS = [
|
|
18
18
|
"#4285F4", "#EA4335", "#FBBC04", "#34A853", "#FF6D01",
|
package/dist/debug-issue.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* debug-issue.ts
|
|
3
|
-
*
|
|
4
|
-
* Targeted debugging guide for common SceneView issues.
|
|
5
|
-
* Given a symptom, returns a focused diagnostic checklist.
|
|
6
|
-
*/
|
|
1
|
+
import { LATEST_SCENEVIEW_RELEASE } from "./generated/version.js";
|
|
7
2
|
export const DEBUG_CATEGORIES = [
|
|
8
3
|
"model-not-showing",
|
|
9
4
|
"ar-not-working",
|
|
@@ -305,7 +300,7 @@ fun DebugModelViewer() {
|
|
|
305
300
|
title: "Build / Gradle Errors",
|
|
306
301
|
guide: `## Debugging: Build Errors
|
|
307
302
|
|
|
308
|
-
### "Cannot resolve io.github.sceneview:sceneview
|
|
303
|
+
### "Cannot resolve io.github.sceneview:sceneview:${LATEST_SCENEVIEW_RELEASE}"
|
|
309
304
|
|
|
310
305
|
1. Check repositories in \`settings.gradle.kts\`:
|
|
311
306
|
\`\`\`kotlin
|
|
@@ -348,7 +343,7 @@ SceneView bundles Filament. If you also depend on Filament directly:
|
|
|
348
343
|
\`\`\`kotlin
|
|
349
344
|
// Remove direct Filament dependency — SceneView includes it
|
|
350
345
|
// implementation("com.google.android.filament:filament-android:1.x.x") // REMOVE
|
|
351
|
-
implementation("io.github.sceneview:sceneview
|
|
346
|
+
implementation("io.github.sceneview:sceneview:${LATEST_SCENEVIEW_RELEASE}") // This includes Filament
|
|
352
347
|
\`\`\`
|
|
353
348
|
|
|
354
349
|
### "Cannot find Filament material"
|
|
@@ -672,10 +667,40 @@ export function autoDetectIssue(description) {
|
|
|
672
667
|
if (lower.includes("wrong thread") || lower.includes("off main thread") || lower.includes("dispatchers.io") || lower.includes("background thread")) {
|
|
673
668
|
return "crash";
|
|
674
669
|
}
|
|
675
|
-
if (lower.includes("not showing") || lower.includes("invisible") || lower.includes("can't see") || lower.includes("model doesn't appear") || lower.includes("model not visible") || lower.includes("nothing shows up") || lower.includes("model is null") || lower.includes("remembermodelinstance returns null")) {
|
|
670
|
+
if (lower.includes("not showing") || lower.includes("invisible") || lower.includes("can't see") || lower.includes("model doesn't appear") || lower.includes("model not visible") || lower.includes("nothing shows up") || lower.includes("model is null") || lower.includes("remembermodelinstance returns null") || lower.includes("no model")) {
|
|
676
671
|
return "model-not-showing";
|
|
677
672
|
}
|
|
678
|
-
|
|
673
|
+
// AR-not-working catches "the AR camera feed is dark" and the half-dozen
|
|
674
|
+
// ways a user describes a non-functional AR session. Pre-#940 only "ar not",
|
|
675
|
+
// "ar doesn't", and the technical terms (arcore/plane/anchor/hit test)
|
|
676
|
+
// matched — the natural phrasings "ar camera is black", "my AR is black",
|
|
677
|
+
// "ARScene shows nothing", etc. fell through to null.
|
|
678
|
+
//
|
|
679
|
+
// The regex `\b(ar|arscene|arsceneview|arcore)\b.*\b(black|dark)\b` catches
|
|
680
|
+
// any sentence where "AR" (in any of its forms) and a "no signal" keyword
|
|
681
|
+
// both appear, independent of the connecting words ("is", "feed is",
|
|
682
|
+
// "camera was", etc.). "dark" is added per the #940 review — "AR mode is
|
|
683
|
+
// dark" / "AR feed dimmed" are common synonyms users reach for.
|
|
684
|
+
//
|
|
685
|
+
// The bare `\bcamera\b.*\b(black|dark)\b` check is NOW gated on the
|
|
686
|
+
// sentence containing an AR-flavoured token — without that gate it
|
|
687
|
+
// false-positives on "the orbit camera in my 3D scene renders a black
|
|
688
|
+
// background" (a 3D-only issue that should route to model-not-showing
|
|
689
|
+
// or material). Caught by the #940 follow-up review.
|
|
690
|
+
const hasArContext = /\b(ar|arscene|arsceneview|arcore|arkit|arcore)\b/.test(lower)
|
|
691
|
+
|| lower.includes("augmented reality");
|
|
692
|
+
const arBlackHints = (hasArContext && /\b(black|dark|dimmed)\b/.test(lower))
|
|
693
|
+
|| /\b(ar|arscene|arsceneview|arcore)\b.*\b(black|dark|dimmed)\b/.test(lower);
|
|
694
|
+
if (lower.includes("ar not") ||
|
|
695
|
+
lower.includes("ar doesn't") ||
|
|
696
|
+
lower.includes("arcore") ||
|
|
697
|
+
lower.includes("plane") ||
|
|
698
|
+
lower.includes("anchor") ||
|
|
699
|
+
lower.includes("camera permission") ||
|
|
700
|
+
lower.includes("augmented reality") ||
|
|
701
|
+
lower.includes("hit test") ||
|
|
702
|
+
lower.includes("hitresult") ||
|
|
703
|
+
arBlackHints) {
|
|
679
704
|
return "ar-not-working";
|
|
680
705
|
}
|
|
681
706
|
if (lower.includes("crash") || lower.includes("sigabrt") || lower.includes("native crash") || lower.includes("fatal") || lower.includes("exception") || lower.includes("destroy") || lower.includes("double free") || lower.includes("segfault") || (lower.includes("oom") && !lower.includes("zoom")) || lower.includes("out of memory") || lower.includes("nullpointerexception") || lower.includes("npe")) {
|
package/dist/examples.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline example resources exposed via MCP `examples://` URIs.
|
|
3
|
+
*
|
|
4
|
+
* Two resources, intentionally inline (no fs reads / no fetches) so the
|
|
5
|
+
* `sceneview-mcp` package stays a pure code dependency:
|
|
6
|
+
*
|
|
7
|
+
* - `examples://demo-with-settings` — DemoScaffold v2 pattern (PR #1169 under
|
|
8
|
+
* issue #1154). Full-screen 3D / AR scene + ModalBottomSheet controls
|
|
9
|
+
* + horizontal FilterChip picker row.
|
|
10
|
+
*
|
|
11
|
+
* - `examples://sketchfab-streaming` — `SketchfabAssetResolver` pattern
|
|
12
|
+
* (Stage 2 of umbrella issue #1152). Stream CC-BY models from Sketchfab
|
|
13
|
+
* with per-slug bundled fallback + LRU cache + bounds sanity check.
|
|
14
|
+
*
|
|
15
|
+
* These strings deliberately stay small (≤ 4 KB each) so the resource list
|
|
16
|
+
* doesn't bloat the client's context window. The full recipes live at
|
|
17
|
+
* `docs/docs/recipes/sketchfab-streaming.md` and
|
|
18
|
+
* `docs/docs/recipes/demo-settings-sheet.md`.
|
|
19
|
+
*/
|
|
20
|
+
export const DEMO_WITH_SETTINGS_EXAMPLE = `# Example — DemoScaffold v2 (full-screen scene + ModalBottomSheet controls)
|
|
21
|
+
|
|
22
|
+
\`DemoScaffold\` v2 is the shared scaffold every SceneView sample-app demo uses. It renders the 3D / AR scene **full-screen** under the top bar, with a \`Tune\` FAB pinned bottom-right that opens a Material 3 ModalBottomSheet containing the controls.
|
|
23
|
+
|
|
24
|
+
\`\`\`kotlin
|
|
25
|
+
@Composable
|
|
26
|
+
fun MyDemo(onBack: () -> Unit) {
|
|
27
|
+
var iblIntensity by remember { mutableFloatStateOf(5_000f) }
|
|
28
|
+
var spinScene by remember { mutableStateOf(true) }
|
|
29
|
+
val engine = rememberEngine()
|
|
30
|
+
val modelLoader = rememberModelLoader(engine)
|
|
31
|
+
|
|
32
|
+
DemoScaffold(
|
|
33
|
+
title = "My Demo",
|
|
34
|
+
onBack = onBack,
|
|
35
|
+
controls = {
|
|
36
|
+
// Same Column scope you'd use in any settings sheet.
|
|
37
|
+
Text(
|
|
38
|
+
"IBL intensity: \${iblIntensity.toInt()} lux",
|
|
39
|
+
style = MaterialTheme.typography.labelLarge,
|
|
40
|
+
)
|
|
41
|
+
Slider(
|
|
42
|
+
value = iblIntensity,
|
|
43
|
+
onValueChange = { iblIntensity = it },
|
|
44
|
+
valueRange = 0f..10_000f,
|
|
45
|
+
)
|
|
46
|
+
Spacer(modifier = Modifier.height(8.dp))
|
|
47
|
+
Row(
|
|
48
|
+
modifier = Modifier.fillMaxWidth(),
|
|
49
|
+
horizontalArrangement = Arrangement.SpaceBetween,
|
|
50
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
51
|
+
) {
|
|
52
|
+
Text("Spin scene", style = MaterialTheme.typography.bodyMedium)
|
|
53
|
+
Switch(checked = spinScene, onCheckedChange = { spinScene = it })
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
) {
|
|
57
|
+
// BoxScope — full-screen scene.
|
|
58
|
+
SceneView(
|
|
59
|
+
modifier = Modifier.fillMaxSize(),
|
|
60
|
+
engine = engine,
|
|
61
|
+
modelLoader = modelLoader,
|
|
62
|
+
) {
|
|
63
|
+
// … nodes go here.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
**Gestures.** Tap FAB → opens the sheet. Tap peek chip ("Settings", above the FAB) → opens the sheet (discoverability — added under issue #951). Long-press peek chip → toggles \`DemoSettings.qaMode\` for deterministic screenshot captures. Drag handle / outside tap / back → dismiss. AR sessions keep tracking underneath.
|
|
70
|
+
|
|
71
|
+
**Picker pattern.** Combine with the FilterChip horizontal row for bundled-vs-streamed asset selection — see \`examples://sketchfab-streaming\`.
|
|
72
|
+
|
|
73
|
+
**Full doc:** \`docs/docs/recipes/demo-settings-sheet.md\` (PR #1169, issue #1154).
|
|
74
|
+
`;
|
|
75
|
+
export const SKETCHFAB_STREAMING_EXAMPLE = `# Example — Stream Sketchfab CC-BY models into a SceneView demo
|
|
76
|
+
|
|
77
|
+
The sample app (\`samples/android-demo\`) streams CC-BY licensed glTF models from Sketchfab on demand instead of bundling 30 MB of GLBs in the APK. The same pattern works in any SceneView consumer.
|
|
78
|
+
|
|
79
|
+
\`\`\`kotlin
|
|
80
|
+
@Composable
|
|
81
|
+
fun MyStreamedDemo(onBack: () -> Unit) {
|
|
82
|
+
val context = LocalContext.current
|
|
83
|
+
val resolver = remember { SketchfabAssetResolver.getInstance(context) }
|
|
84
|
+
val engine = rememberEngine()
|
|
85
|
+
val modelLoader = rememberModelLoader(engine)
|
|
86
|
+
|
|
87
|
+
// Warm the cache so the first frame doesn't pop in. Concurrent calls for
|
|
88
|
+
// the same slug deduplicate at the service layer.
|
|
89
|
+
LaunchedEffect(Unit) {
|
|
90
|
+
runCatching { resolver.prefetchAll("animation") }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Pick a slug from the curated registry — categories: solar, gallery,
|
|
94
|
+
// animation, park, ar_placement, physics, materials.
|
|
95
|
+
val slug = remember { SampleAssets.byCategory["animation"].orEmpty().first() }
|
|
96
|
+
|
|
97
|
+
// Resolve to a local file (null while downloading / staging the fallback).
|
|
98
|
+
val file: File? by produceState<File?>(initialValue = null, key1 = slug.uid) {
|
|
99
|
+
value = runCatching { resolver.resolve(slug) }.getOrNull()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
val instance = file?.let {
|
|
103
|
+
rememberModelInstance(modelLoader, "file://\${it.absolutePath}")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
DemoScaffold(title = slug.displayName, onBack = onBack) {
|
|
107
|
+
SceneView(
|
|
108
|
+
modifier = Modifier.fillMaxSize(),
|
|
109
|
+
engine = engine,
|
|
110
|
+
modelLoader = modelLoader,
|
|
111
|
+
) {
|
|
112
|
+
instance?.let {
|
|
113
|
+
ModelNode(
|
|
114
|
+
modelInstance = it,
|
|
115
|
+
scaleToUnits = slug.scaleToUnits,
|
|
116
|
+
autoAnimate = slug.hasBakedAnimation,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
\`\`\`
|
|
123
|
+
|
|
124
|
+
**Hard rules.**
|
|
125
|
+
|
|
126
|
+
- **CC-BY 4.0 only.** \`SketchfabSlug\`'s constructor rejects non-CC-BY models so the registry can't silently regress.
|
|
127
|
+
- **No Sketchfab WebView / external link.** All loading is in-process; Sketchfab is an invisible CDN, not a UX surface.
|
|
128
|
+
- **Never ship a build that needs the network to render something.** The resolver returns a bundled fallback when \`SketchfabConfig.apiKey == null\` or the download fails.
|
|
129
|
+
- **Attribute the author.** Every streamed model surfaced in the UI must show \`slug.author\` — CC-BY 4.0 attribution requirement.
|
|
130
|
+
|
|
131
|
+
**LRU cache.** \`Context.cacheDir/sketchfab/\` (250 MB samples-side cap, evicted oldest-first by \`lastModified\`).
|
|
132
|
+
|
|
133
|
+
**Bounds sanity check.** The resolver verifies the \`glTF\` magic header + size ≥ 12 B before returning a streamed file. Truncated downloads / wrong-format payloads fall back to the bundled asset.
|
|
134
|
+
|
|
135
|
+
**Full doc:** \`docs/docs/recipes/sketchfab-streaming.md\` (umbrella issue #1152, Stage 1 PR #1168).
|
|
136
|
+
`;
|
package/dist/extra-guides.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Material, collision, model optimization, and web rendering guides.
|
|
5
5
|
*/
|
|
6
|
+
import { LATEST_SCENEVIEW_RELEASE } from "./generated/version.js";
|
|
6
7
|
// ─── Material Guide ─────────────────────────────────────────────────────────
|
|
7
8
|
export const MATERIAL_GUIDE = `# SceneView Material & Shader Guide
|
|
8
9
|
|
|
@@ -406,7 +407,7 @@ export const WEB_RENDERING_GUIDE = `# SceneView Web Rendering Guide (Filament.js
|
|
|
406
407
|
|
|
407
408
|
## Architecture
|
|
408
409
|
|
|
409
|
-
SceneView Web uses **Filament.js v1.70.
|
|
410
|
+
SceneView Web uses **Filament.js v1.70.2** — Google's Filament engine compiled to WebAssembly. This is the **same PBR rendering engine** as SceneView Android, ensuring visual parity.
|
|
410
411
|
|
|
411
412
|
\`\`\`
|
|
412
413
|
Browser → WebGL2 → Filament.js (WASM) → GPU
|
|
@@ -419,7 +420,7 @@ Browser → WebGL2 → Filament.js (WASM) → GPU
|
|
|
419
420
|
### Using sceneview.js (npm or local)
|
|
420
421
|
\`\`\`html
|
|
421
422
|
<!-- Option 1: npm CDN -->
|
|
422
|
-
<script src="https://cdn.jsdelivr.net/npm/sceneview-web
|
|
423
|
+
<script src="https://cdn.jsdelivr.net/npm/sceneview-web@${LATEST_SCENEVIEW_RELEASE}/sceneview-web.js"></script>
|
|
423
424
|
|
|
424
425
|
<!-- Option 2: local hosting (recommended for production) -->
|
|
425
426
|
<!-- Copy js/filament/ directory to your server for faster WASM loading -->
|
|
@@ -517,7 +518,7 @@ camera {
|
|
|
517
518
|
|
|
518
519
|
| Feature | SceneView (Filament.js) | model-viewer |
|
|
519
520
|
|---------|------------------------|--------------|
|
|
520
|
-
| **Engine** | Filament v1.70.
|
|
521
|
+
| **Engine** | Filament v1.70.2 WASM | Filament WASM (same engine) |
|
|
521
522
|
| **Bundle size** | ~215KB JS + 3.3MB WASM | ~800 KB (subset) |
|
|
522
523
|
| **Procedural geometry** | Yes (cubes, spheres, etc.) | No |
|
|
523
524
|
| **Custom materials** | Yes (full Filament API) | Limited |
|
package/dist/generate-scene.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* generate-scene.ts
|
|
3
|
-
*
|
|
4
|
-
* Generates a complete SceneView{} or ARSceneView{} composable from a text description.
|
|
5
|
-
* Maps common objects/concepts to SceneView node types and builds compilable code.
|
|
6
|
-
*
|
|
7
|
-
* All generated code targets SceneView v4.0.0 API and is verified against llms.txt.
|
|
8
|
-
*/
|
|
9
1
|
const OBJECT_MAPPINGS = [
|
|
10
2
|
// Furniture
|
|
11
3
|
{ keywords: ["table"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.0, defaultPosition: [0, 0.4, 0], color: "Color(0.55f, 0.35f, 0.17f)", comment: "Table (flat cube)" },
|
|
@@ -245,7 +237,7 @@ export function generateScene(description) {
|
|
|
245
237
|
}
|
|
246
238
|
// Build the code
|
|
247
239
|
const isAR = parsed.isAR;
|
|
248
|
-
dependencies.push(isAR ? "io.github.sceneview:arsceneview
|
|
240
|
+
dependencies.push(isAR ? "io.github.sceneview:arsceneview:${LATEST_SCENEVIEW_RELEASE}" : "io.github.sceneview:sceneview:${LATEST_SCENEVIEW_RELEASE}");
|
|
249
241
|
// Build model instance declarations
|
|
250
242
|
const modelElements = elements.filter((e) => e.type === "model");
|
|
251
243
|
const uniqueModels = new Map();
|