@pascal-app/mcp 0.3.1 → 0.3.2
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 +2 -1
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +6 -0
- package/dist/operations/scene-operations.js +1 -0
- package/dist/resources/constraints.js +1 -1
- package/dist/tools/construction-tools.d.ts +21 -21
- package/dist/tools/construction-tools.d.ts.map +1 -1
- package/dist/tools/construction-tools.js +45 -19
- package/dist/tools/create-level.d.ts +1 -1
- package/dist/tools/create-level.d.ts.map +1 -1
- package/dist/tools/create-level.js +5 -1
- package/dist/tools/create-wall.d.ts +2 -2
- package/dist/tools/create-wall.d.ts.map +1 -1
- package/dist/tools/create-wall.js +6 -2
- package/dist/tools/cut-opening.d.ts +3 -3
- package/dist/tools/cut-opening.d.ts.map +1 -1
- package/dist/tools/cut-opening.js +3 -2
- package/dist/tools/find-nodes.d.ts +2 -2
- package/dist/tools/find-nodes.js +1 -1
- package/dist/tools/measurement.d.ts +37 -0
- package/dist/tools/measurement.d.ts.map +1 -0
- package/dist/tools/measurement.js +94 -0
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts +2 -2
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.js +9 -2
- package/dist/tools/place-item.d.ts +1 -1
- package/dist/tools/place-item.d.ts.map +1 -1
- package/dist/tools/place-item.js +2 -1
- package/dist/tools/room-tools.d.ts +8 -8
- package/dist/tools/room-tools.d.ts.map +1 -1
- package/dist/tools/room-tools.js +17 -7
- package/dist/tools/scene-query.js +2 -2
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -251,7 +251,8 @@ plus the element's own height; slabs additionally carry an absolute
|
|
|
251
251
|
|
|
252
252
|
**Heads-up when you compute coordinates outside the editor.** Pascal's
|
|
253
253
|
viewports apply their own rotations on top of the world axes: the 2-D plan
|
|
254
|
-
panel
|
|
254
|
+
panel rotates its content by the user's view rotation (north-aligned = 0°,
|
|
255
|
+
`FLOORPLAN_VIEW_ROTATION_DEG` baseline, north = world −Z), and
|
|
255
256
|
the 3-D "top-down" snap preserves the camera's current azimuth, so when invoked
|
|
256
257
|
from the iso default position, world and screen axes are offset by ~45° until
|
|
257
258
|
you orbit to an axis-aligned view. So a layout authored as if
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,KAAK,SAAS,EAA4B,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAGpG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,eAAe,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAC/E,MAAM,MAAM,gBAAgB,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAA;AAE5E,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC/E,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AACjF,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAC5E,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAA;AAC3D,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,SAAS,EACT,IAAI,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CACrE,CAAA;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAA+B;IAElD;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAW3C,cAAc,IAAI,eAAe,GAAG,IAAI;IAIxC,gBAAgB,IAAI,IAAI;IAIxB,6EAA6E;IAC7E,WAAW,IAAI,IAAI;IAInB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,IAAI;IAI3E,uDAAuD;IACvD,UAAU,IAAI,UAAU,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE;
|
|
1
|
+
{"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,9 @@ export class SceneBridge {
|
|
|
48
48
|
nodes: state.nodes,
|
|
49
49
|
rootNodeIds: state.rootNodeIds,
|
|
50
50
|
collections: state.collections ?? {},
|
|
51
|
+
...(state.hasExplicitPluginInstallState || state.installedPlugins.length > 0
|
|
52
|
+
? { installedPlugins: state.installedPlugins }
|
|
53
|
+
: {}),
|
|
51
54
|
}));
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
@@ -88,6 +91,9 @@ export class SceneBridge {
|
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
this.setScene(nodes, rootNodeIds);
|
|
94
|
+
if (Array.isArray(obj.installedPlugins)) {
|
|
95
|
+
useScene.getState().setInstalledPlugins(obj.installedPlugins.filter((id) => typeof id === 'string'), { explicit: true });
|
|
96
|
+
}
|
|
91
97
|
}
|
|
92
98
|
/** Read a single node, or `null` if not present. */
|
|
93
99
|
getNode(id) {
|
|
@@ -4,12 +4,12 @@ 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.ZodDefault<z.ZodNumber
|
|
8
|
-
wallThickness: z.ZodDefault<z.ZodNumber
|
|
7
|
+
wallHeight: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
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>;
|
|
11
|
-
slabElevation: z.ZodDefault<z.ZodNumber
|
|
12
|
-
ceilingHeight: z.ZodOptional<z.ZodNumber
|
|
11
|
+
slabElevation: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
12
|
+
ceilingHeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
13
13
|
namePrefix: z.ZodOptional<z.ZodString>;
|
|
14
14
|
wallMaterialPreset: z.ZodOptional<z.ZodString>;
|
|
15
15
|
slabMaterialPreset: z.ZodOptional<z.ZodString>;
|
|
@@ -28,10 +28,10 @@ export declare const createRoofInput: {
|
|
|
28
28
|
useDedicatedRoofLevel: z.ZodDefault<z.ZodBoolean>;
|
|
29
29
|
roofLevelLabel: z.ZodDefault<z.ZodString>;
|
|
30
30
|
roofLevelElevation: z.ZodOptional<z.ZodNumber>;
|
|
31
|
-
roofLevelHeight: z.ZodOptional<z.ZodNumber
|
|
31
|
+
roofLevelHeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
32
32
|
center: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
33
|
-
width: z.ZodNumber
|
|
34
|
-
depth: z.ZodNumber
|
|
33
|
+
width: z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>;
|
|
34
|
+
depth: z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>;
|
|
35
35
|
roofType: z.ZodDefault<z.ZodEnum<{
|
|
36
36
|
flat: "flat";
|
|
37
37
|
hip: "hip";
|
|
@@ -41,10 +41,10 @@ export declare const createRoofInput: {
|
|
|
41
41
|
dutch: "dutch";
|
|
42
42
|
mansard: "mansard";
|
|
43
43
|
}>>;
|
|
44
|
-
pitch: z.ZodDefault<z.ZodNumber
|
|
45
|
-
wallHeight: z.ZodDefault<z.ZodNumber
|
|
46
|
-
wallThickness: z.ZodDefault<z.ZodNumber
|
|
47
|
-
overhang: z.ZodDefault<z.ZodNumber
|
|
44
|
+
pitch: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
45
|
+
wallHeight: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
46
|
+
wallThickness: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
47
|
+
overhang: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
48
48
|
materialPreset: z.ZodOptional<z.ZodString>;
|
|
49
49
|
name: z.ZodOptional<z.ZodString>;
|
|
50
50
|
};
|
|
@@ -59,26 +59,26 @@ export declare const createStairBetweenLevelsInput: {
|
|
|
59
59
|
fromLevelId: z.ZodString;
|
|
60
60
|
toLevelId: z.ZodString;
|
|
61
61
|
position: z.ZodArray<z.ZodNumber>;
|
|
62
|
-
rotation: z.ZodDefault<z.ZodNumber
|
|
63
|
-
width: z.ZodDefault<z.ZodNumber
|
|
64
|
-
runLength: z.ZodDefault<z.ZodNumber
|
|
65
|
-
totalRise: z.ZodDefault<z.ZodNumber
|
|
62
|
+
rotation: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
63
|
+
width: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
64
|
+
runLength: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
65
|
+
totalRise: z.ZodDefault<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";
|
|
69
|
-
left: "left";
|
|
70
|
-
right: "right";
|
|
71
69
|
both: "both";
|
|
70
|
+
right: "right";
|
|
71
|
+
left: "left";
|
|
72
72
|
}>>;
|
|
73
73
|
destinationSlabId: z.ZodOptional<z.ZodString>;
|
|
74
74
|
sourceCeilingId: z.ZodOptional<z.ZodString>;
|
|
75
75
|
createDestinationSlabOpening: z.ZodDefault<z.ZodBoolean>;
|
|
76
76
|
createSourceCeilingOpening: z.ZodDefault<z.ZodBoolean>;
|
|
77
|
-
openingWidth: z.ZodOptional<z.ZodNumber
|
|
78
|
-
openingLength: z.ZodOptional<z.ZodNumber
|
|
79
|
-
openingOffset: z.ZodDefault<z.ZodNumber
|
|
77
|
+
openingWidth: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
78
|
+
openingLength: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
79
|
+
openingOffset: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
80
80
|
openingCenter: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
81
|
-
openingRotation: z.ZodOptional<z.ZodNumber
|
|
81
|
+
openingRotation: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
82
82
|
materialPreset: z.ZodOptional<z.ZodString>;
|
|
83
83
|
name: z.ZodOptional<z.ZodString>;
|
|
84
84
|
};
|
|
@@ -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;AAaxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,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;AAaxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAQpD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;CAqBjC,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,CAiT1F"}
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { CeilingNode, getActiveRoofHeight, LevelNode, RoofNode, RoofSegmentNode, SlabNode, StairNode, StairSegmentNode, WallNode, } from '@pascal-app/core/schema';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
4
|
+
import { measurement } from './measurement';
|
|
4
5
|
import { NodeIdSchema, Vec2Schema, Vec3Schema } from './schemas';
|
|
5
6
|
const ROOF_TYPES = ['hip', 'gable', 'shed', 'gambrel', 'dutch', 'mansard', 'flat'];
|
|
6
7
|
const RAILING_MODES = ['none', 'left', 'right', 'both'];
|
|
7
8
|
export const createStoryShellInput = {
|
|
8
9
|
levelId: NodeIdSchema,
|
|
9
10
|
footprint: z.array(Vec2Schema).min(3),
|
|
10
|
-
wallHeight:
|
|
11
|
-
wallThickness:
|
|
11
|
+
wallHeight: measurement('length', 'm', { positive: true, description: 'Wall height.' }).default(2.8),
|
|
12
|
+
wallThickness: measurement('length', 'm', {
|
|
13
|
+
positive: true,
|
|
14
|
+
description: 'Wall thickness.',
|
|
15
|
+
}).default(0.16),
|
|
12
16
|
createSlab: z.boolean().default(true),
|
|
13
17
|
createCeiling: z.boolean().default(true),
|
|
14
|
-
slabElevation:
|
|
15
|
-
ceilingHeight:
|
|
18
|
+
slabElevation: measurement('length', 'm', { description: 'Slab elevation.' }).default(0.1),
|
|
19
|
+
ceilingHeight: measurement('length', 'm', {
|
|
20
|
+
positive: true,
|
|
21
|
+
description: 'Ceiling height.',
|
|
22
|
+
}).optional(),
|
|
16
23
|
namePrefix: z.string().optional(),
|
|
17
24
|
wallMaterialPreset: z.string().optional(),
|
|
18
25
|
slabMaterialPreset: z.string().optional(),
|
|
@@ -30,16 +37,23 @@ export const createRoofInput = {
|
|
|
30
37
|
roofLevelId: NodeIdSchema.optional(),
|
|
31
38
|
useDedicatedRoofLevel: z.boolean().default(true),
|
|
32
39
|
roofLevelLabel: z.string().default('Roof'),
|
|
40
|
+
// A level ordinal (story index), not a length — kept numeric.
|
|
33
41
|
roofLevelElevation: z.number().optional(),
|
|
34
|
-
roofLevelHeight:
|
|
42
|
+
roofLevelHeight: measurement('length', 'm', {
|
|
43
|
+
positive: true,
|
|
44
|
+
description: 'Roof level height.',
|
|
45
|
+
}).optional(),
|
|
35
46
|
center: Vec3Schema.optional(),
|
|
36
|
-
width:
|
|
37
|
-
depth:
|
|
47
|
+
width: measurement('length', 'm', { positive: true, description: 'Roof width.' }),
|
|
48
|
+
depth: measurement('length', 'm', { positive: true, description: 'Roof depth.' }),
|
|
38
49
|
roofType: z.enum(ROOF_TYPES).default('hip'),
|
|
39
|
-
pitch:
|
|
40
|
-
wallHeight:
|
|
41
|
-
wallThickness:
|
|
42
|
-
|
|
50
|
+
pitch: measurement('angle', 'deg', { min: 0, max: 85, description: 'Roof pitch.' }).default(35),
|
|
51
|
+
wallHeight: measurement('length', 'm', { min: 0, description: 'Knee-wall height.' }).default(0.35),
|
|
52
|
+
wallThickness: measurement('length', 'm', {
|
|
53
|
+
positive: true,
|
|
54
|
+
description: 'Wall thickness.',
|
|
55
|
+
}).default(0.16),
|
|
56
|
+
overhang: measurement('length', 'm', { min: 0, description: 'Eave overhang.' }).default(0.45),
|
|
43
57
|
materialPreset: z.string().optional(),
|
|
44
58
|
name: z.string().optional(),
|
|
45
59
|
};
|
|
@@ -54,21 +68,33 @@ export const createStairBetweenLevelsInput = {
|
|
|
54
68
|
fromLevelId: NodeIdSchema,
|
|
55
69
|
toLevelId: NodeIdSchema,
|
|
56
70
|
position: Vec3Schema,
|
|
57
|
-
rotation:
|
|
58
|
-
width:
|
|
59
|
-
runLength:
|
|
60
|
-
|
|
71
|
+
rotation: measurement('angle', 'rad', { description: 'Y-axis rotation.' }).default(0),
|
|
72
|
+
width: measurement('length', 'm', { positive: true, description: 'Stair width.' }).default(1),
|
|
73
|
+
runLength: measurement('length', 'm', {
|
|
74
|
+
positive: true,
|
|
75
|
+
description: 'Horizontal run length.',
|
|
76
|
+
}).default(3),
|
|
77
|
+
totalRise: measurement('length', 'm', {
|
|
78
|
+
positive: true,
|
|
79
|
+
description: 'Total vertical rise.',
|
|
80
|
+
}).default(2.8),
|
|
61
81
|
stepCount: z.number().int().positive().default(14),
|
|
62
82
|
railingMode: z.enum(RAILING_MODES).default('both'),
|
|
63
83
|
destinationSlabId: NodeIdSchema.optional(),
|
|
64
84
|
sourceCeilingId: NodeIdSchema.optional(),
|
|
65
85
|
createDestinationSlabOpening: z.boolean().default(true),
|
|
66
86
|
createSourceCeilingOpening: z.boolean().default(true),
|
|
67
|
-
openingWidth:
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
openingWidth: measurement('length', 'm', {
|
|
88
|
+
positive: true,
|
|
89
|
+
description: 'Floor opening width.',
|
|
90
|
+
}).optional(),
|
|
91
|
+
openingLength: measurement('length', 'm', {
|
|
92
|
+
positive: true,
|
|
93
|
+
description: 'Floor opening length.',
|
|
94
|
+
}).optional(),
|
|
95
|
+
openingOffset: measurement('length', 'm', { min: 0, description: 'Opening offset.' }).default(0),
|
|
70
96
|
openingCenter: Vec2Schema.optional(),
|
|
71
|
-
openingRotation:
|
|
97
|
+
openingRotation: measurement('angle', 'rad', { description: 'Opening rotation.' }).optional(),
|
|
72
98
|
materialPreset: z.string().optional(),
|
|
73
99
|
name: z.string().optional(),
|
|
74
100
|
};
|
|
@@ -4,7 +4,7 @@ import type { SceneOperations } from '../operations';
|
|
|
4
4
|
export declare const createLevelInput: {
|
|
5
5
|
buildingId: z.ZodString;
|
|
6
6
|
elevation: z.ZodOptional<z.ZodNumber>;
|
|
7
|
-
height: z.ZodOptional<z.ZodNumber
|
|
7
|
+
height: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
8
8
|
label: z.ZodOptional<z.ZodString>;
|
|
9
9
|
};
|
|
10
10
|
export declare const createLevelOutput: {
|
|
@@ -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;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,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;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,gBAAgB;;;;;CAQ5B,CAAA;AAED,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA0CpF"}
|
|
@@ -2,11 +2,15 @@ import { LevelNode } from '@pascal-app/core/schema';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { ErrorCode, throwMcpError } from './errors';
|
|
4
4
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
5
|
+
import { measurement } from './measurement';
|
|
5
6
|
import { NodeIdSchema } from './schemas';
|
|
6
7
|
export const createLevelInput = {
|
|
7
8
|
buildingId: NodeIdSchema,
|
|
8
9
|
elevation: z.number().optional(),
|
|
9
|
-
height:
|
|
10
|
+
height: measurement('length', 'm', {
|
|
11
|
+
min: 0,
|
|
12
|
+
description: 'Level height (stored in metadata).',
|
|
13
|
+
}).optional(),
|
|
10
14
|
label: z.string().optional(),
|
|
11
15
|
};
|
|
12
16
|
export const createLevelOutput = {
|
|
@@ -5,8 +5,8 @@ export declare const createWallInput: {
|
|
|
5
5
|
levelId: z.ZodString;
|
|
6
6
|
start: z.ZodArray<z.ZodNumber>;
|
|
7
7
|
end: z.ZodArray<z.ZodNumber>;
|
|
8
|
-
thickness: z.ZodOptional<z.ZodNumber
|
|
9
|
-
height: z.ZodOptional<z.ZodNumber
|
|
8
|
+
thickness: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
9
|
+
height: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
10
10
|
};
|
|
11
11
|
export declare const createWallOutput: {
|
|
12
12
|
wallId: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-wall.d.ts","sourceRoot":"","sources":["../../src/tools/create-wall.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;
|
|
1
|
+
{"version":3,"file":"create-wall.d.ts","sourceRoot":"","sources":["../../src/tools/create-wall.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;AAMpD,eAAO,MAAM,eAAe;;;;;;CAS3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAgDnF"}
|
|
@@ -2,13 +2,17 @@ import { WallNode } from '@pascal-app/core/schema';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { ErrorCode, throwMcpError } from './errors';
|
|
4
4
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
5
|
+
import { measurement } from './measurement';
|
|
5
6
|
import { NodeIdSchema, Vec2Schema } from './schemas';
|
|
6
7
|
export const createWallInput = {
|
|
7
8
|
levelId: NodeIdSchema,
|
|
8
9
|
start: Vec2Schema,
|
|
9
10
|
end: Vec2Schema,
|
|
10
|
-
thickness:
|
|
11
|
-
|
|
11
|
+
thickness: measurement('length', 'm', {
|
|
12
|
+
positive: true,
|
|
13
|
+
description: 'Wall thickness.',
|
|
14
|
+
}).optional(),
|
|
15
|
+
height: measurement('length', 'm', { positive: true, description: 'Wall height.' }).optional(),
|
|
12
16
|
};
|
|
13
17
|
export const createWallOutput = {
|
|
14
18
|
wallId: z.string(),
|
|
@@ -4,12 +4,12 @@ import type { SceneOperations } from '../operations';
|
|
|
4
4
|
export declare const cutOpeningInput: {
|
|
5
5
|
wallId: z.ZodString;
|
|
6
6
|
type: z.ZodEnum<{
|
|
7
|
-
window: "window";
|
|
8
7
|
door: "door";
|
|
8
|
+
window: "window";
|
|
9
9
|
}>;
|
|
10
10
|
position: z.ZodNumber;
|
|
11
|
-
width: z.ZodNumber
|
|
12
|
-
height: z.ZodNumber
|
|
11
|
+
width: z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>;
|
|
12
|
+
height: z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>;
|
|
13
13
|
};
|
|
14
14
|
export declare const cutOpeningOutput: {
|
|
15
15
|
openingId: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cut-opening.d.ts","sourceRoot":"","sources":["../../src/tools/cut-opening.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;
|
|
1
|
+
{"version":3,"file":"cut-opening.d.ts","sourceRoot":"","sources":["../../src/tools/cut-opening.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;AAOpD,eAAO,MAAM,eAAe;;;;;;;;;CAM3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAyDnF"}
|
|
@@ -3,13 +3,14 @@ import { z } from 'zod';
|
|
|
3
3
|
import { ErrorCode, throwMcpError } from './errors';
|
|
4
4
|
import { wallLength, wallLocalXFromT } from './geometry';
|
|
5
5
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
6
|
+
import { measurement } from './measurement';
|
|
6
7
|
import { NodeIdSchema } from './schemas';
|
|
7
8
|
export const cutOpeningInput = {
|
|
8
9
|
wallId: NodeIdSchema,
|
|
9
10
|
type: z.enum(['door', 'window']),
|
|
10
11
|
position: z.number().min(0).max(1),
|
|
11
|
-
width:
|
|
12
|
-
height:
|
|
12
|
+
width: measurement('length', 'm', { positive: true, description: 'Opening width.' }),
|
|
13
|
+
height: measurement('length', 'm', { positive: true, description: 'Opening height.' }),
|
|
13
14
|
};
|
|
14
15
|
export const cutOpeningOutput = {
|
|
15
16
|
openingId: z.string(),
|
|
@@ -8,9 +8,10 @@ export declare const findNodesInput: {
|
|
|
8
8
|
level: "level";
|
|
9
9
|
wall: "wall";
|
|
10
10
|
fence: "fence";
|
|
11
|
+
slab: "slab";
|
|
12
|
+
door: "door";
|
|
11
13
|
item: "item";
|
|
12
14
|
zone: "zone";
|
|
13
|
-
slab: "slab";
|
|
14
15
|
stair: "stair";
|
|
15
16
|
ceiling: "ceiling";
|
|
16
17
|
roof: "roof";
|
|
@@ -19,7 +20,6 @@ export declare const findNodesInput: {
|
|
|
19
20
|
scan: "scan";
|
|
20
21
|
guide: "guide";
|
|
21
22
|
window: "window";
|
|
22
|
-
door: "door";
|
|
23
23
|
}>>;
|
|
24
24
|
parentId: z.ZodOptional<z.ZodString>;
|
|
25
25
|
levelId: z.ZodOptional<z.ZodString>;
|
package/dist/tools/find-nodes.js
CHANGED
|
@@ -79,7 +79,7 @@ export function registerFindNodes(server, bridge) {
|
|
|
79
79
|
// Zone-polygon filter: point-in-polygon on a representative 2D point.
|
|
80
80
|
if (zoneId) {
|
|
81
81
|
const zone = bridge.getNode(zoneId);
|
|
82
|
-
if (
|
|
82
|
+
if (zone?.type !== 'zone') {
|
|
83
83
|
// Unknown zoneId → return empty list rather than throw; matches
|
|
84
84
|
// typical "filter" semantics.
|
|
85
85
|
results = [];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Kind } from '@pascal-app/lingo';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* A zod field for a measurement tool argument that a model may emit as a bare
|
|
5
|
+
* number OR as natural language. `measurement('length', 'm')` accepts `0.15`,
|
|
6
|
+
* `"6 in"`, `"180cm"`, `"2 ft 3 in"`; `measurement('angle', 'deg')` accepts
|
|
7
|
+
* `45`, `"45°"`, `"1.57rad"`, `"0.25 turn"`. The value is canonicalized (via
|
|
8
|
+
* `@pascal-app/lingo`) to a number in `unit` — the exact unit the tool handler
|
|
9
|
+
* already expects — so no handler change is needed. `min`/`max` (in `unit`)
|
|
10
|
+
* reject out-of-range values with a model-readable message.
|
|
11
|
+
*
|
|
12
|
+
* The emitted JSON Schema is `number | string`, so the model is free to answer
|
|
13
|
+
* in whatever unit it is thinking in; AI SDK v6 applies the transform and
|
|
14
|
+
* forwards the canonical number to the tool executor.
|
|
15
|
+
*
|
|
16
|
+
* Tool-boundary safety: genuinely ambiguous separators (`"1,234"`, which could
|
|
17
|
+
* mean 1234 or 1.234) are rejected rather than silently absorbed, so a European
|
|
18
|
+
* decimal never becomes a 1000× value.
|
|
19
|
+
*
|
|
20
|
+
* NOTE: intentionally duplicated as `editor/packages/mcp/src/tools/measurement.ts`
|
|
21
|
+
* (MCP tools) and `packages/ai/src/tools/scene/measurement.ts` (AI-chat tools).
|
|
22
|
+
* The two tool stacks live in different packages — one inside the `editor`
|
|
23
|
+
* submodule — so they can't share a module. Keep the two copies identical; the
|
|
24
|
+
* clean long-term dedup is a zod-field adapter exported from `@pascal-app/lingo`.
|
|
25
|
+
*/
|
|
26
|
+
export interface MeasurementOptions {
|
|
27
|
+
/** Semantic description (e.g. "Wall thickness"). The natural-language note is appended. */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Upper bound, in `unit`. */
|
|
30
|
+
max?: number;
|
|
31
|
+
/** Inclusive lower bound, in `unit`. */
|
|
32
|
+
min?: number;
|
|
33
|
+
/** Require the value to be strictly greater than 0 (for non-zero dimensions). */
|
|
34
|
+
positive?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function measurement(kind: Kind, unit: string, opts?: MeasurementOptions): z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>;
|
|
37
|
+
//# sourceMappingURL=measurement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measurement.d.ts","sourceRoot":"","sources":["../../src/tools/measurement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAiB,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AA6CD,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,kBAAuB,uGAoDlF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { parseQuantity } from '@pascal-app/lingo';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
function unitNoun(unit) {
|
|
4
|
+
switch (unit) {
|
|
5
|
+
case 'm':
|
|
6
|
+
return 'meters';
|
|
7
|
+
case 'deg':
|
|
8
|
+
return 'degrees';
|
|
9
|
+
case 'rad':
|
|
10
|
+
return 'radians';
|
|
11
|
+
default:
|
|
12
|
+
return unit;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function naturalLanguageNote(kind, unit) {
|
|
16
|
+
let examples;
|
|
17
|
+
if (kind === 'angle') {
|
|
18
|
+
// Lead with an example in the field's own unit so a bare number is read
|
|
19
|
+
// the way the model intends (a bare "45" in a radians field is 45 rad).
|
|
20
|
+
examples =
|
|
21
|
+
unit === 'rad' ? '1.5708, "90°", "1.57rad", "0.25 turn"' : '45, "45°", "1.57rad", "0.25 turn"';
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
examples = '0.9, "6 ft", "180cm", "2 ft 3 in"';
|
|
25
|
+
}
|
|
26
|
+
return `Accepts a number (${unitNoun(unit)}) or a natural-language string; other units are converted. e.g. ${examples}.`;
|
|
27
|
+
}
|
|
28
|
+
function boundsNote(unit, min, max, positive) {
|
|
29
|
+
if (positive) {
|
|
30
|
+
return max === undefined
|
|
31
|
+
? ` Must be greater than 0 ${unit}.`
|
|
32
|
+
: ` Greater than 0, up to ${max} ${unit}.`;
|
|
33
|
+
}
|
|
34
|
+
if (min !== undefined && max !== undefined)
|
|
35
|
+
return ` Range ${min}–${max} ${unit}.`;
|
|
36
|
+
if (min !== undefined)
|
|
37
|
+
return ` Minimum ${min} ${unit}.`;
|
|
38
|
+
if (max !== undefined)
|
|
39
|
+
return ` Maximum ${max} ${unit}.`;
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
export function measurement(kind, unit, opts = {}) {
|
|
43
|
+
const { min, max, positive = false } = opts;
|
|
44
|
+
const description = [
|
|
45
|
+
opts.description,
|
|
46
|
+
naturalLanguageNote(kind, unit) + boundsNote(unit, min, max, positive),
|
|
47
|
+
]
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(' ');
|
|
50
|
+
return z
|
|
51
|
+
.union([z.number(), z.string()])
|
|
52
|
+
.transform((val, ctx) => {
|
|
53
|
+
let value;
|
|
54
|
+
if (typeof val === 'number') {
|
|
55
|
+
value = val;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const result = parseQuantity(val, {
|
|
59
|
+
kind,
|
|
60
|
+
unit,
|
|
61
|
+
strictness: 'forgiving',
|
|
62
|
+
// A genuinely ambiguous separator ("1,234") must not silently become
|
|
63
|
+
// a 1000× value at a tool boundary — fail so the model self-corrects.
|
|
64
|
+
escalate: { AMBIGUOUS_NUMBER: 'error' },
|
|
65
|
+
});
|
|
66
|
+
if (!result.ok) {
|
|
67
|
+
ctx.addIssue({
|
|
68
|
+
code: 'custom',
|
|
69
|
+
message: result.issues[0]?.message ?? `Could not read "${val}" as a ${kind}.`,
|
|
70
|
+
});
|
|
71
|
+
return z.NEVER;
|
|
72
|
+
}
|
|
73
|
+
value = result.quantity.to(unit).value;
|
|
74
|
+
}
|
|
75
|
+
if (!Number.isFinite(value)) {
|
|
76
|
+
ctx.addIssue({ code: 'custom', message: `Value must be a finite ${kind} in ${unit}.` });
|
|
77
|
+
return z.NEVER;
|
|
78
|
+
}
|
|
79
|
+
if (positive && value <= 0) {
|
|
80
|
+
ctx.addIssue({ code: 'custom', message: `Must be greater than 0 ${unit} (got ${value}).` });
|
|
81
|
+
return z.NEVER;
|
|
82
|
+
}
|
|
83
|
+
if (min !== undefined && value < min) {
|
|
84
|
+
ctx.addIssue({ code: 'custom', message: `Must be at least ${min} ${unit} (got ${value}).` });
|
|
85
|
+
return z.NEVER;
|
|
86
|
+
}
|
|
87
|
+
if (max !== undefined && value > max) {
|
|
88
|
+
ctx.addIssue({ code: 'custom', message: `Must be at most ${max} ${unit} (got ${value}).` });
|
|
89
|
+
return z.NEVER;
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
})
|
|
93
|
+
.describe(description);
|
|
94
|
+
}
|
|
@@ -10,8 +10,8 @@ export declare const photoToSceneInput: {
|
|
|
10
10
|
scaleHint: z.ZodOptional<z.ZodString>;
|
|
11
11
|
name: z.ZodDefault<z.ZodString>;
|
|
12
12
|
save: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
-
defaultWallThickness: z.ZodDefault<z.ZodNumber
|
|
14
|
-
defaultWallHeight: z.ZodDefault<z.ZodNumber
|
|
13
|
+
defaultWallThickness: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
14
|
+
defaultWallHeight: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
15
15
|
};
|
|
16
16
|
export declare const photoToSceneOutput: {
|
|
17
17
|
sceneId: z.ZodOptional<z.ZodString>;
|
|
@@ -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;
|
|
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;AAiTD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA6ErF"}
|
|
@@ -2,6 +2,7 @@ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
import { AnyNode, BuildingNode, LevelNode, SiteNode, WallNode, ZoneNode, } from '@pascal-app/core/schema';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { appendLiveSceneEvent } from '../live-sync';
|
|
5
|
+
import { measurement } from '../measurement';
|
|
5
6
|
/**
|
|
6
7
|
* Input shape for the `photo_to_scene` orchestrator. `image` matches the
|
|
7
8
|
* contract documented on `analyze_floorplan_image` — base64 or http(s) URL.
|
|
@@ -11,8 +12,14 @@ export const photoToSceneInput = {
|
|
|
11
12
|
scaleHint: z.string().optional().describe('e.g. "1 cm = 1 m" or "approx 80 m²"'),
|
|
12
13
|
name: z.string().default('Scene from photo'),
|
|
13
14
|
save: z.boolean().default(true),
|
|
14
|
-
defaultWallThickness:
|
|
15
|
-
|
|
15
|
+
defaultWallThickness: measurement('length', 'm', {
|
|
16
|
+
positive: true,
|
|
17
|
+
description: 'Default wall thickness.',
|
|
18
|
+
}).default(0.2),
|
|
19
|
+
defaultWallHeight: measurement('length', 'm', {
|
|
20
|
+
positive: true,
|
|
21
|
+
description: 'Default wall height.',
|
|
22
|
+
}).default(2.6),
|
|
16
23
|
};
|
|
17
24
|
export const photoToSceneOutput = {
|
|
18
25
|
sceneId: z.string().optional(),
|
|
@@ -5,7 +5,7 @@ export declare const placeItemInput: {
|
|
|
5
5
|
catalogItemId: z.ZodString;
|
|
6
6
|
targetNodeId: z.ZodString;
|
|
7
7
|
position: z.ZodArray<z.ZodNumber>;
|
|
8
|
-
rotation: z.ZodOptional<z.ZodNumber
|
|
8
|
+
rotation: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
9
9
|
};
|
|
10
10
|
export declare const placeItemOutput: {
|
|
11
11
|
itemId: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"place-item.d.ts","sourceRoot":"","sources":["../../src/tools/place-item.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;
|
|
1
|
+
{"version":3,"file":"place-item.d.ts","sourceRoot":"","sources":["../../src/tools/place-item.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;AAQpD,eAAO,MAAM,cAAc;;;;;CAK1B,CAAA;AAED,eAAO,MAAM,eAAe;;;CAG3B,CAAA;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsFlF"}
|
package/dist/tools/place-item.js
CHANGED
|
@@ -4,12 +4,13 @@ import { findCatalogItem } from './asset-catalog';
|
|
|
4
4
|
import { ErrorCode, throwMcpError } from './errors';
|
|
5
5
|
import { projectWorldPointToWallLocalX, wallLength } from './geometry';
|
|
6
6
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
7
|
+
import { measurement } from './measurement';
|
|
7
8
|
import { NodeIdSchema, Vec3Schema } from './schemas';
|
|
8
9
|
export const placeItemInput = {
|
|
9
10
|
catalogItemId: z.string().min(1),
|
|
10
11
|
targetNodeId: NodeIdSchema,
|
|
11
12
|
position: Vec3Schema,
|
|
12
|
-
rotation:
|
|
13
|
+
rotation: measurement('angle', 'rad', { description: 'Y-axis rotation.' }).optional(),
|
|
13
14
|
};
|
|
14
15
|
export const placeItemOutput = {
|
|
15
16
|
itemId: z.string(),
|
|
@@ -14,8 +14,8 @@ export declare const createRoomInput: {
|
|
|
14
14
|
name: z.ZodString;
|
|
15
15
|
polygon: z.ZodArray<z.ZodArray<z.ZodNumber>>;
|
|
16
16
|
color: z.ZodOptional<z.ZodString>;
|
|
17
|
-
wallHeight: z.ZodOptional<z.ZodNumber
|
|
18
|
-
wallThickness: z.ZodOptional<z.ZodNumber
|
|
17
|
+
wallHeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
18
|
+
wallThickness: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
19
19
|
};
|
|
20
20
|
export declare const createRoomOutput: {
|
|
21
21
|
zoneId: z.ZodString;
|
|
@@ -28,11 +28,11 @@ export declare const addDoorInput: {
|
|
|
28
28
|
wallId: z.ZodString;
|
|
29
29
|
t: z.ZodOptional<z.ZodNumber>;
|
|
30
30
|
position: z.ZodOptional<z.ZodNumber>;
|
|
31
|
-
width: z.ZodOptional<z.ZodNumber
|
|
32
|
-
height: z.ZodOptional<z.ZodNumber
|
|
31
|
+
width: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
32
|
+
height: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
33
33
|
hingesSide: z.ZodOptional<z.ZodEnum<{
|
|
34
|
-
left: "left";
|
|
35
34
|
right: "right";
|
|
35
|
+
left: "left";
|
|
36
36
|
}>>;
|
|
37
37
|
swingDirection: z.ZodOptional<z.ZodEnum<{
|
|
38
38
|
inward: "inward";
|
|
@@ -52,9 +52,9 @@ export declare const addWindowInput: {
|
|
|
52
52
|
wallId: z.ZodString;
|
|
53
53
|
t: z.ZodOptional<z.ZodNumber>;
|
|
54
54
|
position: z.ZodOptional<z.ZodNumber>;
|
|
55
|
-
width: z.ZodOptional<z.ZodNumber
|
|
56
|
-
height: z.ZodOptional<z.ZodNumber
|
|
57
|
-
sillHeight: z.ZodOptional<z.ZodNumber
|
|
55
|
+
width: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
56
|
+
height: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
57
|
+
sillHeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
58
58
|
};
|
|
59
59
|
export declare const addWindowOutput: {
|
|
60
60
|
windowId: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"room-tools.d.ts","sourceRoot":"","sources":["../../src/tools/room-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAWxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"room-tools.d.ts","sourceRoot":"","sources":["../../src/tools/room-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAWxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AA2BpD,eAAO,MAAM,iBAAiB;;;CAG7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;CAG9B,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;CAa3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;CAM5B,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAQxB,CAAA;AAED,eAAO,MAAM,aAAa;;;;;;;;CAQzB,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;;CAU1B,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;;;CAS3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;CAM5B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAA;AA+PD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsB5D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAqDnF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA2ChF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA0ClF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsEpF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAMlF"}
|
package/dist/tools/room-tools.js
CHANGED
|
@@ -4,6 +4,7 @@ import { findCatalogItem, searchCatalogItems } from './asset-catalog';
|
|
|
4
4
|
import { ErrorCode, throwMcpError } from './errors';
|
|
5
5
|
import { pointInBoundsWithPadding, polygonArea, polygonBounds, wallLength, wallLocalXFromT, } from './geometry';
|
|
6
6
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
7
|
+
import { measurement } from './measurement';
|
|
7
8
|
import { NodeIdSchema, Vec2Schema } from './schemas';
|
|
8
9
|
const ROOM_TYPES = [
|
|
9
10
|
'bedroom',
|
|
@@ -29,8 +30,14 @@ export const createRoomInput = {
|
|
|
29
30
|
name: z.string().min(1),
|
|
30
31
|
polygon: z.array(Vec2Schema).min(3),
|
|
31
32
|
color: z.string().optional(),
|
|
32
|
-
wallHeight:
|
|
33
|
-
|
|
33
|
+
wallHeight: measurement('length', 'm', {
|
|
34
|
+
positive: true,
|
|
35
|
+
description: 'Wall height.',
|
|
36
|
+
}).optional(),
|
|
37
|
+
wallThickness: measurement('length', 'm', {
|
|
38
|
+
positive: true,
|
|
39
|
+
description: 'Wall thickness.',
|
|
40
|
+
}).optional(),
|
|
34
41
|
};
|
|
35
42
|
export const createRoomOutput = {
|
|
36
43
|
zoneId: z.string(),
|
|
@@ -43,8 +50,8 @@ export const addDoorInput = {
|
|
|
43
50
|
wallId: NodeIdSchema,
|
|
44
51
|
t: z.number().min(0).max(1).optional(),
|
|
45
52
|
position: z.number().min(0).max(1).optional(),
|
|
46
|
-
width:
|
|
47
|
-
height:
|
|
53
|
+
width: measurement('length', 'm', { positive: true, description: 'Door width.' }).optional(),
|
|
54
|
+
height: measurement('length', 'm', { positive: true, description: 'Door height.' }).optional(),
|
|
48
55
|
hingesSide: z.enum(['left', 'right']).optional(),
|
|
49
56
|
swingDirection: z.enum(['inward', 'outward']).optional(),
|
|
50
57
|
};
|
|
@@ -61,9 +68,12 @@ export const addWindowInput = {
|
|
|
61
68
|
wallId: NodeIdSchema,
|
|
62
69
|
t: z.number().min(0).max(1).optional(),
|
|
63
70
|
position: z.number().min(0).max(1).optional(),
|
|
64
|
-
width:
|
|
65
|
-
height:
|
|
66
|
-
sillHeight:
|
|
71
|
+
width: measurement('length', 'm', { positive: true, description: 'Window width.' }).optional(),
|
|
72
|
+
height: measurement('length', 'm', { positive: true, description: 'Window height.' }).optional(),
|
|
73
|
+
sillHeight: measurement('length', 'm', {
|
|
74
|
+
min: 0,
|
|
75
|
+
description: 'Sill height above floor.',
|
|
76
|
+
}).optional(),
|
|
67
77
|
};
|
|
68
78
|
export const addWindowOutput = {
|
|
69
79
|
windowId: z.string(),
|
|
@@ -305,7 +305,7 @@ function parentListsChild(parent, childId) {
|
|
|
305
305
|
}
|
|
306
306
|
function levelSummary(bridge, levelId) {
|
|
307
307
|
const level = bridge.getNode(levelId);
|
|
308
|
-
if (
|
|
308
|
+
if (level?.type !== 'level') {
|
|
309
309
|
throw new Error(`Level not found: ${levelId}`);
|
|
310
310
|
}
|
|
311
311
|
const nodes = nodesOnLevel(bridge, levelId);
|
|
@@ -541,7 +541,7 @@ export function registerVerifyScene(server, bridge) {
|
|
|
541
541
|
for (const node of Object.values(bridge.getNodes())) {
|
|
542
542
|
if (node.type === 'door' || node.type === 'window') {
|
|
543
543
|
const parent = node.parentId ? bridge.getNode(node.parentId) : null;
|
|
544
|
-
if (
|
|
544
|
+
if (parent?.type !== 'wall') {
|
|
545
545
|
issues.push(`${node.type} ${node.id} is not parented to a wall`);
|
|
546
546
|
continue;
|
|
547
547
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Model Context Protocol server for Pascal 3D editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -48,21 +48,22 @@
|
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsc --build",
|
|
51
|
-
"dev": "
|
|
51
|
+
"dev": "tsgo --build --watch",
|
|
52
52
|
"start": "bun dist/bin/pascal-mcp.js",
|
|
53
53
|
"test": "bun test",
|
|
54
54
|
"smoke": "bun run scripts/smoke.ts",
|
|
55
55
|
"prepublishOnly": "bun run build && bun test"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@pascal-app/core": "^0.9.
|
|
58
|
+
"@pascal-app/core": "^0.9.2"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
62
|
+
"@pascal-app/lingo": "^0.2.0",
|
|
62
63
|
"zod": "^4.3.5"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
|
-
"@pascal-app/core": "^0.9.
|
|
66
|
+
"@pascal-app/core": "^0.9.2",
|
|
66
67
|
"@pascal/typescript-config": "*",
|
|
67
68
|
"@types/node": "^22.19.20",
|
|
68
69
|
"typescript": "6.0.3"
|