@pascal-app/mcp 0.3.1 → 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.
Files changed (41) hide show
  1. package/README.md +2 -1
  2. package/dist/bridge/scene-bridge.d.ts.map +1 -1
  3. package/dist/bridge/scene-bridge.js +6 -0
  4. package/dist/operations/scene-operations.js +7 -6
  5. package/dist/resources/constraints.js +1 -1
  6. package/dist/templates/empty-studio.d.ts.map +1 -1
  7. package/dist/templates/empty-studio.js +2 -4
  8. package/dist/templates/garden-house.js +1 -1
  9. package/dist/templates/two-bedroom.js +1 -1
  10. package/dist/tools/construction-tools.d.ts +21 -21
  11. package/dist/tools/construction-tools.d.ts.map +1 -1
  12. package/dist/tools/construction-tools.js +81 -33
  13. package/dist/tools/create-level.d.ts +1 -1
  14. package/dist/tools/create-level.d.ts.map +1 -1
  15. package/dist/tools/create-level.js +19 -7
  16. package/dist/tools/create-wall.d.ts +2 -2
  17. package/dist/tools/create-wall.d.ts.map +1 -1
  18. package/dist/tools/create-wall.js +6 -2
  19. package/dist/tools/cut-opening.d.ts +3 -3
  20. package/dist/tools/cut-opening.d.ts.map +1 -1
  21. package/dist/tools/cut-opening.js +3 -2
  22. package/dist/tools/describe-node.d.ts.map +1 -1
  23. package/dist/tools/describe-node.js +14 -5
  24. package/dist/tools/find-nodes.d.ts +2 -2
  25. package/dist/tools/find-nodes.js +1 -1
  26. package/dist/tools/measurement.d.ts +37 -0
  27. package/dist/tools/measurement.d.ts.map +1 -0
  28. package/dist/tools/measurement.js +94 -0
  29. package/dist/tools/photo-to-scene/photo-to-scene.d.ts +2 -2
  30. package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
  31. package/dist/tools/photo-to-scene/photo-to-scene.js +14 -5
  32. package/dist/tools/place-item.d.ts +1 -1
  33. package/dist/tools/place-item.d.ts.map +1 -1
  34. package/dist/tools/place-item.js +2 -1
  35. package/dist/tools/room-tools.d.ts +8 -8
  36. package/dist/tools/room-tools.d.ts.map +1 -1
  37. package/dist/tools/room-tools.js +17 -7
  38. package/dist/tools/scene-query.d.ts +4 -0
  39. package/dist/tools/scene-query.d.ts.map +1 -1
  40. package/dist/tools/scene-query.js +24 -11
  41. 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 wraps its content in a 90° rotation (`FLOORPLAN_VIEW_ROTATION_DEG`), and
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":"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"}
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;IAenE;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IA6CzC,oDAAoD;IACpD,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,GAAG,IAAI;IAKtC,iEAAiE;IACjE,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC;IAItC,qBAAqB;IACrB,cAAc,IAAI,SAAS,EAAE;IAI7B;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,QAAQ,EAAE,SAAS,GAAG,OAAO,EAAE;IAuC3C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,EAAE;IAqBrC,qEAAqE;IACrE,SAAS,CAAC,MAAM,EAAE;QAChB,IAAI,CAAC,EAAE,WAAW,CAAA;QAClB,QAAQ,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;QAC3B,OAAO,CAAC,EAAE,SAAS,CAAA;KACpB,GAAG,OAAO,EAAE;IAiBb,4EAA4E;IAC5E,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IAQ/C;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS;IAK1D,iEAAiE;IACjE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;IAOvD;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,UAAQ,GAAG,MAAM,EAAE;IAwBpD;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG;QAC5B,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,SAAS,EAAE,CAAA;QACvB,UAAU,EAAE,SAAS,EAAE,CAAA;KACxB;IAkHD,yDAAyD;IACzD,IAAI,CAAC,KAAK,SAAI,GAAG,MAAM;IAOvB,yDAAyD;IACzD,IAAI,CAAC,KAAK,SAAI,GAAG,MAAM;IAOvB;;;OAGG;IACH,aAAa,IAAI,gBAAgB;IAiBjC;;;OAGG;IACH,UAAU,IAAI,MAAM,EAAE;IAStB,yCAAyC;IACzC,UAAU,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;IAQxD,4CAA4C;IAC5C,YAAY,IAAI,IAAI;IAMpB;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAqBjC;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;CAkB5B"}
@@ -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) {
@@ -56,6 +56,7 @@ class SceneOperationsFacade {
56
56
  nodes: exported.nodes,
57
57
  rootNodeIds: exported.rootNodeIds,
58
58
  collections: exported.collections,
59
+ installedPlugins: exported.installedPlugins,
59
60
  };
60
61
  }
