sceneview-mcp 3.4.12 → 3.4.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.
@@ -0,0 +1,203 @@
1
+ /**
2
+ * migrate-code.ts
3
+ *
4
+ * Automatic migration of SceneView 2.x Kotlin code to 3.x.
5
+ * Applies known renames, API changes, and pattern transformations.
6
+ */
7
+ const MIGRATION_RULES = [
8
+ // ── Composable renames ──────────────────────────────────────────────────
9
+ {
10
+ id: "rename-sceneview-to-scene",
11
+ pattern: /\bSceneView\s*\(/g,
12
+ replacement: "Scene(",
13
+ explanation: "`SceneView(...)` renamed to `Scene(...)` in 3.0.",
14
+ },
15
+ {
16
+ id: "rename-arsceneview-to-arscene",
17
+ pattern: /\bArSceneView\s*\(/g,
18
+ replacement: "ARScene(",
19
+ explanation: "`ArSceneView(...)` renamed to `ARScene(...)` in 3.0.",
20
+ },
21
+ // ── Model loading ──────────────────────────────────────────────────────
22
+ {
23
+ id: "replace-loadModelAsync",
24
+ pattern: /modelLoader\.loadModelAsync\s*\(\s*"([^"]+)"\s*\)/g,
25
+ replacement: 'rememberModelInstance(modelLoader, "$1")',
26
+ explanation: "`loadModelAsync` removed. Use `rememberModelInstance(modelLoader, path)` which returns null while loading.",
27
+ },
28
+ {
29
+ id: "replace-loadModel",
30
+ pattern: /modelLoader\.loadModel\s*\(\s*"([^"]+)"\s*\)(?!Instance)/g,
31
+ replacement: 'rememberModelInstance(modelLoader, "$1")',
32
+ explanation: "`loadModel` replaced by `rememberModelInstance` in composables or `loadModelInstanceAsync` for imperative code.",
33
+ },
34
+ // ── Removed nodes ──────────────────────────────────────────────────────
35
+ {
36
+ id: "replace-transformable-node",
37
+ pattern: /\bTransformableNode\s*\([^)]*\)\s*\.apply\s*\{[^}]*setParent\([^)]*\)[^}]*\}/g,
38
+ replacement: "// TransformableNode removed in 3.0 — use `isEditable = true` on ModelNode instead",
39
+ explanation: "`TransformableNode` removed. Set `isEditable = true` on `ModelNode` for pinch-to-scale + drag-to-rotate.",
40
+ },
41
+ {
42
+ id: "replace-transformable-node-simple",
43
+ pattern: /\bTransformableNode\b/g,
44
+ replacement: "/* TransformableNode removed — use ModelNode(isEditable = true) */",
45
+ explanation: "`TransformableNode` removed. Set `isEditable = true` on `ModelNode`.",
46
+ },
47
+ {
48
+ id: "replace-placement-node",
49
+ pattern: /\bPlacementNode\b/g,
50
+ replacement: "/* PlacementNode removed — use AnchorNode + HitResultNode */",
51
+ explanation: "`PlacementNode` removed. Use `AnchorNode(anchor = hitResult.createAnchor())` + `HitResultNode`.",
52
+ },
53
+ {
54
+ id: "replace-view-renderable",
55
+ pattern: /ViewRenderable\.builder\(\)/g,
56
+ replacement: "/* ViewRenderable removed — use ViewNode with @Composable lambda */",
57
+ explanation: "`ViewRenderable` removed. Use `ViewNode(windowManager = rememberViewNodeManager()) { /* Compose content */ }`.",
58
+ },
59
+ // ── LightNode trailing lambda fix ──────────────────────────────────────
60
+ {
61
+ id: "fix-lightnode-trailing-lambda",
62
+ pattern: /LightNode\s*\(([^)]*)\)\s*\{/g,
63
+ replacement: "LightNode($1, apply = {",
64
+ explanation: "`LightNode`'s configuration is `apply = { }` (named param), not a trailing lambda. Without `apply =` the block is silently ignored.",
65
+ },
66
+ // ── Engine lifecycle ──────────────────────────────────────────────────
67
+ {
68
+ id: "replace-engine-create",
69
+ pattern: /val\s+(\w+)\s*=\s*Engine\.create\(\)/g,
70
+ replacement: "val $1 = rememberEngine()",
71
+ explanation: "`Engine.create()` replaced by `rememberEngine()` which ties lifecycle to composition.",
72
+ },
73
+ {
74
+ id: "remove-engine-destroy",
75
+ pattern: /\bengine\.(safeD|d)estroy\(\)/g,
76
+ replacement: "// engine.destroy() removed — rememberEngine() handles cleanup automatically",
77
+ explanation: "Manual `engine.destroy()` removed. `rememberEngine()` destroys on composition disposal.",
78
+ },
79
+ // ── Environment loading ────────────────────────────────────────────────
80
+ {
81
+ id: "replace-loadEnvironment",
82
+ pattern: /environmentLoader\.loadEnvironment\s*\(\s*"([^"]+)"\s*\)/g,
83
+ replacement: 'environmentLoader.createHDREnvironment("$1")',
84
+ explanation: "`loadEnvironment` renamed to `createHDREnvironment` in 3.0.",
85
+ },
86
+ // ── Import renames ─────────────────────────────────────────────────────
87
+ {
88
+ id: "replace-sceneform-import",
89
+ pattern: /import\s+com\.google\.ar\.sceneform\./g,
90
+ replacement: "import io.github.sceneview.",
91
+ explanation: "Sceneform imports (`com.google.ar.sceneform.*`) replaced by `io.github.sceneview.*`. SceneView is the official Sceneform successor.",
92
+ },
93
+ // ── addChildNode → composable DSL ──────────────────────────────────────
94
+ {
95
+ id: "replace-addChildNode",
96
+ pattern: /(\w+)\.addChildNode\((\w+)\)/g,
97
+ replacement: "// $1.addChildNode($2) — in 3.0, declare nodes as composables inside Scene { } instead of adding imperatively",
98
+ explanation: "Imperative `addChildNode` replaced by declarative composable DSL. Declare nodes inside `Scene { }` content block.",
99
+ },
100
+ // ── AR worldPosition → AnchorNode ──────────────────────────────────────
101
+ {
102
+ id: "replace-worldPosition-ar",
103
+ pattern: /(\w+)\.worldPosition\s*=\s*(.+)/g,
104
+ replacement: "// $1.worldPosition = $2 ← replaced by AnchorNode(anchor = hitResult.createAnchor()) in 3.0",
105
+ explanation: "Manual `worldPosition` in AR causes drift. Use `AnchorNode(anchor = hitResult.createAnchor())` instead.",
106
+ },
107
+ // ── Camera manipulator ─────────────────────────────────────────────────
108
+ {
109
+ id: "replace-camera-manipulator",
110
+ pattern: /setCameraManipulator\(([^)]*)\)/g,
111
+ replacement: "// setCameraManipulator removed — use cameraManipulator = rememberCameraManipulator() on Scene",
112
+ explanation: "`setCameraManipulator` replaced by `cameraManipulator = rememberCameraManipulator()` parameter on `Scene`.",
113
+ },
114
+ ];
115
+ export function migrateCode(code) {
116
+ const changes = [];
117
+ const warnings = [];
118
+ let result = code;
119
+ // Apply each rule
120
+ for (const rule of MIGRATION_RULES) {
121
+ const pattern = new RegExp(rule.pattern.source, rule.pattern.flags);
122
+ let match;
123
+ const localMatches = [];
124
+ // Collect matches first
125
+ while ((match = pattern.exec(result)) !== null) {
126
+ localMatches.push({ index: match.index, match: match[0] });
127
+ if (rule.once)
128
+ break;
129
+ }
130
+ if (localMatches.length > 0) {
131
+ // Compute line numbers from original positions
132
+ for (const m of localMatches) {
133
+ const lineNum = result.substring(0, m.index).split("\n").length;
134
+ const replaced = m.match.replace(new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", "")), rule.replacement);
135
+ changes.push({
136
+ line: lineNum,
137
+ before: m.match.trim(),
138
+ after: replaced.trim(),
139
+ rule: rule.id,
140
+ explanation: rule.explanation,
141
+ });
142
+ }
143
+ // Apply replacement
144
+ result = result.replace(new RegExp(rule.pattern.source, rule.pattern.flags), rule.replacement);
145
+ }
146
+ }
147
+ // Add warnings for things that need manual attention
148
+ if (code.includes("ModelRenderable.builder")) {
149
+ warnings.push("`ModelRenderable.builder()` is completely removed in 3.0 — it has no direct equivalent. Use GLB/glTF assets with `rememberModelInstance(modelLoader, path)` instead.");
150
+ }
151
+ if (code.includes("onTapArPlane")) {
152
+ warnings.push("`onTapArPlane` callback renamed/restructured. In 3.0, use `onTouchEvent` on `ARScene` and call `hitResult.createAnchor()` yourself.");
153
+ }
154
+ if (code.includes("rememberEngine") && !result.includes("rememberEngine")) {
155
+ // Already using 3.0 style
156
+ }
157
+ if (code.includes("Scene(") && !code.includes("engine")) {
158
+ warnings.push("`Scene(...)` requires an explicit `engine` parameter in 3.0. Add `val engine = rememberEngine()` and pass it.");
159
+ }
160
+ if (/node\w*\.destroy\(\)/.test(code) && /Scene\s*[({]/.test(code)) {
161
+ warnings.push("Manual `node.destroy()` calls inside composable Scenes should be removed — Compose manages node lifecycle automatically.");
162
+ }
163
+ return {
164
+ originalCode: code,
165
+ migratedCode: result,
166
+ changes,
167
+ warnings,
168
+ };
169
+ }
170
+ export function formatMigrationResult(result) {
171
+ if (result.changes.length === 0 && result.warnings.length === 0) {
172
+ return "No 2.x patterns detected. This code appears to already use SceneView 3.x APIs.\n\nIf you're still having issues, try `get_migration_guide` for the full migration reference.";
173
+ }
174
+ const parts = [
175
+ `## Migration Result\n`,
176
+ `**${result.changes.length} change(s) applied**, ${result.warnings.length} warning(s).\n`,
177
+ ];
178
+ if (result.changes.length > 0) {
179
+ parts.push(`### Changes Applied\n`);
180
+ for (let i = 0; i < result.changes.length; i++) {
181
+ const c = result.changes[i];
182
+ parts.push(`**${i + 1}. \`${c.rule}\`** (line ${c.line})`, `- Before: \`${c.before}\``, `- After: \`${c.after}\``, `- ${c.explanation}`, ``);
183
+ }
184
+ }
185
+ if (result.warnings.length > 0) {
186
+ parts.push(`### Manual Attention Required\n`);
187
+ result.warnings.forEach((w, i) => {
188
+ parts.push(`${i + 1}. ${w}`);
189
+ });
190
+ parts.push("");
191
+ }
192
+ parts.push(`### Migrated Code\n`);
193
+ parts.push("```kotlin");
194
+ parts.push(result.migratedCode);
195
+ parts.push("```");
196
+ parts.push(`\n### Migration Checklist\n`);
197
+ parts.push("- [ ] Review all `// removed` comments and refactor manually");
198
+ parts.push("- [ ] Add `val engine = rememberEngine()` if not present");
199
+ parts.push("- [ ] Add `val modelLoader = rememberModelLoader(engine)` if loading models");
200
+ parts.push("- [ ] Null-check every `rememberModelInstance` result");
201
+ parts.push("- [ ] Test compilation and runtime");
202
+ return parts.join("\n");
203
+ }