@pascal-app/core 0.7.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 (318) hide show
  1. package/dist/events/bus.d.ts +47 -18
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/events/bus.js +1 -1
  4. package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
  5. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  6. package/dist/hooks/scene-registry/scene-registry.js +33 -26
  7. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  8. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  9. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
  19. package/dist/index.d.ts +22 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +19 -2
  22. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  23. package/dist/lib/polygon-relations.d.ts +10 -0
  24. package/dist/lib/polygon-relations.d.ts.map +1 -0
  25. package/dist/lib/polygon-relations.js +73 -0
  26. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  27. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  28. package/dist/lib/space-detection-pause.test.js +42 -0
  29. package/dist/lib/space-detection.d.ts +30 -1
  30. package/dist/lib/space-detection.d.ts.map +1 -1
  31. package/dist/lib/space-detection.js +222 -41
  32. package/dist/lib/space-detection.test.d.ts +2 -0
  33. package/dist/lib/space-detection.test.d.ts.map +1 -0
  34. package/dist/lib/space-detection.test.js +77 -0
  35. package/dist/material-library.d.ts +1 -1
  36. package/dist/material-library.d.ts.map +1 -1
  37. package/dist/material-library.js +1957 -141
  38. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  39. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  40. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  41. package/dist/registry/handles.d.ts +321 -0
  42. package/dist/registry/handles.d.ts.map +1 -0
  43. package/dist/registry/handles.js +24 -0
  44. package/dist/registry/index.d.ts +7 -0
  45. package/dist/registry/index.d.ts.map +1 -0
  46. package/dist/registry/index.js +4 -0
  47. package/dist/registry/registry.d.ts +93 -0
  48. package/dist/registry/registry.d.ts.map +1 -0
  49. package/dist/registry/registry.js +201 -0
  50. package/dist/registry/registry.test.d.ts +2 -0
  51. package/dist/registry/registry.test.d.ts.map +1 -0
  52. package/dist/registry/registry.test.js +183 -0
  53. package/dist/registry/relations-resolver.d.ts +42 -0
  54. package/dist/registry/relations-resolver.d.ts.map +1 -0
  55. package/dist/registry/relations-resolver.js +77 -0
  56. package/dist/registry/relations-resolver.test.d.ts +2 -0
  57. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  58. package/dist/registry/relations-resolver.test.js +183 -0
  59. package/dist/registry/scene-api.d.ts +44 -0
  60. package/dist/registry/scene-api.d.ts.map +1 -0
  61. package/dist/registry/scene-api.js +111 -0
  62. package/dist/registry/scene-api.test.d.ts +2 -0
  63. package/dist/registry/scene-api.test.d.ts.map +1 -0
  64. package/dist/registry/scene-api.test.js +183 -0
  65. package/dist/registry/subtree.d.ts +67 -0
  66. package/dist/registry/subtree.d.ts.map +1 -0
  67. package/dist/registry/subtree.js +119 -0
  68. package/dist/registry/subtree.test.d.ts +2 -0
  69. package/dist/registry/subtree.test.d.ts.map +1 -0
  70. package/dist/registry/subtree.test.js +133 -0
  71. package/dist/registry/types.d.ts +1420 -0
  72. package/dist/registry/types.d.ts.map +1 -0
  73. package/dist/registry/types.js +1 -0
  74. package/dist/schema/asset-url.test.js +0 -4
  75. package/dist/schema/index.d.ts +17 -2
  76. package/dist/schema/index.d.ts.map +1 -1
  77. package/dist/schema/index.js +17 -3
  78. package/dist/schema/material.d.ts +12 -2
  79. package/dist/schema/material.d.ts.map +1 -1
  80. package/dist/schema/material.js +10 -0
  81. package/dist/schema/nodes/box-vent.d.ts +74 -0
  82. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  83. package/dist/schema/nodes/box-vent.js +63 -0
  84. package/dist/schema/nodes/building.d.ts +1 -1
  85. package/dist/schema/nodes/building.d.ts.map +1 -1
  86. package/dist/schema/nodes/building.js +3 -2
  87. package/dist/schema/nodes/ceiling.d.ts +3 -1
  88. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  89. package/dist/schema/nodes/ceiling.js +1 -1
  90. package/dist/schema/nodes/chimney.d.ts +158 -0
  91. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  92. package/dist/schema/nodes/chimney.js +67 -0
  93. package/dist/schema/nodes/column.d.ts +598 -6
  94. package/dist/schema/nodes/column.d.ts.map +1 -1
  95. package/dist/schema/nodes/column.js +586 -6
  96. package/dist/schema/nodes/cupola.d.ts +66 -0
  97. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  98. package/dist/schema/nodes/cupola.js +28 -0
  99. package/dist/schema/nodes/door.d.ts +1 -1
  100. package/dist/schema/nodes/dormer.d.ts +228 -0
  101. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  102. package/dist/schema/nodes/dormer.js +119 -0
  103. package/dist/schema/nodes/downspout.d.ts +75 -0
  104. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  105. package/dist/schema/nodes/downspout.js +63 -0
  106. package/dist/schema/nodes/elevator.d.ts +105 -0
  107. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  108. package/dist/schema/nodes/elevator.js +54 -0
  109. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  110. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  111. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  112. package/dist/schema/nodes/fence.d.ts +3 -2
  113. package/dist/schema/nodes/fence.d.ts.map +1 -1
  114. package/dist/schema/nodes/fence.js +4 -2
  115. package/dist/schema/nodes/gutter.d.ts +84 -0
  116. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  117. package/dist/schema/nodes/gutter.js +76 -0
  118. package/dist/schema/nodes/item.d.ts +16 -0
  119. package/dist/schema/nodes/item.d.ts.map +1 -1
  120. package/dist/schema/nodes/item.js +21 -0
  121. package/dist/schema/nodes/level.d.ts +1 -1
  122. package/dist/schema/nodes/level.d.ts.map +1 -1
  123. package/dist/schema/nodes/level.js +2 -0
  124. package/dist/schema/nodes/ridge-vent.d.ts +67 -0
  125. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  126. package/dist/schema/nodes/ridge-vent.js +30 -0
  127. package/dist/schema/nodes/roof-segment.d.ts +198 -4
  128. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  129. package/dist/schema/nodes/roof-segment.js +254 -3
  130. package/dist/schema/nodes/roof.d.ts +6 -6
  131. package/dist/schema/nodes/roof.d.ts.map +1 -1
  132. package/dist/schema/nodes/roof.js +9 -21
  133. package/dist/schema/nodes/shelf.d.ts +105 -0
  134. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  135. package/dist/schema/nodes/shelf.js +84 -0
  136. package/dist/schema/nodes/site.d.ts +1 -116
  137. package/dist/schema/nodes/site.d.ts.map +1 -1
  138. package/dist/schema/nodes/site.js +2 -6
  139. package/dist/schema/nodes/skylight.d.ts +279 -0
  140. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  141. package/dist/schema/nodes/skylight.js +152 -0
  142. package/dist/schema/nodes/slab.d.ts +3 -1
  143. package/dist/schema/nodes/slab.d.ts.map +1 -1
  144. package/dist/schema/nodes/slab.js +1 -1
  145. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  146. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  147. package/dist/schema/nodes/solar-panel.js +47 -0
  148. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  149. package/dist/schema/nodes/stair.d.ts +6 -6
  150. package/dist/schema/nodes/stair.d.ts.map +1 -1
  151. package/dist/schema/nodes/stair.js +9 -7
  152. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  153. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  154. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  155. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  156. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  157. package/dist/schema/nodes/turbine-vent.js +41 -0
  158. package/dist/schema/nodes/wall.d.ts +3 -3
  159. package/dist/schema/nodes/window.d.ts +1 -1
  160. package/dist/schema/types.d.ts +1448 -236
  161. package/dist/schema/types.d.ts.map +1 -1
  162. package/dist/schema/types.js +26 -0
  163. package/dist/services/alignment-anchors.d.ts +100 -0
  164. package/dist/services/alignment-anchors.d.ts.map +1 -0
  165. package/dist/services/alignment-anchors.js +283 -0
  166. package/dist/services/alignment-anchors.test.d.ts +2 -0
  167. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  168. package/dist/services/alignment-anchors.test.js +383 -0
  169. package/dist/services/alignment.d.ts +126 -0
  170. package/dist/services/alignment.d.ts.map +1 -0
  171. package/dist/services/alignment.js +169 -0
  172. package/dist/services/alignment.test.d.ts +2 -0
  173. package/dist/services/alignment.test.d.ts.map +1 -0
  174. package/dist/services/alignment.test.js +92 -0
  175. package/dist/services/drag-session.d.ts +47 -0
  176. package/dist/services/drag-session.d.ts.map +1 -0
  177. package/dist/services/drag-session.js +100 -0
  178. package/dist/services/drag-session.test.d.ts +2 -0
  179. package/dist/services/drag-session.test.d.ts.map +1 -0
  180. package/dist/services/drag-session.test.js +219 -0
  181. package/dist/services/hosting.d.ts +80 -0
  182. package/dist/services/hosting.d.ts.map +1 -0
  183. package/dist/services/hosting.js +122 -0
  184. package/dist/services/hosting.test.d.ts +2 -0
  185. package/dist/services/hosting.test.d.ts.map +1 -0
  186. package/dist/services/hosting.test.js +199 -0
  187. package/dist/services/index.d.ts +7 -0
  188. package/dist/services/index.d.ts.map +1 -0
  189. package/dist/services/index.js +6 -0
  190. package/dist/services/movement.d.ts +48 -0
  191. package/dist/services/movement.d.ts.map +1 -0
  192. package/dist/services/movement.js +65 -0
  193. package/dist/services/movement.test.d.ts +2 -0
  194. package/dist/services/movement.test.d.ts.map +1 -0
  195. package/dist/services/movement.test.js +133 -0
  196. package/dist/services/single-undo-dance.test.d.ts +2 -0
  197. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  198. package/dist/services/single-undo-dance.test.js +210 -0
  199. package/dist/services/snap.d.ts +72 -0
  200. package/dist/services/snap.d.ts.map +1 -0
  201. package/dist/services/snap.js +120 -0
  202. package/dist/services/snap.test.d.ts +2 -0
  203. package/dist/services/snap.test.d.ts.map +1 -0
  204. package/dist/services/snap.test.js +95 -0
  205. package/dist/solar-panel-presets.d.ts +17 -0
  206. package/dist/solar-panel-presets.d.ts.map +1 -0
  207. package/dist/solar-panel-presets.js +43 -0
  208. package/dist/store/actions/node-actions.d.ts +14 -2
  209. package/dist/store/actions/node-actions.d.ts.map +1 -1
  210. package/dist/store/actions/node-actions.js +499 -20
  211. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  212. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  213. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  214. package/dist/store/actions/reparent.test.d.ts +2 -0
  215. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  216. package/dist/store/actions/reparent.test.js +212 -0
  217. package/dist/store/use-interactive.d.ts +39 -0
  218. package/dist/store/use-interactive.d.ts.map +1 -1
  219. package/dist/store/use-interactive.js +75 -0
  220. package/dist/store/use-live-node-overrides.d.ts +22 -0
  221. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  222. package/dist/store/use-live-node-overrides.js +59 -0
  223. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  224. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  225. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  226. package/dist/store/use-scene.d.ts +11 -0
  227. package/dist/store/use-scene.d.ts.map +1 -1
  228. package/dist/store/use-scene.js +207 -11
  229. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  230. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  231. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  232. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  233. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  234. package/dist/systems/elevator/elevator-geometry.js +50 -0
  235. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  236. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  237. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  238. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  239. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  240. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  241. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  242. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  243. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  244. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  245. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  246. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  247. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  248. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  249. package/dist/systems/elevator/elevator-runtime.js +225 -0
  250. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  251. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  252. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  253. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  254. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  255. package/dist/systems/elevator/elevator-service.js +111 -0
  256. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  257. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  258. package/dist/systems/stair/stair-footprint.js +168 -0
  259. package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
  260. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  261. package/dist/systems/stair/stair-opening-preview.js +93 -0
  262. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  263. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  264. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  265. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  266. package/dist/systems/stair/stair-opening-sync.js +183 -185
  267. package/dist/systems/stair/stair-opening-sync.test.js +385 -4
  268. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  269. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  270. package/dist/systems/stair/stair-opening-system.js +104 -0
  271. package/dist/systems/wall/wall-curve.d.ts +1 -1
  272. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  273. package/dist/systems/wall/wall-curve.js +1 -1
  274. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  275. package/dist/systems/wall/wall-mitering.js +2 -6
  276. package/dist/systems/wall/wall-move.d.ts +48 -0
  277. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  278. package/dist/systems/wall/wall-move.js +193 -0
  279. package/dist/utils/clone-scene-graph.d.ts +6 -4
  280. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  281. package/dist/utils/clone-scene-graph.js +6 -4
  282. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  283. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  284. package/dist/utils/clone-scene-graph.test.js +59 -0
  285. package/dist/validation/validate-build-json.d.ts +43 -0
  286. package/dist/validation/validate-build-json.d.ts.map +1 -0
  287. package/dist/validation/validate-build-json.js +227 -0
  288. package/package.json +11 -3
  289. package/dist/materials.d.ts +0 -10
  290. package/dist/materials.d.ts.map +0 -1
  291. package/dist/materials.js +0 -22
  292. package/dist/systems/ceiling/ceiling-system.d.ts +0 -8
  293. package/dist/systems/ceiling/ceiling-system.d.ts.map +0 -1
  294. package/dist/systems/ceiling/ceiling-system.js +0 -92
  295. package/dist/systems/door/door-system.d.ts +0 -2
  296. package/dist/systems/door/door-system.d.ts.map +0 -1
  297. package/dist/systems/door/door-system.js +0 -195
  298. package/dist/systems/fence/fence-system.d.ts +0 -2
  299. package/dist/systems/fence/fence-system.d.ts.map +0 -1
  300. package/dist/systems/fence/fence-system.js +0 -187
  301. package/dist/systems/item/item-system.d.ts +0 -2
  302. package/dist/systems/item/item-system.d.ts.map +0 -1
  303. package/dist/systems/item/item-system.js +0 -48
  304. package/dist/systems/roof/roof-system.d.ts +0 -16
  305. package/dist/systems/roof/roof-system.d.ts.map +0 -1
  306. package/dist/systems/roof/roof-system.js +0 -797
  307. package/dist/systems/slab/slab-system.d.ts +0 -8
  308. package/dist/systems/slab/slab-system.d.ts.map +0 -1
  309. package/dist/systems/slab/slab-system.js +0 -214
  310. package/dist/systems/stair/stair-system.d.ts +0 -2
  311. package/dist/systems/stair/stair-system.d.ts.map +0 -1
  312. package/dist/systems/stair/stair-system.js +0 -776
  313. package/dist/systems/wall/wall-system.d.ts +0 -12
  314. package/dist/systems/wall/wall-system.d.ts.map +0 -1
  315. package/dist/systems/wall/wall-system.js +0 -455
  316. package/dist/systems/window/window-system.d.ts +0 -2
  317. package/dist/systems/window/window-system.d.ts.map +0 -1
  318. package/dist/systems/window/window-system.js +0 -131
