@pascal-app/mcp 0.3.2 → 1.0.0-beta.1
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/dist/operations/scene-operations.js +6 -6
- package/dist/templates/empty-studio.d.ts.map +1 -1
- package/dist/templates/empty-studio.js +2 -4
- package/dist/templates/garden-house.js +1 -1
- package/dist/templates/two-bedroom.js +1 -1
- package/dist/tools/construction-tools.d.ts +2 -2
- package/dist/tools/construction-tools.d.ts.map +1 -1
- package/dist/tools/construction-tools.js +38 -16
- package/dist/tools/create-level.d.ts.map +1 -1
- package/dist/tools/create-level.js +15 -7
- package/dist/tools/describe-node.d.ts.map +1 -1
- package/dist/tools/describe-node.js +14 -5
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.js +5 -3
- package/dist/tools/scene-query.d.ts +4 -0
- package/dist/tools/scene-query.d.ts.map +1 -1
- package/dist/tools/scene-query.js +22 -9
- package/package.json +3 -3
|
@@ -166,17 +166,17 @@ class SceneOperationsFacade {
|
|
|
166
166
|
return this.requireStore().rename(id, newName, options);
|
|
167
167
|
}
|
|
168
168
|
async appendSceneEvent(options) {
|
|
169
|
-
const
|
|
170
|
-
if (!
|
|
169
|
+
const store = this.requireStore();
|
|
170
|
+
if (!store.appendSceneEvent)
|
|
171
171
|
return null;
|
|
172
|
-
return
|
|
172
|
+
return store.appendSceneEvent(options);
|
|
173
173
|
}
|
|
174
174
|
async listSceneEvents(id, options) {
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
175
|
+
const store = this.requireStore();
|
|
176
|
+
if (!store.listSceneEvents) {
|
|
177
177
|
throw new Error('scene_events_unavailable');
|
|
178
178
|
}
|
|
179
|
-
return
|
|
179
|
+
return store.listSceneEvents(id, options);
|
|
180
180
|
}
|
|
181
181
|
requireBridge() {
|
|
182
182
|
if (!this.#bridge) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empty-studio.d.ts","sourceRoot":"","sources":["../../src/templates/empty-studio.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"empty-studio.d.ts","sourceRoot":"","sources":["../../src/templates/empty-studio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAmPpE,eAAO,MAAM,QAAQ,EAAE,UAA4B,CAAA;AAEnD,eAAO,MAAM,QAAQ;;;;CAKX,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_LEVEL_HEIGHT } from '@pascal-app/core';
|
|
1
2
|
/**
|
|
2
3
|
* 40 m² studio apartment — a single open room with one window, one front door
|
|
3
4
|
* and a single "Living/Kitchen" zone. Used as a starting point for small unit
|
|
@@ -117,7 +118,6 @@ function buildNodes() {
|
|
|
117
118
|
metadata: {},
|
|
118
119
|
children: [],
|
|
119
120
|
thickness: 0.1,
|
|
120
|
-
height: 2.5,
|
|
121
121
|
start: [-W, -D],
|
|
122
122
|
end: [W, -D],
|
|
123
123
|
frontSide: 'unknown',
|
|
@@ -132,7 +132,6 @@ function buildNodes() {
|
|
|
132
132
|
metadata: {},
|
|
133
133
|
children: [],
|
|
134
134
|
thickness: 0.1,
|
|
135
|
-
height: 2.5,
|
|
136
135
|
start: [W, -D],
|
|
137
136
|
end: [W, D],
|
|
138
137
|
frontSide: 'unknown',
|
|
@@ -147,7 +146,6 @@ function buildNodes() {
|
|
|
147
146
|
metadata: {},
|
|
148
147
|
children: ['door_front'],
|
|
149
148
|
thickness: 0.1,
|
|
150
|
-
height: 2.5,
|
|
151
149
|
start: [W, D],
|
|
152
150
|
end: [-W, D],
|
|
153
151
|
frontSide: 'unknown',
|
|
@@ -162,7 +160,6 @@ function buildNodes() {
|
|
|
162
160
|
metadata: {},
|
|
163
161
|
children: ['window_w'],
|
|
164
162
|
thickness: 0.1,
|
|
165
|
-
height: 2.5,
|
|
166
163
|
start: [-W, D],
|
|
167
164
|
end: [-W, -D],
|
|
168
165
|
frontSide: 'unknown',
|
|
@@ -192,6 +189,7 @@ function buildNodes() {
|
|
|
192
189
|
visible: true,
|
|
193
190
|
metadata: {},
|
|
194
191
|
level: 0,
|
|
192
|
+
height: DEFAULT_LEVEL_HEIGHT,
|
|
195
193
|
children: [...wallIds, 'zone_living'],
|
|
196
194
|
};
|
|
197
195
|
return {
|
|
@@ -28,7 +28,6 @@ function wall(id, start, end, children = []) {
|
|
|
28
28
|
metadata: {},
|
|
29
29
|
children,
|
|
30
30
|
thickness: WALL_THICKNESS,
|
|
31
|
-
height: WALL_HEIGHT,
|
|
32
31
|
start,
|
|
33
32
|
end,
|
|
34
33
|
frontSide: 'unknown',
|
|
@@ -216,6 +215,7 @@ function buildTemplate() {
|
|
|
216
215
|
visible: true,
|
|
217
216
|
metadata: {},
|
|
218
217
|
level: 0,
|
|
218
|
+
height: WALL_HEIGHT,
|
|
219
219
|
children: [
|
|
220
220
|
'wall_n',
|
|
221
221
|
'wall_e',
|
|
@@ -31,7 +31,6 @@ function wall(id, start, end, children = []) {
|
|
|
31
31
|
metadata: {},
|
|
32
32
|
children,
|
|
33
33
|
thickness: WALL_THICKNESS,
|
|
34
|
-
height: WALL_HEIGHT,
|
|
35
34
|
start,
|
|
36
35
|
end,
|
|
37
36
|
frontSide: 'unknown',
|
|
@@ -240,6 +239,7 @@ function buildTemplate() {
|
|
|
240
239
|
visible: true,
|
|
241
240
|
metadata: {},
|
|
242
241
|
level: 0,
|
|
242
|
+
height: WALL_HEIGHT,
|
|
243
243
|
children: [
|
|
244
244
|
'wall_n',
|
|
245
245
|
'wall_e',
|
|
@@ -4,7 +4,7 @@ import type { SceneOperations } from '../operations';
|
|
|
4
4
|
export declare const createStoryShellInput: {
|
|
5
5
|
levelId: z.ZodString;
|
|
6
6
|
footprint: z.ZodArray<z.ZodArray<z.ZodNumber>>;
|
|
7
|
-
wallHeight: z.
|
|
7
|
+
wallHeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
8
8
|
wallThickness: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
9
9
|
createSlab: z.ZodDefault<z.ZodBoolean>;
|
|
10
10
|
createCeiling: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -62,7 +62,7 @@ export declare const createStairBetweenLevelsInput: {
|
|
|
62
62
|
rotation: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
63
63
|
width: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
64
64
|
runLength: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
65
|
-
totalRise: z.
|
|
65
|
+
totalRise: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
66
66
|
stepCount: z.ZodDefault<z.ZodNumber>;
|
|
67
67
|
railingMode: z.ZodDefault<z.ZodEnum<{
|
|
68
68
|
none: "none";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"construction-tools.d.ts","sourceRoot":"","sources":["../../src/tools/construction-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"construction-tools.d.ts","sourceRoot":"","sources":["../../src/tools/construction-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAcxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAQpD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;CAsBjC,CAAA;AAED,eAAO,MAAM,sBAAsB;;;;;;CAMlC,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;CA0B3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;CAM5B,CAAA;AAED,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCzC,CAAA;AAED,eAAO,MAAM,8BAA8B;;;;;;CAM1C,CAAA;AA4GD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmU1F"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveStairTotalRise } from '@pascal-app/core';
|
|
1
2
|
import { CeilingNode, getActiveRoofHeight, LevelNode, RoofNode, RoofSegmentNode, SlabNode, StairNode, StairSegmentNode, WallNode, } from '@pascal-app/core/schema';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
@@ -8,7 +9,10 @@ const RAILING_MODES = ['none', 'left', 'right', 'both'];
|
|
|
8
9
|
export const createStoryShellInput = {
|
|
9
10
|
levelId: NodeIdSchema,
|
|
10
11
|
footprint: z.array(Vec2Schema).min(3),
|
|
11
|
-
wallHeight: measurement('length', 'm', {
|
|
12
|
+
wallHeight: measurement('length', 'm', {
|
|
13
|
+
positive: true,
|
|
14
|
+
description: 'Explicit wall height override. Omit for level-plane-bound walls.',
|
|
15
|
+
}).optional(),
|
|
12
16
|
wallThickness: measurement('length', 'm', {
|
|
13
17
|
positive: true,
|
|
14
18
|
description: 'Wall thickness.',
|
|
@@ -77,7 +81,7 @@ export const createStairBetweenLevelsInput = {
|
|
|
77
81
|
totalRise: measurement('length', 'm', {
|
|
78
82
|
positive: true,
|
|
79
83
|
description: 'Total vertical rise.',
|
|
80
|
-
}).
|
|
84
|
+
}).optional(),
|
|
81
85
|
stepCount: z.number().int().positive().default(14),
|
|
82
86
|
railingMode: z.enum(RAILING_MODES).default('both'),
|
|
83
87
|
destinationSlabId: NodeIdSchema.optional(),
|
|
@@ -200,7 +204,7 @@ export function registerConstructionTools(server, bridge) {
|
|
|
200
204
|
start: points[i],
|
|
201
205
|
end: points[(i + 1) % points.length],
|
|
202
206
|
thickness: wallThickness,
|
|
203
|
-
height: wallHeight,
|
|
207
|
+
...(wallHeight !== undefined ? { height: wallHeight } : {}),
|
|
204
208
|
frontSide: 'exterior',
|
|
205
209
|
backSide: 'interior',
|
|
206
210
|
...(wallMaterialPreset ? { materialPreset: wallMaterialPreset } : {}),
|
|
@@ -215,6 +219,11 @@ export function registerConstructionTools(server, bridge) {
|
|
|
215
219
|
name: namePrefix ? `${namePrefix} Slab` : undefined,
|
|
216
220
|
polygon: points,
|
|
217
221
|
elevation: slabElevation,
|
|
222
|
+
// Grounded solid: underside on the level plane, so the created
|
|
223
|
+
// story slab occupies [0, slabElevation] like the legacy
|
|
224
|
+
// extrude-from-zero model.
|
|
225
|
+
thickness: Math.max(slabElevation, 0),
|
|
226
|
+
recessed: slabElevation < 0,
|
|
218
227
|
...(slabMaterialPreset ? { materialPreset: slabMaterialPreset } : {}),
|
|
219
228
|
metadata: { role: 'story-slab' },
|
|
220
229
|
});
|
|
@@ -223,10 +232,13 @@ export function registerConstructionTools(server, bridge) {
|
|
|
223
232
|
}
|
|
224
233
|
let ceilingId = null;
|
|
225
234
|
if (createCeiling) {
|
|
235
|
+
// Height-less unless the caller pinned one: a new story ceiling
|
|
236
|
+
// follows the level top automatically.
|
|
237
|
+
const explicitCeilingHeight = ceilingHeight ?? wallHeight;
|
|
226
238
|
const ceiling = CeilingNode.parse({
|
|
227
239
|
name: namePrefix ? `${namePrefix} Ceiling` : undefined,
|
|
228
240
|
polygon: points,
|
|
229
|
-
height:
|
|
241
|
+
...(explicitCeilingHeight !== undefined ? { height: explicitCeilingHeight } : {}),
|
|
230
242
|
...(ceilingMaterialPreset ? { materialPreset: ceilingMaterialPreset } : {}),
|
|
231
243
|
metadata: { role: 'story-ceiling' },
|
|
232
244
|
});
|
|
@@ -268,12 +280,12 @@ export function registerConstructionTools(server, bridge) {
|
|
|
268
280
|
const roofLevel = LevelNode.parse({
|
|
269
281
|
name: roofLevelLabel,
|
|
270
282
|
level: roofLevelElevation ?? nextLevelIndex(bridge, buildingId, referenceLevel),
|
|
283
|
+
height: roofLevelHeight ?? Math.max(wallHeight + peakHeight, 0.2),
|
|
271
284
|
children: [],
|
|
272
285
|
metadata: {
|
|
273
286
|
role: 'roof',
|
|
274
287
|
label: roofLevelLabel,
|
|
275
288
|
referenceLevelId: levelId,
|
|
276
|
-
height: roofLevelHeight ?? Math.max(wallHeight + peakHeight, 0.2),
|
|
277
289
|
},
|
|
278
290
|
});
|
|
279
291
|
targetRoofLevelId = roofLevel.id;
|
|
@@ -325,15 +337,7 @@ export function registerConstructionTools(server, bridge) {
|
|
|
325
337
|
if (isRoofLevel(fromLevel) || isRoofLevel(toLevel)) {
|
|
326
338
|
throw new Error('Roof support levels are not occupied stories; create a separate occupied attic/story level if a stair-accessible attic is required');
|
|
327
339
|
}
|
|
328
|
-
const
|
|
329
|
-
segmentType: 'stair',
|
|
330
|
-
width,
|
|
331
|
-
length: runLength,
|
|
332
|
-
height: totalRise,
|
|
333
|
-
stepCount,
|
|
334
|
-
...(materialPreset ? { materialPreset } : {}),
|
|
335
|
-
});
|
|
336
|
-
const stair = StairNode.parse({
|
|
340
|
+
const stairDraft = StairNode.parse({
|
|
337
341
|
name: name ?? 'Stair',
|
|
338
342
|
position: position,
|
|
339
343
|
rotation,
|
|
@@ -343,15 +347,33 @@ export function registerConstructionTools(server, bridge) {
|
|
|
343
347
|
slabOpeningMode: 'none',
|
|
344
348
|
openingOffset,
|
|
345
349
|
width,
|
|
346
|
-
totalRise,
|
|
350
|
+
...(totalRise !== undefined ? { totalRise } : {}),
|
|
347
351
|
stepCount,
|
|
348
352
|
railingMode,
|
|
349
|
-
children: [
|
|
353
|
+
children: [],
|
|
350
354
|
...(materialPreset ? { materialPreset } : {}),
|
|
351
355
|
metadata: {
|
|
352
356
|
openingManaged: 'manual-rectangular',
|
|
353
357
|
},
|
|
354
358
|
});
|
|
359
|
+
const riseNodes = {
|
|
360
|
+
...bridge.getNodes(),
|
|
361
|
+
[fromLevel.id]: {
|
|
362
|
+
...fromLevel,
|
|
363
|
+
children: [...fromLevel.children, stairDraft.id],
|
|
364
|
+
},
|
|
365
|
+
[stairDraft.id]: stairDraft,
|
|
366
|
+
};
|
|
367
|
+
const resolvedTotalRise = resolveStairTotalRise(stairDraft, riseNodes);
|
|
368
|
+
const segment = StairSegmentNode.parse({
|
|
369
|
+
segmentType: 'stair',
|
|
370
|
+
width,
|
|
371
|
+
length: runLength,
|
|
372
|
+
height: resolvedTotalRise,
|
|
373
|
+
stepCount,
|
|
374
|
+
...(materialPreset ? { materialPreset } : {}),
|
|
375
|
+
});
|
|
376
|
+
const stair = { ...stairDraft, children: [segment.id] };
|
|
355
377
|
const openingPolygon = rectangularOpening({
|
|
356
378
|
position: position,
|
|
357
379
|
rotation,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-level.d.ts","sourceRoot":"","sources":["../../src/tools/create-level.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"create-level.d.ts","sourceRoot":"","sources":["../../src/tools/create-level.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAIxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,gBAAgB;;;;;CAW5B,CAAA;AAED,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA+CpF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_LEVEL_HEIGHT } from '@pascal-app/core';
|
|
1
2
|
import { LevelNode } from '@pascal-app/core/schema';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { ErrorCode, throwMcpError } from './errors';
|
|
@@ -6,10 +7,13 @@ import { measurement } from './measurement';
|
|
|
6
7
|
import { NodeIdSchema } from './schemas';
|
|
7
8
|
export const createLevelInput = {
|
|
8
9
|
buildingId: NodeIdSchema,
|
|
9
|
-
elevation: z
|
|
10
|
+
elevation: z
|
|
11
|
+
.number()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Legacy parameter; new levels are appended above the building's current top level."),
|
|
10
14
|
height: measurement('length', 'm', {
|
|
11
15
|
min: 0,
|
|
12
|
-
description: '
|
|
16
|
+
description: 'Stored floor-to-floor storey height.',
|
|
13
17
|
}).optional(),
|
|
14
18
|
label: z.string().optional(),
|
|
15
19
|
};
|
|
@@ -19,10 +23,10 @@ export const createLevelOutput = {
|
|
|
19
23
|
export function registerCreateLevel(server, bridge) {
|
|
20
24
|
server.registerTool('create_level', {
|
|
21
25
|
title: 'Create level',
|
|
22
|
-
description:
|
|
26
|
+
description: "Append a new level above the given building's current top level. height is stored as the level's floor-to-floor storey height.",
|
|
23
27
|
inputSchema: createLevelInput,
|
|
24
28
|
outputSchema: createLevelOutput,
|
|
25
|
-
}, async ({ buildingId,
|
|
29
|
+
}, async ({ buildingId, height, label }) => {
|
|
26
30
|
const parent = bridge.getNode(buildingId);
|
|
27
31
|
if (!parent) {
|
|
28
32
|
throwMcpError(ErrorCode.InvalidParams, `Building not found: ${buildingId}`);
|
|
@@ -31,12 +35,16 @@ export function registerCreateLevel(server, bridge) {
|
|
|
31
35
|
throwMcpError(ErrorCode.InvalidParams, `Node ${buildingId} is a ${parent.type}, expected building`);
|
|
32
36
|
}
|
|
33
37
|
const metadata = {};
|
|
34
|
-
if (height !== undefined)
|
|
35
|
-
metadata.height = height;
|
|
36
38
|
if (label !== undefined)
|
|
37
39
|
metadata.label = label;
|
|
40
|
+
const existingOrdinals = bridge
|
|
41
|
+
.getChildren(buildingId)
|
|
42
|
+
.filter((node) => node.type === 'level')
|
|
43
|
+
.map((node) => node.level);
|
|
44
|
+
const nextOrdinal = Math.max(-1, ...existingOrdinals) + 1;
|
|
38
45
|
const levelNode = LevelNode.parse({
|
|
39
|
-
level:
|
|
46
|
+
level: nextOrdinal,
|
|
47
|
+
height: height ?? DEFAULT_LEVEL_HEIGHT,
|
|
40
48
|
children: [],
|
|
41
49
|
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
42
50
|
...(label !== undefined ? { name: label } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-node.d.ts","sourceRoot":"","sources":["../../src/tools/describe-node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"describe-node.d.ts","sourceRoot":"","sources":["../../src/tools/describe-node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAKpD,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AA0CD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAgDrF"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { resolveCeilingHeight } from '@pascal-app/core';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { ErrorCode, throwMcpError } from './errors';
|
|
4
|
+
import { resolveReportedWallHeight } from './scene-query';
|
|
3
5
|
import { NodeIdSchema } from './schemas';
|
|
4
6
|
export const describeNodeInput = {
|
|
5
7
|
id: NodeIdSchema,
|
|
@@ -17,13 +19,13 @@ export const describeNodeOutput = {
|
|
|
17
19
|
* Build a short, human-readable one-liner describing the node.
|
|
18
20
|
* Covers the common shapes; falls back to a generic sentence otherwise.
|
|
19
21
|
*/
|
|
20
|
-
function describe(node) {
|
|
22
|
+
function describe(node, bridge) {
|
|
21
23
|
switch (node.type) {
|
|
22
24
|
case 'wall': {
|
|
23
25
|
const [x1, z1] = node.start;
|
|
24
26
|
const [x2, z2] = node.end;
|
|
25
27
|
const t = node.thickness ?? 0.1;
|
|
26
|
-
const h = node
|
|
28
|
+
const h = resolveReportedWallHeight(bridge, node);
|
|
27
29
|
return `Wall from (${x1},${z1}) to (${x2},${z2}), thickness ${t.toFixed(2)}m, height ${h.toFixed(2)}m`;
|
|
28
30
|
}
|
|
29
31
|
case 'level':
|
|
@@ -39,7 +41,7 @@ function describe(node) {
|
|
|
39
41
|
case 'slab':
|
|
40
42
|
return `Slab with ${node.polygon.length} vertices`;
|
|
41
43
|
case 'ceiling':
|
|
42
|
-
return `Ceiling with ${node.polygon.length} vertices, height ${node.
|
|
44
|
+
return `Ceiling with ${node.polygon.length} vertices, height ${resolveCeilingHeight(node, bridge.getNodes()).toFixed(2)}m`;
|
|
43
45
|
case 'door':
|
|
44
46
|
return `Door (${node.width.toFixed(2)}m x ${node.height.toFixed(2)}m)`;
|
|
45
47
|
case 'window':
|
|
@@ -69,14 +71,21 @@ export function registerDescribeNode(server, bridge) {
|
|
|
69
71
|
const children = bridge.getChildren(id);
|
|
70
72
|
const childrenIds = children.map((n) => n.id);
|
|
71
73
|
const n = node;
|
|
74
|
+
const properties = n.type === 'wall'
|
|
75
|
+
? {
|
|
76
|
+
...n,
|
|
77
|
+
resolvedHeight: resolveReportedWallHeight(bridge, n),
|
|
78
|
+
heightIsExplicit: n.height !== undefined,
|
|
79
|
+
}
|
|
80
|
+
: n;
|
|
72
81
|
const payload = {
|
|
73
82
|
id: n.id,
|
|
74
83
|
type: n.type,
|
|
75
84
|
parentId: (n.parentId ?? null),
|
|
76
85
|
ancestryIds,
|
|
77
86
|
childrenIds,
|
|
78
|
-
properties:
|
|
79
|
-
description: describe(n),
|
|
87
|
+
properties: properties,
|
|
88
|
+
description: describe(n, bridge),
|
|
80
89
|
};
|
|
81
90
|
return {
|
|
82
91
|
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"photo-to-scene.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/photo-to-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAYxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAIvD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;;;CAa7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;
|
|
1
|
+
{"version":3,"file":"photo-to-scene.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/photo-to-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAYxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAIvD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;;;CAa7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AAmTD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA6ErF"}
|
|
@@ -40,6 +40,7 @@ const VisionResponseSchema = z.object({
|
|
|
40
40
|
start: z.tuple([z.number(), z.number()]),
|
|
41
41
|
end: z.tuple([z.number(), z.number()]),
|
|
42
42
|
thickness: z.number().optional(),
|
|
43
|
+
height: z.number().positive().optional(),
|
|
43
44
|
})),
|
|
44
45
|
rooms: z.array(z.object({
|
|
45
46
|
name: z.string(),
|
|
@@ -61,13 +62,14 @@ const SYSTEM_PROMPT = `You are a vision assistant that extracts structured floor
|
|
|
61
62
|
Your ONLY job: return a JSON object that exactly matches this schema — no prose, no markdown fences.
|
|
62
63
|
|
|
63
64
|
{
|
|
64
|
-
"walls": [{ "start": [x, z], "end": [x, z], "thickness": number? }, ...],
|
|
65
|
+
"walls": [{ "start": [x, z], "end": [x, z], "thickness": number?, "height": number? }, ...],
|
|
65
66
|
"rooms": [{ "name": string, "polygon": [[x,z], ...], "approximateAreaSqM": number? }, ...],
|
|
66
67
|
"approximateDimensions": { "widthM": number, "depthM": number },
|
|
67
68
|
"confidence": number 0..1
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
Coordinates are in metres. Origin can be the floor plan's centre or bottom-left — be consistent.
|
|
72
|
+
Only include a wall height when it is visibly measured or annotated in the image.
|
|
71
73
|
If the image is unclear, lower the confidence score but still produce your best attempt.
|
|
72
74
|
DO NOT wrap the JSON in markdown. DO NOT explain. Just output the raw JSON.`;
|
|
73
75
|
const DATA_URI_RE = /^data:(image\/[a-z0-9.+-]+);base64,(.+)$/i;
|
|
@@ -170,7 +172,7 @@ function buildSceneGraphFromVision(vision, defaultWallThickness, defaultWallHeig
|
|
|
170
172
|
const warnings = [];
|
|
171
173
|
// Build the skeleton: site → building → level.
|
|
172
174
|
const building = BuildingNode.parse({});
|
|
173
|
-
const level = LevelNode.parse({ level: 0 });
|
|
175
|
+
const level = LevelNode.parse({ level: 0, height: defaultWallHeight });
|
|
174
176
|
const site = SiteNode.parse({ children: [building.id] });
|
|
175
177
|
// Link parent ids so downstream traversal works.
|
|
176
178
|
const siteId = site.id;
|
|
@@ -208,7 +210,7 @@ function buildSceneGraphFromVision(vision, defaultWallThickness, defaultWallHeig
|
|
|
208
210
|
start: w.start,
|
|
209
211
|
end: w.end,
|
|
210
212
|
thickness: w.thickness ?? defaultWallThickness,
|
|
211
|
-
height:
|
|
213
|
+
...(w.height !== undefined ? { height: w.height } : {}),
|
|
212
214
|
});
|
|
213
215
|
const linkedWall = {
|
|
214
216
|
...wall,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { AnyNode } from '@pascal-app/core/schema';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import type { SceneOperations } from '../operations';
|
|
4
5
|
export declare const levelScopedInput: {
|
|
@@ -48,6 +49,9 @@ export declare const verifySceneOutput: {
|
|
|
48
49
|
issues: z.ZodArray<z.ZodString>;
|
|
49
50
|
hasIssues: z.ZodBoolean;
|
|
50
51
|
};
|
|
52
|
+
export declare function resolveReportedWallHeight(bridge: SceneOperations, wall: Extract<AnyNode, {
|
|
53
|
+
type: 'wall';
|
|
54
|
+
}>): number;
|
|
51
55
|
export declare function registerListLevels(server: McpServer, bridge: SceneOperations): void;
|
|
52
56
|
export declare function registerGetLevelSummary(server: McpServer, bridge: SceneOperations): void;
|
|
53
57
|
export declare function registerGetWalls(server: McpServer, bridge: SceneOperations): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-query.d.ts","sourceRoot":"","sources":["../../src/tools/scene-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"scene-query.d.ts","sourceRoot":"","sources":["../../src/tools/scene-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAQxE,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAWpD,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAID,eAAO,MAAM,gBAAgB;;;;;;;CAO5B,CAAA;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;CAcjC,CAAA;AAED,eAAO,MAAM,cAAc;;;CAG1B,CAAA;AAED,eAAO,MAAM,cAAc;;;CAG1B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;CAY7B,CAAA;AA8CD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GACvC,MAAM,CAgBR;AAkWD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAyCnF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAgBxF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA4PpF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAMxF"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DEFAULT_LEVEL_HEIGHT, getStoredLevelHeight, getWallPlaneTop, resolveStairTotalRise, resolveWallEffectiveHeight, } from '@pascal-app/core';
|
|
2
|
+
import { computeWallSlabSupport } from '@pascal-app/core/spatial-grid';
|
|
1
3
|
import { z } from 'zod';
|
|
2
4
|
import { distance2D, pointInPolygon, polygonArea, polygonContainsPolygon, wallLength, } from './geometry';
|
|
3
5
|
import { NodeIdSchema } from './schemas';
|
|
@@ -71,6 +73,19 @@ function getDefaultLevelId(bridge, requested) {
|
|
|
71
73
|
function nodesOnLevel(bridge, levelId) {
|
|
72
74
|
return Object.values(bridge.getNodes()).filter((node) => node.id !== levelId && bridge.resolveLevelId(node.id) === levelId);
|
|
73
75
|
}
|
|
76
|
+
export function resolveReportedWallHeight(bridge, wall) {
|
|
77
|
+
const levelId = bridge.resolveLevelId(wall.id);
|
|
78
|
+
// Covering-clamped plane for plane-bound walls; explicit heights pass
|
|
79
|
+
// through resolveWallEffectiveHeight untouched.
|
|
80
|
+
const planeTop = levelId
|
|
81
|
+
? getWallPlaneTop(wall, levelId, bridge.getNodes())
|
|
82
|
+
: DEFAULT_LEVEL_HEIGHT;
|
|
83
|
+
const levelNodes = levelId ? nodesOnLevel(bridge, levelId) : [];
|
|
84
|
+
const slabs = levelNodes.filter((node) => node.type === 'slab');
|
|
85
|
+
const walls = levelNodes.filter((node) => node.type === 'wall');
|
|
86
|
+
const support = computeWallSlabSupport(wall, slabs, walls, wall.supportSlabId);
|
|
87
|
+
return resolveWallEffectiveHeight(wall, planeTop, support.elevation);
|
|
88
|
+
}
|
|
74
89
|
function metadataRecord(node) {
|
|
75
90
|
return typeof node.metadata === 'object' && node.metadata !== null
|
|
76
91
|
? node.metadata
|
|
@@ -116,6 +131,7 @@ function wallSummary(bridge, wall) {
|
|
|
116
131
|
if (wall.type !== 'wall')
|
|
117
132
|
return null;
|
|
118
133
|
const length = distance2D(wall.start, wall.end);
|
|
134
|
+
const resolvedHeight = resolveReportedWallHeight(bridge, wall);
|
|
119
135
|
return {
|
|
120
136
|
id: wall.id,
|
|
121
137
|
name: wall.name,
|
|
@@ -123,6 +139,8 @@ function wallSummary(bridge, wall) {
|
|
|
123
139
|
end: wall.end,
|
|
124
140
|
length: Math.round(length * 100) / 100,
|
|
125
141
|
height: wall.height,
|
|
142
|
+
resolvedHeight,
|
|
143
|
+
heightIsExplicit: wall.height !== undefined,
|
|
126
144
|
thickness: wall.thickness,
|
|
127
145
|
openings: openingSummaries(bridge, wall.id),
|
|
128
146
|
};
|
|
@@ -235,7 +253,7 @@ function stairFootprintPolygons(bridge, stair) {
|
|
|
235
253
|
{
|
|
236
254
|
width: stair.width ?? 1,
|
|
237
255
|
length: 3,
|
|
238
|
-
height: stair
|
|
256
|
+
height: resolveStairTotalRise(stair, nodes),
|
|
239
257
|
stepCount: stair.stepCount ?? 10,
|
|
240
258
|
attachmentSide: 'front',
|
|
241
259
|
},
|
|
@@ -524,14 +542,9 @@ export function registerVerifyScene(server, bridge) {
|
|
|
524
542
|
const summary = levels.find((entry) => entry.levelId === level.id);
|
|
525
543
|
if (!summary?.isOccupiedStory)
|
|
526
544
|
continue;
|
|
527
|
-
const expectedHeight =
|
|
528
|
-
level.metadata !== null &&
|
|
529
|
-
'height' in level.metadata &&
|
|
530
|
-
typeof level.metadata.height === 'number'
|
|
531
|
-
? level.metadata.height
|
|
532
|
-
: 3.2;
|
|
545
|
+
const expectedHeight = getStoredLevelHeight(level);
|
|
533
546
|
for (const wall of nodesOnLevel(bridge, level.id).filter((node) => node.type === 'wall')) {
|
|
534
|
-
const wallHeight = wall
|
|
547
|
+
const wallHeight = resolveReportedWallHeight(bridge, wall);
|
|
535
548
|
if (wallHeight > expectedHeight + 0.25) {
|
|
536
549
|
issues.push(`Wall ${wall.name ?? wall.id} on ${level.name ?? level.id} is ${wallHeight}m high; multi-story exterior walls should be split into level-owned story walls`);
|
|
537
550
|
}
|
|
@@ -558,7 +571,7 @@ export function registerVerifyScene(server, bridge) {
|
|
|
558
571
|
if (localX - width / 2 < -0.01 || localX + width / 2 > length + 0.01) {
|
|
559
572
|
issues.push(`${node.type} ${node.id} extends outside wall ${parent.id}`);
|
|
560
573
|
}
|
|
561
|
-
const wallHeight = parent
|
|
574
|
+
const wallHeight = resolveReportedWallHeight(bridge, parent);
|
|
562
575
|
const bottom = node.position[1] - height / 2;
|
|
563
576
|
const top = node.position[1] + height / 2;
|
|
564
577
|
if (bottom < -0.01 || top > wallHeight + 0.01) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "Model Context Protocol server for Pascal 3D editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"prepublishOnly": "bun run build && bun test"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@pascal-app/core": "^0.
|
|
58
|
+
"@pascal-app/core": "^1.0.0-beta.1"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"zod": "^4.3.5"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@pascal-app/core": "^0.
|
|
66
|
+
"@pascal-app/core": "^1.0.0-beta.1",
|
|
67
67
|
"@pascal/typescript-config": "*",
|
|
68
68
|
"@types/node": "^22.19.20",
|
|
69
69
|
"typescript": "6.0.3"
|