@pascal-app/core 1.0.7 → 1.1.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 (266) hide show
  1. package/dist/agent-operations/__fixtures__/cases.d.ts +3 -2
  2. package/dist/agent-operations/__fixtures__/cases.d.ts.map +1 -1
  3. package/dist/agent-operations/__fixtures__/native-materials.d.ts +4 -0
  4. package/dist/agent-operations/__fixtures__/native-materials.d.ts.map +1 -1
  5. package/dist/agent-operations/__fixtures__/place-items-cases.d.ts.map +1 -1
  6. package/dist/agent-operations/__fixtures__/place-items-cases.js +28 -0
  7. package/dist/agent-operations/__fixtures__/roof-bearing-cases.d.ts +9567 -9563
  8. package/dist/agent-operations/__fixtures__/roof-bearing-cases.d.ts.map +1 -1
  9. package/dist/agent-operations/__fixtures__/verify-scene-cases.d.ts +77 -77
  10. package/dist/agent-operations/asset-catalog.d.ts +21 -11
  11. package/dist/agent-operations/asset-catalog.d.ts.map +1 -1
  12. package/dist/agent-operations/asset-catalog.js +28 -10
  13. package/dist/agent-operations/asset-views.d.ts +8 -3
  14. package/dist/agent-operations/asset-views.d.ts.map +1 -1
  15. package/dist/agent-operations/asset-views.js +19 -3
  16. package/dist/agent-operations/author-scene.d.ts.map +1 -1
  17. package/dist/agent-operations/author-scene.js +2 -3
  18. package/dist/agent-operations/batch.d.ts +7 -1
  19. package/dist/agent-operations/batch.d.ts.map +1 -1
  20. package/dist/agent-operations/batch.js +17 -2
  21. package/dist/agent-operations/catalog-asset.d.ts +15 -0
  22. package/dist/agent-operations/catalog-asset.d.ts.map +1 -0
  23. package/dist/agent-operations/catalog-asset.js +4 -0
  24. package/dist/agent-operations/circulation-spaces.d.ts +1724 -1723
  25. package/dist/agent-operations/circulation-spaces.d.ts.map +1 -1
  26. package/dist/agent-operations/design-brief.d.ts.map +1 -1
  27. package/dist/agent-operations/design-brief.js +2 -0
  28. package/dist/agent-operations/furnish-room.d.ts.map +1 -1
  29. package/dist/agent-operations/furnish-room.js +2 -1
  30. package/dist/agent-operations/index.d.ts +3 -0
  31. package/dist/agent-operations/index.d.ts.map +1 -1
  32. package/dist/agent-operations/index.js +4 -1
  33. package/dist/agent-operations/level-reads.d.ts.map +1 -1
  34. package/dist/agent-operations/level-reads.js +5 -2
  35. package/dist/agent-operations/move-plan.d.ts.map +1 -1
  36. package/dist/agent-operations/move-plan.js +11 -9
  37. package/dist/agent-operations/place-in-room.d.ts.map +1 -1
  38. package/dist/agent-operations/place-in-room.js +2 -1
  39. package/dist/agent-operations/place-items.d.ts.map +1 -1
  40. package/dist/agent-operations/place-items.js +2 -1
  41. package/dist/agent-operations/scene-queries.d.ts +2944 -2943
  42. package/dist/agent-operations/scene-queries.d.ts.map +1 -1
  43. package/dist/agent-operations/scene-view.d.ts.map +1 -1
  44. package/dist/agent-operations/scene-view.js +38 -13
  45. package/dist/agent-operations/search-assets.d.ts.map +1 -1
  46. package/dist/agent-operations/search-assets.js +6 -0
  47. package/dist/agent-operations/types.d.ts +3 -2
  48. package/dist/agent-operations/types.d.ts.map +1 -1
  49. package/dist/agent-operations/usage-checks.js +18 -3
  50. package/dist/agent-operations/verify-scene.d.ts.map +1 -1
  51. package/dist/agent-operations/verify-scene.js +4 -2
  52. package/dist/agent-operations/wall-object-observations.d.ts.map +1 -1
  53. package/dist/agent-operations/wall-object-observations.js +1 -1
  54. package/dist/agent-tools/assets.js +1 -1
  55. package/dist/agent-tools/wall-openings.js +2 -2
  56. package/dist/building/__fixtures__/wall-opening-cases.d.ts +13 -12
  57. package/dist/building/__fixtures__/wall-opening-cases.d.ts.map +1 -1
  58. package/dist/building/level-duplication.d.ts +1281 -1279
  59. package/dist/building/level-duplication.d.ts.map +1 -1
  60. package/dist/building/level-duplication.js +3 -1
  61. package/dist/building/merge-windows.d.ts.map +1 -1
  62. package/dist/building/merge-windows.js +4 -3
  63. package/dist/building/wall-openings.d.ts +95 -81
  64. package/dist/building/wall-openings.d.ts.map +1 -1
  65. package/dist/building/wall-openings.js +74 -25
  66. package/dist/commands/structure/shared.d.ts +3203 -3202
  67. package/dist/commands/structure/shared.d.ts.map +1 -1
  68. package/dist/commands/structure/zone-wall-merge.d.ts +2028 -2025
  69. package/dist/commands/structure/zone-wall-merge.d.ts.map +1 -1
  70. package/dist/contracts/reference-inventory.d.ts.map +1 -1
  71. package/dist/contracts/reference-inventory.js +0 -1
  72. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +6 -1
  73. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  74. package/dist/hooks/spatial-grid/spatial-grid-manager.js +30 -9
  75. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2101 -2100
  76. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  77. package/dist/hooks/spatial-grid/spatial-grid-sync.js +8 -4
  78. package/dist/index.d.ts +13 -4
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +13 -4
  81. package/dist/lib/__fixtures__/mezzanine.d.ts +6428 -6424
  82. package/dist/lib/__fixtures__/mezzanine.d.ts.map +1 -1
  83. package/dist/lib/absorb-wall-separators.d.ts +2267 -2266
  84. package/dist/lib/absorb-wall-separators.d.ts.map +1 -1
  85. package/dist/lib/asset-storage.d.ts.map +1 -1
  86. package/dist/lib/asset-storage.js +3 -0
  87. package/dist/lib/build-mode.d.ts +11 -0
  88. package/dist/lib/build-mode.d.ts.map +1 -0
  89. package/dist/lib/build-mode.js +24 -0
  90. package/dist/lib/corner-pair.d.ts +2 -0
  91. package/dist/lib/corner-pair.d.ts.map +1 -1
  92. package/dist/lib/door-interaction.d.ts +14 -0
  93. package/dist/lib/door-interaction.d.ts.map +1 -0
  94. package/dist/lib/door-interaction.js +62 -0
  95. package/dist/lib/elevator-support.d.ts +15 -0
  96. package/dist/lib/elevator-support.d.ts.map +1 -0
  97. package/dist/lib/elevator-support.js +40 -0
  98. package/dist/lib/floor-foundation-datum.d.ts.map +1 -1
  99. package/dist/lib/floor-foundation-datum.js +5 -1
  100. package/dist/lib/floor-intent-changes.d.ts.map +1 -1
  101. package/dist/lib/floor-intent-changes.js +6 -4
  102. package/dist/lib/floorplan/context.d.ts +95 -0
  103. package/dist/lib/floorplan/context.d.ts.map +1 -0
  104. package/dist/lib/floorplan/context.js +155 -0
  105. package/dist/lib/floorplan/index.d.ts +6 -0
  106. package/dist/lib/floorplan/index.d.ts.map +1 -0
  107. package/dist/lib/floorplan/index.js +5 -0
  108. package/dist/lib/floorplan/marks.d.ts +71 -0
  109. package/dist/lib/floorplan/marks.d.ts.map +1 -0
  110. package/dist/lib/floorplan/marks.js +157 -0
  111. package/dist/lib/floorplan/plan-math.d.ts +18 -0
  112. package/dist/lib/floorplan/plan-math.d.ts.map +1 -0
  113. package/dist/lib/floorplan/plan-math.js +82 -0
  114. package/dist/lib/floorplan/svg-paths.d.ts +17 -0
  115. package/dist/lib/floorplan/svg-paths.d.ts.map +1 -0
  116. package/dist/lib/floorplan/svg-paths.js +92 -0
  117. package/dist/lib/floorplan/wall-affordances.d.ts +31 -0
  118. package/dist/lib/floorplan/wall-affordances.d.ts.map +1 -0
  119. package/dist/lib/floorplan/wall-affordances.js +24 -0
  120. package/dist/lib/hierarchy.d.ts +19 -0
  121. package/dist/lib/hierarchy.d.ts.map +1 -0
  122. package/dist/lib/hierarchy.js +53 -0
  123. package/dist/lib/opening-floor-datum.d.ts +1 -1
  124. package/dist/lib/opening-floor-datum.d.ts.map +1 -1
  125. package/dist/lib/opening-floor-datum.js +2 -2
  126. package/dist/lib/own-floor-intent.d.ts.map +1 -1
  127. package/dist/lib/own-floor-intent.js +2 -2
  128. package/dist/lib/polygon-boolean.d.ts.map +1 -1
  129. package/dist/lib/polygon-boolean.js +2 -1
  130. package/dist/lib/room-floor-elevation.d.ts +12 -0
  131. package/dist/lib/room-floor-elevation.d.ts.map +1 -0
  132. package/dist/lib/room-floor-elevation.js +41 -0
  133. package/dist/lib/room-floor-feasibility.d.ts.map +1 -1
  134. package/dist/lib/room-floor-feasibility.js +1 -1
  135. package/dist/lib/space-detection.d.ts.map +1 -1
  136. package/dist/lib/space-detection.js +1 -0
  137. package/dist/lib/wall-face-script.d.ts.map +1 -1
  138. package/dist/lib/wall-face-script.js +3 -3
  139. package/dist/lib/window-interaction.d.ts +16 -0
  140. package/dist/lib/window-interaction.d.ts.map +1 -0
  141. package/dist/lib/window-interaction.js +82 -0
  142. package/dist/lib/zone-quantities.js +1 -1
  143. package/dist/node-slots/column.d.ts +5 -0
  144. package/dist/node-slots/column.d.ts.map +1 -1
  145. package/dist/node-slots/column.js +10 -0
  146. package/dist/registry/__fixtures__/roof-planes.d.ts +69 -0
  147. package/dist/registry/__fixtures__/roof-planes.d.ts.map +1 -0
  148. package/dist/registry/__fixtures__/roof-planes.js +59 -0
  149. package/dist/registry/index.d.ts +3 -2
  150. package/dist/registry/index.d.ts.map +1 -1
  151. package/dist/registry/index.js +2 -1
  152. package/dist/registry/registry.d.ts +5 -0
  153. package/dist/registry/registry.d.ts.map +1 -1
  154. package/dist/registry/registry.js +10 -0
  155. package/dist/registry/roofs.d.ts +34 -0
  156. package/dist/registry/roofs.d.ts.map +1 -0
  157. package/dist/registry/roofs.js +61 -0
  158. package/dist/registry/types.d.ts +54 -0
  159. package/dist/registry/types.d.ts.map +1 -1
  160. package/dist/registry/validate-relations.d.ts +5 -1
  161. package/dist/registry/validate-relations.d.ts.map +1 -1
  162. package/dist/registry/validate-relations.js +6 -2
  163. package/dist/schema/asset-url.d.ts +8 -0
  164. package/dist/schema/asset-url.d.ts.map +1 -1
  165. package/dist/schema/asset-url.js +17 -0
  166. package/dist/schema/index.d.ts +1 -1
  167. package/dist/schema/index.d.ts.map +1 -1
  168. package/dist/schema/index.js +1 -1
  169. package/dist/schema/nodes/roof-segment-resize.d.ts.map +1 -1
  170. package/dist/schema/nodes/roof-segment-resize.js +2 -2
  171. package/dist/schema/nodes/roof-segment.d.ts +2 -0
  172. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  173. package/dist/schema/nodes/roof-segment.js +40 -2
  174. package/dist/schema/nodes/roof.d.ts +1 -1
  175. package/dist/schema/nodes/roof.d.ts.map +1 -1
  176. package/dist/schema/nodes/roof.js +13 -8
  177. package/dist/schema/nodes/wall.d.ts +1 -0
  178. package/dist/schema/nodes/wall.d.ts.map +1 -1
  179. package/dist/schema/nodes/wall.js +6 -0
  180. package/dist/schema/types.d.ts +2 -1
  181. package/dist/schema/types.d.ts.map +1 -1
  182. package/dist/services/index.d.ts +1 -1
  183. package/dist/services/index.d.ts.map +1 -1
  184. package/dist/services/index.js +1 -1
  185. package/dist/services/movement.d.ts +7 -0
  186. package/dist/services/movement.d.ts.map +1 -1
  187. package/dist/services/movement.js +13 -0
  188. package/dist/services/port-connectivity.d.ts +15 -6
  189. package/dist/services/port-connectivity.d.ts.map +1 -1
  190. package/dist/services/port-connectivity.js +83 -17
  191. package/dist/services/roof-bearing.js +2 -1
  192. package/dist/services/roof-level.d.ts.map +1 -1
  193. package/dist/services/roof-level.js +12 -2
  194. package/dist/services/roof-move.d.ts +4 -2
  195. package/dist/services/roof-move.d.ts.map +1 -1
  196. package/dist/services/roof-move.js +21 -9
  197. package/dist/services/storey.d.ts +6 -1
  198. package/dist/services/storey.d.ts.map +1 -1
  199. package/dist/services/storey.js +11 -2
  200. package/dist/spatial/usage-field/findings.d.ts +7 -1
  201. package/dist/spatial/usage-field/findings.d.ts.map +1 -1
  202. package/dist/spatial/usage-field/findings.js +42 -10
  203. package/dist/spatial/usage-field/path.d.ts.map +1 -1
  204. package/dist/spatial/usage-field/path.js +28 -18
  205. package/dist/spatial/usage-field/swings.d.ts +1 -0
  206. package/dist/spatial/usage-field/swings.d.ts.map +1 -1
  207. package/dist/spatial/usage-field/walls.d.ts +2 -0
  208. package/dist/spatial/usage-field/walls.d.ts.map +1 -1
  209. package/dist/spatial/usage-field/walls.js +18 -4
  210. package/dist/store/actions/node-actions.d.ts +2 -1
  211. package/dist/store/actions/node-actions.d.ts.map +1 -1
  212. package/dist/store/actions/node-actions.js +23 -5
  213. package/dist/store/derived-node-guard.d.ts.map +1 -1
  214. package/dist/store/derived-node-guard.js +2 -1
  215. package/dist/store/scene-annotations.d.ts +4634 -4632
  216. package/dist/store/scene-annotations.d.ts.map +1 -1
  217. package/dist/systems/document-reconcile.d.ts.map +1 -1
  218. package/dist/systems/document-reconcile.js +1 -2
  219. package/dist/systems/elevator/elevator-geometry.d.ts +25 -0
  220. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -1
  221. package/dist/systems/elevator/elevator-geometry.js +40 -8
  222. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -1
  223. package/dist/systems/elevator/elevator-opening-sync.js +4 -35
  224. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -1
  225. package/dist/systems/elevator/elevator-runtime.js +95 -18
  226. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  227. package/dist/systems/elevator/elevator-service.js +24 -4
  228. package/dist/systems/roof/roof-elevation.d.ts +11 -6
  229. package/dist/systems/roof/roof-elevation.d.ts.map +1 -1
  230. package/dist/systems/roof/roof-elevation.js +26 -12
  231. package/dist/systems/slab/__fixtures__/floor-step.d.ts +5 -1
  232. package/dist/systems/slab/__fixtures__/floor-step.d.ts.map +1 -1
  233. package/dist/systems/slab/__fixtures__/raised-room.d.ts +2218 -2214
  234. package/dist/systems/slab/__fixtures__/raised-room.d.ts.map +1 -1
  235. package/dist/systems/stair/stair-opening-preview.d.ts +3203 -3202
  236. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  237. package/dist/systems/wall/wall-curve.d.ts +10 -0
  238. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  239. package/dist/systems/wall/wall-curve.js +38 -0
  240. package/dist/systems/wall/wall-frame.d.ts.map +1 -1
  241. package/dist/systems/wall/wall-frame.js +8 -0
  242. package/dist/systems/wall/wall-merge.d.ts +9 -1
  243. package/dist/systems/wall/wall-merge.d.ts.map +1 -1
  244. package/dist/systems/wall/wall-merge.js +38 -6
  245. package/dist/systems/wall/wall-top.d.ts +10 -3
  246. package/dist/systems/wall/wall-top.d.ts.map +1 -1
  247. package/dist/systems/wall/wall-top.js +24 -8
  248. package/dist/systems/wall/wall-topology.d.ts.map +1 -1
  249. package/dist/systems/wall/wall-topology.js +19 -1
  250. package/dist/utils/ensure-scene-openings.d.ts +5595 -5593
  251. package/dist/utils/ensure-scene-openings.d.ts.map +1 -1
  252. package/dist/utils/floor-plate-migration.d.ts +2267 -2266
  253. package/dist/utils/floor-plate-migration.d.ts.map +1 -1
  254. package/dist/utils/measurement-format.d.ts +27 -0
  255. package/dist/utils/measurement-format.d.ts.map +1 -0
  256. package/dist/utils/measurement-format.js +143 -0
  257. package/dist/utils/owned-floor-opening-migration.d.ts +653 -652
  258. package/dist/utils/owned-floor-opening-migration.d.ts.map +1 -1
  259. package/dist/utils/reconcile-structure-on-load.d.ts +1724 -1723
  260. package/dist/utils/reconcile-structure-on-load.d.ts.map +1 -1
  261. package/dist/utils/scene-bounds.d.ts +32 -0
  262. package/dist/utils/scene-bounds.d.ts.map +1 -0
  263. package/dist/utils/scene-bounds.js +201 -0
  264. package/dist/validation/validate-build-json.d.ts.map +1 -1
  265. package/dist/validation/validate-build-json.js +3 -1
  266. package/package.json +1 -1