@@ -1,10 +1,8 @@
1
- // @ts-expect-error — bun:test is provided by the Bun runtime; core does not
2
- // depend on @types/bun so the import type is unresolved at compile time.
3
1
  import { describe, expect, test } from 'bun:test';
4
- import { BuildingNode, LevelNode, SlabNode, StairNode, StairSegmentNode } from '../../schema';
2
+ import { BuildingNode, CeilingNode, LevelNode, SlabNode, StairNode, StairSegmentNode, } from '../../schema';
5
3
  import { syncAutoStairOpenings } from './stair-opening-sync';
6
4
  describe('syncAutoStairOpenings', () => {
7
- 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', () => {
8
6
  const building = BuildingNode.parse({ name: 'Building' });
9
7
  const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
10
8
  const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
@@ -62,4 +60,387 @@ describe('syncAutoStairOpenings', () => {
62
60
  expect(landingUpdate?.data.holeMetadata).toEqual([{ source: 'stair', stairId: stair.id }]);
63
61
  expect(bedroomUpdate).toBeUndefined();
64
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
+ });
65
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
+ };
@@ -1,5 +1,5 @@
1
- import type { Point2D } from './wall-mitering';
2
1
  import type { FenceNode, WallNode } from '../../schema';
2
+ import type { Point2D } from './wall-mitering';
3
3
  type WallCurveLike = Pick<WallNode | FenceNode, 'start' | 'end' | 'curveOffset'>;
4
4
  type CurveFrame = {
5
5
  point: Point2D;
@@ -1 +1 @@
1
- {"version":3,"file":"wall-curve.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-curve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAKvD,KAAK,aAAa,GAAG,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAC,CAAA;AAEhF,KAAK,UAAU,GAAG;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,KAAK,yBAAyB,GAAG;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAcD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE9D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,UAErD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,UAExD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAE5D;AAWD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,UAG3E;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAI5D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,WAE/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa;;;;;;;;;;;;;EA6BpD;AA+BD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAwC9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,WAE7D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,aAG3F;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,UASzF;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC,EAC/E,QAAQ,SAA0B,EAClC,cAAc,CAAC,EAAE,yBAAyB,aA2B3C"}
1
+ {"version":3,"file":"wall-curve.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-curve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAK9C,KAAK,aAAa,GAAG,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAC,CAAA;AAEhF,KAAK,UAAU,GAAG;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,KAAK,yBAAyB,GAAG;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAcD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE9D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,UAErD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,UAExD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAE5D;AAWD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,UAG3E;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAI5D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,WAE/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa;;;;;;;;;;;;;EA6BpD;AA+BD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAwC9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,WAE7D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,aAM3F;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,UASzF;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC,EAC/E,QAAQ,SAA0B,EAClC,cAAc,CAAC,EAAE,yBAAyB,aA2B3C"}
@@ -78,7 +78,7 @@ function getWallArcData(wall) {
78
78
  return null;
79
79
  }
80
80
  const absSagitta = Math.abs(sagitta);
81
- const radius = chord.length * chord.length / (8 * absSagitta) + absSagitta / 2;
81
+ const radius = (chord.length * chord.length) / (8 * absSagitta) + absSagitta / 2;
82
82
  const centerOffset = radius - absSagitta;
83
83
  const direction = Math.sign(sagitta) || 1;
84
84
  const center = {
@@ -1 +1 @@
1
- {"version":3,"file":"wall-mitering.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-mitering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAO5C,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AASD,KAAK,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAGzE,KAAK,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAQlD,iBAAS,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,SAAY,GAAG,MAAM,CAG7D;AA8CD,UAAU,QAAQ;IAChB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAA;KAAE,CAAC,CAAA;CACpF;AA+ND,MAAM,WAAW,aAAa;IAE5B,YAAY,EAAE,YAAY,CAAA;IAE1B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAWrE;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,aAAa,GACvB,uBAAuB,GAAG,IAAI,CA0BhC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA8C/F;AAGD,OAAO,EAAE,UAAU,EAAE,CAAA"}
1
+ {"version":3,"file":"wall-mitering.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-mitering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAO5C,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AASD,KAAK,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAGzE,KAAK,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAQlD,iBAAS,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,SAAY,GAAG,MAAM,CAG7D;AA8CD,UAAU,QAAQ;IAChB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAA;KAAE,CAAC,CAAA;CACpF;AAoND,MAAM,WAAW,aAAa;IAE5B,YAAY,EAAE,YAAY,CAAA;IAE1B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAWrE;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,aAAa,GACvB,uBAAuB,GAAG,IAAI,CA0BhC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA8C/F;AAGD,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -89,9 +89,7 @@ function getWallDirectionFromJunction(wall, endType) {
89
89
  }
90
90
  if (isCurvedWall(wall)) {
91
91
  const frame = getWallCurveFrameAt(wall, endType === 'start' ? 0 : 1);
92
- return endType === 'start'
93
- ? frame.tangent
94
- : { x: -frame.tangent.x, y: -frame.tangent.y };
92
+ return endType === 'start' ? frame.tangent : { x: -frame.tangent.x, y: -frame.tangent.y };
95
93
  }
96
94
  return endType === 'start'
97
95
  ? { x: wall.end[0] - wall.start[0], y: wall.end[1] - wall.start[1] }
@@ -102,9 +100,7 @@ function getWallBoundaryFrame(wall, endType) {
102
100
  const frame = getWallCurveFrameAt(wall, endType === 'start' ? 0 : 1);
103
101
  return {
104
102
  point: frame.point,
105
- tangent: endType === 'start'
106
- ? frame.tangent
107
- : { x: -frame.tangent.x, y: -frame.tangent.y },
103
+ tangent: endType === 'start' ? frame.tangent : { x: -frame.tangent.x, y: -frame.tangent.y },
108
104
  normal: frame.normal,
109
105
  };
110
106
  }
@@ -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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"}