@compilr-dev/sdk 0.39.0 → 0.40.0

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.
@@ -48,6 +48,11 @@ export type SceneOp =
48
48
  op: 'layers';
49
49
  layers: unknown;
50
50
  }
51
+ /** Replace the lights (§20.5); `[]` restores the default studio light. */
52
+ | {
53
+ op: 'lights';
54
+ lights: unknown;
55
+ }
51
56
  /** Whole-scene replace (scene_create, JSON import). */
52
57
  | {
53
58
  op: 'replace';
@@ -59,7 +64,7 @@ export type SceneEditSource = {
59
64
  } | {
60
65
  kind: 'user';
61
66
  };
62
- export type SceneChangeKind = 'moved' | 'resized' | 'rotated' | 'recoloured' | 'renamed' | 'removed' | 'added' | 'edited' | 'camera' | 'layers' | 'replaced';
67
+ export type SceneChangeKind = 'moved' | 'resized' | 'rotated' | 'recoloured' | 'renamed' | 'removed' | 'added' | 'edited' | 'camera' | 'layers' | 'lights' | 'replaced';
63
68
  /** One journalled change (the writer stamps `rev`). */
64
69
  export interface SceneChange {
65
70
  rev: number;
@@ -291,6 +291,43 @@ function setLayers(scene, layers) {
291
291
  warnings: [],
292
292
  };
293
293
  }
294
+ /**
295
+ * Replace the lights.
296
+ *
297
+ * `[]` DELETES the field rather than storing an empty list, which is the difference between
298
+ * "the default studio light" and "no light at all" — an empty array would render a black scene
299
+ * and read in the JSON as if lighting had been configured.
300
+ */
301
+ function setLights(scene, lights) {
302
+ if (!Array.isArray(lights)) {
303
+ return {
304
+ error: 'lights must be an array of { type: "sun" | "ambient", … } — or [] to go back to the default studio light.',
305
+ };
306
+ }
307
+ const probe = validateScene({ ...scene, lights: lights.length === 0 ? undefined : lights });
308
+ if (!probe.ok)
309
+ return { error: formatValidationErrors(probe.errors) };
310
+ const before = scene.lights ?? [];
311
+ const next = { ...scene };
312
+ if (lights.length === 0)
313
+ delete next.lights;
314
+ else
315
+ next.lights = probe.scene.lights;
316
+ const summary = lights.length === 0
317
+ ? 'Lights cleared — back to the default studio light.'
318
+ : `Lights set: ${lights
319
+ .map((l) => isRecord(l) && typeof l.type === 'string'
320
+ ? `${l.type} ${typeof l.intensity === 'number' ? String(l.intensity) : 'default'}`
321
+ : '?')
322
+ .join(', ')}.`;
323
+ return {
324
+ scene: next,
325
+ summary,
326
+ ids: [],
327
+ changes: [{ id: '', what: 'lights', from: before, to: lights }],
328
+ warnings: [],
329
+ };
330
+ }
294
331
  function setCamera(scene, camera) {
295
332
  if (isRecord(camera) && camera.fit === true) {
296
333
  const rest = { ...scene };
@@ -335,6 +372,8 @@ export function applySceneOp(scene, op, opts) {
335
372
  return setCamera(scene, op.camera);
336
373
  case 'layers':
337
374
  return setLayers(scene, op.layers);
375
+ case 'lights':
376
+ return setLights(scene, op.lights);
338
377
  case 'replace': {
339
378
  const v = validateScene(op.scene);
340
379
  if (!v.ok)
@@ -431,6 +470,8 @@ function describeChange(c) {
431
470
  return 'changed the camera';
432
471
  case 'layers':
433
472
  return 'changed which layers can be hidden';
473
+ case 'lights':
474
+ return 'changed the lighting';
434
475
  case 'replaced':
435
476
  return 'replaced the whole scene';
436
477
  case 'edited':
@@ -280,6 +280,7 @@ export const CAPABILITY_PACKS = {
280
280
  'scene_remove_object',
281
281
  'scene_set_camera',
282
282
  'scene_set_layers',
283
+ 'scene_set_lights',
283
284
  'scene_screenshot',
284
285
  ],
285
286
  readOnly: false,
@@ -15,7 +15,7 @@
15
15
  import type { PlatformContext, PlatformToolsConfig } from '../context.js';
16
16
  import { type ISceneWriter } from '../scene-writer.js';
17
17
  import { type SceneFile } from '../../canvas/scene.js';
18
- export declare const SCENE_TOOL_NAMES: readonly ["scene_create", "scene_get", "scene_add_object", "scene_update_object", "scene_remove_object", "scene_set_camera", "scene_set_layers", "scene_screenshot"];
18
+ export declare const SCENE_TOOL_NAMES: readonly ["scene_create", "scene_get", "scene_add_object", "scene_update_object", "scene_remove_object", "scene_set_camera", "scene_set_layers", "scene_set_lights", "scene_screenshot"];
19
19
  /**
20
20
  * "sofa · box · Sofa" rows, indented under their group parent; `#tag` suffixes when `withTags`.
21
21
  *
@@ -103,6 +103,9 @@ export declare function createSceneTools(config: PlatformToolsConfig, options?:
103
103
  }> | import("@compilr-dev/agents").Tool<{
104
104
  canvas_id: number;
105
105
  layers?: unknown;
106
+ }> | import("@compilr-dev/agents").Tool<{
107
+ canvas_id: number;
108
+ lights?: unknown;
106
109
  }> | import("@compilr-dev/agents").Tool<{
107
110
  canvas_id: number;
108
111
  width?: number;
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { defineTool, createErrorResult } from '@compilr-dev/agents';
16
16
  import { createSceneWriter } from '../scene-writer.js';
17
- import { SCENE_MAX_BATCH, SCENE_MAX_LAYERS, SCENE_OBJECT_TYPES, SCENE_LABEL_FACES, describeScene, serializeScene, } from '../../canvas/scene.js';
17
+ import { SCENE_MAX_BATCH, SCENE_MAX_LAYERS, SCENE_OBJECT_TYPES, SCENE_MAX_LIGHTS, SCENE_MAX_SHADOW_LIGHTS, SCENE_LIGHT_INTENSITY_MAX, SCENE_LABEL_FACES, describeScene, serializeScene, } from '../../canvas/scene.js';
18
18
  import { describeLayers, emptyLayers, undeclaredTags } from '../../canvas/scene-layers.js';
19
19
  import { formatUserEditNotice, } from '../../canvas/scene-ops.js';
20
20
  export const SCENE_TOOL_NAMES = [
@@ -25,6 +25,7 @@ export const SCENE_TOOL_NAMES = [
25
25
  'scene_remove_object',
26
26
  'scene_set_camera',
27
27
  'scene_set_layers',
28
+ 'scene_set_lights',
28
29
  'scene_screenshot',
29
30
  ];
30
31
  const DEFAULT_SHOT = { width: 1280, height: 800 };
@@ -305,7 +306,13 @@ const OBJECT_PROPERTIES = {
305
306
  },
306
307
  emissiveIntensity: {
307
308
  type: 'number',
308
- description: '0–5, default 1. Does nothing without emissive. Past ~3 the colour clips to white.',
309
+ description: '0–5, default 1. Does nothing without emissive. ⚠️ MEASURED on a real scene: a sphere at ' +
310
+ '1.5 loses its shading and reads as a flat disc, and a ring at 2 washes to near-white and ' +
311
+ 'loses its hue. Emission adds a flat colour OVER the shading, so the bigger or paler the ' +
312
+ 'object, the sooner it stops looking like a solid. Stay at or below ~0.3 for anything ' +
313
+ 'larger than a token and save the high end for small accents. Against a bright scene a ' +
314
+ 'glow reads poorly whatever the number — dim the scene with scene_set_lights rather than ' +
315
+ 'turning this up.',
309
316
  },
310
317
  wireframe: {
311
318
  type: 'boolean',
@@ -683,6 +690,56 @@ export function createSceneTools(config, options) {
683
690
  },
684
691
  });
685
692
  // ---------------------------------------------------------------------------
693
+ const sceneSetLightsTool = defineTool({
694
+ name: 'scene_set_lights',
695
+ description: "Replace a scene's lighting. Until this existed, lights could only be set at creation, so " +
696
+ 'dimming a finished scene meant rebuilding every object in it. ' +
697
+ 'Two kinds: { type: "ambient", intensity } is the overall fill — it also sets how much of ' +
698
+ 'the room glossy and metallic surfaces reflect, so dropping it to 0 makes a genuinely dark ' +
699
+ 'scene — and { type: "sun", position: [x, y, z], intensity, castShadow } is a directional ' +
700
+ 'light that can cast shadows. [] goes back to the default studio light (ambient fill + one ' +
701
+ 'sun from above-right), which is what most scenes want. ' +
702
+ `Up to ${String(SCENE_MAX_LIGHTS)} lights, ${String(SCENE_MAX_SHADOW_LIGHTS)} of them casting shadows ` +
703
+ '(each shadow costs a render pass). ⚠️ Lighting a scene darker makes emissive objects stand ' +
704
+ 'out; it also makes everything else harder to read, so change it for a reason.',
705
+ inputSchema: {
706
+ type: 'object',
707
+ properties: {
708
+ canvas_id: { type: 'number', description: 'The scene canvas id.' },
709
+ lights: {
710
+ type: 'array',
711
+ maxItems: SCENE_MAX_LIGHTS,
712
+ description: '[] restores the default studio light.',
713
+ items: {
714
+ type: 'object',
715
+ properties: {
716
+ type: { type: 'string', enum: ['sun', 'ambient'] },
717
+ position: { ...VEC3, description: 'sun only: where it shines FROM, in metres.' },
718
+ intensity: {
719
+ type: 'number',
720
+ description: `0–${String(SCENE_LIGHT_INTENSITY_MAX)}. The default studio light is ambient ~1 and sun ~1.6.`,
721
+ },
722
+ castShadow: { type: 'boolean', description: 'sun only. Default false.' },
723
+ },
724
+ required: ['type'],
725
+ },
726
+ },
727
+ },
728
+ required: ['canvas_id', 'lights'],
729
+ },
730
+ execute: async (input) => {
731
+ try {
732
+ if (!Array.isArray(input.lights)) {
733
+ return createErrorResult('lights must be an array of { type: "sun" | "ambient", … } — or [] to go back to the default studio light.');
734
+ }
735
+ return await applyAsAgent(input.canvas_id, [{ op: 'lights', lights: input.lights }], () => null);
736
+ }
737
+ catch (error) {
738
+ return createErrorResult(errorMessage('set lights', error));
739
+ }
740
+ },
741
+ });
742
+ // ---------------------------------------------------------------------------
686
743
  const sceneScreenshotTool = defineTool({
687
744
  name: 'scene_screenshot',
688
745
  description: 'RENDER a 3D scene and SEE it — check proportions, overlaps, gaps and floating objects after ' +
@@ -736,6 +793,7 @@ export function createSceneTools(config, options) {
736
793
  sceneRemoveObjectTool,
737
794
  sceneSetCameraTool,
738
795
  sceneSetLayersTool,
796
+ sceneSetLightsTool,
739
797
  sceneScreenshotTool,
740
798
  ];
741
799
  }
@@ -315,6 +315,7 @@ export const TOOL_GROUPS = {
315
315
  'scene_remove_object',
316
316
  'scene_set_camera',
317
317
  'scene_set_layers',
318
+ 'scene_set_lights',
318
319
  'scene_screenshot',
319
320
  ],
320
321
  readOnly: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",