@pascal-app/mcp 1.0.1 → 1.0.4
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 +47 -26
- package/dist/bin/pascal-mcp.js +8 -2
- package/dist/bridge/scene-bridge.d.ts +41 -6
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +230 -45
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/operations/scene-operations.d.ts +23 -3
- package/dist/operations/scene-operations.d.ts.map +1 -1
- package/dist/operations/scene-operations.js +31 -1
- package/dist/prompts/from-brief.js +1 -1
- package/dist/prompts/iterate-on-feedback.js +1 -1
- package/dist/prompts/scene-guidance.js +3 -3
- package/dist/resources/agent-guide.d.ts.map +1 -1
- package/dist/resources/agent-guide.js +22 -1
- package/dist/resources/catalog-items.d.ts +4 -5
- package/dist/resources/catalog-items.d.ts.map +1 -1
- package/dist/resources/catalog-items.js +7 -9
- package/dist/resources/design-schema.d.ts +9 -0
- package/dist/resources/design-schema.d.ts.map +1 -0
- package/dist/resources/design-schema.js +21 -0
- package/dist/resources/index.d.ts +3 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +5 -2
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +14 -6
- package/dist/storage/disk-artifact-store.d.ts +9 -0
- package/dist/storage/disk-artifact-store.d.ts.map +1 -0
- package/dist/storage/disk-artifact-store.js +46 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -0
- package/dist/storage/sqlite-scene-store.d.ts +2 -2
- package/dist/storage/sqlite-scene-store.d.ts.map +1 -1
- package/dist/storage/sqlite-scene-store.js +4 -3
- package/dist/storage/types.d.ts +32 -3
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -0
- package/dist/tools/add-column.d.ts +5 -0
- package/dist/tools/add-column.d.ts.map +1 -0
- package/dist/tools/add-column.js +58 -0
- package/dist/tools/add-object.d.ts +60 -0
- package/dist/tools/add-object.d.ts.map +1 -0
- package/dist/tools/add-object.js +156 -0
- package/dist/tools/apply-patch.d.ts +3 -0
- package/dist/tools/apply-patch.d.ts.map +1 -1
- package/dist/tools/apply-patch.js +28 -2
- package/dist/tools/asset-catalog.d.ts +9 -5
- package/dist/tools/asset-catalog.d.ts.map +1 -1
- package/dist/tools/asset-catalog.js +10 -12
- package/dist/tools/check-collisions.d.ts +1 -1
- package/dist/tools/check-collisions.d.ts.map +1 -1
- package/dist/tools/check-collisions.js +1 -2
- package/dist/tools/clear-scene.d.ts +9 -0
- package/dist/tools/clear-scene.d.ts.map +1 -0
- package/dist/tools/clear-scene.js +59 -0
- package/dist/tools/collections.d.ts +4 -0
- package/dist/tools/collections.d.ts.map +1 -0
- package/dist/tools/collections.js +52 -0
- package/dist/tools/construction-tools.d.ts +8 -41
- package/dist/tools/construction-tools.d.ts.map +1 -1
- package/dist/tools/construction-tools.js +120 -230
- package/dist/tools/create-unit.d.ts +2 -0
- package/dist/tools/create-unit.d.ts.map +1 -1
- package/dist/tools/describe-node.d.ts.map +1 -1
- package/dist/tools/describe-node.js +8 -6
- package/dist/tools/errors.d.ts +11 -0
- package/dist/tools/errors.d.ts.map +1 -1
- package/dist/tools/errors.js +10 -0
- package/dist/tools/find-nodes.d.ts +3 -19
- package/dist/tools/find-nodes.d.ts.map +1 -1
- package/dist/tools/find-nodes.js +150 -53
- package/dist/tools/geometry-script-worker.d.ts +2 -0
- package/dist/tools/geometry-script-worker.d.ts.map +1 -0
- package/dist/tools/geometry-script-worker.js +21 -0
- package/dist/tools/get-scene.d.ts +1 -0
- package/dist/tools/get-scene.d.ts.map +1 -1
- package/dist/tools/get-scene.js +3 -1
- package/dist/tools/honest-patch-guard.d.ts +2 -0
- package/dist/tools/honest-patch-guard.d.ts.map +1 -0
- package/dist/tools/honest-patch-guard.js +32 -0
- package/dist/tools/hosted-services.d.ts +4 -0
- package/dist/tools/hosted-services.d.ts.map +1 -0
- package/dist/tools/hosted-services.js +30 -0
- package/dist/tools/index.d.ts +10 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +17 -26
- package/dist/tools/live-sync.d.ts +24 -5
- package/dist/tools/live-sync.d.ts.map +1 -1
- package/dist/tools/live-sync.js +47 -26
- package/dist/tools/local-geometry-scripts.d.ts +8 -0
- package/dist/tools/local-geometry-scripts.d.ts.map +1 -0
- package/dist/tools/local-geometry-scripts.js +67 -0
- package/dist/tools/measure.d.ts +6 -0
- package/dist/tools/measure.d.ts.map +1 -1
- package/dist/tools/measure.js +33 -65
- package/dist/tools/measurement.d.ts +1 -36
- package/dist/tools/measurement.d.ts.map +1 -1
- package/dist/tools/measurement.js +2 -94
- package/dist/tools/node-world-point.d.ts +37 -0
- package/dist/tools/node-world-point.d.ts.map +1 -0
- package/dist/tools/node-world-point.js +457 -0
- package/dist/tools/normalize-schema-dialect.d.ts.map +1 -1
- package/dist/tools/normalize-schema-dialect.js +20 -0
- package/dist/tools/patch-guards.d.ts +58 -0
- package/dist/tools/patch-guards.d.ts.map +1 -0
- package/dist/tools/patch-guards.js +253 -0
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.js +3 -2
- package/dist/tools/place-design.d.ts +34 -0
- package/dist/tools/place-design.d.ts.map +1 -0
- package/dist/tools/place-design.js +91 -0
- package/dist/tools/redo.d.ts +2 -0
- package/dist/tools/redo.d.ts.map +1 -1
- package/dist/tools/redo.js +2 -2
- package/dist/tools/room-tools.d.ts +50 -113
- package/dist/tools/room-tools.d.ts.map +1 -1
- package/dist/tools/room-tools.js +129 -565
- package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/create-project.js +26 -1
- package/dist/tools/scene-lifecycle/delete-scene.d.ts +2 -0
- package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/delete-scene.js +4 -3
- package/dist/tools/scene-lifecycle/save-scene.d.ts +1 -0
- package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/save-scene.js +46 -6
- package/dist/tools/scene-lifecycle/test-utils.d.ts +2 -2
- package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/test-utils.js +2 -1
- package/dist/tools/set-unit-members.d.ts +2 -0
- package/dist/tools/set-unit-members.d.ts.map +1 -1
- package/dist/tools/set-zone.d.ts +2 -0
- package/dist/tools/set-zone.d.ts.map +1 -1
- package/dist/tools/set-zone.js +2 -2
- package/dist/tools/shared-tools.d.ts +30 -0
- package/dist/tools/shared-tools.d.ts.map +1 -0
- package/dist/tools/shared-tools.js +361 -0
- package/dist/tools/structure-tools.d.ts +57 -0
- package/dist/tools/structure-tools.d.ts.map +1 -0
- package/dist/tools/structure-tools.js +57 -0
- package/dist/tools/undo.d.ts +2 -0
- package/dist/tools/undo.d.ts.map +1 -1
- package/dist/tools/undo.js +2 -2
- package/dist/tools/validate-design.d.ts +136 -0
- package/dist/tools/validate-design.d.ts.map +1 -0
- package/dist/tools/validate-design.js +28 -0
- package/dist/tools/validate-scene.d.ts +5 -0
- package/dist/tools/validate-scene.d.ts.map +1 -1
- package/dist/tools/validate-scene.js +8 -6
- package/dist/tools/variants/mutations.js +1 -1
- package/dist/tools/view-scene.d.ts +38 -0
- package/dist/tools/view-scene.d.ts.map +1 -0
- package/dist/tools/view-scene.js +63 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +17 -3
- package/examples/embed-in-agent.ts +1 -1
- package/examples/generate-apartment.md +9 -11
- package/examples/photo-to-scene.md +1 -1
- package/examples/renovate-from-photos.md +3 -4
- package/package.json +12 -6
- package/dist/tools/create-level.d.ts +0 -21
- package/dist/tools/create-level.d.ts.map +0 -1
- package/dist/tools/create-level.js +0 -63
- package/dist/tools/create-wall.d.ts +0 -22
- package/dist/tools/create-wall.d.ts.map +0 -1
- package/dist/tools/create-wall.js +0 -57
- package/dist/tools/cut-opening.d.ts +0 -25
- package/dist/tools/cut-opening.d.ts.map +0 -1
- package/dist/tools/cut-opening.js +0 -61
- package/dist/tools/delete-node.d.ts +0 -19
- package/dist/tools/delete-node.d.ts.map +0 -1
- package/dist/tools/delete-node.js +0 -40
- package/dist/tools/door-clearance.d.ts +0 -135
- package/dist/tools/door-clearance.d.ts.map +0 -1
- package/dist/tools/door-clearance.js +0 -334
- package/dist/tools/duplicate-level.d.ts +0 -19
- package/dist/tools/duplicate-level.d.ts.map +0 -1
- package/dist/tools/duplicate-level.js +0 -60
- package/dist/tools/geometry.d.ts +0 -24
- package/dist/tools/geometry.d.ts.map +0 -1
- package/dist/tools/geometry.js +0 -94
- package/dist/tools/get-node.d.ts +0 -11
- package/dist/tools/get-node.d.ts.map +0 -1
- package/dist/tools/get-node.js +0 -29
- package/dist/tools/layout-clearance.d.ts +0 -105
- package/dist/tools/layout-clearance.d.ts.map +0 -1
- package/dist/tools/layout-clearance.js +0 -190
- package/dist/tools/place-item.d.ts +0 -22
- package/dist/tools/place-item.d.ts.map +0 -1
- package/dist/tools/place-item.js +0 -89
- package/dist/tools/scene-query.d.ts +0 -62
- package/dist/tools/scene-query.d.ts.map +0 -1
- package/dist/tools/scene-query.js +0 -670
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Side-effect import MUST come first: installs RAF polyfill before core loads.
|
|
2
2
|
import './node-shims.js';
|
|
3
|
-
import {
|
|
3
|
+
import { adjacentLevelId, applyStructureReconciliation, assertDerivedNodeWrites, changedStairOpeningOwners, floorOpeningTargets, HIDDEN_SITE_NOTE, pauseSceneHistory, pauseSpaceDetection, planNodeDeletion, planOwnedFloorOpenings, planWallDeletion, resumeSceneHistory, resumeSpaceDetection, runAsSingleSceneHistoryStep, stairDeckLevelId, structureChangeBatch, } from '@pascal-app/core';
|
|
4
|
+
import { AnyNode as AnyNodeSchema, generateId, parseNode, SceneMaterial, } from '@pascal-app/core/schema';
|
|
4
5
|
// Per PLAN §0.6: `useScene` is the DEFAULT export from `@pascal-app/core/store`.
|
|
5
6
|
import useScene from '@pascal-app/core/store';
|
|
6
7
|
/**
|
|
@@ -11,6 +12,8 @@ import useScene from '@pascal-app/core/store';
|
|
|
11
12
|
* `flushDirty()` for observability.
|
|
12
13
|
*/
|
|
13
14
|
export class SceneBridge {
|
|
15
|
+
/** `applyPatch` takes `upsert_material` patches, in the patch's one history step. */
|
|
16
|
+
supportsMaterialUpserts = true;
|
|
14
17
|
activeScene = null;
|
|
15
18
|
/**
|
|
16
19
|
* Scene identity currently bound to this bridge. MCP tools use this to know
|
|
@@ -24,6 +27,7 @@ export class SceneBridge {
|
|
|
24
27
|
ownerId: meta.ownerId,
|
|
25
28
|
thumbnailUrl: meta.thumbnailUrl,
|
|
26
29
|
version: meta.version,
|
|
30
|
+
...(meta.graphHash === undefined ? {} : { graphHash: meta.graphHash }),
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
getActiveScene() {
|
|
@@ -118,6 +122,17 @@ export class SceneBridge {
|
|
|
118
122
|
getNodes() {
|
|
119
123
|
return useScene.getState().nodes;
|
|
120
124
|
}
|
|
125
|
+
getCollections() {
|
|
126
|
+
return useScene.getState().collections;
|
|
127
|
+
}
|
|
128
|
+
getMaterials() {
|
|
129
|
+
return useScene.getState().materials;
|
|
130
|
+
}
|
|
131
|
+
setCollections(collections) {
|
|
132
|
+
if (useScene.getState().readOnly)
|
|
133
|
+
return;
|
|
134
|
+
useScene.setState({ collections });
|
|
135
|
+
}
|
|
121
136
|
/** Root node IDs. */
|
|
122
137
|
getRootNodeIds() {
|
|
123
138
|
return useScene.getState().rootNodeIds;
|
|
@@ -236,7 +251,12 @@ export class SceneBridge {
|
|
|
236
251
|
* Returns the generated id.
|
|
237
252
|
*/
|
|
238
253
|
createNode(node, parentId) {
|
|
239
|
-
useScene.getState().
|
|
254
|
+
const before = useScene.getState().nodes;
|
|
255
|
+
assertDerivedNodeWrites(before, { create: [{ node: parentId ? { ...node, parentId } : node }] });
|
|
256
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
257
|
+
useScene.getState().createNode(node, parentId);
|
|
258
|
+
this.reconcileDependentStructure(before);
|
|
259
|
+
});
|
|
240
260
|
return node.id;
|
|
241
261
|
}
|
|
242
262
|
/** Update node fields (shallow merge through the core store). */
|
|
@@ -244,7 +264,12 @@ export class SceneBridge {
|
|
|
244
264
|
if (!useScene.getState().nodes[id]) {
|
|
245
265
|
throw new Error(`node not found: ${id}`);
|
|
246
266
|
}
|
|
247
|
-
useScene.getState().
|
|
267
|
+
const before = useScene.getState().nodes;
|
|
268
|
+
assertDerivedNodeWrites(before, { update: [{ id, data }] });
|
|
269
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
270
|
+
useScene.getState().updateNode(id, data);
|
|
271
|
+
this.reconcileDependentStructure(before);
|
|
272
|
+
});
|
|
248
273
|
}
|
|
249
274
|
/**
|
|
250
275
|
* Delete a node. If the node has children and `cascade === false`, throws.
|
|
@@ -264,7 +289,18 @@ export class SceneBridge {
|
|
|
264
289
|
throw new Error(`node has ${descendants.length - 1} descendant(s); pass cascade: true to delete recursively`);
|
|
265
290
|
}
|
|
266
291
|
const before = new Set(Object.keys(state.nodes));
|
|
267
|
-
|
|
292
|
+
const wallLevels = [
|
|
293
|
+
...new Set(descendants.flatMap((nodeId) => {
|
|
294
|
+
const deleted = state.nodes[nodeId];
|
|
295
|
+
return deleted?.type === 'wall' && deleted.parentId ? [deleted.parentId] : [];
|
|
296
|
+
})),
|
|
297
|
+
];
|
|
298
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
299
|
+
useScene.getState().deleteNode(id);
|
|
300
|
+
if (wallLevels.length)
|
|
301
|
+
this.deriveStructure(wallLevels);
|
|
302
|
+
this.reconcileDependentStructure(state.nodes);
|
|
303
|
+
});
|
|
268
304
|
const afterNodes = useScene.getState().nodes;
|
|
269
305
|
const removed = [];
|
|
270
306
|
for (const prevId of before) {
|
|
@@ -275,8 +311,8 @@ export class SceneBridge {
|
|
|
275
311
|
}
|
|
276
312
|
/**
|
|
277
313
|
* Atomic multi-op patch. Validates EVERY patch first (dry run); only if all
|
|
278
|
-
* pass does it apply
|
|
279
|
-
*
|
|
314
|
+
* pass does it apply through the store actions in one history step. Throws on
|
|
315
|
+
* validation failure without mutating state.
|
|
280
316
|
*/
|
|
281
317
|
applyPatch(patches) {
|
|
282
318
|
const state = useScene.getState();
|
|
@@ -289,11 +325,18 @@ export class SceneBridge {
|
|
|
289
325
|
// Zod-normalised copy (which has a generated id if the caller omitted one)
|
|
290
326
|
// instead of the unparsed input.
|
|
291
327
|
const parsedCreateNodes = new Map();
|
|
328
|
+
const materialUpserts = new Map();
|
|
292
329
|
for (let i = 0; i < patches.length; i++) {
|
|
293
330
|
const p = patches[i];
|
|
294
331
|
if (!p)
|
|
295
332
|
throw new Error(`invalid patch: patches[${i}] is undefined`);
|
|
296
|
-
if (p.op === '
|
|
333
|
+
if (p.op === 'upsert_material') {
|
|
334
|
+
const parsed = SceneMaterial.safeParse(p.material);
|
|
335
|
+
if (!parsed.success)
|
|
336
|
+
throw new Error(`invalid patch: patches[${i}] material failed schema: ${parsed.error.message}`);
|
|
337
|
+
materialUpserts.set(parsed.data.id, parsed.data);
|
|
338
|
+
}
|
|
339
|
+
else if (p.op === 'create') {
|
|
297
340
|
const res = parseNode(p.node);
|
|
298
341
|
if (!res.success) {
|
|
299
342
|
throw new Error(`invalid patch: patches[${i}] create node failed schema: ${res.error.message}`);
|
|
@@ -301,7 +344,7 @@ export class SceneBridge {
|
|
|
301
344
|
if (p.parentId !== undefined && !simAvailable.has(p.parentId)) {
|
|
302
345
|
throw new Error(`invalid patch: patches[${i}] create parentId "${p.parentId}" not found`);
|
|
303
346
|
}
|
|
304
|
-
parsedCreateNodes.set(i, res.data);
|
|
347
|
+
parsedCreateNodes.set(i, p.parentId ? { ...res.data, parentId: p.parentId } : res.data);
|
|
305
348
|
simAvailable.add(res.data.id);
|
|
306
349
|
}
|
|
307
350
|
else if (p.op === 'update') {
|
|
@@ -333,47 +376,75 @@ export class SceneBridge {
|
|
|
333
376
|
throw new Error(`invalid patch: patches[${i}] unknown op`);
|
|
334
377
|
}
|
|
335
378
|
}
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
379
|
+
// Derived construction is refused BEFORE anything is applied, so a patch
|
|
380
|
+
// that tries to author a floor plate or an auto ceiling leaves the scene
|
|
381
|
+
// untouched like any other invalid patch.
|
|
382
|
+
assertDerivedNodeWrites(nodes, {
|
|
383
|
+
create: [...parsedCreateNodes.values()].map((node) => ({ node })),
|
|
384
|
+
update: patches.flatMap((patch) => patch.op === 'update' ? [{ id: patch.id, data: patch.data }] : []),
|
|
385
|
+
});
|
|
386
|
+
const createOps = new Map();
|
|
387
|
+
const updateOps = new Map();
|
|
388
|
+
const deleteIds = new Set();
|
|
389
|
+
const nextNodes = { ...nodes };
|
|
390
|
+
for (const [index, patch] of patches.entries()) {
|
|
391
|
+
if (patch.op === 'create') {
|
|
392
|
+
const parsed = parsedCreateNodes.get(index);
|
|
393
|
+
const node = { ...parsed, parentId: patch.parentId ?? parsed.parentId };
|
|
394
|
+
createOps.set(node.id, { node, parentId: patch.parentId });
|
|
395
|
+
deleteIds.delete(node.id);
|
|
396
|
+
nextNodes[node.id] = node;
|
|
353
397
|
}
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
|
|
398
|
+
else if (patch.op === 'update') {
|
|
399
|
+
nextNodes[patch.id] = { ...nextNodes[patch.id], ...patch.data };
|
|
400
|
+
const created = createOps.get(patch.id);
|
|
401
|
+
if (created) {
|
|
402
|
+
created.node = nextNodes[patch.id];
|
|
403
|
+
if (patch.data.parentId !== undefined)
|
|
404
|
+
created.parentId = patch.data.parentId;
|
|
405
|
+
}
|
|
406
|
+
else
|
|
407
|
+
updateOps.set(patch.id, { ...updateOps.get(patch.id), ...patch.data });
|
|
357
408
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const parsedNode = parsedCreateNodes.get(i);
|
|
364
|
-
createOps.push({ node: parsedNode, parentId: p.parentId });
|
|
365
|
-
createdIds.push(parsedNode.id);
|
|
409
|
+
else if (patch.op === 'delete') {
|
|
410
|
+
deleteIds.add(patch.id);
|
|
411
|
+
createOps.delete(patch.id);
|
|
412
|
+
updateOps.delete(patch.id);
|
|
413
|
+
delete nextNodes[patch.id];
|
|
366
414
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
415
|
+
}
|
|
416
|
+
// Plan the entire deletion selection before applying anything. Interleaved
|
|
417
|
+
// update operations must not turn an all-boundaries deletion into balconies.
|
|
418
|
+
const planned = structureChangeBatch(planWallDeletion(nodes, {
|
|
419
|
+
nodeIds: [...deleteIds],
|
|
420
|
+
nextNodes,
|
|
421
|
+
mintId: generateId,
|
|
422
|
+
}).changes);
|
|
423
|
+
for (const { id, data } of planned.update)
|
|
424
|
+
updateOps.set(id, { ...updateOps.get(id), ...data });
|
|
425
|
+
const wallLevels = [
|
|
426
|
+
...new Set(planned.delete.flatMap((id) => {
|
|
427
|
+
const node = nodes[id];
|
|
428
|
+
return node?.type === 'wall' && node.parentId ? [node.parentId] : [];
|
|
429
|
+
})),
|
|
430
|
+
];
|
|
431
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
432
|
+
pauseSpaceDetection();
|
|
433
|
+
try {
|
|
434
|
+
for (const material of materialUpserts.values())
|
|
435
|
+
useScene.getState().addSceneMaterial(material);
|
|
436
|
+
useScene.getState().createNodes([...createOps.values(), ...planned.create]);
|
|
437
|
+
useScene.getState().updateNodes([...updateOps].map(([id, data]) => ({ id, data })));
|
|
438
|
+
useScene.getState().deleteNodes(planned.delete);
|
|
370
439
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
deleteIds.push(p.id);
|
|
440
|
+
finally {
|
|
441
|
+
resumeSpaceDetection();
|
|
374
442
|
}
|
|
375
|
-
|
|
376
|
-
|
|
443
|
+
if (wallLevels.length)
|
|
444
|
+
this.deriveStructure(wallLevels);
|
|
445
|
+
this.reconcileDependentStructure(nodes);
|
|
446
|
+
});
|
|
447
|
+
const createdIds = Object.keys(useScene.getState().nodes).filter((id) => !nodes[id]);
|
|
377
448
|
// Compute actual deleted ids by diffing pre/post snapshots.
|
|
378
449
|
const postNodes = useScene.getState().nodes;
|
|
379
450
|
const deletedIds = [];
|
|
@@ -387,6 +458,40 @@ export class SceneBridge {
|
|
|
387
458
|
createdIds,
|
|
388
459
|
};
|
|
389
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* Preview what deleting `ids` does to `scene` with this bridge's semantics
|
|
463
|
+
* (core's store planner), without committing or minting default gutters.
|
|
464
|
+
* The apply_patch guard runs its dry run through this.
|
|
465
|
+
*/
|
|
466
|
+
planDeletion(scene, ids) {
|
|
467
|
+
return planNodeDeletion(scene, ids, { mintDefaults: false });
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Derive this scene's construction the way the browser's commit subscriber
|
|
471
|
+
* does: one pass of the shared kernel writes the room zones, the auto
|
|
472
|
+
* ceilings, the wall side classification and the floor plates.
|
|
473
|
+
*
|
|
474
|
+
* The headless bridge has no space-detection subscriber, so the semantic
|
|
475
|
+
* tools that state room intent call this to materialise the construction
|
|
476
|
+
* they report. Writes are history-paused, so undo returns to the state
|
|
477
|
+
* before the triggering tool call rather than stranding derived nodes.
|
|
478
|
+
*/
|
|
479
|
+
deriveStructure(levelIds) {
|
|
480
|
+
pauseSceneHistory(useScene);
|
|
481
|
+
try {
|
|
482
|
+
const patches = applyStructureReconciliation(useScene, {
|
|
483
|
+
levelIds,
|
|
484
|
+
mintId: (kind) => generateId(kind),
|
|
485
|
+
});
|
|
486
|
+
return {
|
|
487
|
+
createdIds: patches.flatMap((patch) => patch.op === 'create' ? [patch.node.id] : []),
|
|
488
|
+
deletedIds: patches.flatMap((patch) => (patch.op === 'delete' ? [patch.id] : [])),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
finally {
|
|
492
|
+
resumeSceneHistory(useScene);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
390
495
|
/** Undo. Returns the number of steps actually undone. */
|
|
391
496
|
undo(steps = 1) {
|
|
392
497
|
const before = useScene.temporal.getState().pastStates.length;
|
|
@@ -407,8 +512,12 @@ export class SceneBridge {
|
|
|
407
512
|
*/
|
|
408
513
|
validateScene() {
|
|
409
514
|
const errors = [];
|
|
515
|
+
const warnings = [];
|
|
410
516
|
const nodes = useScene.getState().nodes;
|
|
411
517
|
for (const [id, node] of Object.entries(nodes)) {
|
|
518
|
+
if (node.type === 'site' && node.visible === false) {
|
|
519
|
+
warnings.push({ nodeId: id, path: 'visible', message: HIDDEN_SITE_NOTE });
|
|
520
|
+
}
|
|
412
521
|
const res = AnyNodeSchema.safeParse(node);
|
|
413
522
|
if (res.success)
|
|
414
523
|
continue;
|
|
@@ -420,7 +529,7 @@ export class SceneBridge {
|
|
|
420
529
|
});
|
|
421
530
|
}
|
|
422
531
|
}
|
|
423
|
-
return { valid: errors.length === 0, errors };
|
|
532
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
424
533
|
}
|
|
425
534
|
/**
|
|
426
535
|
* Drain the dirtyNodes set. Returns the ids that were present. No-op for
|
|
@@ -446,6 +555,82 @@ export class SceneBridge {
|
|
|
446
555
|
clearHistory() {
|
|
447
556
|
useScene.temporal.getState().clear();
|
|
448
557
|
}
|
|
558
|
+
runAsSingleHistoryStep(run) {
|
|
559
|
+
return runAsSingleSceneHistoryStep(useScene, run);
|
|
560
|
+
}
|
|
561
|
+
reconcileDependentStructure(before) {
|
|
562
|
+
const currentNodes = this.getNodes();
|
|
563
|
+
const authoredInputChanged = [
|
|
564
|
+
...new Set([...Object.keys(before), ...Object.keys(currentNodes)]),
|
|
565
|
+
].some((id) => {
|
|
566
|
+
if (before[id] === currentNodes[id])
|
|
567
|
+
return false;
|
|
568
|
+
const type = (currentNodes[id] ?? before[id])?.type;
|
|
569
|
+
return (type === 'stair' ||
|
|
570
|
+
type === 'stair-segment' ||
|
|
571
|
+
type === 'elevator' ||
|
|
572
|
+
type === 'level' ||
|
|
573
|
+
type === 'building' ||
|
|
574
|
+
type === 'slab' ||
|
|
575
|
+
type === 'ceiling');
|
|
576
|
+
});
|
|
577
|
+
if (authoredInputChanged) {
|
|
578
|
+
const ownerIds = changedStairOpeningOwners(before, currentNodes);
|
|
579
|
+
for (const node of [...Object.values(before), ...Object.values(currentNodes)]) {
|
|
580
|
+
if (node.type === 'elevator' &&
|
|
581
|
+
(before[node.id] !== currentNodes[node.id] ||
|
|
582
|
+
Object.values(currentNodes).some((level) => level.type === 'level' && before[level.id] !== level)))
|
|
583
|
+
ownerIds.add(node.id);
|
|
584
|
+
}
|
|
585
|
+
const owned = planOwnedFloorOpenings(currentNodes, { ownerIds });
|
|
586
|
+
if (owned.length) {
|
|
587
|
+
pauseSpaceDetection();
|
|
588
|
+
try {
|
|
589
|
+
useScene.getState().applyNodeChanges(structureChangeBatch(owned));
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
resumeSpaceDetection();
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const current = this.getNodes();
|
|
597
|
+
const levels = new Set();
|
|
598
|
+
for (const id of new Set([...Object.keys(before), ...Object.keys(current)])) {
|
|
599
|
+
if (before[id] === current[id])
|
|
600
|
+
continue;
|
|
601
|
+
for (const nodes of [before, current]) {
|
|
602
|
+
const level = stairDeckLevelId(nodes, nodes[id]);
|
|
603
|
+
if (level)
|
|
604
|
+
levels.add(level);
|
|
605
|
+
const node = nodes[id];
|
|
606
|
+
if (node?.type === 'level') {
|
|
607
|
+
levels.add(node.id);
|
|
608
|
+
const lower = adjacentLevelId(nodes, node.id, -1);
|
|
609
|
+
if (lower)
|
|
610
|
+
levels.add(lower);
|
|
611
|
+
}
|
|
612
|
+
if (node?.type === 'stair')
|
|
613
|
+
for (const opening of Object.values(nodes))
|
|
614
|
+
if (opening.type === 'floor-opening' && opening.ownerId === node.id)
|
|
615
|
+
for (const target of floorOpeningTargets(nodes, opening))
|
|
616
|
+
levels.add(target.levelId);
|
|
617
|
+
if (node?.type === 'floor-opening')
|
|
618
|
+
for (const target of floorOpeningTargets(nodes, node))
|
|
619
|
+
levels.add(target.levelId);
|
|
620
|
+
if (node?.type === 'door' || node?.type === 'window') {
|
|
621
|
+
const wall = nodes[node.parentId];
|
|
622
|
+
if (wall?.type === 'wall' && wall.parentId)
|
|
623
|
+
levels.add(wall.parentId);
|
|
624
|
+
}
|
|
625
|
+
else if ((node?.type === 'zone' || node?.type === 'slab' || node?.type === 'wall') &&
|
|
626
|
+
node.parentId) {
|
|
627
|
+
levels.add(node.parentId);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (levels.size)
|
|
632
|
+
this.deriveStructure([...levels]);
|
|
633
|
+
}
|
|
449
634
|
// ---- internal helpers ----
|
|
450
635
|
/**
|
|
451
636
|
* Return the node whose `children` array (string or object form) contains
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { SceneBridge } from './bridge/scene-bridge.js';
|
|
2
2
|
export { createSceneOperations, type SceneOperations } from './operations/index.js';
|
|
3
3
|
export { type CreatePascalMcpServerOptions, createPascalMcpServer } from './server.js';
|
|
4
|
+
export type { GeometryScriptHost, ScriptedKind } from './tools/add-object.js';
|
|
5
|
+
export { type AssetCatalog, builtInCatalog } from './tools/asset-catalog.js';
|
|
6
|
+
export type { SceneViewCapture, SceneViewHost } from './tools/view-scene.js';
|
|
4
7
|
export { version } from './version.js';
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAC1E,OAAO,EAAE,KAAK,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAC1E,OAAO,EAAE,KAAK,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnF,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC1E,OAAO,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACzE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SceneBridge } from './bridge/scene-bridge.js';
|
|
2
2
|
export { createSceneOperations } from './operations/index.js';
|
|
3
3
|
export { createPascalMcpServer } from './server.js';
|
|
4
|
+
export { builtInCatalog } from './tools/asset-catalog.js';
|
|
4
5
|
export { version } from './version.js';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { NodeDeletionPlan, NodeDeletionScene } from '@pascal-app/core';
|
|
1
2
|
import type { SceneGraph } from '@pascal-app/core/clone-scene-graph';
|
|
2
|
-
import type { AnyNode, AnyNodeId, AnyNodeType } from '@pascal-app/core/schema';
|
|
3
|
+
import type { AnyNode, AnyNodeId, AnyNodeType, Collection, CollectionId, SceneMaterial, SceneMaterialId } from '@pascal-app/core/schema';
|
|
3
4
|
import type { ActiveSceneMeta, Patch, SceneBridge, ValidationResult } from '../bridge/scene-bridge.js';
|
|
4
|
-
import type { ProjectCreateOptions, ProjectStatus, SceneEvent, SceneEventAppendOptions, SceneEventListOptions, SceneListOptions, SceneMeta, SceneMutateOptions, SceneSaveOptions, SceneStore, SceneWithGraph } from '../storage/types.js';
|
|
5
|
+
import type { ProjectCreateOptions, ProjectStatus, SceneDeleteResult, SceneEvent, SceneEventAppendOptions, SceneEventListOptions, SceneListOptions, SceneMeta, SceneMutateOptions, SceneSaveOptions, SceneStore, SceneWithGraph } from '../storage/types.js';
|
|
5
6
|
export type CreateSceneOperationsOptions = {
|
|
6
7
|
bridge?: SceneBridge;
|
|
7
8
|
store?: SceneStore;
|
|
@@ -15,6 +16,11 @@ export interface SceneOperations {
|
|
|
15
16
|
readonly canCreateProject: boolean;
|
|
16
17
|
readonly canGetProjectStatus: boolean;
|
|
17
18
|
readonly storeBackend: SceneStore['backend'] | null;
|
|
19
|
+
/**
|
|
20
|
+
* Whether `applyPatch` takes `upsert_material` patches with the nodes that use them, in one
|
|
21
|
+
* history step. A host that does lends `getMaterials`, and paint keeps native materials there.
|
|
22
|
+
*/
|
|
23
|
+
readonly supportsMaterialUpserts?: boolean;
|
|
18
24
|
setActiveScene(meta: ActiveSceneMeta): void;
|
|
19
25
|
getActiveScene(): ActiveSceneMeta | null;
|
|
20
26
|
clearActiveScene(): void;
|
|
@@ -27,6 +33,10 @@ export interface SceneOperations {
|
|
|
27
33
|
loadJSON(json: string | SceneGraph): void;
|
|
28
34
|
getNode(id: AnyNodeId): AnyNode | null;
|
|
29
35
|
getNodes(): Record<AnyNodeId, AnyNode>;
|
|
36
|
+
getCollections(): Record<CollectionId, Collection>;
|
|
37
|
+
getMaterials?(): Readonly<Record<SceneMaterialId, SceneMaterial>>;
|
|
38
|
+
/** Replace the scene's collections (one undo step, like any edit). */
|
|
39
|
+
setCollections(collections: Record<CollectionId, Collection>): void;
|
|
30
40
|
getRootNodeIds(): AnyNodeId[];
|
|
31
41
|
getChildren(parentId: AnyNodeId): AnyNode[];
|
|
32
42
|
getAncestry(id: AnyNodeId): AnyNode[];
|
|
@@ -44,6 +54,15 @@ export interface SceneOperations {
|
|
|
44
54
|
deletedIds: AnyNodeId[];
|
|
45
55
|
createdIds: AnyNodeId[];
|
|
46
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* The active bridge's deletion preview, when it has one. Absent for bridges
|
|
59
|
+
* that cannot preview their own deletes; the apply_patch guard is then stricter.
|
|
60
|
+
*/
|
|
61
|
+
readonly planDeletion?: (scene: NodeDeletionScene, ids: AnyNodeId[]) => NodeDeletionPlan;
|
|
62
|
+
deriveStructure(levelIds?: AnyNodeId[]): {
|
|
63
|
+
createdIds: AnyNodeId[];
|
|
64
|
+
deletedIds: AnyNodeId[];
|
|
65
|
+
};
|
|
47
66
|
undo(steps?: number): number;
|
|
48
67
|
redo(steps?: number): number;
|
|
49
68
|
validateScene(): ValidationResult;
|
|
@@ -53,12 +72,13 @@ export interface SceneOperations {
|
|
|
53
72
|
futureCount: number;
|
|
54
73
|
};
|
|
55
74
|
clearHistory(): void;
|
|
75
|
+
runAsSingleHistoryStep<T>(run: () => T): T;
|
|
56
76
|
createProject(options: ProjectCreateOptions): Promise<ProjectStatus>;
|
|
57
77
|
getProjectStatus(id: string): Promise<ProjectStatus | null>;
|
|
58
78
|
saveScene(options: SceneSaveOptions): Promise<SceneMeta>;
|
|
59
79
|
loadStoredScene(id: string): Promise<SceneWithGraph | null>;
|
|
60
80
|
listScenes(options?: SceneListOptions): Promise<SceneMeta[]>;
|
|
61
|
-
deleteStoredScene(id: string, options?: SceneMutateOptions): Promise<
|
|
81
|
+
deleteStoredScene(id: string, options?: SceneMutateOptions): Promise<SceneDeleteResult>;
|
|
62
82
|
renameStoredScene(id: string, newName: string, options?: SceneMutateOptions): Promise<SceneMeta>;
|
|
63
83
|
appendSceneEvent(options: SceneEventAppendOptions): Promise<SceneEvent | null>;
|
|
64
84
|
listSceneEvents(id: string, options?: SceneEventListOptions): Promise<SceneEvent[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-operations.d.ts","sourceRoot":"","sources":["../../src/operations/scene-operations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AACpE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"scene-operations.d.ts","sourceRoot":"","sources":["../../src/operations/scene-operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,13 +4,21 @@ export function createSceneOperations(options) {
|
|
|
4
4
|
class SceneOperationsFacade {
|
|
5
5
|
#bridge;
|
|
6
6
|
#store;
|
|
7
|
+
planDeletion;
|
|
7
8
|
constructor(options) {
|
|
8
9
|
this.#bridge = options.bridge;
|
|
9
10
|
this.#store = options.store;
|
|
11
|
+
const bridge = options.bridge;
|
|
12
|
+
if (typeof bridge?.planDeletion === 'function') {
|
|
13
|
+
this.planDeletion = (scene, ids) => bridge.planDeletion(scene, ids);
|
|
14
|
+
}
|
|
10
15
|
}
|
|
11
16
|
get hasBridge() {
|
|
12
17
|
return this.#bridge !== undefined;
|
|
13
18
|
}
|
|
19
|
+
get supportsMaterialUpserts() {
|
|
20
|
+
return this.#bridge?.supportsMaterialUpserts === true;
|
|
21
|
+
}
|
|
14
22
|
get hasStore() {
|
|
15
23
|
return this.#store !== undefined;
|
|
16
24
|
}
|
|
@@ -61,7 +69,9 @@ class SceneOperationsFacade {
|
|
|
61
69
|
};
|
|
62
70
|
}
|
|
63
71
|
loadJSON(json) {
|
|
64
|
-
this.requireBridge()
|
|
72
|
+
const bridge = this.requireBridge();
|
|
73
|
+
bridge.loadJSON(json);
|
|
74
|
+
bridge.clearHistory();
|
|
65
75
|
}
|
|
66
76
|
getNode(id) {
|
|
67
77
|
return this.requireBridge().getNode(id);
|
|
@@ -69,6 +79,15 @@ class SceneOperationsFacade {
|
|
|
69
79
|
getNodes() {
|
|
70
80
|
return this.requireBridge().getNodes();
|
|
71
81
|
}
|
|
82
|
+
getCollections() {
|
|
83
|
+
return this.requireBridge().getCollections();
|
|
84
|
+
}
|
|
85
|
+
getMaterials() {
|
|
86
|
+
return this.requireBridge().getMaterials();
|
|
87
|
+
}
|
|
88
|
+
setCollections(collections) {
|
|
89
|
+
this.requireBridge().setCollections(collections);
|
|
90
|
+
}
|
|
72
91
|
getRootNodeIds() {
|
|
73
92
|
return this.requireBridge().getRootNodeIds();
|
|
74
93
|
}
|
|
@@ -96,6 +115,14 @@ class SceneOperationsFacade {
|
|
|
96
115
|
applyPatch(patches) {
|
|
97
116
|
return this.requireBridge().applyPatch(patches);
|
|
98
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Derive construction from the intent the tools just wrote. The hosted
|
|
120
|
+
* bridge reconciles inside every mutation and exposes no hook, so the call
|
|
121
|
+
* is optional: there it is already done by the time a tool builds its result.
|
|
122
|
+
*/
|
|
123
|
+
deriveStructure(levelIds) {
|
|
124
|
+
return this.requireBridge().deriveStructure?.(levelIds) ?? { createdIds: [], deletedIds: [] };
|
|
125
|
+
}
|
|
99
126
|
undo(steps) {
|
|
100
127
|
return this.requireBridge().undo(steps);
|
|
101
128
|
}
|
|
@@ -114,6 +141,9 @@ class SceneOperationsFacade {
|
|
|
114
141
|
clearHistory() {
|
|
115
142
|
this.requireBridge().clearHistory();
|
|
116
143
|
}
|
|
144
|
+
runAsSingleHistoryStep(run) {
|
|
145
|
+
return this.requireBridge().runAsSingleHistoryStep(run);
|
|
146
|
+
}
|
|
117
147
|
async createProject(options) {
|
|
118
148
|
const store = this.requireStore();
|
|
119
149
|
if (!store.createProject) {
|
|
@@ -25,7 +25,7 @@ export function buildFromBriefPrompt(args) {
|
|
|
25
25
|
if (args.constraints && args.constraints.trim().length > 0) {
|
|
26
26
|
parts.push('', '## Constraints', args.constraints.trim());
|
|
27
27
|
}
|
|
28
|
-
parts.push('', '## Task', 'Produce tool calls that realise the brief within the stated constraints.', '1. Bind a scene — `create_project` (new), `list_scenes` then `load_scene` (existing), or `create_house_from_brief` (starter template).', '2. Build the design — `create_story_shell` for exterior shells, `create_room`/`add_door`/`add_window`/`
|
|
28
|
+
parts.push('', '## Task', 'Produce tool calls that realise the brief within the stated constraints.', '1. Bind a scene — `create_project` (new), `list_scenes` then `load_scene` (existing), or `create_house_from_brief` (starter template).', '2. Build the design — `create_story_shell` for exterior shells, `create_room`/`add_door`/`add_window`/`create_stair`/`furnish_room` for interior layout, `create_roof` for roofing, `apply_patch` for exact bulk graph work.', '3. Finish — call `validate_scene`, `verify_scene`, and `get_project_status`, then return the `editorUrl`.', 'Call `save_scene` with `saveMode: "checkpoint"` only when the design reaches a meaningful milestone.');
|
|
29
29
|
return parts.join('\n');
|
|
30
30
|
}
|
|
31
31
|
export function registerFromBrief(server, _bridge) {
|
|
@@ -7,7 +7,7 @@ const PREAMBLE = [
|
|
|
7
7
|
' - Prefer updates over create+delete pairs when a field change will do.',
|
|
8
8
|
' - Do not re-create nodes that already exist.',
|
|
9
9
|
' - Do not touch nodes that are unrelated to the feedback.',
|
|
10
|
-
' - Prefer semantic tools such as create_room, add_door, add_window, furnish_room, and
|
|
10
|
+
' - Prefer semantic tools such as create_room, add_door, add_window, furnish_room, and place_items when they match the request.',
|
|
11
11
|
' - Bundle related mutations into a single `apply_patch` call so they share one undo step.',
|
|
12
12
|
' - For multi-room changes, call verify_scene after the mutation and fix reported issues.',
|
|
13
13
|
SCENE_DESIGN_GUIDANCE,
|
|
@@ -11,13 +11,13 @@ export const SCENE_DESIGN_GUIDANCE = [
|
|
|
11
11
|
'',
|
|
12
12
|
'Preferred phased tool workflow:',
|
|
13
13
|
' - Query first with list_levels, get_level_summary, get_walls, or get_zones when editing an existing scene.',
|
|
14
|
-
' - Create visible massing early:
|
|
14
|
+
' - Create visible massing early: add_level as needed, then create_story_shell once per story.',
|
|
15
15
|
' - For rooms, prefer create_room, then add_door/add_window, then furnish_room.',
|
|
16
|
-
' - For stairs between floors,
|
|
16
|
+
' - For stairs between floors, use create_stair: the stair owns the floor openings it cuts, so moving or deleting it moves or closes them.',
|
|
17
17
|
' - For roofs, prefer create_roof and let it create/use a dedicated roof level above the top occupied story so solo/exploded level views can isolate the roof.',
|
|
18
18
|
' - verify_scene reports both levelCount and occupiedStoryCount. Use occupiedStoryCount when checking whether a one-story/two-story brief was satisfied.',
|
|
19
19
|
' - add_door/add_window use t = 0..1 along a wall: 0 is start, 0.5 is center, 1 is end.',
|
|
20
|
-
' - Use search_assets before
|
|
20
|
+
' - Use search_assets before place_items when placing catalog items.',
|
|
21
21
|
' - Use apply_patch for precise bulk edits that the semantic tools cannot express.',
|
|
22
22
|
' - After each major phase, call get_level_summary or pascal://scene/current/summary so progress is visible and errors are easier to localize.',
|
|
23
23
|
' - After multi-room or full-floor work, call validate_scene and verify_scene, then fix reported issues before finishing.',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-guide.d.ts","sourceRoot":"","sources":["../../src/resources/agent-guide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"agent-guide.d.ts","sourceRoot":"","sources":["../../src/resources/agent-guide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,eAAO,MAAM,WAAW,QAgFZ,CAAA;AAEZ,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAuCpF"}
|