@pascal-app/core 1.0.0-beta.4 → 1.0.0

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 (263) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/capture/index.d.ts +3 -0
  4. package/dist/capture/index.d.ts.map +1 -0
  5. package/dist/capture/index.js +2 -0
  6. package/dist/capture/schema.d.ts +417 -0
  7. package/dist/capture/schema.d.ts.map +1 -0
  8. package/dist/capture/schema.js +348 -0
  9. package/dist/capture/source.d.ts +60 -0
  10. package/dist/capture/source.d.ts.map +1 -0
  11. package/dist/capture/source.js +254 -0
  12. package/dist/events/bus.d.ts +55 -7
  13. package/dist/events/bus.d.ts.map +1 -1
  14. package/dist/events/hidden-wall-pointer-hold.d.ts +28 -0
  15. package/dist/events/hidden-wall-pointer-hold.d.ts.map +1 -0
  16. package/dist/events/hidden-wall-pointer-hold.js +37 -0
  17. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +12 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.js +39 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2785 -1
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.js +132 -19
  23. package/dist/hooks/spatial-grid/support-host-patch.d.ts +45 -1
  24. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
  25. package/dist/hooks/spatial-grid/support-host-patch.js +171 -6
  26. package/dist/index.d.ts +15 -6
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +17 -5
  29. package/dist/lib/asset-storage.d.ts.map +1 -1
  30. package/dist/lib/asset-storage.js +4 -1
  31. package/dist/lib/polygon-union.d.ts +4 -0
  32. package/dist/lib/polygon-union.d.ts.map +1 -0
  33. package/dist/lib/polygon-union.js +306 -0
  34. package/dist/lib/roof-overlap.d.ts +32 -0
  35. package/dist/lib/roof-overlap.d.ts.map +1 -0
  36. package/dist/lib/roof-overlap.js +72 -0
  37. package/dist/lib/room-topology-index.d.ts +50 -0
  38. package/dist/lib/room-topology-index.d.ts.map +1 -0
  39. package/dist/lib/room-topology-index.js +237 -0
  40. package/dist/lib/slab-polygon.d.ts +35 -2
  41. package/dist/lib/slab-polygon.d.ts.map +1 -1
  42. package/dist/lib/slab-polygon.js +133 -27
  43. package/dist/lib/space-detection.d.ts +40 -3
  44. package/dist/lib/space-detection.d.ts.map +1 -1
  45. package/dist/lib/space-detection.js +890 -246
  46. package/dist/registry/handles.d.ts +24 -3
  47. package/dist/registry/handles.d.ts.map +1 -1
  48. package/dist/registry/index.d.ts +3 -3
  49. package/dist/registry/index.d.ts.map +1 -1
  50. package/dist/registry/index.js +1 -1
  51. package/dist/registry/registry.d.ts +29 -6
  52. package/dist/registry/registry.d.ts.map +1 -1
  53. package/dist/registry/registry.js +111 -4
  54. package/dist/registry/scene-api.d.ts +16 -0
  55. package/dist/registry/scene-api.d.ts.map +1 -1
  56. package/dist/registry/scene-api.js +49 -1
  57. package/dist/registry/types.d.ts +261 -13
  58. package/dist/registry/types.d.ts.map +1 -1
  59. package/dist/registry/use-registry-version.d.ts +11 -0
  60. package/dist/registry/use-registry-version.d.ts.map +1 -0
  61. package/dist/registry/use-registry-version.js +15 -0
  62. package/dist/schema/__bench__/node-parsers.bench.d.ts +24 -0
  63. package/dist/schema/__bench__/node-parsers.bench.d.ts.map +1 -0
  64. package/dist/schema/__bench__/node-parsers.bench.js +314 -0
  65. package/dist/schema/__fixtures__/node-fixtures.d.ts +28 -0
  66. package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -0
  67. package/dist/schema/__fixtures__/node-fixtures.js +125 -0
  68. package/dist/schema/base.d.ts +1 -1
  69. package/dist/schema/base.d.ts.map +1 -1
  70. package/dist/schema/base.js +8 -1
  71. package/dist/schema/compiled-node-parsers.d.ts +40 -0
  72. package/dist/schema/compiled-node-parsers.d.ts.map +1 -0
  73. package/dist/schema/compiled-node-parsers.js +95 -0
  74. package/dist/schema/index.d.ts +20 -15
  75. package/dist/schema/index.d.ts.map +1 -1
  76. package/dist/schema/index.js +19 -12
  77. package/dist/schema/material.d.ts +2 -1
  78. package/dist/schema/material.d.ts.map +1 -1
  79. package/dist/schema/material.js +1 -0
  80. package/dist/schema/nodes/automatic-downspout.d.ts +17 -0
  81. package/dist/schema/nodes/automatic-downspout.d.ts.map +1 -0
  82. package/dist/schema/nodes/automatic-downspout.js +237 -0
  83. package/dist/schema/nodes/block.d.ts +107 -0
  84. package/dist/schema/nodes/block.d.ts.map +1 -0
  85. package/dist/schema/nodes/block.js +207 -0
  86. package/dist/schema/nodes/box-vent.d.ts +8 -2
  87. package/dist/schema/nodes/box-vent.d.ts.map +1 -1
  88. package/dist/schema/nodes/box-vent.js +2 -0
  89. package/dist/schema/nodes/building.d.ts +1 -1
  90. package/dist/schema/nodes/cabinet.d.ts +25 -3
  91. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  92. package/dist/schema/nodes/cabinet.js +26 -5
  93. package/dist/schema/nodes/ceiling.d.ts +1 -1
  94. package/dist/schema/nodes/chimney.d.ts +2 -2
  95. package/dist/schema/nodes/column.d.ts +6 -6
  96. package/dist/schema/nodes/construction-dimension.d.ts +4 -4
  97. package/dist/schema/nodes/cupola.d.ts +9 -1
  98. package/dist/schema/nodes/cupola.d.ts.map +1 -1
  99. package/dist/schema/nodes/cupola.js +2 -0
  100. package/dist/schema/nodes/door.d.ts +1 -1
  101. package/dist/schema/nodes/dormer.d.ts +44 -1
  102. package/dist/schema/nodes/dormer.d.ts.map +1 -1
  103. package/dist/schema/nodes/dormer.js +131 -7
  104. package/dist/schema/nodes/downspout.d.ts +11 -1
  105. package/dist/schema/nodes/downspout.d.ts.map +1 -1
  106. package/dist/schema/nodes/downspout.js +24 -0
  107. package/dist/schema/nodes/duct-fitting.d.ts +18 -1
  108. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
  109. package/dist/schema/nodes/duct-fitting.js +25 -4
  110. package/dist/schema/nodes/duct-segment.d.ts +23 -1
  111. package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
  112. package/dist/schema/nodes/duct-segment.js +20 -0
  113. package/dist/schema/nodes/duct-terminal.d.ts +1 -1
  114. package/dist/schema/nodes/elevator.d.ts +1 -1
  115. package/dist/schema/nodes/eyebrow-vent.d.ts +8 -2
  116. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
  117. package/dist/schema/nodes/eyebrow-vent.js +2 -0
  118. package/dist/schema/nodes/fence.d.ts +1 -1
  119. package/dist/schema/nodes/guide.d.ts +1 -1
  120. package/dist/schema/nodes/gutter.d.ts +36 -2
  121. package/dist/schema/nodes/gutter.d.ts.map +1 -1
  122. package/dist/schema/nodes/gutter.js +308 -1
  123. package/dist/schema/nodes/hanger-overrides.d.ts +7 -0
  124. package/dist/schema/nodes/hanger-overrides.d.ts.map +1 -0
  125. package/dist/schema/nodes/hanger-overrides.js +6 -0
  126. package/dist/schema/nodes/hvac-equipment.d.ts +1 -1
  127. package/dist/schema/nodes/item.d.ts +4 -3
  128. package/dist/schema/nodes/item.d.ts.map +1 -1
  129. package/dist/schema/nodes/item.js +5 -0
  130. package/dist/schema/nodes/lean-to-extension.d.ts +212 -0
  131. package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -0
  132. package/dist/schema/nodes/lean-to-extension.js +114 -0
  133. package/dist/schema/nodes/level.d.ts +5 -2
  134. package/dist/schema/nodes/level.d.ts.map +1 -1
  135. package/dist/schema/nodes/level.js +9 -0
  136. package/dist/schema/nodes/lineset.d.ts +1 -1
  137. package/dist/schema/nodes/liquid-line.d.ts +1 -1
  138. package/dist/schema/nodes/measurement.d.ts +1 -1
  139. package/dist/schema/nodes/pipe-fitting.d.ts +9 -1
  140. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
  141. package/dist/schema/nodes/pipe-fitting.js +6 -2
  142. package/dist/schema/nodes/pipe-segment.d.ts +23 -1
  143. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
  144. package/dist/schema/nodes/pipe-segment.js +20 -0
  145. package/dist/schema/nodes/pipe-trap.d.ts +1 -1
  146. package/dist/schema/nodes/ridge-vent.d.ts +1 -1
  147. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  148. package/dist/schema/nodes/ridge-vent.js +4 -1
  149. package/dist/schema/nodes/roof-segment-shape.d.ts +4 -0
  150. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -1
  151. package/dist/schema/nodes/roof-segment-shape.js +64 -1
  152. package/dist/schema/nodes/roof-segment-walls.d.ts +3 -1
  153. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
  154. package/dist/schema/nodes/roof-segment-walls.js +1 -1
  155. package/dist/schema/nodes/roof-segment.d.ts +41 -1
  156. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  157. package/dist/schema/nodes/roof-segment.js +79 -2
  158. package/dist/schema/nodes/roof.d.ts +22 -1
  159. package/dist/schema/nodes/roof.d.ts.map +1 -1
  160. package/dist/schema/nodes/roof.js +14 -0
  161. package/dist/schema/nodes/scan.d.ts +19 -2
  162. package/dist/schema/nodes/scan.d.ts.map +1 -1
  163. package/dist/schema/nodes/scan.js +11 -1
  164. package/dist/schema/nodes/shelf.d.ts +2 -2
  165. package/dist/schema/nodes/site.d.ts +1 -1
  166. package/dist/schema/nodes/skylight.d.ts +1 -1
  167. package/dist/schema/nodes/slab.d.ts +1 -1
  168. package/dist/schema/nodes/solar-panel.d.ts +1 -1
  169. package/dist/schema/nodes/spawn.d.ts +1 -1
  170. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  171. package/dist/schema/nodes/stair.d.ts +1 -1
  172. package/dist/schema/nodes/structural-grid.d.ts +1 -1
  173. package/dist/schema/nodes/turbine-vent.d.ts +7 -1
  174. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
  175. package/dist/schema/nodes/turbine-vent.js +2 -0
  176. package/dist/schema/nodes/wall.d.ts +4 -4
  177. package/dist/schema/nodes/wall.d.ts.map +1 -1
  178. package/dist/schema/nodes/wall.js +7 -1
  179. package/dist/schema/nodes/window.d.ts +10 -3
  180. package/dist/schema/nodes/window.d.ts.map +1 -1
  181. package/dist/schema/nodes/window.js +4 -0
  182. package/dist/schema/nodes/zone.d.ts +2 -2
  183. package/dist/schema/nodes/zone.js +1 -1
  184. package/dist/schema/types.d.ts +485 -109
  185. package/dist/schema/types.d.ts.map +1 -1
  186. package/dist/schema/types.js +27 -1
  187. package/dist/services/hosting.d.ts +2 -2
  188. package/dist/services/hosting.d.ts.map +1 -1
  189. package/dist/services/hosting.js +4 -4
  190. package/dist/services/index.d.ts +2 -2
  191. package/dist/services/index.d.ts.map +1 -1
  192. package/dist/services/index.js +2 -2
  193. package/dist/services/storey.d.ts +3 -10
  194. package/dist/services/storey.d.ts.map +1 -1
  195. package/dist/services/storey.js +45 -15
  196. package/dist/services/system-graph.d.ts +11 -0
  197. package/dist/services/system-graph.d.ts.map +1 -1
  198. package/dist/services/system-graph.js +35 -5
  199. package/dist/store/actions/node-actions.d.ts +15 -4
  200. package/dist/store/actions/node-actions.d.ts.map +1 -1
  201. package/dist/store/actions/node-actions.js +458 -19
  202. package/dist/store/history-control.d.ts +5 -0
  203. package/dist/store/history-control.d.ts.map +1 -1
  204. package/dist/store/history-control.js +68 -6
  205. package/dist/store/history-invalidation.d.ts +3 -0
  206. package/dist/store/history-invalidation.d.ts.map +1 -0
  207. package/dist/store/history-invalidation.js +72 -0
  208. package/dist/store/scene-hydration.d.ts +5 -0
  209. package/dist/store/scene-hydration.d.ts.map +1 -0
  210. package/dist/store/scene-hydration.js +57 -0
  211. package/dist/store/use-scene.d.ts +3 -0
  212. package/dist/store/use-scene.d.ts.map +1 -1
  213. package/dist/store/use-scene.js +407 -227
  214. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  215. package/dist/systems/elevator/elevator-service.js +16 -11
  216. package/dist/systems/roof/roof-elevation-system.d.ts +3 -0
  217. package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -0
  218. package/dist/systems/roof/roof-elevation-system.js +94 -0
  219. package/dist/systems/roof/roof-elevation.d.ts +4 -0
  220. package/dist/systems/roof/roof-elevation.d.ts.map +1 -0
  221. package/dist/systems/roof/roof-elevation.js +95 -0
  222. package/dist/systems/roof/roof-footprint.d.ts +17 -0
  223. package/dist/systems/roof/roof-footprint.d.ts.map +1 -0
  224. package/dist/systems/roof/roof-footprint.js +83 -0
  225. package/dist/systems/slab/slab-support.d.ts +0 -31
  226. package/dist/systems/slab/slab-support.d.ts.map +1 -1
  227. package/dist/systems/slab/slab-support.js +26 -4
  228. package/dist/systems/stair/stair-flight.d.ts +17 -0
  229. package/dist/systems/stair/stair-flight.d.ts.map +1 -0
  230. package/dist/systems/stair/stair-flight.js +27 -0
  231. package/dist/systems/stair/stair-opening-preview.d.ts +1556 -1328
  232. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  233. package/dist/systems/stair/stair-opening-system.d.ts +1 -0
  234. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  235. package/dist/systems/stair/stair-opening-system.js +93 -96
  236. package/dist/systems/stair/stair-rise.js +21 -20
  237. package/dist/systems/wall/wall-curve.d.ts +1 -0
  238. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  239. package/dist/systems/wall/wall-curve.js +73 -0
  240. package/dist/systems/wall/wall-footprint.d.ts +1 -0
  241. package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
  242. package/dist/systems/wall/wall-footprint.js +1 -0
  243. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  244. package/dist/systems/wall/wall-mitering.js +88 -9
  245. package/dist/systems/wall/wall-topology.d.ts +53 -0
  246. package/dist/systems/wall/wall-topology.d.ts.map +1 -0
  247. package/dist/systems/wall/wall-topology.js +414 -0
  248. package/dist/utils/clone-scene-graph.d.ts +2 -0
  249. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  250. package/dist/utils/clone-scene-graph.js +39 -2
  251. package/dist/utils/scene-migrations.d.ts +1 -0
  252. package/dist/utils/scene-migrations.d.ts.map +1 -1
  253. package/dist/utils/scene-migrations.js +1 -0
  254. package/dist/utils/vertical-scene-migration.d.ts +13 -0
  255. package/dist/utils/vertical-scene-migration.d.ts.map +1 -0
  256. package/dist/utils/vertical-scene-migration.js +179 -0
  257. package/dist/validation/validate-build-json.d.ts +3 -0
  258. package/dist/validation/validate-build-json.d.ts.map +1 -1
  259. package/dist/validation/validate-build-json.js +49 -2
  260. package/package.json +10 -3
  261. package/dist/systems/elevator/elevator-runtime-system.d.ts +0 -2
  262. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +0 -1
  263. package/dist/systems/elevator/elevator-runtime-system.js +0 -8
