@pascal-app/core 0.8.0 → 0.9.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 (272) hide show
  1. package/dist/events/bus.d.ts +39 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
  4. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  5. package/dist/hooks/scene-registry/scene-registry.js +33 -26
  6. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  7. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  8. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  9. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
  12. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
  15. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
  18. package/dist/index.d.ts +22 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +19 -2
  21. package/dist/lib/polygon-relations.d.ts +10 -0
  22. package/dist/lib/polygon-relations.d.ts.map +1 -0
  23. package/dist/lib/polygon-relations.js +73 -0
  24. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  25. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  26. package/dist/lib/space-detection-pause.test.js +42 -0
  27. package/dist/lib/space-detection.d.ts +30 -1
  28. package/dist/lib/space-detection.d.ts.map +1 -1
  29. package/dist/lib/space-detection.js +215 -36
  30. package/dist/lib/space-detection.test.d.ts +2 -0
  31. package/dist/lib/space-detection.test.d.ts.map +1 -0
  32. package/dist/lib/space-detection.test.js +77 -0
  33. package/dist/material-library.d.ts +1 -1
  34. package/dist/material-library.d.ts.map +1 -1
  35. package/dist/material-library.js +1940 -143
  36. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  37. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  38. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  39. package/dist/registry/handles.d.ts +321 -0
  40. package/dist/registry/handles.d.ts.map +1 -0
  41. package/dist/registry/handles.js +24 -0
  42. package/dist/registry/index.d.ts +7 -0
  43. package/dist/registry/index.d.ts.map +1 -0
  44. package/dist/registry/index.js +4 -0
  45. package/dist/registry/registry.d.ts +93 -0
  46. package/dist/registry/registry.d.ts.map +1 -0
  47. package/dist/registry/registry.js +201 -0
  48. package/dist/registry/registry.test.d.ts +2 -0
  49. package/dist/registry/registry.test.d.ts.map +1 -0
  50. package/dist/registry/registry.test.js +183 -0
  51. package/dist/registry/relations-resolver.d.ts +42 -0
  52. package/dist/registry/relations-resolver.d.ts.map +1 -0
  53. package/dist/registry/relations-resolver.js +77 -0
  54. package/dist/registry/relations-resolver.test.d.ts +2 -0
  55. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  56. package/dist/registry/relations-resolver.test.js +183 -0
  57. package/dist/registry/scene-api.d.ts +44 -0
  58. package/dist/registry/scene-api.d.ts.map +1 -0
  59. package/dist/registry/scene-api.js +111 -0
  60. package/dist/registry/scene-api.test.d.ts +2 -0
  61. package/dist/registry/scene-api.test.d.ts.map +1 -0
  62. package/dist/registry/scene-api.test.js +183 -0
  63. package/dist/registry/subtree.d.ts +67 -0
  64. package/dist/registry/subtree.d.ts.map +1 -0
  65. package/dist/registry/subtree.js +119 -0
  66. package/dist/registry/subtree.test.d.ts +2 -0
  67. package/dist/registry/subtree.test.d.ts.map +1 -0
  68. package/dist/registry/subtree.test.js +133 -0
  69. package/dist/registry/types.d.ts +1420 -0
  70. package/dist/registry/types.d.ts.map +1 -0
  71. package/dist/registry/types.js +1 -0
  72. package/dist/schema/index.d.ts +17 -2
  73. package/dist/schema/index.d.ts.map +1 -1
  74. package/dist/schema/index.js +17 -3
  75. package/dist/schema/material.d.ts +10 -0
  76. package/dist/schema/material.d.ts.map +1 -1
  77. package/dist/schema/material.js +10 -0
  78. package/dist/schema/nodes/box-vent.d.ts +74 -0
  79. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  80. package/dist/schema/nodes/box-vent.js +63 -0
  81. package/dist/schema/nodes/building.d.ts +1 -1
  82. package/dist/schema/nodes/building.d.ts.map +1 -1
  83. package/dist/schema/nodes/building.js +3 -2
  84. package/dist/schema/nodes/ceiling.d.ts +2 -0
  85. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  86. package/dist/schema/nodes/ceiling.js +1 -1
  87. package/dist/schema/nodes/chimney.d.ts +158 -0
  88. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  89. package/dist/schema/nodes/chimney.js +67 -0
  90. package/dist/schema/nodes/column.d.ts +597 -5
  91. package/dist/schema/nodes/column.d.ts.map +1 -1
  92. package/dist/schema/nodes/column.js +586 -6
  93. package/dist/schema/nodes/cupola.d.ts +66 -0
  94. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  95. package/dist/schema/nodes/cupola.js +28 -0
  96. package/dist/schema/nodes/dormer.d.ts +228 -0
  97. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  98. package/dist/schema/nodes/dormer.js +119 -0
  99. package/dist/schema/nodes/downspout.d.ts +75 -0
  100. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  101. package/dist/schema/nodes/downspout.js +63 -0
  102. package/dist/schema/nodes/elevator.d.ts +105 -0
  103. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  104. package/dist/schema/nodes/elevator.js +54 -0
  105. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  106. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  107. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  108. package/dist/schema/nodes/fence.d.ts +1 -0
  109. package/dist/schema/nodes/fence.d.ts.map +1 -1
  110. package/dist/schema/nodes/fence.js +2 -0
  111. package/dist/schema/nodes/gutter.d.ts +84 -0
  112. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  113. package/dist/schema/nodes/gutter.js +76 -0
  114. package/dist/schema/nodes/item.d.ts +4 -0
  115. package/dist/schema/nodes/item.d.ts.map +1 -1
  116. package/dist/schema/nodes/item.js +9 -0
  117. package/dist/schema/nodes/level.d.ts +1 -1
  118. package/dist/schema/nodes/level.d.ts.map +1 -1
  119. package/dist/schema/nodes/level.js +2 -0
  120. package/dist/schema/nodes/ridge-vent.d.ts +67 -0
  121. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  122. package/dist/schema/nodes/ridge-vent.js +30 -0
  123. package/dist/schema/nodes/roof-segment.d.ts +195 -1
  124. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  125. package/dist/schema/nodes/roof-segment.js +254 -3
  126. package/dist/schema/nodes/roof.d.ts.map +1 -1
  127. package/dist/schema/nodes/roof.js +4 -16
  128. package/dist/schema/nodes/shelf.d.ts +105 -0
  129. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  130. package/dist/schema/nodes/shelf.js +84 -0
  131. package/dist/schema/nodes/site.d.ts +1 -122
  132. package/dist/schema/nodes/site.d.ts.map +1 -1
  133. package/dist/schema/nodes/site.js +2 -6
  134. package/dist/schema/nodes/skylight.d.ts +279 -0
  135. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  136. package/dist/schema/nodes/skylight.js +152 -0
  137. package/dist/schema/nodes/slab.d.ts +2 -0
  138. package/dist/schema/nodes/slab.d.ts.map +1 -1
  139. package/dist/schema/nodes/slab.js +1 -1
  140. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  141. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  142. package/dist/schema/nodes/solar-panel.js +47 -0
  143. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  144. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  145. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  146. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  147. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  148. package/dist/schema/nodes/turbine-vent.js +41 -0
  149. package/dist/schema/types.d.ts +1325 -125
  150. package/dist/schema/types.d.ts.map +1 -1
  151. package/dist/schema/types.js +26 -0
  152. package/dist/services/alignment-anchors.d.ts +100 -0
  153. package/dist/services/alignment-anchors.d.ts.map +1 -0
  154. package/dist/services/alignment-anchors.js +283 -0
  155. package/dist/services/alignment-anchors.test.d.ts +2 -0
  156. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  157. package/dist/services/alignment-anchors.test.js +383 -0
  158. package/dist/services/alignment.d.ts +126 -0
  159. package/dist/services/alignment.d.ts.map +1 -0
  160. package/dist/services/alignment.js +169 -0
  161. package/dist/services/alignment.test.d.ts +2 -0
  162. package/dist/services/alignment.test.d.ts.map +1 -0
  163. package/dist/services/alignment.test.js +92 -0
  164. package/dist/services/drag-session.d.ts +47 -0
  165. package/dist/services/drag-session.d.ts.map +1 -0
  166. package/dist/services/drag-session.js +100 -0
  167. package/dist/services/drag-session.test.d.ts +2 -0
  168. package/dist/services/drag-session.test.d.ts.map +1 -0
  169. package/dist/services/drag-session.test.js +219 -0
  170. package/dist/services/hosting.d.ts +80 -0
  171. package/dist/services/hosting.d.ts.map +1 -0
  172. package/dist/services/hosting.js +122 -0
  173. package/dist/services/hosting.test.d.ts +2 -0
  174. package/dist/services/hosting.test.d.ts.map +1 -0
  175. package/dist/services/hosting.test.js +199 -0
  176. package/dist/services/index.d.ts +7 -0
  177. package/dist/services/index.d.ts.map +1 -0
  178. package/dist/services/index.js +6 -0
  179. package/dist/services/movement.d.ts +48 -0
  180. package/dist/services/movement.d.ts.map +1 -0
  181. package/dist/services/movement.js +65 -0
  182. package/dist/services/movement.test.d.ts +2 -0
  183. package/dist/services/movement.test.d.ts.map +1 -0
  184. package/dist/services/movement.test.js +133 -0
  185. package/dist/services/single-undo-dance.test.d.ts +2 -0
  186. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  187. package/dist/services/single-undo-dance.test.js +210 -0
  188. package/dist/services/snap.d.ts +72 -0
  189. package/dist/services/snap.d.ts.map +1 -0
  190. package/dist/services/snap.js +120 -0
  191. package/dist/services/snap.test.d.ts +2 -0
  192. package/dist/services/snap.test.d.ts.map +1 -0
  193. package/dist/services/snap.test.js +95 -0
  194. package/dist/solar-panel-presets.d.ts +17 -0
  195. package/dist/solar-panel-presets.d.ts.map +1 -0
  196. package/dist/solar-panel-presets.js +43 -0
  197. package/dist/store/actions/node-actions.d.ts +14 -2
  198. package/dist/store/actions/node-actions.d.ts.map +1 -1
  199. package/dist/store/actions/node-actions.js +492 -15
  200. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  201. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  202. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  203. package/dist/store/actions/reparent.test.d.ts +2 -0
  204. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  205. package/dist/store/actions/reparent.test.js +212 -0
  206. package/dist/store/use-interactive.d.ts +39 -0
  207. package/dist/store/use-interactive.d.ts.map +1 -1
  208. package/dist/store/use-interactive.js +75 -0
  209. package/dist/store/use-live-node-overrides.d.ts +22 -0
  210. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  211. package/dist/store/use-live-node-overrides.js +59 -0
  212. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  213. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  214. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  215. package/dist/store/use-scene.d.ts +11 -0
  216. package/dist/store/use-scene.d.ts.map +1 -1
  217. package/dist/store/use-scene.js +196 -6
  218. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  219. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  220. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  221. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  222. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  223. package/dist/systems/elevator/elevator-geometry.js +50 -0
  224. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  225. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  226. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  227. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  228. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  229. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  230. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  231. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  232. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  233. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  234. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  235. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  236. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  237. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  238. package/dist/systems/elevator/elevator-runtime.js +225 -0
  239. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  240. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  241. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  242. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  243. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  244. package/dist/systems/elevator/elevator-service.js +111 -0
  245. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  246. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  247. package/dist/systems/stair/stair-footprint.js +168 -0
  248. package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
  249. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  250. package/dist/systems/stair/stair-opening-preview.js +93 -0
  251. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  252. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  253. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  254. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  255. package/dist/systems/stair/stair-opening-sync.js +172 -147
  256. package/dist/systems/stair/stair-opening-sync.test.js +385 -2
  257. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  258. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  259. package/dist/systems/stair/stair-opening-system.js +104 -0
  260. package/dist/systems/wall/wall-move.d.ts +48 -0
  261. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  262. package/dist/systems/wall/wall-move.js +193 -0
  263. package/dist/utils/clone-scene-graph.d.ts +6 -4
  264. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  265. package/dist/utils/clone-scene-graph.js +6 -4
  266. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  267. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  268. package/dist/utils/clone-scene-graph.test.js +59 -0
  269. package/dist/validation/validate-build-json.d.ts +43 -0
  270. package/dist/validation/validate-build-json.d.ts.map +1 -0
  271. package/dist/validation/validate-build-json.js +227 -0
  272. package/package.json +9 -2
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { BuildingNode, LevelNode, SlabNode, StairNode, StairSegmentNode } from '../../schema';
2
+ import { BuildingNode, CeilingNode, LevelNode, SlabNode, StairNode, StairSegmentNode, } from '../../schema';
3
3
  import { syncAutoStairOpenings } from './stair-opening-sync';
