@pascal-app/mcp 1.0.3 → 1.0.5
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/CHANGELOG.md +76 -0
- package/README.md +181 -56
- package/dist/bin/pascal-mcp.js +9 -3
- package/dist/bridge/agent-session.d.ts +18 -0
- package/dist/bridge/agent-session.d.ts.map +1 -0
- package/dist/bridge/agent-session.js +15 -0
- package/dist/bridge/index.d.ts +1 -1
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.d.ts +42 -6
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +232 -45
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/safe-fetch.d.ts +22 -4
- package/dist/lib/safe-fetch.d.ts.map +1 -1
- package/dist/lib/safe-fetch.js +233 -134
- package/dist/operations/scene-operations.d.ts +41 -3
- package/dist/operations/scene-operations.d.ts.map +1 -1
- package/dist/operations/scene-operations.js +42 -1
- package/dist/prompts/from-brief.d.ts +1 -1
- package/dist/prompts/from-brief.d.ts.map +1 -1
- package/dist/prompts/from-brief.js +3 -3
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/iterate-on-feedback.d.ts +1 -1
- package/dist/prompts/iterate-on-feedback.d.ts.map +1 -1
- package/dist/prompts/iterate-on-feedback.js +3 -3
- package/dist/prompts/renovation-from-photos.d.ts +1 -1
- package/dist/prompts/renovation-from-photos.d.ts.map +1 -1
- package/dist/prompts/renovation-from-photos.js +2 -2
- package/dist/prompts/scene-guidance.d.ts.map +1 -1
- package/dist/prompts/scene-guidance.js +13 -6
- package/dist/resources/agent-guide.d.ts +1 -1
- package/dist/resources/agent-guide.d.ts.map +1 -1
- package/dist/resources/agent-guide.js +62 -33
- package/dist/resources/catalog-items.d.ts +5 -6
- package/dist/resources/catalog-items.d.ts.map +1 -1
- package/dist/resources/catalog-items.js +7 -9
- package/dist/resources/constraints.d.ts +1 -1
- package/dist/resources/constraints.d.ts.map +1 -1
- package/dist/resources/constraints.js +1 -1
- 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 +4 -2
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +5 -2
- package/dist/resources/scene-current.d.ts +1 -1
- package/dist/resources/scene-current.d.ts.map +1 -1
- package/dist/resources/scene-summary.d.ts +1 -1
- package/dist/resources/scene-summary.d.ts.map +1 -1
- package/dist/server.d.ts +25 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +71 -25
- 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 +3 -3
- 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 +36 -7
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -0
- package/dist/templates/empty-studio.d.ts +3 -3
- package/dist/templates/empty-studio.d.ts.map +1 -1
- package/dist/templates/garden-house.d.ts +3 -3
- package/dist/templates/garden-house.d.ts.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/two-bedroom.d.ts +3 -3
- package/dist/templates/two-bedroom.d.ts.map +1 -1
- 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 +59 -0
- package/dist/tools/add-object.d.ts +76 -0
- package/dist/tools/add-object.d.ts.map +1 -0
- package/dist/tools/add-object.js +187 -0
- package/dist/tools/annotations.d.ts.map +1 -1
- package/dist/tools/apply-patch.d.ts +9 -2
- package/dist/tools/apply-patch.d.ts.map +1 -1
- package/dist/tools/apply-patch.js +36 -5
- 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 +33 -12
- package/dist/tools/author-scene.d.ts +10 -0
- package/dist/tools/author-scene.d.ts.map +1 -0
- package/dist/tools/author-scene.js +140 -0
- package/dist/tools/check-collisions.d.ts +7 -7
- package/dist/tools/check-collisions.d.ts.map +1 -1
- package/dist/tools/check-collisions.js +7 -8
- 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 +53 -0
- package/dist/tools/construction-tools.d.ts +12 -88
- package/dist/tools/construction-tools.d.ts.map +1 -1
- package/dist/tools/construction-tools.js +122 -381
- package/dist/tools/create-unit.d.ts +5 -3
- package/dist/tools/create-unit.d.ts.map +1 -1
- package/dist/tools/create-unit.js +2 -2
- package/dist/tools/errors.d.ts +15 -4
- package/dist/tools/errors.d.ts.map +1 -1
- package/dist/tools/errors.js +14 -4
- package/dist/tools/export-glb.d.ts +1 -1
- package/dist/tools/export-glb.d.ts.map +1 -1
- package/dist/tools/export-glb.js +2 -2
- package/dist/tools/export-json.d.ts +1 -1
- package/dist/tools/export-json.d.ts.map +1 -1
- package/dist/tools/export-json.js +2 -2
- 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 +2 -1
- package/dist/tools/get-scene.d.ts.map +1 -1
- package/dist/tools/get-scene.js +5 -3
- 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 +40 -0
- package/dist/tools/index.d.ts +12 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +18 -32
- package/dist/tools/level-height-guard.d.ts +2 -0
- package/dist/tools/level-height-guard.d.ts.map +1 -0
- package/dist/tools/level-height-guard.js +11 -0
- package/dist/tools/list-units.d.ts +2 -2
- package/dist/tools/list-units.d.ts.map +1 -1
- package/dist/tools/list-units.js +2 -2
- package/dist/tools/live-sync.d.ts +25 -6
- package/dist/tools/live-sync.d.ts.map +1 -1
- package/dist/tools/live-sync.js +48 -27
- 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 +7 -1
- package/dist/tools/measure.d.ts.map +1 -1
- package/dist/tools/measure.js +38 -70
- 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 +1 -1
- package/dist/tools/normalize-schema-dialect.d.ts.map +1 -1
- package/dist/tools/normalize-schema-dialect.js +22 -3
- package/dist/tools/patch-guards.d.ts +60 -0
- package/dist/tools/patch-guards.d.ts.map +1 -0
- package/dist/tools/patch-guards.js +256 -0
- package/dist/tools/photo-to-scene/index.d.ts +1 -1
- package/dist/tools/photo-to-scene/index.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts +3 -3
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.js +10 -9
- package/dist/tools/project-files.d.ts +4 -0
- package/dist/tools/project-files.d.ts.map +1 -0
- package/dist/tools/project-files.js +30 -0
- package/dist/tools/redo.d.ts +4 -2
- package/dist/tools/redo.d.ts.map +1 -1
- package/dist/tools/redo.js +4 -4
- package/dist/tools/retired-tools.d.ts +10 -0
- package/dist/tools/retired-tools.d.ts.map +1 -0
- package/dist/tools/retired-tools.js +22 -0
- package/dist/tools/roof-level-guard.d.ts +2 -0
- package/dist/tools/roof-level-guard.d.ts.map +1 -0
- package/dist/tools/roof-level-guard.js +22 -0
- package/dist/tools/room-tools.d.ts +36 -98
- package/dist/tools/room-tools.d.ts.map +1 -1
- package/dist/tools/room-tools.js +130 -565
- package/dist/tools/scene-lifecycle/create-project.d.ts +1 -1
- package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/create-project.js +31 -6
- package/dist/tools/scene-lifecycle/delete-scene.d.ts +3 -1
- package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/delete-scene.js +10 -9
- package/dist/tools/scene-lifecycle/get-project-status.d.ts +1 -1
- package/dist/tools/scene-lifecycle/get-project-status.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/get-project-status.js +6 -6
- package/dist/tools/scene-lifecycle/index.d.ts +1 -1
- package/dist/tools/scene-lifecycle/index.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.d.ts +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.js +4 -4
- package/dist/tools/scene-lifecycle/load-scene.d.ts +2 -2
- package/dist/tools/scene-lifecycle/load-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/load-scene.js +5 -5
- package/dist/tools/scene-lifecycle/metadata.d.ts +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.d.ts +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.js +6 -6
- package/dist/tools/scene-lifecycle/save-scene.d.ts +6 -3
- package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/save-scene.js +124 -16
- package/dist/tools/scene-lifecycle/test-utils.d.ts +3 -3
- 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 +4 -2
- package/dist/tools/set-unit-members.d.ts.map +1 -1
- package/dist/tools/set-unit-members.js +6 -6
- package/dist/tools/set-zone.d.ts +4 -2
- package/dist/tools/set-zone.d.ts.map +1 -1
- package/dist/tools/set-zone.js +30 -18
- package/dist/tools/shared-tools.d.ts +40 -0
- package/dist/tools/shared-tools.d.ts.map +1 -0
- package/dist/tools/shared-tools.js +574 -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/templates/create-from-template.d.ts +2 -2
- package/dist/tools/templates/create-from-template.d.ts.map +1 -1
- package/dist/tools/templates/create-from-template.js +6 -6
- package/dist/tools/templates/create-house-from-brief.d.ts +1 -1
- package/dist/tools/templates/create-house-from-brief.d.ts.map +1 -1
- package/dist/tools/templates/create-house-from-brief.js +6 -6
- package/dist/tools/templates/index.d.ts +1 -1
- package/dist/tools/templates/index.d.ts.map +1 -1
- package/dist/tools/templates/list-templates.d.ts +1 -1
- package/dist/tools/templates/list-templates.d.ts.map +1 -1
- package/dist/tools/templates/list-templates.js +2 -2
- package/dist/tools/undo.d.ts +4 -2
- package/dist/tools/undo.d.ts.map +1 -1
- package/dist/tools/undo.js +4 -4
- package/dist/tools/unit-members.d.ts.map +1 -1
- package/dist/tools/unit-members.js +6 -6
- package/dist/tools/variants/generate-variants.d.ts +6 -6
- package/dist/tools/variants/generate-variants.d.ts.map +1 -1
- package/dist/tools/variants/generate-variants.js +6 -6
- package/dist/tools/variants/index.d.ts +1 -1
- package/dist/tools/variants/index.d.ts.map +1 -1
- package/dist/tools/variants/mutations.js +1 -1
- package/dist/tools/view-scene.d.ts +44 -0
- package/dist/tools/view-scene.d.ts.map +1 -0
- package/dist/tools/view-scene.js +75 -0
- package/dist/tools/vision/analyze-floorplan-image.d.ts +1 -1
- package/dist/tools/vision/analyze-floorplan-image.d.ts.map +1 -1
- package/dist/tools/vision/analyze-floorplan-image.js +7 -7
- package/dist/tools/vision/analyze-room-photo.d.ts +1 -1
- package/dist/tools/vision/analyze-room-photo.d.ts.map +1 -1
- package/dist/tools/vision/analyze-room-photo.js +7 -7
- package/dist/tools/vision/index.d.ts +1 -1
- package/dist/tools/vision/index.d.ts.map +1 -1
- package/dist/transports/http.d.ts +8 -2
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +9 -3
- package/dist/transports/stdio.d.ts +15 -9
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +18 -11
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +17 -3
- package/examples/coordinate-conventions-demo.md +12 -13
- package/examples/embed-in-agent.ts +6 -7
- package/examples/generate-apartment.md +13 -15
- package/examples/photo-to-scene.md +3 -4
- package/examples/renovate-from-photos.md +6 -9
- package/package.json +19 -9
- 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/describe-node.d.ts +0 -17
- package/dist/tools/describe-node.d.ts.map +0 -1
- package/dist/tools/describe-node.js +0 -97
- 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/find-nodes.d.ts +0 -32
- package/dist/tools/find-nodes.d.ts.map +0 -1
- package/dist/tools/find-nodes.js +0 -107
- 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
- package/dist/tools/validate-scene.d.ts +0 -19
- package/dist/tools/validate-scene.d.ts.map +0 -1
- package/dist/tools/validate-scene.js +0 -28
package/dist/tools/room-tools.js
CHANGED
|
@@ -1,591 +1,156 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isScriptedNode, scriptedObjectMeta } from '@pascal-app/core';
|
|
2
|
+
import { achievedChanges, addWallOpening, editedScriptLanguage, editedScriptParams, rebuiltOpeningResult, rescriptOpening, } from '@pascal-app/core/agent-operations';
|
|
3
|
+
import { addDoorOutput as addDoorResult, addDoorTool, addWindowOutput as addWindowResult, addWindowTool, isAgentRefusal, } from '@pascal-app/core/agent-tools';
|
|
4
|
+
import { GeometryReuseFields } from '@pascal-app/core/schema';
|
|
2
5
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { NodeIdSchema, Vec2Schema } from './schemas.js';
|
|
12
|
-
const ROOM_TYPES = [
|
|
13
|
-
'bedroom',
|
|
14
|
-
'kitchen',
|
|
15
|
-
'bathroom',
|
|
16
|
-
'living',
|
|
17
|
-
'dining',
|
|
18
|
-
'hallway',
|
|
19
|
-
'entry',
|
|
20
|
-
'laundry',
|
|
21
|
-
'storage',
|
|
22
|
-
];
|
|
23
|
-
export const searchAssetsInput = {
|
|
24
|
-
query: z.string().min(1),
|
|
25
|
-
category: z.string().optional(),
|
|
26
|
-
};
|
|
27
|
-
export const searchAssetsOutput = {
|
|
28
|
-
results: z.array(z.record(z.string(), z.unknown())),
|
|
29
|
-
total: z.number(),
|
|
30
|
-
};
|
|
31
|
-
export const createRoomInput = {
|
|
32
|
-
levelId: NodeIdSchema,
|
|
33
|
-
name: z.string().min(1),
|
|
34
|
-
polygon: z.array(Vec2Schema).min(3),
|
|
35
|
-
color: z.string().optional(),
|
|
36
|
-
wallHeight: measurement('length', 'm', {
|
|
37
|
-
positive: true,
|
|
38
|
-
description: 'Wall height.',
|
|
39
|
-
}).optional(),
|
|
40
|
-
wallThickness: measurement('length', 'm', {
|
|
41
|
-
positive: true,
|
|
42
|
-
description: 'Wall thickness.',
|
|
43
|
-
}).optional(),
|
|
44
|
-
};
|
|
45
|
-
export const createRoomOutput = {
|
|
46
|
-
zoneId: z.string(),
|
|
47
|
-
slabId: z.string(),
|
|
48
|
-
ceilingId: z.string(),
|
|
49
|
-
wallIds: z.array(z.string()),
|
|
50
|
-
areaSqMeters: z.number(),
|
|
51
|
-
...liveSyncOutput,
|
|
52
|
-
};
|
|
53
|
-
export const addDoorInput = {
|
|
54
|
-
wallId: NodeIdSchema,
|
|
55
|
-
t: z.number().min(0).max(1).optional(),
|
|
56
|
-
position: z.number().min(0).max(1).optional(),
|
|
57
|
-
width: measurement('length', 'm', { positive: true, description: 'Door width.' }).optional(),
|
|
58
|
-
height: measurement('length', 'm', { positive: true, description: 'Door height.' }).optional(),
|
|
59
|
-
hingesSide: z.enum(['left', 'right']).optional(),
|
|
60
|
-
swingDirection: z.enum(['inward', 'outward']).optional(),
|
|
61
|
-
};
|
|
62
|
-
export const addDoorOutput = {
|
|
63
|
-
doorId: z.string(),
|
|
64
|
-
localX: z.number(),
|
|
65
|
-
t: z.number(),
|
|
66
|
-
position: z.number(),
|
|
67
|
-
wallLength: z.number(),
|
|
68
|
-
clamped: z.boolean(),
|
|
69
|
-
coordinateSystem: z.literal('wall-local-meters'),
|
|
70
|
-
...liveSyncOutput,
|
|
71
|
-
};
|
|
72
|
-
export const addWindowInput = {
|
|
73
|
-
wallId: NodeIdSchema,
|
|
74
|
-
t: z.number().min(0).max(1).optional(),
|
|
75
|
-
position: z.number().min(0).max(1).optional(),
|
|
76
|
-
width: measurement('length', 'm', { positive: true, description: 'Window width.' }).optional(),
|
|
77
|
-
height: measurement('length', 'm', { positive: true, description: 'Window height.' }).optional(),
|
|
78
|
-
sillHeight: measurement('length', 'm', {
|
|
79
|
-
min: 0,
|
|
80
|
-
description: 'Sill height above floor.',
|
|
81
|
-
}).optional(),
|
|
82
|
-
};
|
|
83
|
-
export const addWindowOutput = {
|
|
84
|
-
windowId: z.string(),
|
|
85
|
-
localX: z.number(),
|
|
86
|
-
t: z.number(),
|
|
87
|
-
position: z.number(),
|
|
88
|
-
wallLength: z.number(),
|
|
89
|
-
clamped: z.boolean(),
|
|
90
|
-
coordinateSystem: z.literal('wall-local-meters'),
|
|
91
|
-
sillHeight: z.number(),
|
|
92
|
-
...liveSyncOutput,
|
|
93
|
-
};
|
|
94
|
-
export const furnishRoomInput = {
|
|
95
|
-
levelId: NodeIdSchema.optional(),
|
|
96
|
-
zoneId: NodeIdSchema.optional(),
|
|
97
|
-
roomType: z.enum(ROOM_TYPES),
|
|
98
|
-
polygon: z.array(Vec2Schema).min(3).optional(),
|
|
99
|
-
doorWallIndex: z.number().int().min(0).optional(),
|
|
100
|
-
};
|
|
101
|
-
export const furnishRoomOutput = {
|
|
102
|
-
placed: z.number(),
|
|
103
|
-
itemIds: z.array(z.string()),
|
|
104
|
-
skipped: z.array(z.string()),
|
|
105
|
-
...liveSyncOutput,
|
|
106
|
-
};
|
|
6
|
+
import { compileAndStore, readScript } from './add-object.js';
|
|
7
|
+
import { ADDITIVE_TOOL_ANNOTATIONS } from './annotations.js';
|
|
8
|
+
import { refusalResult, toolError } from './errors.js';
|
|
9
|
+
import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
|
|
10
|
+
import { toPatches } from './shared-tools.js';
|
|
11
|
+
/** The contract's one result (core `addWallOpening`), and the live sync's note. */
|
|
12
|
+
export const addDoorOutput = { ...addDoorResult, ...liveSyncOutput };
|
|
13
|
+
export const addWindowOutput = { ...addWindowResult, ...liveSyncOutput };
|
|
107
14
|
function textResult(payload) {
|
|
108
15
|
return {
|
|
109
16
|
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
110
17
|
structuredContent: payload,
|
|
111
18
|
};
|
|
112
19
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
20
|
+
/**
|
|
21
|
+
* `add_door` / `add_window` with a nodeId: rebuild that opening from new code,
|
|
22
|
+
* or its stored script with new params, through the shared operation.
|
|
23
|
+
*/
|
|
24
|
+
async function rebuildOpening(kind, bridge, host, input) {
|
|
25
|
+
if (!host)
|
|
26
|
+
return toolError('This Pascal server cannot run geometry scripts.', {
|
|
27
|
+
code: 'scripts_unavailable',
|
|
28
|
+
});
|
|
29
|
+
const scene = bridge.getActiveScene();
|
|
30
|
+
if (!scene)
|
|
31
|
+
return toolError('Open or save a scene first.', { code: 'no_active_scene' });
|
|
32
|
+
const nodes = bridge.getNodes();
|
|
33
|
+
let outcome;
|
|
34
|
+
try {
|
|
35
|
+
const code = input.code ?? (await readScript(host, scene.id, bridge, input.nodeId));
|
|
36
|
+
const previous = nodes[input.nodeId];
|
|
37
|
+
const language = editedScriptLanguage(previous, input);
|
|
38
|
+
const params = editedScriptParams(previous, input.params);
|
|
39
|
+
const compiled = await compileAndStore(host, scene.id, { code, language, params }, kind, {
|
|
40
|
+
nodeId: input.nodeId,
|
|
41
|
+
metadata: {
|
|
42
|
+
...(isScriptedNode(previous) ? scriptedObjectMeta(previous) : {}),
|
|
43
|
+
...GeometryReuseFields.parse(input),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
outcome = rescriptOpening(nodes, { ...input, compiled }, { activeLevelId: bridge.activeLevelId?.() ?? null });
|
|
125
47
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (wall.type !== 'wall') {
|
|
133
|
-
throwMcpError(ErrorCode.InvalidParams, `Node ${wallId} is a ${wall.type}, expected wall`);
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (isAgentRefusal(error))
|
|
50
|
+
return refusalResult(error);
|
|
51
|
+
return toolError(error instanceof Error ? error.message : String(error), {
|
|
52
|
+
code: 'script_failed',
|
|
53
|
+
});
|
|
134
54
|
}
|
|
135
|
-
|
|
55
|
+
const achieved = outcome.changes
|
|
56
|
+
? achievedChanges(nodes, outcome.changes)
|
|
57
|
+
: achievedChanges(nodes, {});
|
|
58
|
+
if (outcome.changes)
|
|
59
|
+
bridge.applyPatch(toPatches(outcome.changes));
|
|
60
|
+
const persistence = await publishLiveSceneSnapshot(bridge, `add_${kind}`);
|
|
61
|
+
return textResult({
|
|
62
|
+
...rebuiltOpeningResult(bridge.getNodes(), input.nodeId, outcome.result, achieved),
|
|
63
|
+
...persistencePayload(persistence),
|
|
64
|
+
});
|
|
136
65
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
66
|
+
/** A door or window passed `code`: compiled and stored the way add_object does, or the tool's error. */
|
|
67
|
+
async function compileOpeningScript(kind, bridge, host, input) {
|
|
68
|
+
if (!input.code)
|
|
69
|
+
return {};
|
|
70
|
+
if (!host)
|
|
71
|
+
return {
|
|
72
|
+
error: toolError('This Pascal server cannot run geometry scripts; use the fields.', {
|
|
73
|
+
code: 'scripts_unavailable',
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
const scene = bridge.getActiveScene();
|
|
77
|
+
if (!scene)
|
|
78
|
+
return { error: toolError('Open or save a scene first.', { code: 'no_active_scene' }) };
|
|
79
|
+
try {
|
|
80
|
+
return {
|
|
81
|
+
script: await compileAndStore(host, scene.id, { code: input.code, language: input.language ?? 'three', params: input.params }, kind, { metadata: input }),
|
|
82
|
+
};
|
|
152
83
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
throwMcpError(ErrorCode.InvalidParams, `${toolName} requires t or position in the 0..1 range`);
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (isAgentRefusal(error))
|
|
86
|
+
return { error: refusalResult(error) };
|
|
87
|
+
return {
|
|
88
|
+
error: toolError(error instanceof Error ? error.message : String(error), {
|
|
89
|
+
code: 'script_failed',
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
162
92
|
}
|
|
163
|
-
return resolved;
|
|
164
|
-
}
|
|
165
|
-
function makeItemAsset(asset) {
|
|
166
|
-
return {
|
|
167
|
-
id: asset.id,
|
|
168
|
-
name: asset.name,
|
|
169
|
-
category: asset.category,
|
|
170
|
-
thumbnail: asset.thumbnail ?? '',
|
|
171
|
-
src: asset.src,
|
|
172
|
-
dimensions: asset.dimensions ?? [1, 1, 1],
|
|
173
|
-
offset: asset.offset ?? [0, 0, 0],
|
|
174
|
-
rotation: asset.rotation ?? [0, 0, 0],
|
|
175
|
-
scale: asset.scale ?? [1, 1, 1],
|
|
176
|
-
...(asset.attachTo ? { attachTo: asset.attachTo } : {}),
|
|
177
|
-
...(asset.tags ? { tags: asset.tags } : {}),
|
|
178
|
-
...(asset.surface ? { surface: asset.surface } : {}),
|
|
179
|
-
...(asset.interactive ? { interactive: asset.interactive } : {}),
|
|
180
|
-
};
|
|
181
93
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const backPos = (inset, lateral = 0) => [
|
|
202
|
-
backMidX + inX * inset + ax * lateral,
|
|
203
|
-
backMidZ + inZ * inset + az * lateral,
|
|
204
|
-
];
|
|
205
|
-
const sideIdx = (doorWallIndex + 1) % n;
|
|
206
|
-
const sideStart = polygon[sideIdx];
|
|
207
|
-
const sideEnd = polygon[(sideIdx + 1) % n];
|
|
208
|
-
const sideMidX = (sideStart[0] + sideEnd[0]) / 2;
|
|
209
|
-
const sideMidZ = (sideStart[1] + sideEnd[1]) / 2;
|
|
210
|
-
const sideInX = bounds.centerX - sideMidX;
|
|
211
|
-
const sideInZ = bounds.centerZ - sideMidZ;
|
|
212
|
-
const sideInLen = Math.sqrt(sideInX * sideInX + sideInZ * sideInZ) || 1;
|
|
213
|
-
const snX = sideInX / sideInLen;
|
|
214
|
-
const snZ = sideInZ / sideInLen;
|
|
215
|
-
const sideRot = (Math.atan2(snX, snZ) * 180) / Math.PI;
|
|
216
|
-
const sideAlongX = sideEnd[0] - sideStart[0];
|
|
217
|
-
const sideAlongZ = sideEnd[1] - sideStart[1];
|
|
218
|
-
const sideAlongLen = Math.sqrt(sideAlongX * sideAlongX + sideAlongZ * sideAlongZ) || 1;
|
|
219
|
-
const sax = sideAlongX / sideAlongLen;
|
|
220
|
-
const saz = sideAlongZ / sideAlongLen;
|
|
221
|
-
const sidePos = (inset, lateral = 0) => [
|
|
222
|
-
sideMidX + snX * inset + sax * lateral,
|
|
223
|
-
sideMidZ + snZ * inset + saz * lateral,
|
|
224
|
-
];
|
|
225
|
-
const placements = [];
|
|
226
|
-
const area = polygonArea(polygon);
|
|
227
|
-
const addBack = (assetId, inset, lateral = 0, rotationDeg = facingRot) => {
|
|
228
|
-
const [x, z] = backPos(inset, lateral);
|
|
229
|
-
placements.push({
|
|
230
|
-
assetId,
|
|
231
|
-
x,
|
|
232
|
-
z,
|
|
233
|
-
rotationDeg,
|
|
234
|
-
along: { x: ax, z: az },
|
|
235
|
-
inward: { x: inX, z: inZ },
|
|
236
|
-
});
|
|
237
|
-
};
|
|
238
|
-
const addSide = (assetId, inset, lateral = 0, rotationDeg = sideRot) => {
|
|
239
|
-
const [x, z] = sidePos(inset, lateral);
|
|
240
|
-
placements.push({
|
|
241
|
-
assetId,
|
|
242
|
-
x,
|
|
243
|
-
z,
|
|
244
|
-
rotationDeg,
|
|
245
|
-
along: { x: sax, z: saz },
|
|
246
|
-
inward: { x: snX, z: snZ },
|
|
247
|
-
});
|
|
248
|
-
};
|
|
249
|
-
switch (roomType) {
|
|
250
|
-
case 'bedroom': {
|
|
251
|
-
const bedId = Math.max(bounds.width, bounds.depth) >= 3.2 ? 'double-bed' : 'single-bed';
|
|
252
|
-
const bed = findCatalogItem(bedId);
|
|
253
|
-
const [bedW = 2, , bedD = 2.5] = bed?.dimensions ?? [];
|
|
254
|
-
addBack(bedId, bedD / 2 + 0.1);
|
|
255
|
-
if (alongLen > bedW + 1.1) {
|
|
256
|
-
addBack('bedside-table', 0.35, -(bedW / 2 + 0.35));
|
|
257
|
-
addBack('bedside-table', 0.35, bedW / 2 + 0.35);
|
|
258
|
-
}
|
|
259
|
-
if (area >= 10)
|
|
260
|
-
addSide('dresser', 0.55, sideAlongLen * 0.22);
|
|
261
|
-
if (area >= 13)
|
|
262
|
-
addSide('closet', 0.6, -sideAlongLen * 0.22);
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
case 'kitchen':
|
|
266
|
-
addBack(alongLen >= 2.6 ? 'kitchen' : 'kitchen-counter', 0.55);
|
|
267
|
-
if (alongLen >= 3.5)
|
|
268
|
-
addBack('stove', 0.55, alongLen / 2 - 0.65);
|
|
269
|
-
addSide('fridge', 0.6, sideAlongLen * 0.25);
|
|
270
|
-
break;
|
|
271
|
-
case 'bathroom':
|
|
272
|
-
addBack('toilet', 0.55, alongLen * 0.25);
|
|
273
|
-
addBack('bathroom-sink', 0.8, -alongLen * 0.2);
|
|
274
|
-
if (area >= 6.5)
|
|
275
|
-
addSide('bathtub', 0.85);
|
|
276
|
-
else
|
|
277
|
-
placements.push({ assetId: 'shower-square', x: bounds.centerX, z: bounds.centerZ });
|
|
278
|
-
break;
|
|
279
|
-
case 'living': {
|
|
280
|
-
addBack('sofa', 0.9);
|
|
281
|
-
addBack('coffee-table', 2.1);
|
|
282
|
-
addSide('livingroom-chair', 0.85, -sideAlongLen * 0.18);
|
|
283
|
-
// TV faces the sofa from the door wall: use door-wall axes so smart
|
|
284
|
-
// re-place nudges into the room (along wall / inward), not world X/Z.
|
|
285
|
-
const doorIdx = doorWallIndex % n;
|
|
286
|
-
const doorStart = polygon[doorIdx];
|
|
287
|
-
const doorEnd = polygon[(doorIdx + 1) % n];
|
|
288
|
-
const doorMidX = (doorStart[0] + doorEnd[0]) / 2;
|
|
289
|
-
const doorMidZ = (doorStart[1] + doorEnd[1]) / 2;
|
|
290
|
-
// Door-wall inward is opposite of "back wall" inward (into room from door).
|
|
291
|
-
const doorInX = -inX;
|
|
292
|
-
const doorInZ = -inZ;
|
|
293
|
-
placements.push({
|
|
294
|
-
assetId: 'tv-stand',
|
|
295
|
-
x: doorMidX + doorInX * 0.35,
|
|
296
|
-
z: doorMidZ + doorInZ * 0.35,
|
|
297
|
-
rotationDeg: facingRot + 180,
|
|
298
|
-
along: { x: ax, z: az },
|
|
299
|
-
inward: { x: doorInX, z: doorInZ },
|
|
94
|
+
export function registerAddDoor(server, bridge, geometryScripts) {
|
|
95
|
+
server.registerTool(addDoorTool.name, {
|
|
96
|
+
title: addDoorTool.title,
|
|
97
|
+
description: addDoorTool.description,
|
|
98
|
+
inputSchema: z.object(addDoorTool.input),
|
|
99
|
+
outputSchema: z.object(addDoorOutput),
|
|
100
|
+
annotations: ADDITIVE_TOOL_ANNOTATIONS,
|
|
101
|
+
}, async (input) => {
|
|
102
|
+
if (input.nodeId)
|
|
103
|
+
return rebuildOpening('door', bridge, geometryScripts, { ...input, nodeId: input.nodeId });
|
|
104
|
+
const compiled = await compileOpeningScript('door', bridge, geometryScripts, input);
|
|
105
|
+
if ('error' in compiled)
|
|
106
|
+
return compiled.error;
|
|
107
|
+
let opening;
|
|
108
|
+
try {
|
|
109
|
+
opening = addWallOpening(bridge.getNodes(), {
|
|
110
|
+
kind: 'door',
|
|
111
|
+
...input,
|
|
112
|
+
compiled: compiled.script,
|
|
300
113
|
});
|
|
301
|
-
break;
|
|
302
114
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
placements.push({ assetId: 'dining-chair', x: bounds.centerX, z: bounds.centerZ - 0.85 });
|
|
306
|
-
placements.push({
|
|
307
|
-
assetId: 'dining-chair',
|
|
308
|
-
x: bounds.centerX,
|
|
309
|
-
z: bounds.centerZ + 0.85,
|
|
310
|
-
rotationDeg: 180,
|
|
311
|
-
});
|
|
312
|
-
if (Math.min(bounds.width, bounds.depth) >= 3) {
|
|
313
|
-
placements.push({
|
|
314
|
-
assetId: 'dining-chair',
|
|
315
|
-
x: bounds.centerX - 0.85,
|
|
316
|
-
z: bounds.centerZ,
|
|
317
|
-
rotationDeg: 270,
|
|
318
|
-
});
|
|
319
|
-
placements.push({
|
|
320
|
-
assetId: 'dining-chair',
|
|
321
|
-
x: bounds.centerX + 0.85,
|
|
322
|
-
z: bounds.centerZ,
|
|
323
|
-
rotationDeg: 90,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
break;
|
|
327
|
-
case 'laundry':
|
|
328
|
-
addBack('washing-machine', 0.6, -0.55);
|
|
329
|
-
addBack('drying-rack', 0.65, 0.65);
|
|
330
|
-
break;
|
|
331
|
-
case 'entry':
|
|
332
|
-
case 'hallway':
|
|
333
|
-
if (Math.min(bounds.width, bounds.depth) >= 1.4)
|
|
334
|
-
addSide('coat-rack', 0.35);
|
|
335
|
-
break;
|
|
336
|
-
case 'storage':
|
|
337
|
-
addBack('closet', 0.6);
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
return { placements, bounds };
|
|
341
|
-
}
|
|
342
|
-
export function registerSearchAssets(server) {
|
|
343
|
-
server.registerTool('search_assets', {
|
|
344
|
-
title: 'Search assets',
|
|
345
|
-
description: 'Search the built-in MCP item catalog by keyword. Call before place_item when you need a valid catalogItemId.',
|
|
346
|
-
inputSchema: searchAssetsInput,
|
|
347
|
-
outputSchema: searchAssetsOutput,
|
|
348
|
-
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
349
|
-
}, async ({ query, category }) => {
|
|
350
|
-
const results = searchCatalogItems({ query, category }).map((item) => ({
|
|
351
|
-
id: item.id,
|
|
352
|
-
name: item.name,
|
|
353
|
-
category: item.category,
|
|
354
|
-
tags: item.tags ?? [],
|
|
355
|
-
dimensions: item.dimensions,
|
|
356
|
-
attachTo: item.attachTo ?? null,
|
|
357
|
-
}));
|
|
358
|
-
return textResult({ results, total: results.length });
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
export function registerCreateRoom(server, bridge) {
|
|
362
|
-
server.registerTool('create_room', {
|
|
363
|
-
title: 'Create room',
|
|
364
|
-
description: 'Create a room on a level: zone, slab, ceiling, and one wall per polygon edge. Returns wallIds in polygon edge order.',
|
|
365
|
-
inputSchema: createRoomInput,
|
|
366
|
-
outputSchema: createRoomOutput,
|
|
367
|
-
annotations: ADDITIVE_TOOL_ANNOTATIONS,
|
|
368
|
-
}, async ({ levelId, name, polygon, color, wallHeight, wallThickness }) => {
|
|
369
|
-
assertLevel(bridge, levelId);
|
|
370
|
-
const points = polygon;
|
|
371
|
-
const zone = ZoneNode.parse({
|
|
372
|
-
name,
|
|
373
|
-
polygon: points,
|
|
374
|
-
color: color ?? '#60a5fa',
|
|
375
|
-
metadata: { mcpTool: 'create_room' },
|
|
376
|
-
});
|
|
377
|
-
const slab = SlabNode.parse({ polygon: points, metadata: { mcpTool: 'create_room' } });
|
|
378
|
-
const ceiling = CeilingNode.parse({ polygon: points, metadata: { mcpTool: 'create_room' } });
|
|
379
|
-
const walls = points.map((start, index) => WallNode.parse({
|
|
380
|
-
name: `${name} wall ${index + 1}`,
|
|
381
|
-
start,
|
|
382
|
-
end: points[(index + 1) % points.length],
|
|
383
|
-
...(wallHeight !== undefined ? { height: wallHeight } : {}),
|
|
384
|
-
...(wallThickness !== undefined ? { thickness: wallThickness } : {}),
|
|
385
|
-
metadata: { mcpTool: 'create_room', roomName: name, edgeIndex: index },
|
|
386
|
-
}));
|
|
387
|
-
bridge.applyPatch([
|
|
388
|
-
{ op: 'create', node: zone, parentId: levelId },
|
|
389
|
-
{ op: 'create', node: slab, parentId: levelId },
|
|
390
|
-
{ op: 'create', node: ceiling, parentId: levelId },
|
|
391
|
-
...walls.map((wall) => ({
|
|
392
|
-
op: 'create',
|
|
393
|
-
node: wall,
|
|
394
|
-
parentId: levelId,
|
|
395
|
-
})),
|
|
396
|
-
]);
|
|
397
|
-
const persistence = await publishLiveSceneSnapshot(bridge, 'create_room');
|
|
398
|
-
return textResult({
|
|
399
|
-
zoneId: zone.id,
|
|
400
|
-
slabId: slab.id,
|
|
401
|
-
ceilingId: ceiling.id,
|
|
402
|
-
wallIds: walls.map((wall) => wall.id),
|
|
403
|
-
areaSqMeters: Math.round(polygonArea(points) * 100) / 100,
|
|
404
|
-
...persistencePayload(persistence),
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
export function registerAddDoor(server, bridge) {
|
|
409
|
-
server.registerTool('add_door', {
|
|
410
|
-
title: 'Add door',
|
|
411
|
-
description: 'Add a door to an existing wall. t/position is 0..1 along the wall: 0 = start, 0.5 = center, 1 = end.',
|
|
412
|
-
inputSchema: addDoorInput,
|
|
413
|
-
outputSchema: addDoorOutput,
|
|
414
|
-
annotations: ADDITIVE_TOOL_ANNOTATIONS,
|
|
415
|
-
}, async ({ wallId, t, position, width = 0.9, height = 2.1, hingesSide, swingDirection }) => {
|
|
416
|
-
const wall = assertWall(bridge, wallId);
|
|
417
|
-
const length = wallLength(wall);
|
|
418
|
-
if (length < width) {
|
|
419
|
-
throwMcpError(ErrorCode.InvalidParams, `Wall ${wallId} is ${length.toFixed(2)}m long, too short for a ${width.toFixed(2)}m door`);
|
|
115
|
+
catch (error) {
|
|
116
|
+
return refusalResult(error);
|
|
420
117
|
}
|
|
421
|
-
|
|
422
|
-
const localX = wallLocalXFromT(wall, wallT, width);
|
|
423
|
-
const door = DoorNode.parse({
|
|
424
|
-
wallId,
|
|
425
|
-
parentId: wallId,
|
|
426
|
-
position: [localX, height / 2, 0],
|
|
427
|
-
width,
|
|
428
|
-
height,
|
|
429
|
-
...(hingesSide ? { hingesSide } : {}),
|
|
430
|
-
...(swingDirection ? { swingDirection } : {}),
|
|
431
|
-
});
|
|
432
|
-
const id = bridge.createNode(door, wallId);
|
|
118
|
+
bridge.applyPatch(toPatches(opening.changes));
|
|
433
119
|
const persistence = await publishLiveSceneSnapshot(bridge, 'add_door');
|
|
434
|
-
return textResult({
|
|
435
|
-
doorId: id,
|
|
436
|
-
localX,
|
|
437
|
-
t: wallT,
|
|
438
|
-
position: wallT,
|
|
439
|
-
wallLength: length,
|
|
440
|
-
clamped: Math.abs(localX - wallT * length) > 1e-9,
|
|
441
|
-
coordinateSystem: 'wall-local-meters',
|
|
442
|
-
...persistencePayload(persistence),
|
|
443
|
-
});
|
|
120
|
+
return textResult({ ...opening.result, ...persistencePayload(persistence) });
|
|
444
121
|
});
|
|
445
122
|
}
|
|
446
|
-
export function registerAddWindow(server, bridge) {
|
|
447
|
-
server.registerTool(
|
|
448
|
-
title:
|
|
449
|
-
description:
|
|
450
|
-
inputSchema:
|
|
451
|
-
outputSchema: addWindowOutput,
|
|
123
|
+
export function registerAddWindow(server, bridge, geometryScripts) {
|
|
124
|
+
server.registerTool(addWindowTool.name, {
|
|
125
|
+
title: addWindowTool.title,
|
|
126
|
+
description: addWindowTool.description,
|
|
127
|
+
inputSchema: z.object(addWindowTool.input),
|
|
128
|
+
outputSchema: z.object(addWindowOutput),
|
|
452
129
|
annotations: ADDITIVE_TOOL_ANNOTATIONS,
|
|
453
|
-
}, async (
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
width,
|
|
466
|
-
height,
|
|
467
|
-
});
|
|
468
|
-
const id = bridge.createNode(windowNode, wallId);
|
|
469
|
-
const persistence = await publishLiveSceneSnapshot(bridge, 'add_window');
|
|
470
|
-
return textResult({
|
|
471
|
-
windowId: id,
|
|
472
|
-
localX,
|
|
473
|
-
t: wallT,
|
|
474
|
-
position: wallT,
|
|
475
|
-
wallLength: length,
|
|
476
|
-
clamped: Math.abs(localX - wallT * length) > 1e-9,
|
|
477
|
-
coordinateSystem: 'wall-local-meters',
|
|
478
|
-
sillHeight,
|
|
479
|
-
...persistencePayload(persistence),
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
export function registerFurnishRoom(server, bridge) {
|
|
484
|
-
server.registerTool('furnish_room', {
|
|
485
|
-
title: 'Furnish room',
|
|
486
|
-
description: 'Place furniture for a room type (levelId+polygon or zoneId). Skips or nudges poses that block door clear zones or overlap existing items (rotation-aware). Parent floor items to the level.',
|
|
487
|
-
inputSchema: furnishRoomInput,
|
|
488
|
-
outputSchema: furnishRoomOutput,
|
|
489
|
-
annotations: ADDITIVE_TOOL_ANNOTATIONS,
|
|
490
|
-
}, async ({ levelId, zoneId, roomType, polygon, doorWallIndex }) => {
|
|
491
|
-
const room = inferRoomGeometry(bridge, levelId, polygon, zoneId);
|
|
492
|
-
assertLevel(bridge, room.levelId);
|
|
493
|
-
const points = room.polygon;
|
|
494
|
-
const resolvedDoorWallIndex = doorWallIndex ?? 0;
|
|
495
|
-
const { placements, bounds } = buildRoomPlacements(roomType, points, resolvedDoorWallIndex);
|
|
496
|
-
const skipped = [];
|
|
497
|
-
const items = [];
|
|
498
|
-
const allNodes = Object.values(bridge.getNodes());
|
|
499
|
-
// Doors on THIS level only (stacked floors must not interact in plan).
|
|
500
|
-
const existingKeepouts = collectDoorKeepouts(allNodes, { levelId: room.levelId });
|
|
501
|
-
const doorKeepoutAabbs = existingKeepouts.map((k) => k.aabb);
|
|
502
|
-
// Always protect this room's door-wall edge when no keep-out already covers it
|
|
503
|
-
// (other rooms may already have doors elsewhere on the same level).
|
|
504
|
-
const planned = keepoutForPolygonEdge(points, resolvedDoorWallIndex, {
|
|
505
|
-
t: 0.5,
|
|
506
|
-
width: 0.9,
|
|
507
|
-
});
|
|
508
|
-
if (planned &&
|
|
509
|
-
!doorKeepoutAabbs.some((existing) => keepoutCoversPlanned(existing, planned))) {
|
|
510
|
-
doorKeepoutAabbs.push(planned);
|
|
511
|
-
}
|
|
512
|
-
// Existing floor items on this level + footprints we place in this batch.
|
|
513
|
-
const occupied = collectOccupiedFootprints(allNodes, {
|
|
514
|
-
levelId: room.levelId,
|
|
515
|
-
floorOnly: true,
|
|
516
|
-
}).map((f) => f.aabb);
|
|
517
|
-
const roomBounds = {
|
|
518
|
-
minX: bounds.minX,
|
|
519
|
-
maxX: bounds.maxX,
|
|
520
|
-
minZ: bounds.minZ,
|
|
521
|
-
maxZ: bounds.maxZ,
|
|
522
|
-
};
|
|
523
|
-
for (const placement of placements) {
|
|
524
|
-
const asset = findCatalogItem(placement.assetId);
|
|
525
|
-
if (!asset) {
|
|
526
|
-
skipped.push(`${placement.assetId}: asset not found`);
|
|
527
|
-
continue;
|
|
528
|
-
}
|
|
529
|
-
const primary = {
|
|
530
|
-
x: placement.x,
|
|
531
|
-
z: placement.z,
|
|
532
|
-
rotationDeg: placement.rotationDeg ?? 0,
|
|
533
|
-
};
|
|
534
|
-
const resolved = findValidPlacement({
|
|
535
|
-
primary,
|
|
536
|
-
dimensions: asset.dimensions,
|
|
537
|
-
doorKeepouts: doorKeepoutAabbs,
|
|
538
|
-
occupied,
|
|
539
|
-
roomBounds,
|
|
540
|
-
along: placement.along,
|
|
541
|
-
inward: placement.inward,
|
|
130
|
+
}, async (input) => {
|
|
131
|
+
if (input.nodeId)
|
|
132
|
+
return rebuildOpening('window', bridge, geometryScripts, { ...input, nodeId: input.nodeId });
|
|
133
|
+
const compiled = await compileOpeningScript('window', bridge, geometryScripts, input);
|
|
134
|
+
if ('error' in compiled)
|
|
135
|
+
return compiled.error;
|
|
136
|
+
let opening;
|
|
137
|
+
try {
|
|
138
|
+
opening = addWallOpening(bridge.getNodes(), {
|
|
139
|
+
kind: 'window',
|
|
140
|
+
...input,
|
|
141
|
+
compiled: compiled.script,
|
|
542
142
|
});
|
|
543
|
-
if (!resolved.candidate) {
|
|
544
|
-
const reason = resolved.reason === 'blocks_door_clearance'
|
|
545
|
-
? 'blocks door clearance'
|
|
546
|
-
: resolved.reason === 'outside_bounds'
|
|
547
|
-
? 'outside room bounds'
|
|
548
|
-
: 'overlaps another item';
|
|
549
|
-
skipped.push(`${asset.id}: ${reason}`);
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
const { x, z, rotationDeg } = resolved.candidate;
|
|
553
|
-
const rotRad = (rotationDeg * Math.PI) / 180;
|
|
554
|
-
const planAabb = itemPlanAabb([x, 0, z], asset.dimensions, rotRad);
|
|
555
|
-
occupied.push(planAabb);
|
|
556
|
-
items.push(ItemNode.parse({
|
|
557
|
-
name: asset.name,
|
|
558
|
-
position: [x, 0, z],
|
|
559
|
-
rotation: [0, rotRad, 0],
|
|
560
|
-
asset: makeItemAsset(asset),
|
|
561
|
-
metadata: {
|
|
562
|
-
mcpTool: 'furnish_room',
|
|
563
|
-
roomType,
|
|
564
|
-
...(x !== primary.x || z !== primary.z ? { placementAdjusted: true } : {}),
|
|
565
|
-
},
|
|
566
|
-
}));
|
|
567
143
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
bridge.applyPatch(items.map((item) => ({
|
|
571
|
-
op: 'create',
|
|
572
|
-
node: item,
|
|
573
|
-
parentId: room.levelId,
|
|
574
|
-
})));
|
|
575
|
-
persistence = await publishLiveSceneSnapshot(bridge, 'furnish_room');
|
|
144
|
+
catch (error) {
|
|
145
|
+
return refusalResult(error);
|
|
576
146
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
skipped,
|
|
581
|
-
...persistencePayload(persistence),
|
|
582
|
-
});
|
|
147
|
+
bridge.applyPatch(toPatches(opening.changes));
|
|
148
|
+
const persistence = await publishLiveSceneSnapshot(bridge, 'add_window');
|
|
149
|
+
return textResult({ ...opening.result, ...persistencePayload(persistence) });
|
|
583
150
|
});
|
|
584
151
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
registerAddWindow(server, bridge);
|
|
590
|
-
registerFurnishRoom(server, bridge);
|
|
152
|
+
/** add_door and add_window; create_room and furnish_room are shared tools (shared-tools.ts). */
|
|
153
|
+
export function registerRoomTools(server, bridge, geometryScripts) {
|
|
154
|
+
registerAddDoor(server, bridge, geometryScripts);
|
|
155
|
+
registerAddWindow(server, bridge, geometryScripts);
|
|
591
156
|
}
|