@@ -1 +1 @@
1
- {"version":3,"file":"elevator-service.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAa,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAG/E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AA2BD,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,SAAS,EAAE,CAEb;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,EAAE,CAEV;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,SAAS,EAAE,CAsBb;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B;IACD,OAAO,EAAE,kBAAkB,EAAE,CAAA;IAC7B,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB,CA0CA"}
1
+ {"version":3,"file":"elevator-service.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAa,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAG/E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AA2BD,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,SAAS,EAAE,CAEb;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,EAAE,CAEV;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,SAAS,EAAE,CAsBb;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B;IACD,OAAO,EAAE,kBAAkB,EAAE,CAAA;IAC7B,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB,CA6CA"}
@@ -1,4 +1,4 @@
1
- import { getStoredLevelHeight } from '../../services/storey';
1
+ import { getLevelElevations } from '../../services/storey';
2
2
  function getBuildingLevels(elevator, nodes) {
3
3
  const building = elevator.parentId && nodes[elevator.parentId]?.type === 'building'
4
4
  ? nodes[elevator.parentId]
@@ -51,17 +51,12 @@ export function resolveElevatorServiceLevels(elevator, nodes) {
51
51
  }
52
52
  export function resolveElevatorLevels(elevator, nodes) {
53
53
  const allLevels = resolveElevatorBuildingLevels(elevator, nodes);
54
- const baseYByLevelId = new Map();
55
- let cumulativeY = 0;
56
- for (const level of allLevels) {
57
- baseYByLevelId.set(level.id, cumulativeY);
58
- cumulativeY += getStoredLevelHeight(level);
59
- }
54
+ const levelElevations = getLevelElevations(nodes);
60
55
  const serviceLevels = resolveElevatorServiceLevels(elevator, nodes);
61
56
  const entries = serviceLevels.map((level) => ({
62
57
  id: level.id,
63
58
  label: String(level.level),
64
- baseY: baseYByLevelId.get(level.id) ?? 0,
59
+ baseY: levelElevations.get(level.id)?.baseY ?? 0,
65
60
  }));
66
61
  const defaultEntry = entries.find((entry) => entry.id === elevator.defaultLevelId) ??
67
62
  entries.find((entry) => entry.id === elevator.fromLevelId) ??
@@ -69,15 +64,25 @@ export function resolveElevatorLevels(elevator, nodes) {
69
64
  null;
70
65
  const firstServedLevel = serviceLevels[0] ?? null;
71
66
  const lastServedLevel = serviceLevels[serviceLevels.length - 1] ?? null;
72
- const shaftBaseY = firstServedLevel ? (baseYByLevelId.get(firstServedLevel.id) ?? 0) : 0;
67
+ const shaftBaseY = firstServedLevel ? (levelElevations.get(firstServedLevel.id)?.baseY ?? 0) : 0;
73
68
  const lastServedIndex = lastServedLevel
74
69
  ? allLevels.findIndex((level) => level.id === lastServedLevel.id)
75
70
  : -1;
76
71
  const nextLevel = lastServedIndex >= 0 ? allLevels[lastServedIndex + 1] : null;
72
+ // Highest ceiling in the stack, not the topmost level's: a negative
73
+ // baseElevation on the top level can put its ceiling below the level
74
+ // beneath it, and a shaft top under a served level would clip the cab.
75
+ let stackTopY = 0;
76
+ for (const level of allLevels) {
77
+ const elevation = levelElevations.get(level.id);
78
+ if (!elevation)
79
+ continue;
80
+ stackTopY = Math.max(stackTopY, elevation.baseY + elevation.height);
81
+ }
77
82
  const shaftTopY = nextLevel
78
- ? (baseYByLevelId.get(nextLevel.id) ?? cumulativeY)
83
+ ? (levelElevations.get(nextLevel.id)?.baseY ?? stackTopY)
79
84
  : lastServedLevel
80
- ? cumulativeY
85
+ ? stackTopY
81
86
  : elevator.cabHeight + 0.3;
82
87
  return {
83
88
  entries,
@@ -0,0 +1,3 @@
1
+ export declare function initializeRoofElevationSync(): () => void;
2
+ export declare function RoofElevationSystem(): null;
3
+ //# sourceMappingURL=roof-elevation-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-elevation-system.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,2BAA2B,IAAI,MAAM,IAAI,CAmExD;AAED,wBAAgB,mBAAmB,SAGlC"}
@@ -0,0 +1,94 @@
1
+ 'use client';
2
+ import { useEffect } from 'react';
3
+ import { getSceneHistoryPauseDepth, notifySceneCommit, pauseSceneHistory, resumeSceneHistory, } from '../../store/history-control';
4
+ import useScene from '../../store/use-scene';
5
+ import { resolveRoofElevation } from './roof-elevation';
6
+ const ROOF_ELEVATION_EPSILON = 1e-4;
7
+ function sceneSnapshot() {
8
+ const { nodes, rootNodeIds, collections, materials, installedPlugins } = useScene.getState();
9
+ return { nodes, rootNodeIds, collections, materials, installedPlugins };
10
+ }
11
+ function isElevationRelevant(node) {
12
+ return (node?.type === 'roof' ||
13
+ node?.type === 'wall' ||
14
+ node?.type === 'slab' ||
15
+ node?.type === 'level' ||
16
+ node?.type === 'building' ||
17
+ node?.type === 'site');
18
+ }
19
+ export function initializeRoofElevationSync() {
20
+ let disposed = false;
21
+ let queued = false;
22
+ let syncing = false;
23
+ const schedule = () => {
24
+ if (queued)
25
+ return;
26
+ queued = true;
27
+ // Wall bases use the spatial grid, whose listener must finish before this pass.
28
+ queueMicrotask(() => {
29
+ queued = false;
30
+ if (disposed)
31
+ return;
32
+ const { nodes, updateNodes, readOnly } = useScene.getState();
33
+ if (readOnly)
34
+ return;
35
+ const updates = [];
36
+ for (const node of Object.values(nodes)) {
37
+ if (node.type !== 'roof' || node.support?.kind !== 'walls')
38
+ continue;
39
+ const elevation = resolveRoofElevation(node, nodes);
40
+ if (Math.abs(node.position[1] - elevation) <= ROOF_ELEVATION_EPSILON)
41
+ continue;
42
+ updates.push({
43
+ id: node.id,
44
+ data: { position: [node.position[0], elevation, node.position[2]] },
45
+ });
46
+ }
47
+ if (updates.length === 0)
48
+ return;
49
+ const before = sceneSnapshot();
50
+ const publishCommit = useScene.temporal.getState().isTracking && getSceneHistoryPauseDepth() === 0;
51
+ syncing = true;
52
+ pauseSceneHistory(useScene);
53
+ try {
54
+ updateNodes(updates);
55
+ }
56
+ finally {
57
+ resumeSceneHistory(useScene);
58
+ syncing = false;
59
+ }
60
+ // Deferred writes miss the originating commit; publish the settled roof without
61
+ // another undo step. An outer pause belongs to a gesture that owns its commit.
62
+ if (publishCommit) {
63
+ notifySceneCommit({
64
+ origin: 'local',
65
+ before,
66
+ current: sceneSnapshot(),
67
+ changedNodeIds: new Set(updates.map(({ id }) => id)),
68
+ });
69
+ }
70
+ });
71
+ };
72
+ const unsubscribe = useScene.subscribe((state, previous) => {
73
+ if (syncing || state.nodes === previous.nodes)
74
+ return;
75
+ const ids = new Set([...Object.keys(state.nodes), ...Object.keys(previous.nodes)]);
76
+ for (const id of ids) {
77
+ const next = state.nodes[id];
78
+ const prev = previous.nodes[id];
79
+ if (next !== prev && (isElevationRelevant(next) || isElevationRelevant(prev))) {
80
+ schedule();
81
+ return;
82
+ }
83
+ }
84
+ });
85
+ schedule();
86
+ return () => {
87
+ disposed = true;
88
+ unsubscribe();
89
+ };
90
+ }
91
+ export function RoofElevationSystem() {
92
+ useEffect(initializeRoofElevationSync, []);
93
+ return null;
94
+ }
@@ -0,0 +1,4 @@
1
+ import type { AnyNode, LevelNode, RoofNode, WallNode } from '../../schema';
2
+ export declare function resolveRoofWallTopElevation(targetLevelId: LevelNode['id'], wall: WallNode, nodes: Readonly<Record<string, AnyNode>>, elevations?: Map<string, import("../..").LevelElevation>): number;
3
+ export declare function resolveRoofElevation(roof: RoofNode, nodes: Readonly<Record<string, AnyNode>>): number;
4
+ //# sourceMappingURL=roof-elevation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-elevation.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-elevation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAmB,QAAQ,EAAE,MAAM,cAAc,CAAA;AAM3F,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,SAAS,CAAC,IAAI,CAAC,EAC9B,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,UAAU,8CAA4B,GACrC,MAAM,CASR;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,MAAM,CAwFR"}
@@ -0,0 +1,95 @@
1
+ import { getWallBaseElevationForNodes, getWallEffectiveHeightForNodes, } from '../../hooks/spatial-grid/spatial-grid-manager';
2
+ import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync';
3
+ import { findLevelBelowId, getLevelElevations } from '../../services/storey';
4
+ import { wallOverlapsSlabFootprint } from '../slab/slab-support';
5
+ import { getWallArcData } from '../wall/wall-curve';
6
+ import { resolveRoomRoofFootprintOnLevel } from './roof-footprint';
7
+ export function resolveRoofWallTopElevation(targetLevelId, wall, nodes, elevations = getLevelElevations(nodes)) {
8
+ const sourceLevelY = elevations.get(resolveLevelId(wall, nodes))?.baseY ?? 0;
9
+ const targetLevelY = elevations.get(targetLevelId)?.baseY ?? 0;
10
+ return (sourceLevelY +
11
+ getWallBaseElevationForNodes(wall, nodes) +
12
+ getWallEffectiveHeightForNodes(wall, nodes) -
13
+ targetLevelY);
14
+ }
15
+ export function resolveRoofElevation(roof, nodes) {
16
+ if (roof.support?.kind !== 'walls')
17
+ return roof.position[1];
18
+ const levelId = resolveLevelId(roof, nodes);
19
+ if (nodes[levelId]?.type !== 'level')
20
+ return roof.position[1];
21
+ const elevations = getLevelElevations(nodes);
22
+ // A roof usually sits on the storey above its walls, but the top floor (or a
23
+ // roof armed from the walls' own level) keeps roof and walls on one level.
24
+ const belowId = findLevelBelowId(levelId, elevations);
25
+ const candidateWallIds = [levelId, belowId]
26
+ .map((id) => (id ? nodes[id] : undefined))
27
+ .filter((node) => node?.type === 'level')
28
+ .flatMap((level) => level.children);
29
+ const conicalSegments = roof.children
30
+ .map((id) => nodes[id])
31
+ .filter((node) => node?.type === 'roof-segment' && node.roofType === 'conical');
32
+ const cos = Math.cos(roof.rotation);
33
+ const sin = Math.sin(roof.rotation);
34
+ const toLevel = (x, z) => [
35
+ roof.position[0] + x * cos + z * sin,
36
+ roof.position[2] - x * sin + z * cos,
37
+ ];
38
+ // Walls under the footprint, closed room or not: a room missing a wall, an
39
+ // L-shaped room whose centre falls outside, or a redrawn enclosure all still
40
+ // hold the roof up. The band test is curve- and thickness-aware and
41
+ // boundary-inclusive, so perimeter walls on the footprint edge count.
42
+ const footprints = roof.children
43
+ .map((id) => nodes[id])
44
+ .filter((node) => node?.type === 'roof-segment')
45
+ .map((segment) => {
46
+ const c = Math.cos(segment.rotation);
47
+ const s = Math.sin(segment.rotation);
48
+ const halfW = segment.width / 2;
49
+ const halfD = segment.depth / 2;
50
+ const corners = [
51
+ [-halfW, -halfD],
52
+ [halfW, -halfD],
53
+ [halfW, halfD],
54
+ [-halfW, halfD],
55
+ ];
56
+ return corners.map(([x, z]) => toLevel(segment.position[0] + x * c + z * s, segment.position[2] - x * s + z * c));
57
+ });
58
+ const wallIds = conicalSegments.length
59
+ ? candidateWallIds.filter((id) => {
60
+ const wall = nodes[id];
61
+ if (wall?.type !== 'wall')
62
+ return false;
63
+ const arc = getWallArcData(wall);
64
+ if (!arc)
65
+ return false;
66
+ return conicalSegments.some((segment) => {
67
+ const [centerX, centerZ] = toLevel(segment.position[0], segment.position[2]);
68
+ return (Math.hypot(arc.center.x - centerX, arc.center.y - centerZ) <= 1e-4 &&
69
+ Math.abs(arc.radius - segment.width / 2) <= 1e-4);
70
+ });
71
+ })
72
+ : footprints.length
73
+ ? candidateWallIds.filter((id) => {
74
+ const wall = nodes[id];
75
+ return (wall?.type === 'wall' &&
76
+ footprints.some((polygon) => wallOverlapsSlabFootprint(wall, polygon)));
77
+ })
78
+ : ([levelId, belowId]
79
+ .map((id) => id
80
+ ? resolveRoomRoofFootprintOnLevel(id, nodes, [
81
+ roof.position[0],
82
+ roof.position[2],
83
+ ])
84
+ : null)
85
+ .find((target) => target !== null)?.wallIds ?? []);
86
+ let highest;
87
+ for (const id of wallIds) {
88
+ const wall = nodes[id];
89
+ if (wall?.type !== 'wall')
90
+ continue;
91
+ const top = resolveRoofWallTopElevation(levelId, wall, nodes, elevations);
92
+ highest = highest === undefined ? top : Math.max(highest, top);
93
+ }
94
+ return highest ?? roof.position[1];
95
+ }
@@ -0,0 +1,17 @@
1
+ import type { AnyNode, LevelNode, WallNode } from '../../schema';
2
+ export type RoofFootprintTarget = {
3
+ id: string;
4
+ polygon: Array<[number, number]>;
5
+ wallIds: WallNode['id'][];
6
+ center: [number, number];
7
+ width: number;
8
+ depth: number;
9
+ rotation: number;
10
+ rectangular: boolean;
11
+ };
12
+ export declare function fitRoofFootprint(id: string, polygon: Array<[number, number]>, wallIds: WallNode['id'][]): RoofFootprintTarget | null;
13
+ export declare function resolveRoomRoofFootprint(levelId: LevelNode['id'], nodes: Readonly<Record<string, AnyNode>>, point: [number, number], options?: {
14
+ rectangularOnly?: boolean;
15
+ }): RoofFootprintTarget | null;
16
+ export declare function resolveRoomRoofFootprintOnLevel(levelId: LevelNode['id'], nodes: Readonly<Record<string, AnyNode>>, point: [number, number]): RoofFootprintTarget | null;
17
+ //# sourceMappingURL=roof-footprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-footprint.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-footprint.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAChC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;IACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,OAAO,CAAA;CACrB,CAAA;AAWD,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAChC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,GACxB,mBAAmB,GAAG,IAAI,CAqD5B;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,EACxB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACvB,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1C,mBAAmB,GAAG,IAAI,CAW5B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,EACxB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,mBAAmB,GAAG,IAAI,CAW5B"}
@@ -0,0 +1,83 @@
1
+ import { pointInPolygon as pointInPolygon2D } from '../../lib/polygon-relations';
2
+ import { detectSpacesForLevel } from '../../lib/space-detection';
3
+ import { getLevelBelow } from '../../services/storey';
4
+ function polygonArea(polygon) {
5
+ return Math.abs(polygon.reduce((area, point, index) => {
6
+ const next = polygon[(index + 1) % polygon.length];
7
+ return next ? area + point[0] * next[1] - next[0] * point[1] : area;
8
+ }, 0) / 2);
9
+ }
10
+ export function fitRoofFootprint(id, polygon, wallIds) {
11
+ if (polygon.length < 3)
12
+ return null;
13
+ let best;
14
+ for (let index = 0; index < polygon.length; index++) {
15
+ const point = polygon[index];
16
+ const next = polygon[(index + 1) % polygon.length];
17
+ if (!(point && next))
18
+ continue;
19
+ const rotation = Math.atan2(next[1] - point[1], next[0] - point[0]);
20
+ const cos = Math.cos(rotation);
21
+ const sin = Math.sin(rotation);
22
+ const rotated = polygon.map(([x, z]) => [x * cos + z * sin, -x * sin + z * cos]);
23
+ const xs = rotated.map(([x]) => x);
24
+ const zs = rotated.map(([, z]) => z);
25
+ const minX = Math.min(...xs);
26
+ const maxX = Math.max(...xs);
27
+ const minZ = Math.min(...zs);
28
+ const maxZ = Math.max(...zs);
29
+ const width = maxX - minX;
30
+ const depth = maxZ - minZ;
31
+ const area = width * depth;
32
+ if (area <= 0 || (best && best.area <= area))
33
+ continue;
34
+ const localCenterX = (minX + maxX) / 2;
35
+ const localCenterZ = (minZ + maxZ) / 2;
36
+ best = {
37
+ center: [localCenterX * cos - localCenterZ * sin, localCenterX * sin + localCenterZ * cos],
38
+ width,
39
+ depth,
40
+ rotation: -rotation,
41
+ area,
42
+ };
43
+ }
44
+ if (!best)
45
+ return null;
46
+ return {
47
+ id,
48
+ polygon,
49
+ wallIds,
50
+ center: best.center,
51
+ width: best.width,
52
+ depth: best.depth,
53
+ rotation: best.rotation,
54
+ rectangular: polygonArea(polygon) / best.area >= 0.96,
55
+ };
56
+ }
57
+ export function resolveRoomRoofFootprint(levelId, nodes, point, options = {}) {
58
+ const activeTarget = resolveRoomRoofFootprintOnLevel(levelId, nodes, point);
59
+ if (activeTarget && (!options.rectangularOnly || activeTarget.rectangular))
60
+ return activeTarget;
61
+ if (activeTarget)
62
+ return null;
63
+ const levelBelow = getLevelBelow(levelId, nodes);
64
+ const levelBelowTarget = levelBelow
65
+ ? resolveRoomRoofFootprintOnLevel(levelBelow.id, nodes, point)
66
+ : null;
67
+ return levelBelowTarget && (!options.rectangularOnly || levelBelowTarget.rectangular)
68
+ ? levelBelowTarget
69
+ : null;
70
+ }
71
+ export function resolveRoomRoofFootprintOnLevel(levelId, nodes, point) {
72
+ const level = nodes[levelId];
73
+ if (level?.type !== 'level')
74
+ return null;
75
+ const walls = level.children
76
+ .map((id) => nodes[id])
77
+ .filter((node) => node?.type === 'wall');
78
+ const spaces = detectSpacesForLevel(levelId, walls)
79
+ .spaces.filter((space) => !space.isExterior && pointInPolygon2D(point, space.polygon))
80
+ .sort((left, right) => polygonArea(left.polygon) - polygonArea(right.polygon));
81
+ const space = spaces[0];
82
+ return space ? fitRoofFootprint(space.id, space.polygon, space.wallIds) : null;
83
+ }
@@ -114,37 +114,6 @@ export type WallSlabSupportSegment = {
114
114
  end: number;
115
115
  elevation: number;
116
116
  };
117
- /**
118
- * `preferredSlabId` is a persisted support host (`wall.supportSlabId`):
119
- * while that slab is still in the candidate set (still overlaps the wall
120
- * band with enough covered length), the elected `elevation` is pinned to
121
- * it instead of the majority/best-coverage election. `baseSegments` /
122
- * `baseElevation` (fill-down) still derive from ALL supporting slabs
123
- * unchanged. A preferred slab that no longer qualifies is silently
124
- * ignored — deliberately never cleared here, so the host resumes if the
125
- * slab's polygon returns (only slab deletion strips the stored field).
126
- *
127
- * `maxElevation` is the pointer-decided support cap (level-local Y, same
128
- * semantics as the item election): when set, elevation groups whose
129
- * walking surface sits above `maxElevation + SUPPORT_ELEVATION_EPSILON`
130
- * are excluded from the majority/best election — a deck hanging above the
131
- * surface the cursor ray actually hit never captures the elected base.
132
- * `baseSegments` / `baseElevation` stay uncapped (geometry fill-down), and
133
- * an explicit `preferredSlabId` still wins over the cap.
134
- *
135
- * `levelBase` is what "no slab supports this here" evaluates to — the
136
- * sculpted ground under the wall (`levelBaseElevationAt`), or 0 for a level
137
- * with no terrain under it. A caller-resolved scalar rather than a terrain
138
- * lookup in here, so this stays pure and the sample stays at the wall's own
139
- * XZ. It substitutes for every place this function used to write a literal
140
- * `0`, and nowhere else: a slab that supports the wall's faces still wins
141
- * outright, so a slab pad on a hillside keeps its wall at the pad's
142
- * elevation instead of being overruled by the ground around it. The one
143
- * clamp — center-only support, where the old code already clamped a
144
- * recessed slab up to `0` so a wall over a pool didn't sink — generalizes to
145
- * "never below the ground", which is the same rule with the ground no longer
146
- * assumed flat.
147
- */
148
117
  export declare function computeWallSlabSupport(wallLike: WallOverlapInput, slabs: readonly SlabNode[], levelWalls: WallNode[], preferredSlabId?: string | null, maxElevation?: number | null, levelBase?: number): WallSlabSupport;
149
118
  export declare function computeWallSlabElevation(wallLike: WallOverlapInput, slabs: readonly SlabNode[], levelWalls: WallNode[]): number;
150
119
  //# sourceMappingURL=slab-support.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slab-support.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-support.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slab-support.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-support.ts"],"names":[],"mappings":"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"}
@@ -396,6 +396,31 @@ const WALL_SLAB_ELEVATION_POOL_EPSILON = 1e-4;
396
396
  * "never below the ground", which is the same rule with the ground no longer
397
397
  * assumed flat.
398
398
  */
399
+ // A rendered slab polygon depends only on the slab set and the level's walls,
400
+ // never on the wall being tested — but a per-frame pass asks for support once
401
+ // per wall, so the identical polygons were rebuilt for every wall on the level
402
+ // (and each rebuild scans all of `levelWalls`). Keyed on array identity: the
403
+ // caller derives those arrays once and only rebuilds them when the scene or a
404
+ // live preview changes, so a hit means the inputs are the same objects.
405
+ let polygonMemoSlabs = null;
406
+ let polygonMemoWalls = null;
407
+ let polygonMemo = new Map();
408
+ function renderedSlabPolygon(slab, slabs, levelWalls) {
409
+ if (polygonMemoSlabs !== slabs || polygonMemoWalls !== levelWalls) {
410
+ polygonMemoSlabs = slabs;
411
+ polygonMemoWalls = levelWalls;
412
+ polygonMemo = new Map();
413
+ }
414
+ const cached = polygonMemo.get(slab.id);
415
+ if (cached)
416
+ return cached;
417
+ const polygon = getRenderableSlabPolygon(slab, {
418
+ walls: levelWalls,
419
+ siblingSlabs: slabs.filter((other) => other.id !== slab.id),
420
+ });
421
+ polygonMemo.set(slab.id, polygon);
422
+ return polygon;
423
+ }
399
424
  export function computeWallSlabSupport(wallLike, slabs, levelWalls, preferredSlabId, maxElevation, levelBase = 0) {
400
425
  const { start, end, curveOffset = 0, thickness = DEFAULT_WALL_THICKNESS } = wallLike;
401
426
  const halfThickness = Math.max(thickness / 2, 0);
@@ -417,10 +442,7 @@ export function computeWallSlabSupport(wallLike, slabs, levelWalls, preferredSla
417
442
  for (const slab of slabs) {
418
443
  if (slab.polygon.length < 3)
419
444
  continue;
420
- const renderedPolygon = getRenderableSlabPolygon(slab, {
421
- walls: levelWalls,
422
- siblingSlabs: slabs.filter((other) => other.id !== slab.id),
423
- });
445
+ const renderedPolygon = renderedSlabPolygon(slab, slabs, levelWalls);
424
446
  let supported = 0;
425
447
  const perPolyline = polylines.map((line) => {
426
448
  let intervals = polylineInsideIntervals(line, renderedPolygon);
@@ -0,0 +1,17 @@
1
+ import type { AnyNode, StairNode } from '../../schema';
2
+ import { StairSegmentNode } from '../../schema';
3
+ export type StairFlightOverrides = Partial<Pick<StairSegmentNode, 'width' | 'length' | 'height' | 'stepCount' | 'attachmentSide' | 'fillToFloor' | 'thickness'>>;
4
+ /**
5
+ * The single definition of a default straight flight. Anything left out falls
6
+ * through to the `StairSegmentNode` schema defaults (length 3 m, 10 steps,
7
+ * filled to floor) rather than being spelled again per call site, so the stair
8
+ * tool's seed segment, the flight the panel materializes when a curved stair
9
+ * becomes straight, and the viewer's fallback body all describe one stair.
10
+ */
11
+ export declare function createDefaultStairSegment(overrides?: StairFlightOverrides): StairSegmentNode;
12
+ /**
13
+ * The flight a straight stair implies from its own fields — used wherever a
14
+ * straight stair has to stand in for missing `stair-segment` children.
15
+ */
16
+ export declare function createStairFlightFromStair(stair: StairNode, nodes: Record<string, AnyNode>): StairSegmentNode;
17
+ //# sourceMappingURL=stair-flight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-flight.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-flight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAM/C,MAAM,MAAM,oBAAoB,GAAG,OAAO,CACxC,IAAI,CACF,gBAAgB,EAChB,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,gBAAgB,GAAG,aAAa,GAAG,WAAW,CAC7F,CACF,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,GAAE,oBAAyB,GAAG,gBAAgB,CAEhG;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,gBAAgB,CAQlB"}
@@ -0,0 +1,27 @@
1
+ import { StairSegmentNode } from '../../schema';
2
+ import { resolveStairTotalRise } from './stair-rise';
3
+ const MIN_STAIR_FLIGHT_RISE = 0.1;
4
+ const MIN_STAIR_FLIGHT_STEP_COUNT = 2;
5
+ /**
6
+ * The single definition of a default straight flight. Anything left out falls
7
+ * through to the `StairSegmentNode` schema defaults (length 3 m, 10 steps,
8
+ * filled to floor) rather than being spelled again per call site, so the stair
9
+ * tool's seed segment, the flight the panel materializes when a curved stair
10
+ * becomes straight, and the viewer's fallback body all describe one stair.
11
+ */
12
+ export function createDefaultStairSegment(overrides = {}) {
13
+ return StairSegmentNode.parse({ segmentType: 'stair', position: [0, 0, 0], ...overrides });
14
+ }
15
+ /**
16
+ * The flight a straight stair implies from its own fields — used wherever a
17
+ * straight stair has to stand in for missing `stair-segment` children.
18
+ */
19
+ export function createStairFlightFromStair(stair, nodes) {
20
+ return createDefaultStairSegment({
21
+ width: stair.width,
22
+ height: Math.max(resolveStairTotalRise(stair, nodes), MIN_STAIR_FLIGHT_RISE),
23
+ stepCount: Math.max(MIN_STAIR_FLIGHT_STEP_COUNT, Math.round(stair.stepCount ?? 10)),
24
+ thickness: stair.thickness,
25
+ fillToFloor: stair.fillToFloor,
26
+ });
27
+ }