61
62
  loadJSON(json) {
@@ -165,17 +166,17 @@ class SceneOperationsFacade {
165
166
  return this.requireStore().rename(id, newName, options);
166
167
  }
167
168
  async appendSceneEvent(options) {
168
- const append = this.requireStore().appendSceneEvent;
169
- if (!append)
169
+ const store = this.requireStore();
170
+ if (!store.appendSceneEvent)
170
171
  return null;
171
- return append(options);
172
+ return store.appendSceneEvent(options);
172
173
  }
173
174
  async listSceneEvents(id, options) {
174
- const list = this.requireStore().listSceneEvents;
175
- if (!list) {
175
+ const store = this.requireStore();
176
+ if (!store.listSceneEvents) {
176
177
  throw new Error('scene_events_unavailable');
177
178
  }
178
- return list(id, options);
179
+ return store.listSceneEvents(id, options);
179
180
  }
180
181
  requireBridge() {
181
182
  if (!this.#bridge) {
@@ -15,7 +15,7 @@ const EMPTY_MITER_DATA = {
15
15
  };
16
16
  function buildPayload(bridge, levelId) {
17
17
  const level = bridge.getNode(levelId);
18
- if (!level || level.type !== 'level') {
18
+ if (level?.type !== 'level') {
19
19
  return {
20
20
  error: 'level_not_found',
21
21
  levelId,
@@ -1 +1 @@
1
- {"version":3,"file":"empty-studio.d.ts","sourceRoot":"","sources":["../../src/templates/empty-studio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAsPpE,eAAO,MAAM,QAAQ,EAAE,UAA4B,CAAA;AAEnD,eAAO,MAAM,QAAQ;;;;CAKX,CAAA"}
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,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.ZodOptional<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.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";
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;AAOpD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;CAajC,CAAA;AAED,eAAO,MAAM,sBAAsB;;;;;;CAMlC,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;CAiB3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;CAM5B,CAAA;AAED,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;CAqBzC,CAAA;AAED,eAAO,MAAM,8BAA8B;;;;;;CAM1C,CAAA;AA4GD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAiT1F"}
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,18 +1,29 @@
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';
5
+ import { measurement } from './measurement';
4
6
  import { NodeIdSchema, Vec2Schema, Vec3Schema } from './schemas';
5
7
  const ROOF_TYPES = ['hip', 'gable', 'shed', 'gambrel', 'dutch', 'mansard', 'flat'];
6
8
  const RAILING_MODES = ['none', 'left', 'right', 'both'];
7
9
  export const createStoryShellInput = {
8
10
  levelId: NodeIdSchema,
9
11
  footprint: z.array(Vec2Schema).min(3),
10
- wallHeight: z.number().positive().default(2.8),
11
- wallThickness: z.number().positive().default(0.16),
12
+ wallHeight: measurement('length', 'm', {
13
+ positive: true,
14
+ description: 'Explicit wall height override. Omit for level-plane-bound walls.',
15
+ }).optional(),
16
+ wallThickness: measurement('length', 'm', {
17
+ positive: true,
18
+ description: 'Wall thickness.',
19
+ }).default(0.16),
12
20
  createSlab: z.boolean().default(true),
13
21
  createCeiling: z.boolean().default(true),
14
- slabElevation: z.number().default(0.1),
15
- ceilingHeight: z.number().positive().optional(),
22
+ slabElevation: measurement('length', 'm', { description: 'Slab elevation.' }).default(0.1),
23
+ ceilingHeight: measurement('length', 'm', {
24
+ positive: true,
25
+ description: 'Ceiling height.',
26
+ }).optional(),
16
27
  namePrefix: z.string().optional(),
17
28
  wallMaterialPreset: z.string().optional(),
18
29
  slabMaterialPreset: z.string().optional(),
@@ -30,16 +41,23 @@ export const createRoofInput = {
30
41
  roofLevelId: NodeIdSchema.optional(),
31
42
  useDedicatedRoofLevel: z.boolean().default(true),
32
43
  roofLevelLabel: z.string().default('Roof'),
44
+ // A level ordinal (story index), not a length — kept numeric.
33
45
  roofLevelElevation: z.number().optional(),
34
- roofLevelHeight: z.number().positive().optional(),
46
+ roofLevelHeight: measurement('length', 'm', {
47
+ positive: true,
48
+ description: 'Roof level height.',
49
+ }).optional(),
35
50
  center: Vec3Schema.optional(),
36
- width: z.number().positive(),
37
- depth: z.number().positive(),
51
+ width: measurement('length', 'm', { positive: true, description: 'Roof width.' }),
52
+ depth: measurement('length', 'm', { positive: true, description: 'Roof depth.' }),
38
53
  roofType: z.enum(ROOF_TYPES).default('hip'),
39
- pitch: z.number().min(0).max(85).default(35),
40
- wallHeight: z.number().min(0).default(0.35),
41
- wallThickness: z.number().positive().default(0.16),
42
- overhang: z.number().min(0).default(0.45),
54
+ pitch: measurement('angle', 'deg', { min: 0, max: 85, description: 'Roof pitch.' }).default(35),
55
+ wallHeight: measurement('length', 'm', { min: 0, description: 'Knee-wall height.' }).default(0.35),
56
+ wallThickness: measurement('length', 'm', {
57
+ positive: true,
58
+ description: 'Wall thickness.',
59
+ }).default(0.16),
60
+ overhang: measurement('length', 'm', { min: 0, description: 'Eave overhang.' }).default(0.45),
43
61
  materialPreset: z.string().optional(),
44
62
  name: z.string().optional(),
45
63
  };
@@ -54,21 +72,33 @@ export const createStairBetweenLevelsInput = {
54
72
  fromLevelId: NodeIdSchema,
55
73
  toLevelId: NodeIdSchema,
56
74
  position: Vec3Schema,
57
- rotation: z.number().default(0),
58
- width: z.number().positive().default(1),
59
- runLength: z.number().positive().default(3),
60
- totalRise: z.number().positive().default(2.8),
75
+ rotation: measurement('angle', 'rad', { description: 'Y-axis rotation.' }).default(0),
76
+ width: measurement('length', 'm', { positive: true, description: 'Stair width.' }).default(1),
77
+ runLength: measurement('length', 'm', {
78
+ positive: true,
79
+ description: 'Horizontal run length.',
80
+ }).default(3),
81
+ totalRise: measurement('length', 'm', {
82
+ positive: true,
83
+ description: 'Total vertical rise.',
84
+ }).optional(),
61
85
  stepCount: z.number().int().positive().default(14),
62
86
  railingMode: z.enum(RAILING_MODES).default('both'),
63
87
  destinationSlabId: NodeIdSchema.optional(),
64
88
  sourceCeilingId: NodeIdSchema.optional(),
65
89
  createDestinationSlabOpening: z.boolean().default(true),
66
90
  createSourceCeilingOpening: z.boolean().default(true),
67
- openingWidth: z.number().positive().optional(),
68
- openingLength: z.number().positive().optional(),
69
- openingOffset: z.number().min(0).default(0.15),
91
+ openingWidth: measurement('length', 'm', {
92
+ positive: true,
93
+ description: 'Floor opening width.',
94
+ }).optional(),
95
+ openingLength: measurement('length', 'm', {
96
+ positive: true,
97
+ description: 'Floor opening length.',
98
+ }).optional(),
99
+ openingOffset: measurement('length', 'm', { min: 0, description: 'Opening offset.' }).default(0),
70
100
  openingCenter: Vec2Schema.optional(),
71
- openingRotation: z.number().optional(),
101
+ openingRotation: measurement('angle', 'rad', { description: 'Opening rotation.' }).optional(),
72
102
  materialPreset: z.string().optional(),
73
103
  name: z.string().optional(),
74
104
  };
@@ -174,7 +204,7 @@ export function registerConstructionTools(server, bridge) {
174
204
  start: points[i],
175
205
  end: points[(i + 1) % points.length],
176
206
  thickness: wallThickness,
177
- height: wallHeight,
207
+ ...(wallHeight !== undefined ? { height: wallHeight } : {}),
178
208
  frontSide: 'exterior',
179
209
  backSide: 'interior',
180
210
  ...(wallMaterialPreset ? { materialPreset: wallMaterialPreset } : {}),
@@ -189,6 +219,11 @@ export function registerConstructionTools(server, bridge) {
189
219
  name: namePrefix ? `${namePrefix} Slab` : undefined,
190
220
  polygon: points,
191
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,
192
227
  ...(slabMaterialPreset ? { materialPreset: slabMaterialPreset } : {}),
193
228
  metadata: { role: 'story-slab' },
194
229
  });
@@ -197,10 +232,13 @@ export function registerConstructionTools(server, bridge) {
197
232
  }
198
233
  let ceilingId = null;
199
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;
200
238
  const ceiling = CeilingNode.parse({
201
239
  name: namePrefix ? `${namePrefix} Ceiling` : undefined,
202
240
  polygon: points,
203
- height: ceilingHeight ?? wallHeight,
241
+ ...(explicitCeilingHeight !== undefined ? { height: explicitCeilingHeight } : {}),
204
242
  ...(ceilingMaterialPreset ? { materialPreset: ceilingMaterialPreset } : {}),
205
243
  metadata: { role: 'story-ceiling' },
206
244
  });
@@ -242,12 +280,12 @@ export function registerConstructionTools(server, bridge) {
242
280
  const roofLevel = LevelNode.parse({
243
281
  name: roofLevelLabel,
244
282
  level: roofLevelElevation ?? nextLevelIndex(bridge, buildingId, referenceLevel),
283
+ height: roofLevelHeight ?? Math.max(wallHeight + peakHeight, 0.2),
245
284
  children: [],
246
285
  metadata: {
247
286
  role: 'roof',
248
287
  label: roofLevelLabel,
249
288
  referenceLevelId: levelId,
250
- height: roofLevelHeight ?? Math.max(wallHeight + peakHeight, 0.2),
251
289
  },
252
290
  });
253
291
  targetRoofLevelId = roofLevel.id;
@@ -299,15 +337,7 @@ export function registerConstructionTools(server, bridge) {
299
337
  if (isRoofLevel(fromLevel) || isRoofLevel(toLevel)) {
300
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');
301
339
  }
302
- const segment = StairSegmentNode.parse({
303
- segmentType: 'stair',
304
- width,
305
- length: runLength,
306
- height: totalRise,
307
- stepCount,
308
- ...(materialPreset ? { materialPreset } : {}),
309
- });
310
- const stair = StairNode.parse({
340
+ const stairDraft = StairNode.parse({
311
341
  name: name ?? 'Stair',
312
342
  position: position,
313
343
  rotation,
@@ -317,15 +347,33 @@ export function registerConstructionTools(server, bridge) {
317
347
  slabOpeningMode: 'none',
318
348
  openingOffset,
319
349
  width,
320
- totalRise,
350
+ ...(totalRise !== undefined ? { totalRise } : {}),
321
351
  stepCount,
322
352
  railingMode,
323
- children: [segment.id],
353
+ children: [],
324
354
  ...(materialPreset ? { materialPreset } : {}),
325
355
  metadata: {
326
356
  openingManaged: 'manual-rectangular',
327
357
  },
328
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] };
329
377
  const openingPolygon = rectangularOpening({
330
378
  position: position,
331
379
  rotation,
@@ -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;AAKpD,eAAO,MAAM,gBAAgB;;;;;CAK5B,CAAA;AAED,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA0CpF"}
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,12 +1,20 @@
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';
4
5
  import { publishLiveSceneSnapshot } from './live-sync';
6
+ import { measurement } from './measurement';
5
7
  import { NodeIdSchema } from './schemas';
6
8
  export const createLevelInput = {
7
9
  buildingId: NodeIdSchema,
8
- elevation: z.number().optional(),
9
- height: z.number().optional(),
10
+ elevation: z
11
+ .number()
12
+ .optional()
13
+ .describe("Legacy parameter; new levels are appended above the building's current top level."),
14
+ height: measurement('length', 'm', {
15
+ min: 0,
16
+ description: 'Stored floor-to-floor storey height.',
17
+ }).optional(),
10
18
  label: z.string().optional(),
11
19
  };
12
20
  export const createLevelOutput = {
@@ -15,10 +23,10 @@ export const createLevelOutput = {
15
23
  export function registerCreateLevel(server, bridge) {
16
24
  server.registerTool('create_level', {
17
25
  title: 'Create level',
18
- description: 'Create a new level node attached to the given building. height and label are stored in metadata.',
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.",
19
27
  inputSchema: createLevelInput,
20
28
  outputSchema: createLevelOutput,
21
- }, async ({ buildingId, elevation, height, label }) => {
29
+ }, async ({ buildingId, height, label }) => {
22
30
  const parent = bridge.getNode(buildingId);
23
31
  if (!parent) {
24
32
  throwMcpError(ErrorCode.InvalidParams, `Building not found: ${buildingId}`);
@@ -27,12 +35,16 @@ export function registerCreateLevel(server, bridge) {
27
35
  throwMcpError(ErrorCode.InvalidParams, `Node ${buildingId} is a ${parent.type}, expected building`);
28
36
  }
29
37
  const metadata = {};
30
- if (height !== undefined)
31
- metadata.height = height;
32
38
  if (label !== undefined)
33
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;
34
45
  const levelNode = LevelNode.parse({
35
- level: elevation ?? 0,
46
+ level: nextOrdinal,
47
+ height: height ?? DEFAULT_LEVEL_HEIGHT,
36
48
  children: [],
37
49
  ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
38
50
  ...(label !== undefined ? { name: label } : {}),
@@ -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;AAKpD,eAAO,MAAM,eAAe;;;;;;CAM3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAgDnF"}
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: z.number().positive().optional(),
11
- height: z.number().positive().optional(),
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;AAMpD,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"}
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: z.number().positive(),
12
- height: z.number().positive(),
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(),
@@ -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;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAIpD,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AA0CD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAwCrF"}
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"}