sceneview-mcp 3.5.1 → 3.5.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.
@@ -3,21 +3,23 @@
3
3
  *
4
4
  * Generates a complete Scene{} or ARScene{} composable from a text description.
5
5
  * Maps common objects/concepts to SceneView node types and builds compilable code.
6
+ *
7
+ * All generated code targets SceneView v3.4.7 API and is verified against llms.txt.
6
8
  */
7
9
  const OBJECT_MAPPINGS = [
8
10
  // Furniture
9
- { keywords: ["table"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.0, defaultPosition: [0, 0.4, 0], comment: "Table (flat cube)" },
11
+ { 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)" },
10
12
  { keywords: ["chair"], nodeType: "ModelNode", defaultScale: 0.8, defaultPosition: [0, 0, -1], comment: "Chair (use GLB model)" },
11
- { keywords: ["desk"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.2, defaultPosition: [0, 0.35, 0], comment: "Desk" },
12
- { keywords: ["shelf", "bookshelf"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.5, defaultPosition: [2, 0.75, 0], comment: "Shelf" },
13
- { keywords: ["bed"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 2.0, defaultPosition: [0, 0.25, 0], comment: "Bed" },
14
- { keywords: ["sofa", "couch"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.8, defaultPosition: [0, 0.35, 0], comment: "Sofa" },
13
+ { keywords: ["desk"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.2, defaultPosition: [0, 0.35, 0], color: "Color(0.55f, 0.35f, 0.17f)", comment: "Desk" },
14
+ { keywords: ["shelf", "bookshelf"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.5, defaultPosition: [2, 0.75, 0], color: "Color(0.55f, 0.35f, 0.17f)", comment: "Shelf" },
15
+ { keywords: ["bed"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 2.0, defaultPosition: [0, 0.25, 0], color: "Color.White", comment: "Bed" },
16
+ { keywords: ["sofa", "couch"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.8, defaultPosition: [0, 0.35, 0], color: "Color(0.3f, 0.3f, 0.6f)", comment: "Sofa" },
15
17
  // Basic shapes
16
- { keywords: ["box", "cube", "crate"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 0.5, defaultPosition: [0, 0.25, 0], comment: "Box" },
17
- { keywords: ["sphere", "ball", "globe", "orb"], nodeType: "SphereNode", geometryType: "sphere", defaultScale: 0.5, defaultPosition: [0, 0.5, 0], comment: "Sphere" },
18
- { keywords: ["cylinder", "pillar", "column"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.5, defaultPosition: [0, 0.5, 0], comment: "Cylinder" },
19
- { keywords: ["plane", "floor", "ground"], nodeType: "PlaneNode", geometryType: "plane", defaultScale: 5.0, defaultPosition: [0, 0, 0], comment: "Ground plane" },
20
- { keywords: ["wall"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 3.0, defaultPosition: [0, 1.5, -3], comment: "Wall" },
18
+ { keywords: ["box", "cube", "crate"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 0.5, defaultPosition: [0, 0.25, 0], color: "Color.Red", comment: "Box" },
19
+ { keywords: ["sphere", "ball", "globe", "orb"], nodeType: "SphereNode", geometryType: "sphere", defaultScale: 0.5, defaultPosition: [0, 0.5, 0], color: "Color.Blue", comment: "Sphere" },
20
+ { keywords: ["cylinder", "pillar", "column"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.5, defaultPosition: [0, 0.5, 0], color: "Color.Green", comment: "Cylinder" },
21
+ { keywords: ["plane", "floor", "ground"], nodeType: "PlaneNode", geometryType: "plane", defaultScale: 5.0, defaultPosition: [0, 0, 0], color: "Color.DarkGray", comment: "Ground plane" },
22
+ { keywords: ["wall"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 3.0, defaultPosition: [0, 1.5, -3], color: "Color.LightGray", comment: "Wall" },
21
23
  // Environment
22
24
  { keywords: ["tree", "plant"], nodeType: "ModelNode", defaultScale: 2.0, defaultPosition: [2, 0, 2], comment: "Tree (use GLB model)" },
23
25
  { keywords: ["car", "vehicle"], nodeType: "ModelNode", defaultScale: 2.0, defaultPosition: [0, 0, 0], comment: "Car (use GLB model)" },
@@ -41,29 +43,32 @@ const OBJECT_MAPPINGS = [
41
43
  { keywords: ["truck"], nodeType: "ModelNode", defaultScale: 3.0, defaultPosition: [0, 0, -2], comment: "Truck (use GLB model)" },
42
44
  // More environment
43
45
  { keywords: ["flower", "rose"], nodeType: "ModelNode", defaultScale: 0.3, defaultPosition: [1, 0, 1], comment: "Flower (use GLB model)" },
44
- { keywords: ["rock", "stone", "boulder"], nodeType: "SphereNode", geometryType: "sphere", defaultScale: 0.8, defaultPosition: [1, 0.4, 1], comment: "Rock" },
46
+ { keywords: ["rock", "stone", "boulder"], nodeType: "SphereNode", geometryType: "sphere", defaultScale: 0.8, defaultPosition: [1, 0.4, 1], color: "Color.Gray", comment: "Rock" },
45
47
  { keywords: ["mountain", "hill"], nodeType: "ModelNode", defaultScale: 10.0, defaultPosition: [0, 0, -10], comment: "Mountain (use GLB model)" },
46
- { keywords: ["fence"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 2.0, defaultPosition: [3, 0.5, 0], comment: "Fence" },
48
+ { keywords: ["fence"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 2.0, defaultPosition: [3, 0.5, 0], color: "Color(0.55f, 0.35f, 0.17f)", comment: "Fence" },
47
49
  { keywords: ["bridge"], nodeType: "ModelNode", defaultScale: 5.0, defaultPosition: [0, 0, 0], comment: "Bridge (use GLB model)" },
48
50
  // More furniture / objects
49
51
  { keywords: ["lamp", "light fixture"], nodeType: "ModelNode", defaultScale: 0.5, defaultPosition: [1, 0.8, 0], comment: "Lamp (use GLB model)" },
50
- { keywords: ["tv", "television", "screen", "monitor"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.2, defaultPosition: [0, 0.8, -2], comment: "TV/Screen (flat cube)" },
51
- { keywords: ["door", "gate"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 2.0, defaultPosition: [0, 1, -3], comment: "Door" },
52
- { keywords: ["window"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.0, defaultPosition: [2, 1.5, -3], comment: "Window" },
52
+ { keywords: ["tv", "television", "screen", "monitor"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.2, defaultPosition: [0, 0.8, -2], color: "Color.Black", comment: "TV/Screen (flat cube)" },
53
+ { keywords: ["door", "gate"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 2.0, defaultPosition: [0, 1, -3], color: "Color(0.55f, 0.35f, 0.17f)", comment: "Door" },
54
+ { keywords: ["window"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 1.0, defaultPosition: [2, 1.5, -3], color: "Color(0.7f, 0.85f, 1f)", comment: "Window" },
53
55
  { keywords: ["stairs", "staircase"], nodeType: "ModelNode", defaultScale: 2.0, defaultPosition: [3, 0, 0], comment: "Stairs (use GLB model)" },
54
- { keywords: ["book", "books"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 0.2, defaultPosition: [0, 0.5, 0], comment: "Book" },
55
- { keywords: ["bottle", "vase"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.2, defaultPosition: [0, 0.3, 0], comment: "Bottle/Vase" },
56
+ { keywords: ["book", "books"], nodeType: "CubeNode", geometryType: "cube", defaultScale: 0.2, defaultPosition: [0, 0.5, 0], color: "Color(0.6f, 0.1f, 0.1f)", comment: "Book" },
57
+ { keywords: ["bottle", "vase"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.2, defaultPosition: [0, 0.3, 0], color: "Color(0.2f, 0.6f, 0.3f)", comment: "Bottle/Vase" },
56
58
  { keywords: ["trophy", "cup"], nodeType: "ModelNode", defaultScale: 0.3, defaultPosition: [0, 0.5, 0], comment: "Trophy (use GLB model)" },
57
59
  // Food
58
- { keywords: ["pizza"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.3, defaultPosition: [0, 0.5, 0], comment: "Pizza (flat cylinder)" },
59
- { keywords: ["cake"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.3, defaultPosition: [0, 0.5, 0], comment: "Cake (cylinder)" },
60
- { keywords: ["apple", "fruit"], nodeType: "SphereNode", geometryType: "sphere", defaultScale: 0.08, defaultPosition: [0, 0.5, 0], comment: "Apple/Fruit" },
60
+ { keywords: ["pizza"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.3, defaultPosition: [0, 0.5, 0], color: "Color(0.9f, 0.7f, 0.2f)", comment: "Pizza (flat cylinder)" },
61
+ { keywords: ["cake"], nodeType: "CylinderNode", geometryType: "cylinder", defaultScale: 0.3, defaultPosition: [0, 0.5, 0], color: "Color(1f, 0.85f, 0.7f)", comment: "Cake (cylinder)" },
62
+ { keywords: ["apple", "fruit"], nodeType: "SphereNode", geometryType: "sphere", defaultScale: 0.08, defaultPosition: [0, 0.5, 0], color: "Color.Red", comment: "Apple/Fruit" },
61
63
  ];
64
+ // ── Text keywords ───────────────────────────────────────────────────────────
65
+ const TEXT_KEYWORDS = ["label", "text", "sign", "title", "name", "caption", "annotation"];
62
66
  const LIGHT_KEYWORDS = ["sun", "sunlight", "daylight", "bright", "lit", "sunny", "well-lit"];
63
67
  const INDOOR_KEYWORDS = ["room", "indoor", "inside", "interior"];
64
68
  const OUTDOOR_KEYWORDS = ["outdoor", "outside", "garden", "park", "street"];
65
69
  const AR_KEYWORDS = ["ar", "augmented", "real world", "camera", "place in room", "place on"];
66
70
  const DARK_KEYWORDS = ["night", "dark", "dim", "mood", "candlelight"];
71
+ const ANIMATED_KEYWORDS = ["animated", "spinning", "rotating", "orbiting", "moving", "bouncing"];
67
72
  function parseDescription(description) {
68
73
  const lower = description.toLowerCase();
69
74
  const objects = [];
@@ -114,6 +119,8 @@ function parseDescription(description) {
114
119
  isOutdoor: OUTDOOR_KEYWORDS.some((k) => lower.includes(k)),
115
120
  needsSky: OUTDOOR_KEYWORDS.some((k) => lower.includes(k)) || lower.includes("sky"),
116
121
  isDark: DARK_KEYWORDS.some((k) => lower.includes(k)),
122
+ hasText: TEXT_KEYWORDS.some((k) => lower.includes(k)),
123
+ isAnimated: ANIMATED_KEYWORDS.some((k) => lower.includes(k)),
117
124
  };
118
125
  }
119
126
  function spreadPositions(count, basePos, spacing) {
@@ -130,14 +137,21 @@ function spreadPositions(count, basePos, spacing) {
130
137
  }
131
138
  return positions;
132
139
  }
140
+ /** Generate a material variable name from a color expression */
141
+ function colorToVarName(color) {
142
+ return color
143
+ .replace("Color.", "")
144
+ .replace(/Color\(.*\)/, "custom")
145
+ .replace(/[^a-zA-Z0-9]/g, "")
146
+ .toLowerCase() + "Mat";
147
+ }
133
148
  export function generateScene(description) {
134
149
  const parsed = parseDescription(description);
135
150
  const elements = [];
136
151
  const notes = [];
137
152
  const dependencies = [];
138
153
  let hasModel = false;
139
- // Track geometry node counters for naming
140
- let nodeCounter = 0;
154
+ let hasGeometry = false;
141
155
  // Generate elements for each parsed object
142
156
  for (const obj of parsed.objects) {
143
157
  const positions = spreadPositions(obj.count, obj.mapping.defaultPosition, 1.5);
@@ -157,26 +171,42 @@ export function generateScene(description) {
157
171
  });
158
172
  }
159
173
  else {
174
+ hasGeometry = true;
175
+ const color = obj.mapping.color || "Color.Gray";
160
176
  elements.push({
161
177
  type: "geometry",
162
178
  nodeType: obj.mapping.nodeType,
163
179
  properties: {
164
180
  size: String(obj.mapping.defaultScale),
181
+ color,
182
+ geometryType: obj.mapping.geometryType || "cube",
165
183
  position: `Position(${pos[0].toFixed(1)}f, ${pos[1].toFixed(1)}f, ${pos[2].toFixed(1)}f)`,
166
184
  },
167
185
  comment: obj.mapping.comment,
168
186
  });
169
187
  }
170
- nodeCounter++;
188
+ // element added
171
189
  }
172
190
  }
191
+ // Add text node if description mentions text/labels
192
+ if (parsed.hasText) {
193
+ elements.push({
194
+ type: "text",
195
+ nodeType: "TextNode",
196
+ properties: {
197
+ text: "Label",
198
+ position: "Position(0f, 1.5f, 0f)",
199
+ },
200
+ comment: "3D text label",
201
+ });
202
+ }
173
203
  // Add light
174
204
  if (!parsed.isDark) {
175
205
  elements.push({
176
206
  type: "light",
177
207
  nodeType: "LightNode",
178
208
  properties: {
179
- type: "LightManager.Type.DIRECTIONAL",
209
+ type: parsed.isOutdoor ? "LightManager.Type.SUN" : "LightManager.Type.SUN",
180
210
  intensity: parsed.isOutdoor ? "110_000f" : "100_000f",
181
211
  castShadows: "true",
182
212
  },
@@ -192,6 +222,7 @@ export function generateScene(description) {
192
222
  intensity: "50_000f",
193
223
  position: "Position(0f, 2f, 0f)",
194
224
  castShadows: "false",
225
+ falloff: "10.0f",
195
226
  },
196
227
  comment: "Dim ambient light",
197
228
  });
@@ -199,11 +230,14 @@ export function generateScene(description) {
199
230
  // Add ground plane if not already present
200
231
  const hasGround = elements.some((e) => e.nodeType === "PlaneNode" || (e.comment && e.comment.toLowerCase().includes("ground")));
201
232
  if (!hasGround && !parsed.isAR) {
233
+ hasGeometry = true;
202
234
  elements.push({
203
235
  type: "geometry",
204
236
  nodeType: "PlaneNode",
205
237
  properties: {
206
- size: "10.0f",
238
+ size: "10.0",
239
+ color: "Color.DarkGray",
240
+ geometryType: "plane",
207
241
  position: "Position(0f, 0f, 0f)",
208
242
  },
209
243
  comment: "Ground plane",
@@ -211,7 +245,7 @@ export function generateScene(description) {
211
245
  }
212
246
  // Build the code
213
247
  const isAR = parsed.isAR;
214
- dependencies.push(isAR ? "io.github.sceneview:arsceneview:3.3.0" : "io.github.sceneview:sceneview:3.3.0");
248
+ dependencies.push(isAR ? "io.github.sceneview:arsceneview:3.4.7" : "io.github.sceneview:sceneview:3.4.7");
215
249
  // Build model instance declarations
216
250
  const modelElements = elements.filter((e) => e.type === "model");
217
251
  const uniqueModels = new Map();
@@ -222,12 +256,26 @@ export function generateScene(description) {
222
256
  uniqueModels.set(path, varName);
223
257
  }
224
258
  });
259
+ // Collect unique material colors for geometry nodes
260
+ const uniqueColors = new Map();
261
+ const geometryElements = elements.filter((e) => e.type === "geometry");
262
+ for (const e of geometryElements) {
263
+ const color = e.properties.color || "Color.Gray";
264
+ if (!uniqueColors.has(color)) {
265
+ uniqueColors.set(color, colorToVarName(color));
266
+ }
267
+ }
225
268
  // Generate Kotlin code
226
269
  const lines = [];
227
270
  lines.push("@Composable");
228
271
  lines.push(`fun GeneratedScene() {`);
229
272
  lines.push(" val engine = rememberEngine()");
230
- lines.push(" val modelLoader = rememberModelLoader(engine)");
273
+ if (hasModel) {
274
+ lines.push(" val modelLoader = rememberModelLoader(engine)");
275
+ }
276
+ if (hasGeometry) {
277
+ lines.push(" val materialLoader = rememberMaterialLoader(engine)");
278
+ }
231
279
  if (!isAR) {
232
280
  lines.push(" val environmentLoader = rememberEnvironmentLoader(engine)");
233
281
  }
@@ -238,6 +286,16 @@ export function generateScene(description) {
238
286
  }
239
287
  if (uniqueModels.size > 0)
240
288
  lines.push("");
289
+ // Animation state if needed
290
+ if (parsed.isAnimated && !isAR) {
291
+ lines.push(" var rotationAngle by remember { mutableFloatStateOf(0f) }");
292
+ lines.push(" LaunchedEffect(Unit) {");
293
+ lines.push(" while (true) {");
294
+ lines.push(" withFrameNanos { _ -> rotationAngle += 0.5f }");
295
+ lines.push(" }");
296
+ lines.push(" }");
297
+ lines.push("");
298
+ }
241
299
  // Scene or ARScene
242
300
  if (isAR) {
243
301
  lines.push(" var anchor by remember { mutableStateOf<Anchor?>(null) }");
@@ -245,8 +303,12 @@ export function generateScene(description) {
245
303
  lines.push(" ARScene(");
246
304
  lines.push(" modifier = Modifier.fillMaxSize(),");
247
305
  lines.push(" engine = engine,");
248
- lines.push(" modelLoader = modelLoader,");
306
+ if (hasModel)
307
+ lines.push(" modelLoader = modelLoader,");
249
308
  lines.push(" planeRenderer = true,");
309
+ lines.push(" sessionConfiguration = { session, config ->");
310
+ lines.push(" config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR");
311
+ lines.push(" },");
250
312
  lines.push(" onTouchEvent = { event, hitResult ->");
251
313
  lines.push(" if (event.action == MotionEvent.ACTION_UP && hitResult != null) {");
252
314
  lines.push(" anchor = hitResult.createAnchor()");
@@ -261,16 +323,39 @@ export function generateScene(description) {
261
323
  lines.push(" Scene(");
262
324
  lines.push(" modifier = Modifier.fillMaxSize(),");
263
325
  lines.push(" engine = engine,");
264
- lines.push(" modelLoader = modelLoader,");
326
+ if (hasModel)
327
+ lines.push(" modelLoader = modelLoader,");
328
+ lines.push(" cameraManipulator = rememberCameraManipulator(),");
265
329
  if (!parsed.isDark) {
266
330
  lines.push(' environment = rememberEnvironment(environmentLoader) {');
267
331
  lines.push(' environmentLoader.createHDREnvironment("environments/sky_2k.hdr")');
268
332
  lines.push(' ?: createEnvironment(environmentLoader)');
269
- lines.push(" }");
333
+ lines.push(" },");
334
+ lines.push(" mainLightNode = rememberMainLightNode(engine) { intensity = 100_000f }");
270
335
  }
271
336
  lines.push(" ) {");
272
337
  }
273
338
  const indent = isAR ? " " : " ";
339
+ // Declare material instances for geometry nodes
340
+ if (hasGeometry && !isAR) {
341
+ for (const [color, varName] of uniqueColors) {
342
+ const roughness = color.includes("DarkGray") ? "0.9f" : "0.6f";
343
+ lines.push(`${indent}val ${varName} = remember(materialLoader) {`);
344
+ lines.push(`${indent} materialLoader.createColorInstance(${color}, roughness = ${roughness})`);
345
+ lines.push(`${indent}}`);
346
+ }
347
+ lines.push("");
348
+ }
349
+ else if (hasGeometry && isAR) {
350
+ // In AR context, indent is deeper
351
+ const arMatIndent = " ";
352
+ for (const [color, varName] of uniqueColors) {
353
+ lines.push(`${arMatIndent}val ${varName} = remember(materialLoader) {`);
354
+ lines.push(`${arMatIndent} materialLoader.createColorInstance(${color}, roughness = 0.6f)`);
355
+ lines.push(`${arMatIndent}}`);
356
+ }
357
+ lines.push("");
358
+ }
274
359
  // Generate nodes
275
360
  for (const elem of elements) {
276
361
  lines.push(`${indent}// ${elem.comment}`);
@@ -282,7 +367,10 @@ export function generateScene(description) {
282
367
  lines.push(`${indent} modelInstance = instance,`);
283
368
  lines.push(`${indent} scaleToUnits = ${elem.properties.scaleToUnits}f,`);
284
369
  if (elem.properties.position !== "Position(0.0f, 0.0f, 0.0f)") {
285
- lines.push(`${indent} centerOrigin = ${elem.properties.position}`);
370
+ lines.push(`${indent} position = ${elem.properties.position},`);
371
+ }
372
+ if (parsed.isAnimated) {
373
+ lines.push(`${indent} autoAnimate = true`);
286
374
  }
287
375
  lines.push(`${indent} )`);
288
376
  lines.push(`${indent}}`);
@@ -290,20 +378,71 @@ export function generateScene(description) {
290
378
  }
291
379
  case "geometry": {
292
380
  const nodeType = elem.nodeType;
293
- lines.push(`${indent}${nodeType}(`);
294
- lines.push(`${indent} engine = engine,`);
295
- lines.push(`${indent} size = ${elem.properties.size}`);
381
+ const matVar = uniqueColors.get(elem.properties.color || "Color.Gray") || "grayMat";
382
+ const geoType = elem.properties.geometryType;
383
+ const size = parseFloat(elem.properties.size);
384
+ if (nodeType === "PlaneNode") {
385
+ lines.push(`${indent}${nodeType}(`);
386
+ lines.push(`${indent} size = Size(${size.toFixed(1)}f, ${size.toFixed(1)}f),`);
387
+ lines.push(`${indent} materialInstance = ${matVar}`);
388
+ lines.push(`${indent})`);
389
+ }
390
+ else if (nodeType === "SphereNode") {
391
+ lines.push(`${indent}${nodeType}(`);
392
+ lines.push(`${indent} radius = ${(size / 2).toFixed(2)}f,`);
393
+ lines.push(`${indent} materialInstance = ${matVar},`);
394
+ if (elem.properties.position !== "Position(0.0f, 0.0f, 0.0f)") {
395
+ lines.push(`${indent} position = ${elem.properties.position}`);
396
+ }
397
+ lines.push(`${indent})`);
398
+ }
399
+ else if (nodeType === "CylinderNode") {
400
+ lines.push(`${indent}${nodeType}(`);
401
+ lines.push(`${indent} radius = ${(size / 2).toFixed(2)}f,`);
402
+ lines.push(`${indent} height = ${size.toFixed(1)}f,`);
403
+ lines.push(`${indent} materialInstance = ${matVar},`);
404
+ if (elem.properties.position !== "Position(0.0f, 0.0f, 0.0f)") {
405
+ lines.push(`${indent} position = ${elem.properties.position}`);
406
+ }
407
+ lines.push(`${indent})`);
408
+ }
409
+ else {
410
+ // CubeNode
411
+ lines.push(`${indent}${nodeType}(`);
412
+ lines.push(`${indent} size = Size(${size.toFixed(1)}f),`);
413
+ if (elem.properties.position !== "Position(0.0f, 0.0f, 0.0f)") {
414
+ lines.push(`${indent} center = ${elem.properties.position},`);
415
+ }
416
+ lines.push(`${indent} materialInstance = ${matVar}`);
417
+ lines.push(`${indent})`);
418
+ }
419
+ break;
420
+ }
421
+ case "text": {
422
+ lines.push(`${indent}TextNode(`);
423
+ lines.push(`${indent} text = "${elem.properties.text}",`);
424
+ lines.push(`${indent} fontSize = 48f,`);
425
+ lines.push(`${indent} textColor = android.graphics.Color.WHITE,`);
426
+ lines.push(`${indent} backgroundColor = 0xCC000000.toInt(),`);
427
+ lines.push(`${indent} widthMeters = 0.6f,`);
428
+ lines.push(`${indent} heightMeters = 0.2f,`);
429
+ lines.push(`${indent} position = ${elem.properties.position}`);
296
430
  lines.push(`${indent})`);
297
431
  break;
298
432
  }
299
433
  case "light": {
300
434
  lines.push(`${indent}LightNode(`);
301
- lines.push(`${indent} engine = engine,`);
302
435
  lines.push(`${indent} type = ${elem.properties.type},`);
303
436
  lines.push(`${indent} apply = {`);
304
437
  lines.push(`${indent} intensity(${elem.properties.intensity})`);
305
438
  lines.push(`${indent} castShadows(${elem.properties.castShadows})`);
439
+ if (elem.properties.falloff) {
440
+ lines.push(`${indent} falloff(${elem.properties.falloff})`);
441
+ }
306
442
  lines.push(`${indent} }`);
443
+ if (elem.properties.position) {
444
+ lines.push(`${indent} // position = ${elem.properties.position}`);
445
+ }
307
446
  lines.push(`${indent})`);
308
447
  break;
309
448
  }
@@ -325,9 +464,12 @@ export function generateScene(description) {
325
464
  if (isAR) {
326
465
  notes.push("AR requires camera permission at runtime and ARCore on the device. See `get_platform_setup(\"android\", \"ar\")` for full setup.");
327
466
  }
328
- if (elements.length === 1) {
467
+ if (elements.length <= 2 && !hasModel && !hasGeometry) {
329
468
  notes.push("Only a light was generated. The description didn't match any known objects. Try mentioning specific objects like 'table', 'chair', 'sphere', 'cube', etc.");
330
469
  }
470
+ if (parsed.isAnimated) {
471
+ notes.push("Animation is enabled. For model animations, the GLB file must contain embedded animations. For geometry, rotation is applied via LaunchedEffect.");
472
+ }
331
473
  return {
332
474
  code: lines.join("\n"),
333
475
  description,
package/dist/guides.js CHANGED
@@ -6,16 +6,16 @@
6
6
  // ─── Platform Roadmap ─────────────────────────────────────────────────────────
7
7
  export const PLATFORM_ROADMAP = `# SceneView Multi-Platform Roadmap
8
8
 
9
- ## Current Status (v3.3.0)
9
+ ## Current Status (v3.4.7)
10
10
 
11
11
  | Platform | Status | Artifact | Renderer |
12
12
  |----------|--------|----------|----------|
13
- | **Android (Compose)** | Stable | \`io.github.sceneview:sceneview:3.3.0\` | Filament |
14
- | **Android (AR)** | Stable | \`io.github.sceneview:arsceneview:3.3.0\` | Filament + ARCore |
15
- | **iOS (SwiftUI)** | Alpha | SceneViewSwift SPM \`from: "3.3.0"\` | RealityKit + ARKit |
13
+ | **Android (Compose)** | Stable | \`io.github.sceneview:sceneview:3.4.7\` | Filament |
14
+ | **Android (AR)** | Stable | \`io.github.sceneview:arsceneview:3.4.7\` | Filament + ARCore |
15
+ | **iOS (SwiftUI)** | Alpha | SceneViewSwift SPM \`from: "3.4.7"\` | RealityKit + ARKit |
16
16
  | **macOS (SwiftUI)** | Alpha | SceneViewSwift SPM (in Package.swift) | RealityKit |
17
17
  | **visionOS (SwiftUI)** | Alpha | SceneViewSwift SPM (in Package.swift) | RealityKit |
18
- | **KMP Core** | Stable | \`io.github.sceneview:sceneview-core:3.3.0\` | N/A (shared logic) |
18
+ | **KMP Core** | Stable | \`io.github.sceneview:sceneview-core:3.4.7\` | N/A (shared logic) |
19
19
 
20
20
  ## Architecture: Native Renderers per Platform
21
21
 
@@ -230,7 +230,7 @@ export const TROUBLESHOOTING_GUIDE = `# SceneView Troubleshooting Guide
230
230
  1. \`rememberModelInstance\` returns null while loading — are you null-checking it?
231
231
  2. Is the model path correct? Assets go in \`src/main/assets/models/\`
232
232
  3. Is \`scaleToUnits\` too small or too large? Try \`1.0f\`
233
- 4. Is there a light in the scene? Add: \`LightNode(type = LightNode.Type.DIRECTIONAL)\`
233
+ 4. Is there a light in the scene? Add a directional light: \`LightNode(apply = { intensity(100_000.0f); direction(0f, -1f, 0f) })\`
234
234
  5. Is the camera pointing at the model? Default camera is at origin looking -Z
235
235
 
236
236
  ### AR Camera Feed Shows but No Planes Detected
@@ -255,7 +255,7 @@ export const TROUBLESHOOTING_GUIDE = `# SceneView Troubleshooting Guide
255
255
  ### Gradle Sync Fails
256
256
  **Fix:**
257
257
  - Ensure Java 17 is set: \`compileOptions { sourceCompatibility = JavaVersion.VERSION_17 }\`
258
- - Use compatible AGP/Gradle versions (AGP 8.11.1 + Gradle 8.11.1)
258
+ - Use compatible AGP/Gradle versions (AGP 8.7.3 + Gradle 8.11.1)
259
259
  - Clear Gradle cache: \`./gradlew clean && rm -rf ~/.gradle/caches\`
260
260
 
261
261
  ## Performance Issues
@@ -339,7 +339,7 @@ export const AR_SETUP_GUIDE = `# SceneView AR — Complete Setup Guide (Android
339
339
  ## 1. SPM Dependency
340
340
 
341
341
  \`\`\`swift
342
- .package(url: "https://github.com/sceneview/sceneview", from: "3.3.0")
342
+ .package(url: "https://github.com/sceneview/sceneview", from: "3.4.7")
343
343
  \`\`\`
344
344
 
345
345
  ## 2. Info.plist — Camera Permission
@@ -424,7 +424,7 @@ ARSceneView(
424
424
  \`\`\`kotlin
425
425
  // build.gradle.kts (app module)
426
426
  dependencies {
427
- implementation("io.github.sceneview:arsceneview:3.3.0")
427
+ implementation("io.github.sceneview:arsceneview:3.4.7")
428
428
  // arsceneview includes sceneview transitively — no need to add both
429
429
  }
430
430
  \`\`\`
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
46
46
  {
47
47
  uri: "sceneview://api",
48
48
  name: "SceneView API Reference",
49
- description: "Complete SceneView 3.3.0 API — Scene, ARScene, SceneScope DSL, ARSceneScope DSL, node types, resource loading, camera, gestures, math types, threading rules, and common patterns. Read this before writing any SceneView code.",
49
+ description: "Complete SceneView 3.4.7 API — Scene, ARScene, SceneScope DSL, ARSceneScope DSL, node types, resource loading, camera, gestures, math types, threading rules, and common patterns. Read this before writing any SceneView code.",
50
50
  mimeType: "text/markdown",
51
51
  },
52
52
  {
@@ -514,7 +514,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
514
514
  ? [
515
515
  `**SPM dependency:**`,
516
516
  `\`\`\`swift`,
517
- `.package(url: "${sample.spmDependency ?? sample.dependency}", from: "3.3.0")`,
517
+ `.package(url: "${sample.spmDependency ?? sample.dependency}", from: "3.4.7")`,
518
518
  `\`\`\``,
519
519
  ]
520
520
  : [
@@ -587,7 +587,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
587
587
  `### build.gradle.kts`,
588
588
  `\`\`\`kotlin`,
589
589
  `dependencies {`,
590
- ` implementation("io.github.sceneview:sceneview:3.3.0")`,
590
+ ` implementation("io.github.sceneview:sceneview:3.4.7")`,
591
591
  `}`,
592
592
  `\`\`\``,
593
593
  ``,
@@ -608,7 +608,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
608
608
  `### build.gradle.kts`,
609
609
  `\`\`\`kotlin`,
610
610
  `dependencies {`,
611
- ` implementation("io.github.sceneview:arsceneview:3.3.0")`,
611
+ ` implementation("io.github.sceneview:arsceneview:3.4.7")`,
612
612
  `}`,
613
613
  `\`\`\``,
614
614
  ``,
@@ -721,7 +721,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
721
721
  `\`\`\``,
722
722
  `https://github.com/sceneview/sceneview`,
723
723
  `\`\`\``,
724
- `Set version rule to **"from: 3.3.0"**.`,
724
+ `Set version rule to **"from: 3.4.7"**.`,
725
725
  ``,
726
726
  `Or in Package.swift:`,
727
727
  `\`\`\`swift`,
@@ -732,7 +732,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
732
732
  ` name: "MyApp",`,
733
733
  ` platforms: [.iOS(.v17), .macOS(.v14), .visionOS(.v1)],`,
734
734
  ` dependencies: [`,
735
- ` .package(url: "https://github.com/sceneview/sceneview", from: "3.3.0")`,
735
+ ` .package(url: "https://github.com/sceneview/sceneview", from: "3.4.7")`,
736
736
  ` ],`,
737
737
  ` targets: [`,
738
738
  ` .executableTarget(`,
@@ -803,7 +803,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
803
803
  `### 1. Add SPM Dependency`,
804
804
  ``,
805
805
  `\`\`\`swift`,
806
- `.package(url: "https://github.com/sceneview/sceneview", from: "3.3.0")`,
806
+ `.package(url: "https://github.com/sceneview/sceneview", from: "3.4.7")`,
807
807
  `\`\`\``,
808
808
  ``,
809
809
  `### 2. Minimum Platform`,
@@ -1083,15 +1083,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1083
1083
  // ── list_platforms ────────────────────────────────────────────────────────
1084
1084
  case "list_platforms": {
1085
1085
  const platforms = [
1086
- { platform: "Android", renderer: "Filament", framework: "Jetpack Compose", status: "Stable", version: "3.3.0", dependency: "io.github.sceneview:sceneview:3.3.0", features: ["3D", "AR (ARCore)", "Model loading (GLB/glTF)", "Geometry nodes", "Physics", "Gestures"] },
1087
- { platform: "Android TV", renderer: "Filament", framework: "Compose TV", status: "Alpha", version: "3.3.0", dependency: "io.github.sceneview:sceneview:3.3.0", features: ["3D", "D-pad controls", "Auto-rotation", "Model loading"] },
1086
+ { platform: "Android", renderer: "Filament", framework: "Jetpack Compose", status: "Stable", version: "3.4.7", dependency: "io.github.sceneview:sceneview:3.4.7", features: ["3D", "AR (ARCore)", "Model loading (GLB/glTF)", "Geometry nodes", "Physics", "Gestures"] },
1087
+ { platform: "Android TV", renderer: "Filament", framework: "Compose TV", status: "Alpha", version: "3.4.7", dependency: "io.github.sceneview:sceneview:3.4.7", features: ["3D", "D-pad controls", "Auto-rotation", "Model loading"] },
1088
1088
  { platform: "Android XR", renderer: "Jetpack XR SceneCore", framework: "Compose XR", status: "Planned", version: "-", dependency: "-", features: ["Spatial computing", "Hand tracking", "Passthrough"] },
1089
- { platform: "iOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.3.0", dependency: "SceneViewSwift (SPM)", features: ["3D", "AR (ARKit)", "16 node types", "USDZ models"] },
1090
- { platform: "macOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.3.0", dependency: "SceneViewSwift (SPM)", features: ["3D", "Orbit camera", "USDZ models"] },
1091
- { platform: "visionOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.3.0", dependency: "SceneViewSwift (SPM)", features: ["3D", "Immersive spaces", "Hand tracking (planned)"] },
1092
- { platform: "Web", renderer: "Filament.js (WASM)", framework: "Kotlin/JS", status: "Alpha", version: "3.3.0", dependency: "@sceneview/sceneview-web", features: ["3D", "WebXR AR/VR", "GLB models", "WebGL2"] },
1093
- { platform: "Desktop", renderer: "Software / Filament JNI", framework: "Compose Desktop", status: "Alpha", version: "3.3.0", dependency: "sceneview-desktop (local)", features: ["3D", "Software renderer", "Wireframe"] },
1094
- { platform: "Flutter", renderer: "Filament / RealityKit", framework: "PlatformView", status: "Alpha", version: "3.3.0", dependency: "flutter pub: sceneview", features: ["3D", "AR", "Android + iOS bridge"] },
1089
+ { platform: "iOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.4.7", dependency: "SceneViewSwift (SPM)", features: ["3D", "AR (ARKit)", "16 node types", "USDZ models"] },
1090
+ { platform: "macOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.4.7", dependency: "SceneViewSwift (SPM)", features: ["3D", "Orbit camera", "USDZ models"] },
1091
+ { platform: "visionOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: "3.4.7", dependency: "SceneViewSwift (SPM)", features: ["3D", "Immersive spaces", "Hand tracking (planned)"] },
1092
+ { platform: "Web", renderer: "Filament.js (WASM)", framework: "Kotlin/JS", status: "Alpha", version: "3.4.7", dependency: "@sceneview/sceneview-web", features: ["3D", "WebXR AR/VR", "GLB models", "WebGL2"] },
1093
+ { platform: "Desktop", renderer: "Software / Filament JNI", framework: "Compose Desktop", status: "Alpha", version: "3.4.7", dependency: "sceneview-desktop (local)", features: ["3D", "Software renderer", "Wireframe"] },
1094
+ { platform: "Flutter", renderer: "Filament / RealityKit", framework: "PlatformView", status: "Alpha", version: "3.4.7", dependency: "flutter pub: sceneview", features: ["3D", "AR", "Android + iOS bridge"] },
1095
1095
  ];
1096
1096
  const lines = [
1097
1097
  "## SceneView Supported Platforms\n",