@pascal-app/mcp 1.0.3 → 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 +39 -6
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +225 -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/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 +10 -4
- 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
package/dist/tools/room-tools.js
CHANGED
|
@@ -1,591 +1,155 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
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
|
-
};
|
|
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';
|
|
5
|
+
import { compileAndStore, readScript } from './add-object.js';
|
|
6
|
+
import { ADDITIVE_TOOL_ANNOTATIONS } from './annotations.js';
|
|
7
|
+
import { refusalResult, toolError } from './errors.js';
|
|
8
|
+
import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
|
|
9
|
+
import { toPatches } from './shared-tools.js';
|
|
10
|
+
/** The contract's one result (core `addWallOpening`), and the live sync's note. */
|
|
11
|
+
export const addDoorOutput = { ...addDoorResult, ...liveSyncOutput };
|
|
12
|
+
export const addWindowOutput = { ...addWindowResult, ...liveSyncOutput };
|
|
107
13
|
function textResult(payload) {
|
|
108
14
|
return {
|
|
109
15
|
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
110
16
|
structuredContent: payload,
|
|
111
17
|
};
|
|
112
18
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
level.metadata !== null &&
|
|
122
|
-
'role' in level.metadata &&
|
|
123
|
-
level.metadata.role === 'roof') {
|
|
124
|
-
throwMcpError(ErrorCode.InvalidParams, `Roof support level ${levelId} is not an occupied story; create rooms or furnishings on an occupied level instead`);
|
|
125
|
-
}
|
|
126
|
-
return level;
|
|
127
|
-
}
|
|
128
|
-
function assertWall(bridge, wallId) {
|
|
129
|
-
const wall = bridge.getNode(wallId);
|
|
130
|
-
if (!wall)
|
|
131
|
-
throwMcpError(ErrorCode.InvalidParams, `Wall not found: ${wallId}`);
|
|
132
|
-
if (wall.type !== 'wall') {
|
|
133
|
-
throwMcpError(ErrorCode.InvalidParams, `Node ${wallId} is a ${wall.type}, expected wall`);
|
|
134
|
-
}
|
|
135
|
-
return wall;
|
|
136
|
-
}
|
|
137
|
-
function inferRoomGeometry(bridge, levelId, polygon, zoneId) {
|
|
138
|
-
if (levelId && polygon)
|
|
139
|
-
return { levelId, polygon };
|
|
140
|
-
if (!zoneId) {
|
|
141
|
-
throwMcpError(ErrorCode.InvalidParams, 'Provide either levelId + polygon or zoneId so the room can be furnished');
|
|
142
|
-
}
|
|
143
|
-
const zone = bridge.getNode(zoneId);
|
|
144
|
-
if (!zone)
|
|
145
|
-
throwMcpError(ErrorCode.InvalidParams, `Zone not found: ${zoneId}`);
|
|
146
|
-
if (zone.type !== 'zone') {
|
|
147
|
-
throwMcpError(ErrorCode.InvalidParams, `Node ${zoneId} is a ${zone.type}, expected zone`);
|
|
148
|
-
}
|
|
149
|
-
const inferredLevelId = levelId ?? zone.parentId ?? undefined;
|
|
150
|
-
if (!inferredLevelId) {
|
|
151
|
-
throwMcpError(ErrorCode.InvalidParams, `Zone ${zoneId} is missing a parent level`);
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
levelId: inferredLevelId,
|
|
155
|
-
polygon: polygon ?? zone.polygon,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function resolveWallT(toolName, t, position) {
|
|
159
|
-
const resolved = t ?? position;
|
|
160
|
-
if (resolved === undefined) {
|
|
161
|
-
throwMcpError(ErrorCode.InvalidParams, `${toolName} requires t or position in the 0..1 range`);
|
|
162
|
-
}
|
|
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
|
-
}
|
|
182
|
-
function buildRoomPlacements(roomType, polygon, doorWallIndex = 0) {
|
|
183
|
-
const bounds = polygonBounds(polygon);
|
|
184
|
-
const n = polygon.length;
|
|
185
|
-
const backIdx = (doorWallIndex + Math.floor(n / 2)) % n;
|
|
186
|
-
const backStart = polygon[backIdx];
|
|
187
|
-
const backEnd = polygon[(backIdx + 1) % n];
|
|
188
|
-
const backMidX = (backStart[0] + backEnd[0]) / 2;
|
|
189
|
-
const backMidZ = (backStart[1] + backEnd[1]) / 2;
|
|
190
|
-
const inwardX = bounds.centerX - backMidX;
|
|
191
|
-
const inwardZ = bounds.centerZ - backMidZ;
|
|
192
|
-
const inwardLen = Math.sqrt(inwardX * inwardX + inwardZ * inwardZ) || 1;
|
|
193
|
-
const inX = inwardX / inwardLen;
|
|
194
|
-
const inZ = inwardZ / inwardLen;
|
|
195
|
-
const facingRot = (Math.atan2(inX, inZ) * 180) / Math.PI;
|
|
196
|
-
const alongX = backEnd[0] - backStart[0];
|
|
197
|
-
const alongZ = backEnd[1] - backStart[1];
|
|
198
|
-
const alongLen = Math.sqrt(alongX * alongX + alongZ * alongZ) || 1;
|
|
199
|
-
const ax = alongX / alongLen;
|
|
200
|
-
const az = alongZ / alongLen;
|
|
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 },
|
|
19
|
+
/**
|
|
20
|
+
* `add_door` / `add_window` with a nodeId: rebuild that opening from new code,
|
|
21
|
+
* or its stored script with new params, through the shared operation.
|
|
22
|
+
*/
|
|
23
|
+
async function rebuildOpening(kind, bridge, host, input) {
|
|
24
|
+
if (!host)
|
|
25
|
+
return toolError('This Pascal server cannot run geometry scripts.', {
|
|
26
|
+
code: 'scripts_unavailable',
|
|
236
27
|
});
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
28
|
+
const scene = bridge.getActiveScene();
|
|
29
|
+
if (!scene)
|
|
30
|
+
return toolError('Open or save a scene first.', { code: 'no_active_scene' });
|
|
31
|
+
const nodes = bridge.getNodes();
|
|
32
|
+
let outcome;
|
|
33
|
+
try {
|
|
34
|
+
const code = input.code ?? (await readScript(host, scene.id, bridge, input.nodeId));
|
|
35
|
+
const previous = nodes[input.nodeId];
|
|
36
|
+
const language = editedScriptLanguage(previous, input);
|
|
37
|
+
const params = editedScriptParams(previous, input.params);
|
|
38
|
+
const compiled = await compileAndStore(host, scene.id, { code, language, params }, kind, {
|
|
39
|
+
nodeId: input.nodeId,
|
|
40
|
+
metadata: {
|
|
41
|
+
...(isScriptedNode(previous) ? scriptedObjectMeta(previous) : {}),
|
|
42
|
+
...GeometryReuseFields.parse(input),
|
|
43
|
+
},
|
|
247
44
|
});
|
|
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 },
|
|
300
|
-
});
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
case 'dining':
|
|
304
|
-
placements.push({ assetId: 'dining-table', x: bounds.centerX, z: bounds.centerZ });
|
|
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;
|
|
45
|
+
outcome = rescriptOpening(nodes, { ...input, compiled }, { activeLevelId: null });
|
|
339
46
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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),
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (isAgentRefusal(error))
|
|
49
|
+
return refusalResult(error);
|
|
50
|
+
return toolError(error instanceof Error ? error.message : String(error), {
|
|
51
|
+
code: 'script_failed',
|
|
405
52
|
});
|
|
53
|
+
}
|
|
54
|
+
const achieved = outcome.changes
|
|
55
|
+
? achievedChanges(nodes, outcome.changes)
|
|
56
|
+
: achievedChanges(nodes, {});
|
|
57
|
+
if (outcome.changes)
|
|
58
|
+
bridge.applyPatch(toPatches(outcome.changes));
|
|
59
|
+
const persistence = await publishLiveSceneSnapshot(bridge, `add_${kind}`);
|
|
60
|
+
return textResult({
|
|
61
|
+
...rebuiltOpeningResult(bridge.getNodes(), input.nodeId, outcome.result, achieved),
|
|
62
|
+
...persistencePayload(persistence),
|
|
406
63
|
});
|
|
407
64
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
65
|
+
/** A door or window passed `code`: compiled and stored the way add_object does, or the tool's error. */
|
|
66
|
+
async function compileOpeningScript(kind, bridge, host, input) {
|
|
67
|
+
if (!input.code)
|
|
68
|
+
return {};
|
|
69
|
+
if (!host)
|
|
70
|
+
return {
|
|
71
|
+
error: toolError('This Pascal server cannot run geometry scripts; use the fields.', {
|
|
72
|
+
code: 'scripts_unavailable',
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
const scene = bridge.getActiveScene();
|
|
76
|
+
if (!scene)
|
|
77
|
+
return { error: toolError('Open or save a scene first.', { code: 'no_active_scene' }) };
|
|
78
|
+
try {
|
|
79
|
+
return {
|
|
80
|
+
script: await compileAndStore(host, scene.id, { code: input.code, language: input.language ?? 'three', params: input.params }, kind, { metadata: input }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (isAgentRefusal(error))
|
|
85
|
+
return { error: refusalResult(error) };
|
|
86
|
+
return {
|
|
87
|
+
error: toolError(error instanceof Error ? error.message : String(error), {
|
|
88
|
+
code: 'script_failed',
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function registerAddDoor(server, bridge, geometryScripts) {
|
|
94
|
+
server.registerTool(addDoorTool.name, {
|
|
95
|
+
title: addDoorTool.title,
|
|
96
|
+
description: addDoorTool.description,
|
|
97
|
+
inputSchema: addDoorTool.input,
|
|
413
98
|
outputSchema: addDoorOutput,
|
|
414
99
|
annotations: ADDITIVE_TOOL_ANNOTATIONS,
|
|
415
|
-
}, async (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
100
|
+
}, async (input) => {
|
|
101
|
+
if (input.nodeId)
|
|
102
|
+
return rebuildOpening('door', bridge, geometryScripts, { ...input, nodeId: input.nodeId });
|
|
103
|
+
const compiled = await compileOpeningScript('door', bridge, geometryScripts, input);
|
|
104
|
+
if ('error' in compiled)
|
|
105
|
+
return compiled.error;
|
|
106
|
+
let opening;
|
|
107
|
+
try {
|
|
108
|
+
opening = addWallOpening(bridge.getNodes(), {
|
|
109
|
+
kind: 'door',
|
|
110
|
+
...input,
|
|
111
|
+
compiled: compiled.script,
|
|
112
|
+
});
|
|
420
113
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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);
|
|
114
|
+
catch (error) {
|
|
115
|
+
return refusalResult(error);
|
|
116
|
+
}
|
|
117
|
+
bridge.applyPatch(toPatches(opening.changes));
|
|
433
118
|
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
|
-
});
|
|
119
|
+
return textResult({ ...opening.result, ...persistencePayload(persistence) });
|
|
444
120
|
});
|
|
445
121
|
}
|
|
446
|
-
export function registerAddWindow(server, bridge) {
|
|
447
|
-
server.registerTool(
|
|
448
|
-
title:
|
|
449
|
-
description:
|
|
450
|
-
inputSchema:
|
|
122
|
+
export function registerAddWindow(server, bridge, geometryScripts) {
|
|
123
|
+
server.registerTool(addWindowTool.name, {
|
|
124
|
+
title: addWindowTool.title,
|
|
125
|
+
description: addWindowTool.description,
|
|
126
|
+
inputSchema: addWindowTool.input,
|
|
451
127
|
outputSchema: addWindowOutput,
|
|
452
128
|
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,
|
|
129
|
+
}, async (input) => {
|
|
130
|
+
if (input.nodeId)
|
|
131
|
+
return rebuildOpening('window', bridge, geometryScripts, { ...input, nodeId: input.nodeId });
|
|
132
|
+
const compiled = await compileOpeningScript('window', bridge, geometryScripts, input);
|
|
133
|
+
if ('error' in compiled)
|
|
134
|
+
return compiled.error;
|
|
135
|
+
let opening;
|
|
136
|
+
try {
|
|
137
|
+
opening = addWallOpening(bridge.getNodes(), {
|
|
138
|
+
kind: 'window',
|
|
139
|
+
...input,
|
|
140
|
+
compiled: compiled.script,
|
|
542
141
|
});
|
|
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
142
|
}
|
|
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');
|
|
143
|
+
catch (error) {
|
|
144
|
+
return refusalResult(error);
|
|
576
145
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
skipped,
|
|
581
|
-
...persistencePayload(persistence),
|
|
582
|
-
});
|
|
146
|
+
bridge.applyPatch(toPatches(opening.changes));
|
|
147
|
+
const persistence = await publishLiveSceneSnapshot(bridge, 'add_window');
|
|
148
|
+
return textResult({ ...opening.result, ...persistencePayload(persistence) });
|
|
583
149
|
});
|
|
584
150
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
registerAddWindow(server, bridge);
|
|
590
|
-
registerFurnishRoom(server, bridge);
|
|
151
|
+
/** add_door and add_window; create_room and furnish_room are shared tools (shared-tools.ts). */
|
|
152
|
+
export function registerRoomTools(server, bridge, geometryScripts) {
|
|
153
|
+
registerAddDoor(server, bridge, geometryScripts);
|
|
154
|
+
registerAddWindow(server, bridge, geometryScripts);
|
|
591
155
|
}
|