@@ -0,0 +1,71 @@
1
+ import type { AnyNode, AnyNodeId, DoorNode, WindowNode } from '../../schema/index.js';
2
+ /**
3
+ * Deterministic door / window marks per level — WS3.
4
+ *
5
+ * `packages/core/src/schema/nodes/door.ts:75-79` promises "the deterministic
6
+ * level fallback (101, 102, ...)"; this is the implementation.
7
+ *
8
+ * Numbering
9
+ * ---------
10
+ * base = (level.level + 1) * 100 — level 0 → 100, level 1 → 200
11
+ * doors = `D${base + n}` → D101, D102 …
12
+ * windows= `W${base + n}` → W101, W102 …
13
+ *
14
+ * Order
15
+ * -----
16
+ * Openings are visited **clockwise** around the level, starting from the
17
+ * north-west-most exterior wall, exterior walls first and interior walls
18
+ * after. Clockwise is the ascending-angle sweep of the wall midpoint about
19
+ * the level centroid, anchored on the north-west diagonal (plan axes are
20
+ * x east, z south — z grows down the screen — so a screen-clockwise sweep
21
+ * is increasing atan2(z, x)). Openings on the same wall are ordered along
22
+ * the wall direction.
23
+ *
24
+ * Stability
25
+ * ---------
26
+ * An explicit `mark` on the node always wins and is never reassigned.
27
+ * `resolveMarks` also reports, in `assignments`, the marks it invented for
28
+ * nodes that had none — the caller is expected to PERSIST those back onto
29
+ * `node.mark` the first time they are resolved in an edit session
30
+ * (`persistResolvedMarks`). That is what makes numbering stable under
31
+ * insertion: once written, an existing opening keeps its number forever and
32
+ * a newly inserted opening only ever takes the next free number. Without
33
+ * the write-back, inserting a door in the middle of a wall would silently
34
+ * renumber every door after it.
35
+ */
36
+ export type OpeningMarkKind = 'door' | 'window';
37
+ export type MarkResolution = {
38
+ /** Every door and window on the level → its mark. */
39
+ marks: ReadonlyMap<string, string>;
40
+ /** Only the marks that were invented (node.mark was empty). */
41
+ assignments: ReadonlyMap<string, string>;
42
+ /** Duplicate explicit marks and other reportable defects. */
43
+ issues: readonly string[];
44
+ };
45
+ export type MarkSceneInput = {
46
+ nodes: Readonly<Record<string, AnyNode>>;
47
+ };
48
+ /**
49
+ * Resolve marks for every door and window under `levelId`.
50
+ *
51
+ * `scene` may be a `{ nodes }` snapshot or the raw node map.
52
+ */
53
+ export declare function resolveMarks(scene: MarkSceneInput | Readonly<Record<string, AnyNode>>, levelId: AnyNodeId): Map<string, string>;
54
+ export declare function resolveMarkDetail(scene: MarkSceneInput | Readonly<Record<string, AnyNode>>, levelId: AnyNodeId): MarkResolution;
55
+ /**
56
+ * Write invented marks back onto the nodes so numbering never drifts.
57
+ *
58
+ * Call once per level per edit session (the caller decides when — this
59
+ * module stays pure). `update` is typically `sceneApi.update`.
60
+ */
61
+ export declare function persistResolvedMarks(resolution: MarkResolution, update: (id: AnyNodeId, data: Record<string, unknown>) => void): number;
62
+ type Opening = (DoorNode | WindowNode) & {
63
+ type: 'door' | 'window';
64
+ };
65
+ /**
66
+ * Every door and window under the level, clockwise from the NW-most
67
+ * exterior wall, exterior walls first.
68
+ */
69
+ export declare function orderedOpenings(nodes: Readonly<Record<string, AnyNode>>, levelId: AnyNodeId): Opening[];
70
+ export {};
71
+ //# sourceMappingURL=marks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marks.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/marks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAuB,UAAU,EAAE,MAAM,cAAc,CAAA;AAEjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE/C,MAAM,MAAM,cAAc,GAAG;IAC3B,qDAAqD;IACrD,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,+DAA+D;IAC/D,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxC,6DAA6D;IAC7D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACzC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzD,OAAO,EAAE,SAAS,GACjB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAErB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzD,OAAO,EAAE,SAAS,GACjB,cAAc,CAkDhB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,cAAc,EAC1B,MAAM,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAC7D,MAAM,CAOR;AAID,KAAK,OAAO,GAAG,CAAC,QAAQ,GAAG,UAAU,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAA;AAEpE;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,OAAO,EAAE,SAAS,GACjB,OAAO,EAAE,CAuCX"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Resolve marks for every door and window under `levelId`.
3
+ *
4
+ * `scene` may be a `{ nodes }` snapshot or the raw node map.
5
+ */
6
+ export function resolveMarks(scene, levelId) {
7
+ return new Map(resolveMarkDetail(scene, levelId).marks);
8
+ }
9
+ export function resolveMarkDetail(scene, levelId) {
10
+ const nodes = normalizeNodes(scene);
11
+ const level = nodes[levelId];
12
+ const ordinal = level && level.type === 'level' ? (level.level ?? 0) : 0;
13
+ const base = (Math.max(0, ordinal) + 1) * 100;
14
+ const openings = orderedOpenings(nodes, levelId);
15
+ const marks = new Map();
16
+ const assignments = new Map();
17
+ const issues = [];
18
+ for (const kind of ['door', 'window']) {
19
+ const prefix = kind === 'door' ? 'D' : 'W';
20
+ const list = openings.filter((opening) => opening.type === kind);
21
+ const used = new Set();
22
+ const explicitOwners = new Map();
23
+ for (const opening of list) {
24
+ const explicit = opening.mark?.trim();
25
+ if (!explicit)
26
+ continue;
27
+ marks.set(opening.id, explicit);
28
+ const normalized = explicit.toLocaleUpperCase();
29
+ used.add(normalized);
30
+ const owners = explicitOwners.get(normalized);
31
+ if (owners)
32
+ owners.push(opening.id);
33
+ else
34
+ explicitOwners.set(normalized, [opening.id]);
35
+ }
36
+ let sequence = 1;
37
+ for (const opening of list) {
38
+ if (marks.has(opening.id))
39
+ continue;
40
+ let candidate = `${prefix}${base + sequence}`;
41
+ while (used.has(candidate.toLocaleUpperCase())) {
42
+ sequence++;
43
+ candidate = `${prefix}${base + sequence}`;
44
+ }
45
+ marks.set(opening.id, candidate);
46
+ assignments.set(opening.id, candidate);
47
+ used.add(candidate.toLocaleUpperCase());
48
+ sequence++;
49
+ }
50
+ for (const [mark, owners] of explicitOwners) {
51
+ if (owners.length > 1) {
52
+ issues.push(`Duplicate ${kind} mark ${mark} (${owners.length} instances)`);
53
+ }
54
+ }
55
+ }
56
+ return { marks, assignments, issues };
57
+ }
58
+ /**
59
+ * Write invented marks back onto the nodes so numbering never drifts.
60
+ *
61
+ * Call once per level per edit session (the caller decides when — this
62
+ * module stays pure). `update` is typically `sceneApi.update`.
63
+ */
64
+ export function persistResolvedMarks(resolution, update) {
65
+ let written = 0;
66
+ for (const [id, mark] of resolution.assignments) {
67
+ update(id, { mark });
68
+ written++;
69
+ }
70
+ return written;
71
+ }
72
+ /**
73
+ * Every door and window under the level, clockwise from the NW-most
74
+ * exterior wall, exterior walls first.
75
+ */
76
+ export function orderedOpenings(nodes, levelId) {
77
+ const walls = [];
78
+ const orphanOpenings = [];
79
+ const visit = (id) => {
80
+ const node = nodes[id];
81
+ if (!node)
82
+ return;
83
+ if (node.visible === false)
84
+ return;
85
+ if (node.type === 'wall')
86
+ walls.push(node);
87
+ else if (node.type === 'door' || node.type === 'window') {
88
+ const opening = node;
89
+ if (!opening.wallId || !nodes[opening.wallId])
90
+ orphanOpenings.push(opening);
91
+ }
92
+ for (const childId of node.children ?? [])
93
+ visit(childId);
94
+ };
95
+ visit(levelId);
96
+ const centroid = wallCentroid(walls);
97
+ const ordered = [...walls].sort((left, right) => {
98
+ const leftExterior = isExteriorWall(left) ? 0 : 1;
99
+ const rightExterior = isExteriorWall(right) ? 0 : 1;
100
+ if (leftExterior !== rightExterior)
101
+ return leftExterior - rightExterior;
102
+ return clockwiseKey(left, centroid) - clockwiseKey(right, centroid);
103
+ });
104
+ const result = [];
105
+ for (const wall of ordered) {
106
+ const wallOpenings = [];
107
+ for (const childId of wall.children ?? []) {
108
+ const child = nodes[childId];
109
+ if (child && child.visible !== false && (child.type === 'door' || child.type === 'window')) {
110
+ wallOpenings.push(child);
111
+ }
112
+ }
113
+ wallOpenings.sort((left, right) => left.position[0] - right.position[0]);
114
+ result.push(...wallOpenings);
115
+ }
116
+ result.push(...orphanOpenings);
117
+ return result;
118
+ }
119
+ function wallCentroid(walls) {
120
+ if (walls.length === 0)
121
+ return [0, 0];
122
+ let x = 0;
123
+ let z = 0;
124
+ for (const wall of walls) {
125
+ x += (wall.start[0] + wall.end[0]) / 2;
126
+ z += (wall.start[1] + wall.end[1]) / 2;
127
+ }
128
+ return [x / walls.length, z / walls.length];
129
+ }
130
+ /**
131
+ * Sweep key: 0 at the north-west direction, increasing clockwise on screen.
132
+ *
133
+ * Plan axes are x east, z south — z grows DOWN the screen — so a clockwise
134
+ * screen sweep (up → right → down → left) is the INCREASING atan2(z, x)
135
+ * direction. The north-west diagonal sits at atan2(-1, -1) = -3π/4, so
136
+ * anchoring there and wrapping into [0, 2π) yields north, east, south, west.
137
+ */
138
+ const NORTH_WEST_ANGLE = (-3 * Math.PI) / 4;
139
+ function clockwiseKey(wall, centroid) {
140
+ const midX = (wall.start[0] + wall.end[0]) / 2 - centroid[0];
141
+ const midZ = (wall.start[1] + wall.end[1]) / 2 - centroid[1];
142
+ let sweep = Math.atan2(midZ, midX) - NORTH_WEST_ANGLE;
143
+ while (sweep < 0)
144
+ sweep += Math.PI * 2;
145
+ while (sweep >= Math.PI * 2)
146
+ sweep -= Math.PI * 2;
147
+ return sweep;
148
+ }
149
+ function isExteriorWall(wall) {
150
+ return wall.frontSide === 'exterior' || wall.backSide === 'exterior';
151
+ }
152
+ function normalizeNodes(scene) {
153
+ const candidate = scene;
154
+ return candidate && typeof candidate === 'object' && 'nodes' in candidate
155
+ ? candidate.nodes
156
+ : scene;
157
+ }
@@ -0,0 +1,18 @@
1
+ import { type WallJustification } from '../../systems/wall/wall-frame.js';
2
+ import type { Point2D } from '../../systems/wall/wall-mitering.js';
3
+ export declare const FLOORPLAN_VIEW_ROTATION_DEG = 0;
4
+ export declare function clampPlanValue(value: number, min: number, max: number): number;
5
+ export declare function rotatePlanVector(x: number, y: number, rotation: number): [number, number];
6
+ export declare function worldToFloorplanLocalPoint(worldX: number, worldZ: number, buildingPosition: readonly [number, number, number], buildingRotationY: number): Point2D;
7
+ export declare function floorplanLocalToWorldPoint(point: Point2D | [number, number], buildingPosition: readonly [number, number, number], buildingRotationY: number): {
8
+ x: number;
9
+ z: number;
10
+ };
11
+ export declare function interpolatePlanPoint(start: Point2D, end: Point2D, t: number): Point2D;
12
+ export declare function getPlanPointDistance(start: Point2D, end: Point2D): number;
13
+ export declare function movePlanPointTowards(start: Point2D, end: Point2D, distance: number): Point2D;
14
+ export declare function getThickPlanLinePolygon(line: {
15
+ start: Point2D;
16
+ end: Point2D;
17
+ }, thickness: number, justification?: WallJustification): Point2D[];
18
+ //# sourceMappingURL=plan-math.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-math.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/plan-math.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AAC1F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAA;AAY/D,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAErE;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAIzF;AAQD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACnD,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAUT;AAGD,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,gBAAgB,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACnD,iBAAiB,EAAE,MAAM,GACxB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAU1B;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKrF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAEzE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAO5F;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,EACtC,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,iBAAiB,GAChC,OAAO,EAAE,CAqBX"}
@@ -0,0 +1,82 @@
1
+ import { getWallFaceOffsets } from '../../systems/wall/wall-frame.js';
2
+ // Baseline rotation (deg) that orients the plan-local scene "north up" on
3
+ // screen. The on-screen floor-plan scene `<g>` is rotated by
4
+ // `FLOORPLAN_VIEW_ROTATION_DEG + userRotation - buildingRotation`; PDF export
5
+ // uses the same user rotation so its orientation matches the live plan.
6
+ //
7
+ // North is world −Z: with a 0 baseline a rotation-0 reference image (top =
8
+ // −Z) reads upright in the north-aligned view, and "align north" maps to a
9
+ // 3D camera azimuth of 0. The old 90° baseline made north world −X, so
10
+ // north-up floorplan scans displayed sideways whenever the compass claimed
11
+ // the view was aligned.
12
+ export const FLOORPLAN_VIEW_ROTATION_DEG = 0;
13
+ export function clampPlanValue(value, min, max) {
14
+ return Math.min(Math.max(value, min), max);
15
+ }
16
+ export function rotatePlanVector(x, y, rotation) {
17
+ const cos = Math.cos(rotation);
18
+ const sin = Math.sin(rotation);
19
+ return [x * cos + y * sin, -x * sin + y * cos];
20
+ }
21
+ // Converts a world X/Z point into the floor-plan-local (building-local)
22
+ // frame used by the SVG scene `<g>` and every stored node position. The
23
+ // inverse of `floorplanLocalToWorldPoint`. Shared so the floor-plan panel
24
+ // and the 2D move overlay resolve the same frame — feeding a world-space
25
+ // `original` into a local-space cursor solver lands the drop off by the
26
+ // building's world X/Z (worse for an off-origin building).
27
+ export function worldToFloorplanLocalPoint(worldX, worldZ, buildingPosition, buildingRotationY) {
28
+ const dx = worldX - buildingPosition[0];
29
+ const dz = worldZ - buildingPosition[2];
30
+ const cos = Math.cos(buildingRotationY);
31
+ const sin = Math.sin(buildingRotationY);
32
+ return {
33
+ x: dx * cos - dz * sin,
34
+ y: dx * sin + dz * cos,
35
+ };
36
+ }
37
+ // Inverse of `worldToFloorplanLocalPoint`: floor-plan-local X/Y → world X/Z.
38
+ export function floorplanLocalToWorldPoint(point, buildingPosition, buildingRotationY) {
39
+ const localX = Array.isArray(point) ? point[0] : point.x;
40
+ const localY = Array.isArray(point) ? point[1] : point.y;
41
+ const cos = Math.cos(buildingRotationY);
42
+ const sin = Math.sin(buildingRotationY);
43
+ return {
44
+ x: buildingPosition[0] + localX * cos + localY * sin,
45
+ z: buildingPosition[2] - localX * sin + localY * cos,
46
+ };
47
+ }
48
+ export function interpolatePlanPoint(start, end, t) {
49
+ return {
50
+ x: start.x + (end.x - start.x) * t,
51
+ y: start.y + (end.y - start.y) * t,
52
+ };
53
+ }
54
+ export function getPlanPointDistance(start, end) {
55
+ return Math.hypot(end.x - start.x, end.y - start.y);
56
+ }
57
+ export function movePlanPointTowards(start, end, distance) {
58
+ const totalDistance = getPlanPointDistance(start, end);
59
+ if (totalDistance <= Number.EPSILON || distance <= 0) {
60
+ return start;
61
+ }
62
+ return interpolatePlanPoint(start, end, Math.min(1, distance / totalDistance));
63
+ }
64
+ export function getThickPlanLinePolygon(line, thickness, justification) {
65
+ const dx = line.end.x - line.start.x;
66
+ const dy = line.end.y - line.start.y;
67
+ const length = Math.hypot(dx, dy);
68
+ if (length <= Number.EPSILON || thickness <= 0) {
69
+ return [line.start, line.end, line.end, line.start];
70
+ }
71
+ const { a, b } = getWallFaceOffsets({ thickness, justification });
72
+ const normalX = (-dy / length) * a;
73
+ const normalY = (dx / length) * a;
74
+ const backX = (-dy / length) * -b;
75
+ const backY = (dx / length) * -b;
76
+ return [
77
+ { x: line.start.x + normalX, y: line.start.y + normalY },
78
+ { x: line.end.x + normalX, y: line.end.y + normalY },
79
+ { x: line.end.x - backX, y: line.end.y - backY },
80
+ { x: line.start.x - backX, y: line.start.y - backY },
81
+ ];
82
+ }
@@ -0,0 +1,17 @@
1
+ import type { Point2D } from '../../systems/wall/wall-mitering.js';
2
+ export declare function formatPolygonPath(points: Point2D[], holes?: Point2D[][]): string;
3
+ export declare function buildSvgPolylinePath(points: Point2D[]): string | null;
4
+ export declare function getArcPlanPoint(center: Point2D, radius: number, angle: number): Point2D;
5
+ export declare function buildSvgArcPath(center: Point2D, radius: number, startAngle: number, endAngle: number): string;
6
+ export declare function buildSvgAnnularSectorPath(center: Point2D, innerRadius: number, outerRadius: number, startAngle: number, endAngle: number): string;
7
+ export declare function formatSvgPolygonPoints(points: Point2D[]): string;
8
+ /**
9
+ * Three points defining an arrow head — tip + two trailing barbs.
10
+ * Returned as plain `Point2D` objects so consumers can either feed them
11
+ * straight into `formatSvgPolygonPoints` (for SVG `points=""`) or push
12
+ * them onto a `FloorplanGeometry.polygon.points` array. Mixing both
13
+ * downstream paths through a string-returning helper was awkward — see
14
+ * `nodes/src/stair/floorplan.ts` which needs the points as objects.
15
+ */
16
+ export declare function buildSvgArrowHeadPoints(point: Point2D, angle: number, size: number): Point2D[];
17
+ //# sourceMappingURL=svg-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg-paths.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/svg-paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAA;AAiB/D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,GAAE,OAAO,EAAE,EAAO,UAoB3E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,iBAWrD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKvF;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,UAIjB;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,OAAO,EACf,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,UAWjB;AAWD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,UAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAW9F"}
@@ -0,0 +1,92 @@
1
+ function toSvgX(value) {
2
+ return value;
3
+ }
4
+ function toSvgY(value) {
5
+ return value;
6
+ }
7
+ function toSvgPoint(point) {
8
+ return {
9
+ x: toSvgX(point.x),
10
+ y: toSvgY(point.y),
11
+ };
12
+ }
13
+ export function formatPolygonPath(points, holes = []) {
14
+ const formatSubpath = (subpathPoints) => {
15
+ const [firstPoint, ...restPoints] = subpathPoints;
16
+ if (!firstPoint) {
17
+ return null;
18
+ }
19
+ const firstSvgPoint = toSvgPoint(firstPoint);
20
+ return [
21
+ `M ${firstSvgPoint.x} ${firstSvgPoint.y}`,
22
+ ...restPoints.map((point) => {
23
+ const svgPoint = toSvgPoint(point);
24
+ return `L ${svgPoint.x} ${svgPoint.y}`;
25
+ }),
26
+ 'Z',
27
+ ].join(' ');
28
+ };
29
+ return [points, ...holes].map(formatSubpath).filter(Boolean).join(' ');
30
+ }
31
+ export function buildSvgPolylinePath(points) {
32
+ if (points.length < 2) {
33
+ return null;
34
+ }
35
+ return points
36
+ .map((point, index) => {
37
+ const svgPoint = toSvgPoint(point);
38
+ return `${index === 0 ? 'M' : 'L'} ${svgPoint.x} ${svgPoint.y}`;
39
+ })
40
+ .join(' ');
41
+ }
42
+ export function getArcPlanPoint(center, radius, angle) {
43
+ return {
44
+ x: center.x + Math.cos(angle) * radius,
45
+ y: center.y + Math.sin(angle) * radius,
46
+ };
47
+ }
48
+ export function buildSvgArcPath(center, radius, startAngle, endAngle) {
49
+ const start = getArcPlanPoint(center, radius, startAngle);
50
+ return `M ${toSvgX(start.x)} ${toSvgY(start.y)} ${svgArcCommands(center, radius, startAngle, endAngle)}`;
51
+ }
52
+ export function buildSvgAnnularSectorPath(center, innerRadius, outerRadius, startAngle, endAngle) {
53
+ const outerStart = getArcPlanPoint(center, outerRadius, startAngle);
54
+ const innerEnd = getArcPlanPoint(center, innerRadius, endAngle);
55
+ return [
56
+ `M ${toSvgX(outerStart.x)} ${toSvgY(outerStart.y)}`,
57
+ svgArcCommands(center, outerRadius, startAngle, endAngle),
58
+ `L ${toSvgX(innerEnd.x)} ${toSvgY(innerEnd.y)}`,
59
+ svgArcCommands(center, innerRadius, endAngle, startAngle),
60
+ 'Z',
61
+ ].join(' ');
62
+ }
63
+ function svgArcCommands(center, radius, startAngle, endAngle) {
64
+ const delta = endAngle - startAngle;
65
+ const count = Math.max(1, Math.ceil(Math.abs(delta) / Math.PI));
66
+ return Array.from({ length: count }, (_, index) => {
67
+ const end = getArcPlanPoint(center, radius, startAngle + (delta * (index + 1)) / count);
68
+ return `A ${radius} ${radius} 0 0 ${delta >= 0 ? 1 : 0} ${toSvgX(end.x)} ${toSvgY(end.y)}`;
69
+ }).join(' ');
70
+ }
71
+ export function formatSvgPolygonPoints(points) {
72
+ return points.map((point) => `${toSvgX(point.x)},${toSvgY(point.y)}`).join(' ');
73
+ }
74
+ /**
75
+ * Three points defining an arrow head — tip + two trailing barbs.
76
+ * Returned as plain `Point2D` objects so consumers can either feed them
77
+ * straight into `formatSvgPolygonPoints` (for SVG `points=""`) or push
78
+ * them onto a `FloorplanGeometry.polygon.points` array. Mixing both
79
+ * downstream paths through a string-returning helper was awkward — see
80
+ * `nodes/src/stair/floorplan.ts` which needs the points as objects.
81
+ */
82
+ export function buildSvgArrowHeadPoints(point, angle, size) {
83
+ const left = {
84
+ x: point.x - size * Math.cos(angle - Math.PI / 6),
85
+ y: point.y - size * Math.sin(angle - Math.PI / 6),
86
+ };
87
+ const right = {
88
+ x: point.x - size * Math.cos(angle + Math.PI / 6),
89
+ y: point.y - size * Math.sin(angle + Math.PI / 6),
90
+ };
91
+ return [point, left, right];
92
+ }
@@ -0,0 +1,31 @@
1
+ import type { WallNode } from '../../schema/index.js';
2
+ import type { WallPlanPoint } from '../../systems/wall/wall-move.js';
3
+ /** The floor plan's affordance id for a wall end's pull grip (a `move-arrow`, variant `pull`). */
4
+ export declare const WALL_PULL_AFFORDANCE = "wall-pull";
5
+ /** What a pull grip carries: the wall, and which of its ends it pulls from. */
6
+ export type WallPullGripPayload = {
7
+ wallId: string;
8
+ endpoint: 'start' | 'end';
9
+ };
10
+ /** The grip's tooltip, in both views. */
11
+ export declare const WALL_PULL_GRIP_TITLE = "Drag to draw the next wall";
12
+ /**
13
+ * The pulled end of `wall` and the direction out of it: along the wall for a
14
+ * straight one, along its tangent for a curved one.
15
+ */
16
+ export declare function wallPullSeed(wall: Pick<WallNode, 'start' | 'end' | 'curveOffset'>, endpoint: 'start' | 'end'): {
17
+ start: WallPlanPoint;
18
+ reference: WallPlanPoint;
19
+ } | null;
20
+ /**
21
+ * The affordance a floor-plan `move-arrow` carries to be a wall push arrow;
22
+ * its payload is a `WallPushArrowPayload`. The editor's floor plan runs its
23
+ * wall push for it instead of a registry affordance session.
24
+ */
25
+ export declare const WALL_PUSH_AFFORDANCE = "wall-push";
26
+ /** A wall's or an invisible wall's side arrow: `wallId` is either. */
27
+ export type WallPushArrowPayload = {
28
+ wallId: string;
29
+ side: 'a' | 'b';
30
+ };
31
+ //# sourceMappingURL=wall-affordances.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wall-affordances.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/wall-affordances.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAEjE,kGAAkG;AAClG,eAAO,MAAM,oBAAoB,cAAc,CAAA;AAE/C,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAA;CAAE,CAAA;AAE/E,yCAAyC;AACzC,eAAO,MAAM,oBAAoB,+BAA+B,CAAA;AAEhE;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAC,EACrD,QAAQ,EAAE,OAAO,GAAG,KAAK,GACxB;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,aAAa,CAAA;CAAE,GAAG,IAAI,CAQ3D;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,cAAc,CAAA;AAE/C,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAAA;CAAE,CAAA"}
@@ -0,0 +1,24 @@
1
+ import { getWallCurveFrameAt } from '../../systems/wall/wall-curve.js';
2
+ /** The floor plan's affordance id for a wall end's pull grip (a `move-arrow`, variant `pull`). */
3
+ export const WALL_PULL_AFFORDANCE = 'wall-pull';
4
+ /** The grip's tooltip, in both views. */
5
+ export const WALL_PULL_GRIP_TITLE = 'Drag to draw the next wall';
6
+ /**
7
+ * The pulled end of `wall` and the direction out of it: along the wall for a
8
+ * straight one, along its tangent for a curved one.
9
+ */
10
+ export function wallPullSeed(wall, endpoint) {
11
+ const start = endpoint === 'start' ? [wall.start[0], wall.start[1]] : [wall.end[0], wall.end[1]];
12
+ const tangent = getWallCurveFrameAt(wall, endpoint === 'start' ? 0 : 1).tangent;
13
+ const sign = endpoint === 'start' ? -1 : 1;
14
+ const length = Math.hypot(tangent.x, tangent.y);
15
+ if (!(length > 1e-9))
16
+ return null;
17
+ return { start, reference: [(tangent.x / length) * sign, (tangent.y / length) * sign] };
18
+ }
19
+ /**
20
+ * The affordance a floor-plan `move-arrow` carries to be a wall push arrow;
21
+ * its payload is a `WallPushArrowPayload`. The editor's floor plan runs its
22
+ * wall push for it instead of a registry affordance session.
23
+ */
24
+ export const WALL_PUSH_AFFORDANCE = 'wall-push';
@@ -0,0 +1,19 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema/types.js';
2
+ type NodeMap = Readonly<Record<string, Pick<AnyNode, 'parentId'>>>;
3
+ type NodeLookup = (id: AnyNodeId) => Pick<AnyNode, 'parentId'> | undefined;
4
+ /**
5
+ * Whether assigning `newParentId` to `nodeId` would put `nodeId` on a cycle of
6
+ * parent links. A cycle the chain already holds without `nodeId` is not one it
7
+ * creates: counting it would refuse every edit beneath an old cyclic scene.
8
+ */
9
+ export declare function wouldCreateHierarchyCycle(nodeId: AnyNodeId, newParentId: AnyNodeId | null, nodes: NodeMap | NodeLookup): boolean;
10
+ /** Return each distinct cycle encoded by `parentId` links. */
11
+ export declare function findHierarchyCycles(nodes: NodeMap): AnyNodeId[][];
12
+ /** The `parentId` error each node on a cycle raises, as every scene check lists it. */
13
+ export declare function hierarchyCycleErrors(nodes: NodeMap): {
14
+ nodeId: AnyNodeId;
15
+ path: 'parentId';
16
+ message: string;
17
+ }[];
18
+ export {};
19
+ //# sourceMappingURL=hierarchy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../src/lib/hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEzD,KAAK,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;AAClE,KAAK,UAAU,GAAG,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,SAAS,CAAA;AAE1E;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,SAAS,GAAG,IAAI,EAC7B,KAAK,EAAE,OAAO,GAAG,UAAU,GAC1B,OAAO,CAaT;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,EAAE,EAAE,CAwBjE;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,GACb;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAK5D"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Whether assigning `newParentId` to `nodeId` would put `nodeId` on a cycle of
3
+ * parent links. A cycle the chain already holds without `nodeId` is not one it
4
+ * creates: counting it would refuse every edit beneath an old cyclic scene.
5
+ */
6
+ export function wouldCreateHierarchyCycle(nodeId, newParentId, nodes) {
7
+ if (!newParentId)
8
+ return false;
9
+ const nodeAt = typeof nodes === 'function' ? nodes : (id) => nodes[id];
10
+ const seen = new Set();
11
+ let currentId = newParentId;
12
+ while (currentId) {
13
+ if (currentId === nodeId)
14
+ return true;
15
+ if (seen.has(currentId))
16
+ return false;
17
+ seen.add(currentId);
18
+ currentId = nodeAt(currentId)?.parentId ?? null;
19
+ }
20
+ return false;
21
+ }
22
+ /** Return each distinct cycle encoded by `parentId` links. */
23
+ export function findHierarchyCycles(nodes) {
24
+ const visited = new Set();
25
+ const cycles = [];
26
+ for (const startId of Object.keys(nodes)) {
27
+ if (visited.has(startId))
28
+ continue;
29
+ const chain = [];
30
+ const positions = new Map();
31
+ let currentId = startId;
32
+ while (currentId && nodes[currentId] && !visited.has(currentId)) {
33
+ const position = positions.get(currentId);
34
+ if (position !== undefined) {
35
+ cycles.push(chain.slice(position));
36
+ break;
37
+ }
38
+ positions.set(currentId, chain.length);
39
+ chain.push(currentId);
40
+ currentId = nodes[currentId]?.parentId ?? null;
41
+ }
42
+ for (const id of chain)
43
+ visited.add(id);
44
+ }
45
+ return cycles;
46
+ }
47
+ /** The `parentId` error each node on a cycle raises, as every scene check lists it. */
48
+ export function hierarchyCycleErrors(nodes) {
49
+ return findHierarchyCycles(nodes).flatMap((cycle) => {
50
+ const message = `hierarchy cycle detected: ${[...cycle, cycle[0]].join(' -> ')}`;
51
+ return cycle.map((nodeId) => ({ nodeId, path: 'parentId', message }));
52
+ });
53
+ }
@@ -5,7 +5,7 @@ export declare function supportSegmentAt(segment: WallSlabSupportSegment, t: num
5
5
  export declare function openingDatumFromSupport(wall: WallNode, opening: Pick<DoorNode | WindowNode, 'position' | 'width' | 'height'> & Partial<Pick<DoorNode, 'rotation' | 'sidelightLeft' | 'sidelightRight' | 'transomHeight' | 'doorType'>>, support: WallSlabSupport): number;
6
6
  /** Unclamped floor intent, used by authoring feasibility and legacy migration. */
7
7
  export declare function getOpeningFloorTarget(wall: WallNode, opening: Pick<DoorNode | WindowNode, 'position' | 'width' | 'height'> & Partial<Pick<DoorNode, 'rotation' | 'sidelightLeft' | 'sidelightRight' | 'transomHeight' | 'doorType'>>, nodes: Readonly<Record<string, AnyNode>>): number;
8
- export declare function openingFitsAtDatum(wall: WallNode, opening: Pick<DoorNode | WindowNode, 'position' | 'height'> & Partial<Pick<DoorNode, 'transomHeight' | 'doorType'>>, datum: number, nodes: Readonly<Record<string, AnyNode>>, support?: WallSlabSupport): boolean;
8
+ export declare function openingFitsAtDatum(wall: WallNode, opening: Pick<DoorNode | WindowNode, 'position' | 'width' | 'height'> & Partial<Pick<DoorNode, 'transomHeight' | 'doorType'>>, datum: number, nodes: Readonly<Record<string, AnyNode>>, support?: WallSlabSupport): boolean;
9
9
  /**
10
10
  * The lowest the wall reaches under the opening's span, level-local, on its centreline or either
11
11
  * face: where a face goes down beside a floor sunk under the one the wall stands on (a garage
@@ -1 +1 @@
1
- {"version":3,"file":"opening-floor-datum.d.ts","sourceRoot":"","sources":["../../src/lib/opening-floor-datum.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opening-floor-datum.d.ts","sourceRoot":"","sources":["../../src/lib/opening-floor-datum.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { getWallTop } from '../services/storey.js';
1
+ import { getWallTopOver } from '../services/storey.js';
2
2
  import { computeWallSlabSupport, } from '../systems/slab/slab-support.js';
3
3
  import { getWallCurveFrameAt, getWallCurveLength } from '../systems/wall/wall-curve.js';
4
4
  import { calculateLevelMiters, getWallPlanFootprint } from '../systems/wall/wall-footprint.js';
@@ -64,7 +64,7 @@ export function getOpeningFloorTarget(wall, opening, nodes) {
64
64
  return openingDatumFromSupport(wall, opening, wallSupportForNodes(wall, nodes));
65
65
  }
66
66
  export function openingFitsAtDatum(wall, opening, datum, nodes, support = wallSupportForNodes(wall, nodes)) {
67
- const top = getWallTop(wall, wall.parentId, nodes, support.elevation);
67
+ const top = getWallTopOver(wall, wall.parentId, nodes, support.elevation, opening.position[0] - opening.width / 2, opening.position[0] + opening.width / 2);
68
68
  return (datum +
69
69
  opening.position[1] -
70
70
  opening.height / 2 +
@@ -1 +1 @@
1
- {"version":3,"file":"own-floor-intent.d.ts","sourceRoot":"","sources":["../../src/lib/own-floor-intent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAsB,MAAM,WAAW,CAAA;AAavE,KAAK,MAAM,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAEvD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,OAAO,EAAE,SAAS,MAAM,EAAE;aAKT,MAAM,EAAE;;;;;;EA+N1B"}
1
+ {"version":3,"file":"own-floor-intent.d.ts","sourceRoot":"","sources":["../../src/lib/own-floor-intent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAsB,MAAM,WAAW,CAAA;AAavE,KAAK,MAAM,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAEvD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,OAAO,EAAE,SAAS,MAAM,EAAE;aAKT,MAAM,EAAE;;;;;;EAqO1B"}