4
4
  describe('syncAutoStairOpenings', () => {
5
- test('only applies stair holes to destination slabs that contain the opening', () => {
5
+ test('only applies stair holes to destination slabs that overlap the opening', () => {
6
6
  const building = BuildingNode.parse({ name: 'Building' });
7
7
  const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
8
8
  const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
@@ -60,4 +60,387 @@ describe('syncAutoStairOpenings', () => {
60
60
  expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
61
61
  expect(bedroomUpdate).toBeUndefined();
62
62
  });
63
+ test('applies stair holes to a later destination slab when the configured offset overhangs the slab edge', () => {
64
+ const building = BuildingNode.parse({ name: 'Building' });
65
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
66
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
67
+ const landingSlab = SlabNode.parse({
68
+ name: 'Landing Slab',
69
+ parentId: upper.id,
70
+ polygon: [
71
+ [0, 0],
72
+ [4, 0],
73
+ [4, 3],
74
+ [0, 3],
75
+ ],
76
+ });
77
+ const segment = StairSegmentNode.parse({
78
+ parentId: 'stair_edge',
79
+ width: 1,
80
+ length: 2.6,
81
+ height: 2.5,
82
+ stepCount: 12,
83
+ });
84
+ const stair = StairNode.parse({
85
+ id: 'stair_edge',
86
+ name: 'Edge Stair',
87
+ parentId: ground.id,
88
+ position: [2, 0, 0],
89
+ stairType: 'straight',
90
+ fromLevelId: ground.id,
91
+ toLevelId: upper.id,
92
+ slabOpeningMode: 'destination',
93
+ openingOffset: 0.08,
94
+ children: [segment.id],
95
+ });
96
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
97
+ const updates = syncAutoStairOpenings(nodes);
98
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
99
+ const hole = landingUpdate?.data.holes?.[0];
100
+ expect(hole).toBeDefined();
101
+ expect(Math.min(...hole.map(([, z]) => z))).toBeCloseTo(-0.08);
102
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
103
+ });
104
+ test('does not apply stair holes to slabs on another building with a matching level number', () => {
105
+ const buildingA = BuildingNode.parse({ name: 'Building A' });
106
+ const groundA = LevelNode.parse({ name: 'Ground A', level: 0, parentId: buildingA.id });
107
+ const upperA = LevelNode.parse({ name: 'Upper A', level: 1, parentId: buildingA.id });
108
+ const buildingB = BuildingNode.parse({ name: 'Building B' });
109
+ const upperB = LevelNode.parse({ name: 'Upper B', level: 1, parentId: buildingB.id });
110
+ const slabA = SlabNode.parse({
111
+ name: 'Upper A Slab',
112
+ parentId: upperA.id,
113
+ polygon: [
114
+ [0, 0],
115
+ [4, 0],
116
+ [4, 3],
117
+ [0, 3],
118
+ ],
119
+ });
120
+ const slabB = SlabNode.parse({
121
+ name: 'Upper B Slab',
122
+ parentId: upperB.id,
123
+ polygon: [
124
+ [0, 0],
125
+ [4, 0],
126
+ [4, 3],
127
+ [0, 3],
128
+ ],
129
+ });
130
+ const segment = StairSegmentNode.parse({
131
+ parentId: 'stair_scoped',
132
+ width: 1,
133
+ length: 2.6,
134
+ height: 2.5,
135
+ stepCount: 12,
136
+ });
137
+ const stair = StairNode.parse({
138
+ id: 'stair_scoped',
139
+ name: 'Scoped Stair',
140
+ parentId: groundA.id,
141
+ position: [2, 0, 0.2],
142
+ stairType: 'straight',
143
+ fromLevelId: groundA.id,
144
+ toLevelId: upperA.id,
145
+ slabOpeningMode: 'destination',
146
+ children: [segment.id],
147
+ });
148
+ const nodes = Object.fromEntries([
149
+ buildingA,
150
+ groundA,
151
+ upperA,
152
+ buildingB,
153
+ upperB,
154
+ slabA,
155
+ slabB,
156
+ stair,
157
+ { ...segment, parentId: stair.id },
158
+ ].map((node) => [node.id, node]));
159
+ const updates = syncAutoStairOpenings(nodes);
160
+ expect(updates.find((update) => update.id === slabA.id)?.data.holes).toHaveLength(1);
161
+ expect(updates.find((update) => update.id === slabB.id)).toBeUndefined();
162
+ });
163
+ test('uses the parent level when a stair has stale from-level data', () => {
164
+ const building = BuildingNode.parse({ name: 'Building' });
165
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
166
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
167
+ const landingSlab = SlabNode.parse({
168
+ name: 'Landing Slab',
169
+ parentId: upper.id,
170
+ polygon: [
171
+ [0, 0],
172
+ [4, 0],
173
+ [4, 8],
174
+ [0, 8],
175
+ ],
176
+ });
177
+ const segment = StairSegmentNode.parse({
178
+ parentId: 'stair_stale_from',
179
+ width: 1,
180
+ length: 6,
181
+ height: 2.5,
182
+ stepCount: 12,
183
+ });
184
+ const stair = StairNode.parse({
185
+ id: 'stair_stale_from',
186
+ name: 'Stale From Stair',
187
+ parentId: ground.id,
188
+ position: [2, 0, 0.2],
189
+ stairType: 'straight',
190
+ fromLevelId: 'default',
191
+ toLevelId: upper.id,
192
+ slabOpeningMode: 'destination',
193
+ children: [segment.id],
194
+ });
195
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
196
+ const updates = syncAutoStairOpenings(nodes);
197
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
198
+ const hole = landingUpdate?.data.holes?.[0];
199
+ expect(hole).toBeDefined();
200
+ expect(Math.min(...hole.map(([, z]) => z))).toBeGreaterThan(0.9);
201
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
202
+ });
203
+ test('infers the destination level when a destination stair has blank level fields', () => {
204
+ const building = BuildingNode.parse({ name: 'Building' });
205
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
206
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
207
+ const landingSlab = SlabNode.parse({
208
+ name: 'Landing Slab',
209
+ parentId: upper.id,
210
+ polygon: [
211
+ [0, 0],
212
+ [4, 0],
213
+ [4, 8],
214
+ [0, 8],
215
+ ],
216
+ });
217
+ const segment = StairSegmentNode.parse({
218
+ parentId: 'stair_blank_levels',
219
+ width: 1,
220
+ length: 6,
221
+ height: 2.5,
222
+ stepCount: 12,
223
+ });
224
+ const stair = StairNode.parse({
225
+ id: 'stair_blank_levels',
226
+ name: 'Blank Level Stair',
227
+ parentId: ground.id,
228
+ position: [2, 0, 0.2],
229
+ stairType: 'straight',
230
+ fromLevelId: '',
231
+ toLevelId: '',
232
+ slabOpeningMode: 'destination',
233
+ children: [segment.id],
234
+ });
235
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
236
+ const updates = syncAutoStairOpenings(nodes);
237
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
238
+ expect(landingUpdate?.data.holes).toHaveLength(1);
239
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
240
+ });
241
+ test('infers the destination level when a destination stair targets its source level', () => {
242
+ const building = BuildingNode.parse({ name: 'Building' });
243
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
244
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
245
+ const landingSlab = SlabNode.parse({
246
+ name: 'Landing Slab',
247
+ parentId: upper.id,
248
+ polygon: [
249
+ [0, 0],
250
+ [4, 0],
251
+ [4, 8],
252
+ [0, 8],
253
+ ],
254
+ });
255
+ const segment = StairSegmentNode.parse({
256
+ parentId: 'stair_self_target',
257
+ width: 1,
258
+ length: 6,
259
+ height: 2.5,
260
+ stepCount: 12,
261
+ });
262
+ const stair = StairNode.parse({
263
+ id: 'stair_self_target',
264
+ name: 'Self Target Stair',
265
+ parentId: ground.id,
266
+ position: [2, 0, 0.2],
267
+ stairType: 'straight',
268
+ fromLevelId: ground.id,
269
+ toLevelId: ground.id,
270
+ slabOpeningMode: 'destination',
271
+ children: [segment.id],
272
+ });
273
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
274
+ const updates = syncAutoStairOpenings(nodes);
275
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
276
+ expect(landingUpdate?.data.holes).toHaveLength(1);
277
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
278
+ });
279
+ test('does not add stair holes when a manual surface hole already covers them', () => {
280
+ const building = BuildingNode.parse({ name: 'Building' });
281
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
282
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
283
+ const manualOpening = [
284
+ [1.0, 0.0],
285
+ [3.0, 0.0],
286
+ [3.0, 3.0],
287
+ [1.0, 3.0],
288
+ ];
289
+ const sourceCeiling = CeilingNode.parse({
290
+ name: 'Source Ceiling',
291
+ parentId: ground.id,
292
+ polygon: [
293
+ [0, 0],
294
+ [4, 0],
295
+ [4, 3],
296
+ [0, 3],
297
+ ],
298
+ holes: [manualOpening],
299
+ holeMetadata: [{ source: 'manual' }],
300
+ });
301
+ const landingSlab = SlabNode.parse({
302
+ name: 'Landing Slab',
303
+ parentId: upper.id,
304
+ polygon: [
305
+ [0, 0],
306
+ [4, 0],
307
+ [4, 3],
308
+ [0, 3],
309
+ ],
310
+ holes: [manualOpening],
311
+ holeMetadata: [{ source: 'manual' }],
312
+ });
313
+ const segment = StairSegmentNode.parse({
314
+ parentId: 'stair_main',
315
+ width: 1,
316
+ length: 2.6,
317
+ height: 2.5,
318
+ stepCount: 12,
319
+ });
320
+ const stair = StairNode.parse({
321
+ id: 'stair_main',
322
+ name: 'Main Stair',
323
+ parentId: ground.id,
324
+ position: [2, 0, 0.2],
325
+ stairType: 'straight',
326
+ fromLevelId: ground.id,
327
+ toLevelId: upper.id,
328
+ slabOpeningMode: 'destination',
329
+ children: [segment.id],
330
+ });
331
+ const nodes = Object.fromEntries([
332
+ building,
333
+ ground,
334
+ upper,
335
+ sourceCeiling,
336
+ landingSlab,
337
+ stair,
338
+ { ...segment, parentId: stair.id },
339
+ ].map((node) => [node.id, node]));
340
+ const updates = syncAutoStairOpenings(nodes);
341
+ expect(updates.find((update) => update.id === landingSlab.id)).toBeUndefined();
342
+ expect(updates.find((update) => update.id === sourceCeiling.id)).toBeUndefined();
343
+ });
344
+ test('adds stair holes when an existing manual hole is too small', () => {
345
+ const building = BuildingNode.parse({ name: 'Building' });
346
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
347
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
348
+ const smallManualOpening = [
349
+ [1.8, 1.6],
350
+ [2.2, 1.6],
351
+ [2.2, 2.1],
352
+ [1.8, 2.1],
353
+ ];
354
+ const landingSlab = SlabNode.parse({
355
+ name: 'Landing Slab',
356
+ parentId: upper.id,
357
+ polygon: [
358
+ [0, 0],
359
+ [4, 0],
360
+ [4, 3],
361
+ [0, 3],
362
+ ],
363
+ holes: [smallManualOpening],
364
+ holeMetadata: [{ source: 'manual' }],
365
+ });
366
+ const segment = StairSegmentNode.parse({
367
+ parentId: 'stair_main',
368
+ width: 1,
369
+ length: 2.6,
370
+ height: 2.5,
371
+ stepCount: 12,
372
+ });
373
+ const stair = StairNode.parse({
374
+ id: 'stair_main',
375
+ name: 'Main Stair',
376
+ parentId: ground.id,
377
+ position: [2, 0, 0.2],
378
+ stairType: 'straight',
379
+ fromLevelId: ground.id,
380
+ toLevelId: upper.id,
381
+ slabOpeningMode: 'destination',
382
+ children: [segment.id],
383
+ });
384
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
385
+ const updates = syncAutoStairOpenings(nodes);
386
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
387
+ expect(landingUpdate?.data.holes).toHaveLength(2);
388
+ expect(landingUpdate?.data.holes?.[0]).toEqual(smallManualOpening);
389
+ expect(landingUpdate?.data.holeMetadata).toEqual([
390
+ { source: 'manual' },
391
+ { source: 'stair', stairId: stair.id },
392
+ ]);
393
+ });
394
+ test('removes stale auto stair holes when a manual hole overlaps the stair opening', () => {
395
+ const building = BuildingNode.parse({ name: 'Building' });
396
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
397
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
398
+ const manualOpening = [
399
+ [1.0, 0.0],
400
+ [3.0, 0.0],
401
+ [3.0, 3.0],
402
+ [1.0, 3.0],
403
+ ];
404
+ const staleAutoOpening = [
405
+ [1.5, 1],
406
+ [2.5, 1],
407
+ [2.5, 2.8],
408
+ [1.5, 2.8],
409
+ ];
410
+ const landingSlab = SlabNode.parse({
411
+ name: 'Landing Slab',
412
+ parentId: upper.id,
413
+ polygon: [
414
+ [0, 0],
415
+ [4, 0],
416
+ [4, 3],
417
+ [0, 3],
418
+ ],
419
+ holes: [manualOpening, staleAutoOpening],
420
+ holeMetadata: [{ source: 'manual' }, { source: 'stair', stairId: 'stair_main' }],
421
+ });
422
+ const segment = StairSegmentNode.parse({
423
+ parentId: 'stair_main',
424
+ width: 1,
425
+ length: 2.6,
426
+ height: 2.5,
427
+ stepCount: 12,
428
+ });
429
+ const stair = StairNode.parse({
430
+ id: 'stair_main',
431
+ name: 'Main Stair',
432
+ parentId: ground.id,
433
+ position: [2, 0, 0.2],
434
+ stairType: 'straight',
435
+ fromLevelId: ground.id,
436
+ toLevelId: upper.id,
437
+ slabOpeningMode: 'destination',
438
+ children: [segment.id],
439
+ });
440
+ const nodes = Object.fromEntries([building, ground, upper, landingSlab, stair, { ...segment, parentId: stair.id }].map((node) => [node.id, node]));
441
+ const updates = syncAutoStairOpenings(nodes);
442
+ const landingUpdate = updates.find((update) => update.id === landingSlab.id);
443
+ expect(landingUpdate?.data.holes).toEqual([manualOpening]);
444
+ expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'manual' }]);
445
+ });
63
446
  });
@@ -0,0 +1,2 @@
1
+ export declare const StairOpeningSystem: () => null;
2
+ //# sourceMappingURL=stair-opening-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-opening-system.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-opening-system.tsx"],"names":[],"mappings":"AA2CA,eAAO,MAAM,kBAAkB,YAyF9B,CAAA"}
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ import { pauseSceneHistory, resumeSceneHistory } from '../../store/history-control';
4
+ import useLiveNodeOverrides from '../../store/use-live-node-overrides';
5
+ import useLiveTransforms from '../../store/use-live-transforms';
6
+ import useScene from '../../store/use-scene';
7
+ import { createSurfaceOpeningPreviewController, getNodesWithLiveStairOpeningInputs, hasLiveStairOpeningInputs, } from './stair-opening-preview';
8
+ import { syncAutoStairOpenings } from './stair-opening-sync';
9
+ function isOpeningRelevantNode(node) {
10
+ return (node?.type === 'building' ||
11
+ node?.type === 'ceiling' ||
12
+ node?.type === 'level' ||
13
+ node?.type === 'slab' ||
14
+ node?.type === 'stair' ||
15
+ node?.type === 'stair-segment');
16
+ }
17
+ function hasOpeningRelevantNodeChange(nextNodes, prevNodes) {
18
+ if (nextNodes === prevNodes)
19
+ return false;
20
+ const ids = new Set([...Object.keys(nextNodes), ...Object.keys(prevNodes)]);
21
+ for (const id of ids) {
22
+ const nextNode = nextNodes[id];
23
+ const prevNode = prevNodes[id];
24
+ if (nextNode === prevNode)
25
+ continue;
26
+ if (isOpeningRelevantNode(nextNode) || isOpeningRelevantNode(prevNode))
27
+ return true;
28
+ }
29
+ return false;
30
+ }
31
+ export const StairOpeningSystem = () => {
32
+ const syncingAutoOpeningsRef = useRef(false);
33
+ const syncingPreviewOpeningsRef = useRef(false);
34
+ const previewControllerRef = useRef(createSurfaceOpeningPreviewController());
35
+ useEffect(() => {
36
+ const applyUpdates = (updates) => {
37
+ if (updates.length === 0)
38
+ return;
39
+ syncingAutoOpeningsRef.current = true;
40
+ pauseSceneHistory(useScene);
41
+ try {
42
+ useScene.getState().updateNodes(updates);
43
+ }
44
+ finally {
45
+ resumeSceneHistory(useScene);
46
+ }
47
+ queueMicrotask(() => {
48
+ syncingAutoOpeningsRef.current = false;
49
+ });
50
+ };
51
+ const applyPreviewUpdates = (updates) => {
52
+ syncingPreviewOpeningsRef.current = true;
53
+ previewControllerRef.current.apply(updates);
54
+ queueMicrotask(() => {
55
+ syncingPreviewOpeningsRef.current = false;
56
+ });
57
+ };
58
+ const clearPreviewUpdates = () => {
59
+ if (previewControllerRef.current.previewSurfaceIds.size === 0)
60
+ return;
61
+ syncingPreviewOpeningsRef.current = true;
62
+ previewControllerRef.current.clear();
63
+ queueMicrotask(() => {
64
+ syncingPreviewOpeningsRef.current = false;
65
+ });
66
+ };
67
+ const refreshLivePreview = () => {
68
+ if (syncingPreviewOpeningsRef.current)
69
+ return;
70
+ const nodes = useScene.getState().nodes;
71
+ const liveTransforms = useLiveTransforms.getState().transforms;
72
+ const liveOverrides = useLiveNodeOverrides.getState().overrides;
73
+ const previewSurfaceIds = previewControllerRef.current.previewSurfaceIds;
74
+ if (!hasLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, previewSurfaceIds)) {
75
+ clearPreviewUpdates();
76
+ return;
77
+ }
78
+ applyPreviewUpdates(syncAutoStairOpenings(getNodesWithLiveStairOpeningInputs(nodes, liveTransforms, liveOverrides, previewSurfaceIds)));
79
+ };
80
+ applyUpdates(syncAutoStairOpenings(useScene.getState().nodes));
81
+ refreshLivePreview();
82
+ const unsubscribeScene = useScene.subscribe((state, prevState) => {
83
+ if (syncingAutoOpeningsRef.current)
84
+ return;
85
+ if (!hasOpeningRelevantNodeChange(state.nodes, prevState.nodes))
86
+ return;
87
+ applyUpdates(syncAutoStairOpenings(state.nodes));
88
+ refreshLivePreview();
89
+ });
90
+ const unsubscribeLiveTransforms = useLiveTransforms.subscribe(() => {
91
+ refreshLivePreview();
92
+ });
93
+ const unsubscribeLiveOverrides = useLiveNodeOverrides.subscribe(() => {
94
+ refreshLivePreview();
95
+ });
96
+ return () => {
97
+ unsubscribeScene();
98
+ unsubscribeLiveTransforms();
99
+ unsubscribeLiveOverrides();
100
+ previewControllerRef.current.clear();
101
+ };
102
+ }, []);
103
+ return null;
104
+ };
@@ -0,0 +1,48 @@
1
+ import type { WallNode } from '../../schema';
2
+ export type WallPlanPoint = [number, number];
3
+ export type WallMoveAxis = [number, number];
4
+ export type WallMoveEndpoint = 'start' | 'end';
5
+ export type WallMoveBridgePlan<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>> = {
6
+ wall: TWall;
7
+ originalPoint: WallPlanPoint;
8
+ movedEndpoint: WallMoveEndpoint;
9
+ };
10
+ export type WallMoveLinkedWallTargetPlan<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>> = {
11
+ wall: TWall;
12
+ originalPoint: WallPlanPoint;
13
+ targetPoint: WallPlanPoint;
14
+ };
15
+ export type WallMoveJunctionPlan<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>> = {
16
+ linkedWallsToMove: TWall[];
17
+ linkedWallTargetPlans: Array<WallMoveLinkedWallTargetPlan<TWall>>;
18
+ bridgePlans: Array<WallMoveBridgePlan<TWall>>;
19
+ wallsToDelete: TWall[];
20
+ };
21
+ export declare function getPerpendicularWallMoveAxis(start: WallPlanPoint, end: WallPlanPoint): WallMoveAxis | null;
22
+ export declare function constrainWallMoveDeltaToAxis(deltaX: number, deltaZ: number, axis: WallMoveAxis | null): WallPlanPoint;
23
+ /**
24
+ * Apply a junction plan to a list of linked walls, producing the per-wall
25
+ * endpoint updates. Mirrors the 3D `MoveWallTool`'s drag-preview behavior
26
+ * so the 2D move handler can drive the same scene topology.
27
+ *
28
+ * `linkedWallTargetPlans` take precedence over `linkedWallsToMove` — when
29
+ * the planner emits a target plan for a same-direction-consumed wall the
30
+ * matchPoint/targetPoint pair encodes the pivot, not the original ↔ next
31
+ * endpoint mapping.
32
+ */
33
+ export declare function getLinkedWallUpdates<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>>(linkedWalls: Array<{
34
+ wall: TWall;
35
+ matchPoint?: WallPlanPoint;
36
+ targetPoint?: WallPlanPoint;
37
+ }>, originalStart: WallPlanPoint, originalEnd: WallPlanPoint, nextStart: WallPlanPoint, nextEnd: WallPlanPoint): Array<{
38
+ id: TWall['id'];
39
+ start: WallPlanPoint;
40
+ end: WallPlanPoint;
41
+ }>;
42
+ export declare function getPlannedLinkedWallUpdates<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>>(plan: WallMoveJunctionPlan<TWall>, originalStart: WallPlanPoint, originalEnd: WallPlanPoint, nextStart: WallPlanPoint, nextEnd: WallPlanPoint): Array<{
43
+ id: TWall['id'];
44
+ start: WallPlanPoint;
45
+ end: WallPlanPoint;
46
+ }>;
47
+ export declare function planWallMoveJunctions<TWall extends Pick<WallNode, 'id' | 'start' | 'end'>>(linkedWalls: TWall[], originalStart: WallPlanPoint, originalEnd: WallPlanPoint, nextStart: WallPlanPoint, nextEnd: WallPlanPoint): WallMoveJunctionPlan<TWall>;
48
+ //# sourceMappingURL=wall-move.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-move.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-move.ts"],"names":[],"mappings":"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"}