@pascal-app/core 0.9.1 → 0.9.2
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.
- package/dist/events/bus.d.ts +30 -2
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
- package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
- package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts +8 -1
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +27 -1
- package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
- package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
- package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +93 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +69 -12
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +476 -195
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +19 -1
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
- package/dist/index.d.ts +16 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/lib/level-name.d.ts +4 -0
- package/dist/lib/level-name.d.ts.map +1 -0
- package/dist/lib/level-name.js +10 -0
- package/dist/lib/measurement-geometry.d.ts +19 -0
- package/dist/lib/measurement-geometry.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.js +232 -0
- package/dist/lib/measurement-geometry.test.d.ts +2 -0
- package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
- package/dist/lib/measurement-geometry.test.js +102 -0
- package/dist/lib/polygon-geometry.d.ts +0 -1
- package/dist/lib/polygon-geometry.d.ts.map +1 -1
- package/dist/lib/polygon-geometry.js +0 -18
- package/dist/lib/selection-proxy.d.ts +4 -0
- package/dist/lib/selection-proxy.d.ts.map +1 -0
- package/dist/lib/selection-proxy.js +12 -0
- package/dist/lib/slab-polygon.d.ts +31 -2
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +525 -31
- package/dist/lib/slab-polygon.test.d.ts +2 -0
- package/dist/lib/slab-polygon.test.d.ts.map +1 -0
- package/dist/lib/slab-polygon.test.js +635 -0
- package/dist/lib/slots.d.ts +14 -0
- package/dist/lib/slots.d.ts.map +1 -0
- package/dist/lib/slots.js +26 -0
- package/dist/lib/space-detection.d.ts +25 -2
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +414 -118
- package/dist/lib/space-detection.test.js +301 -2
- package/dist/lib/wall-distance.d.ts +60 -0
- package/dist/lib/wall-distance.d.ts.map +1 -0
- package/dist/lib/wall-distance.js +85 -0
- package/dist/lib/zone-quantities.d.ts +21 -0
- package/dist/lib/zone-quantities.d.ts.map +1 -0
- package/dist/lib/zone-quantities.js +183 -0
- package/dist/lib/zone-quantities.test.d.ts +2 -0
- package/dist/lib/zone-quantities.test.d.ts.map +1 -0
- package/dist/lib/zone-quantities.test.js +82 -0
- package/dist/material-library.d.ts +20 -1
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +2181 -441
- package/dist/registry/handles.d.ts +75 -2
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +3 -3
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/registry.d.ts +46 -3
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +82 -1
- package/dist/registry/registry.test.js +14 -1
- package/dist/registry/subtree.d.ts.map +1 -1
- package/dist/registry/subtree.js +4 -0
- package/dist/registry/subtree.test.js +30 -0
- package/dist/registry/types.d.ts +717 -38
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/schema/index.d.ts +21 -5
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +17 -3
- package/dist/schema/material.d.ts +3 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +7 -5
- package/dist/schema/material.test.d.ts +2 -0
- package/dist/schema/material.test.d.ts.map +1 -0
- package/dist/schema/material.test.js +43 -0
- package/dist/schema/nodes/box-vent.d.ts +2 -2
- package/dist/schema/nodes/cabinet.d.ts +534 -0
- package/dist/schema/nodes/cabinet.d.ts.map +1 -0
- package/dist/schema/nodes/cabinet.js +137 -0
- package/dist/schema/nodes/ceiling.d.ts +3 -2
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +4 -0
- package/dist/schema/nodes/chimney.d.ts +4 -4
- package/dist/schema/nodes/column.d.ts +3 -2
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +3 -0
- package/dist/schema/nodes/cupola.d.ts +2 -2
- package/dist/schema/nodes/door.d.ts +13 -5
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +12 -0
- package/dist/schema/nodes/dormer.d.ts +8 -8
- package/dist/schema/nodes/dormer.js +1 -1
- package/dist/schema/nodes/downspout.d.ts +2 -2
- package/dist/schema/nodes/duct-fitting.d.ts +91 -0
- package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/duct-fitting.js +92 -0
- package/dist/schema/nodes/duct-segment.d.ts +61 -0
- package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
- package/dist/schema/nodes/duct-segment.js +73 -0
- package/dist/schema/nodes/duct-terminal.d.ts +57 -0
- package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
- package/dist/schema/nodes/duct-terminal.js +51 -0
- package/dist/schema/nodes/elevator.d.ts +3 -2
- package/dist/schema/nodes/elevator.d.ts.map +1 -1
- package/dist/schema/nodes/elevator.js +3 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
- package/dist/schema/nodes/fence.d.ts +18 -2
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +25 -2
- package/dist/schema/nodes/gutter.d.ts +2 -2
- package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
- package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
- package/dist/schema/nodes/hvac-equipment.js +55 -0
- package/dist/schema/nodes/item.d.ts +8 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +11 -0
- package/dist/schema/nodes/level.d.ts +1 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +2 -0
- package/dist/schema/nodes/lineset.d.ts +42 -0
- package/dist/schema/nodes/lineset.d.ts.map +1 -0
- package/dist/schema/nodes/lineset.js +38 -0
- package/dist/schema/nodes/liquid-line.d.ts +34 -0
- package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
- package/dist/schema/nodes/liquid-line.js +24 -0
- package/dist/schema/nodes/measurement.d.ts +306 -0
- package/dist/schema/nodes/measurement.d.ts.map +1 -0
- package/dist/schema/nodes/measurement.js +87 -0
- package/dist/schema/nodes/measurement.test.d.ts +2 -0
- package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
- package/dist/schema/nodes/measurement.test.js +139 -0
- package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
- package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-fitting.js +45 -0
- package/dist/schema/nodes/pipe-segment.d.ts +48 -0
- package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-segment.js +36 -0
- package/dist/schema/nodes/pipe-trap.d.ts +43 -0
- package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
- package/dist/schema/nodes/pipe-trap.js +36 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
- package/dist/schema/nodes/ridge-vent.d.ts +18 -3
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
- package/dist/schema/nodes/ridge-vent.js +221 -3
- package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
- package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-shape.js +318 -0
- package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
- package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
- package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
- package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-walls.js +330 -0
- package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
- package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
- package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
- package/dist/schema/nodes/roof-segment.d.ts +84 -8
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +290 -5
- package/dist/schema/nodes/roof.d.ts +8 -8
- package/dist/schema/nodes/shelf.d.ts +3 -2
- package/dist/schema/nodes/shelf.d.ts.map +1 -1
- package/dist/schema/nodes/shelf.js +6 -0
- package/dist/schema/nodes/skylight.d.ts +10 -10
- package/dist/schema/nodes/slab.d.ts +3 -2
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +4 -0
- package/dist/schema/nodes/solar-panel.d.ts +4 -4
- package/dist/schema/nodes/stair-segment.d.ts +4 -4
- package/dist/schema/nodes/stair.d.ts +13 -12
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +3 -0
- package/dist/schema/nodes/turbine-vent.d.ts +2 -2
- package/dist/schema/nodes/wall.d.ts +245 -6
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +257 -0
- package/dist/schema/nodes/wall.test.d.ts +2 -0
- package/dist/schema/nodes/wall.test.d.ts.map +1 -0
- package/dist/schema/nodes/wall.test.js +208 -0
- package/dist/schema/nodes/window.d.ts +11 -3
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +12 -0
- package/dist/schema/nodes/zone.d.ts +2 -0
- package/dist/schema/nodes/zone.d.ts.map +1 -1
- package/dist/schema/nodes/zone.js +6 -0
- package/dist/schema/scene-material.d.ts +41 -0
- package/dist/schema/scene-material.d.ts.map +1 -0
- package/dist/schema/scene-material.js +9 -0
- package/dist/schema/types.d.ts +1089 -94
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +23 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -1
- package/dist/services/alignment-anchors.js +30 -1
- package/dist/services/alignment.d.ts +5 -0
- package/dist/services/alignment.d.ts.map +1 -1
- package/dist/services/alignment.js +2 -0
- package/dist/services/hosting.d.ts +8 -0
- package/dist/services/hosting.d.ts.map +1 -1
- package/dist/services/hosting.js +13 -4
- package/dist/services/hosting.test.js +32 -1
- package/dist/services/index.d.ts +8 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +8 -2
- package/dist/services/level-height.d.ts +28 -0
- package/dist/services/level-height.d.ts.map +1 -0
- package/dist/services/level-height.js +62 -0
- package/dist/services/opening-guides.d.ts +141 -0
- package/dist/services/opening-guides.d.ts.map +1 -0
- package/dist/services/opening-guides.js +267 -0
- package/dist/services/opening-guides.test.d.ts +2 -0
- package/dist/services/opening-guides.test.d.ts.map +1 -0
- package/dist/services/opening-guides.test.js +208 -0
- package/dist/services/port-connectivity.d.ts +115 -0
- package/dist/services/port-connectivity.d.ts.map +1 -0
- package/dist/services/port-connectivity.js +300 -0
- package/dist/services/port-connectivity.test.d.ts +2 -0
- package/dist/services/port-connectivity.test.d.ts.map +1 -0
- package/dist/services/port-connectivity.test.js +297 -0
- package/dist/services/riser-diagram.d.ts +40 -0
- package/dist/services/riser-diagram.d.ts.map +1 -0
- package/dist/services/riser-diagram.js +110 -0
- package/dist/services/riser-diagram.test.d.ts +2 -0
- package/dist/services/riser-diagram.test.d.ts.map +1 -0
- package/dist/services/riser-diagram.test.js +66 -0
- package/dist/services/snap.d.ts +9 -1
- package/dist/services/snap.d.ts.map +1 -1
- package/dist/services/snap.js +21 -1
- package/dist/services/snap.test.js +43 -1
- package/dist/services/system-graph.d.ts +30 -0
- package/dist/services/system-graph.d.ts.map +1 -0
- package/dist/services/system-graph.js +164 -0
- package/dist/services/system-graph.test.d.ts +2 -0
- package/dist/services/system-graph.test.d.ts.map +1 -0
- package/dist/services/system-graph.test.js +138 -0
- package/dist/services/validate-dwv.d.ts +32 -0
- package/dist/services/validate-dwv.d.ts.map +1 -0
- package/dist/services/validate-dwv.js +118 -0
- package/dist/services/validate-dwv.test.d.ts +2 -0
- package/dist/services/validate-dwv.test.d.ts.map +1 -0
- package/dist/services/validate-dwv.test.js +109 -0
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +94 -1
- package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
- package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
- package/dist/store/actions/ridge-vent-update.test.js +264 -0
- package/dist/store/history-control.d.ts +11 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +33 -0
- package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
- package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
- package/dist/store/use-scene-dirty-tracking.test.js +68 -0
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
- package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
- package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
- package/dist/store/use-scene-plugins.test.d.ts +2 -0
- package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
- package/dist/store/use-scene-plugins.test.js +51 -0
- package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
- package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
- package/dist/store/use-scene-window-migration.test.d.ts +2 -0
- package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-window-migration.test.js +85 -0
- package/dist/store/use-scene.d.ts +17 -2
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +358 -51
- package/dist/systems/elevator/elevator-service.js +1 -1
- package/dist/systems/fence/fence-centerline.d.ts +12 -0
- package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
- package/dist/systems/fence/fence-centerline.js +37 -0
- package/dist/systems/fence/fence-spline.d.ts +39 -0
- package/dist/systems/fence/fence-spline.d.ts.map +1 -0
- package/dist/systems/fence/fence-spline.js +196 -0
- package/dist/systems/fence/fence-spline.test.d.ts +2 -0
- package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
- package/dist/systems/fence/fence-spline.test.js +136 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
- package/dist/systems/stair/stair-footprint.js +21 -11
- package/dist/systems/stair/stair-opening-preview.d.ts +1139 -469
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +1 -18
- package/dist/systems/stair/stair-opening-sync.test.js +39 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +38 -2
- package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
- package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-mitering.test.js +63 -0
- package/dist/utils/clone-scene-graph.d.ts +1 -0
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +23 -3
- package/dist/utils/clone-scene-graph.test.js +3 -0
- package/dist/utils/heal-scene-graph.d.ts +18 -0
- package/dist/utils/heal-scene-graph.d.ts.map +1 -0
- package/dist/utils/heal-scene-graph.js +90 -0
- package/dist/utils/heal-scene-graph.test.d.ts +2 -0
- package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/heal-scene-graph.test.js +88 -0
- package/dist/validation/validate-build-json.d.ts +1 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +31 -2
- package/package.json +3 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AnyNode, AnyNodeId } from '../schema';
|
|
2
|
+
/**
|
|
3
|
+
* IPC validators for the DWV (drain-waste-vent) system — the "CodeRule"
|
|
4
|
+
* primitive from the domain brief. The slope, minimum-size, and
|
|
5
|
+
* trap-arm rules are all geometric and read straight off the node
|
|
6
|
+
* fields, so they live here in core (pure logic) where the editor can
|
|
7
|
+
* surface them and analyses can reuse them.
|
|
8
|
+
*
|
|
9
|
+
* Scope is residential IPC, simplified:
|
|
10
|
+
* - 704.1 drainage slope by pipe size.
|
|
11
|
+
* - 909 trap-arm maximum developed length by trap size.
|
|
12
|
+
*
|
|
13
|
+
* These are intentionally conservative approximations, not a certified
|
|
14
|
+
* plan-check — enough to flag the mistakes a drawing tool invites.
|
|
15
|
+
*/
|
|
16
|
+
/** Drainage findings, worst-first per consumer's sort. */
|
|
17
|
+
export type DwvSeverity = 'error' | 'warning';
|
|
18
|
+
export type DwvFinding = {
|
|
19
|
+
severity: DwvSeverity;
|
|
20
|
+
/** Stable rule id, e.g. 'slope-too-flat'. */
|
|
21
|
+
code: string;
|
|
22
|
+
/** Human-readable, already-formatted message. */
|
|
23
|
+
message: string;
|
|
24
|
+
/** Nodes the finding implicates (usually one). */
|
|
25
|
+
nodeIds: AnyNodeId[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Run every DWV rule over the scene and return the findings. Empty
|
|
29
|
+
* array = nothing to flag. Pure: no scene/store access, no rendering.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateDwv(nodes: Readonly<Record<AnyNodeId, AnyNode>>): DwvFinding[];
|
|
32
|
+
//# sourceMappingURL=validate-dwv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-dwv.d.ts","sourceRoot":"","sources":["../../src/services/validate-dwv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAGnD;;;;;;;;;;;;;GAaG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;AAE7C,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,WAAW,CAAA;IACrB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAAG,UAAU,EAAE,CAwErF"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { buildPortComponents } from './system-graph';
|
|
2
|
+
/** IPC 704.1 minimum drainage slope (rise/run, dimensionless) by
|
|
3
|
+
* nominal pipe size: ¼"/ft (1:48) under 3", ⅛"/ft (1:96) for 3–6",
|
|
4
|
+
* 1/16"/ft (1:192) at 8"+. */
|
|
5
|
+
function minSlopeFor(diameterIn) {
|
|
6
|
+
if (diameterIn < 3)
|
|
7
|
+
return 1 / 48;
|
|
8
|
+
if (diameterIn < 8)
|
|
9
|
+
return 1 / 96;
|
|
10
|
+
return 1 / 192;
|
|
11
|
+
}
|
|
12
|
+
/** IPC Table 909.1 maximum trap-arm developed length (meters) by trap
|
|
13
|
+
* size: 30" @ 1¼", 42" @ 1½", 60" @ 2", 72" @ 3", 120" @ 4". */
|
|
14
|
+
const TRAP_ARM_MAX_M = [
|
|
15
|
+
[1.25, 30 * 0.0254],
|
|
16
|
+
[1.5, 42 * 0.0254],
|
|
17
|
+
[2, 60 * 0.0254],
|
|
18
|
+
[3, 72 * 0.0254],
|
|
19
|
+
[4, 120 * 0.0254],
|
|
20
|
+
];
|
|
21
|
+
function trapArmMaxFor(diameterIn) {
|
|
22
|
+
let max = Infinity;
|
|
23
|
+
for (const [size, lengthM] of TRAP_ARM_MAX_M) {
|
|
24
|
+
if (diameterIn <= size)
|
|
25
|
+
return lengthM;
|
|
26
|
+
max = lengthM;
|
|
27
|
+
}
|
|
28
|
+
return max;
|
|
29
|
+
}
|
|
30
|
+
/** Slopes shallower than this fraction of the minimum are flagged
|
|
31
|
+
* "too flat" — a small tolerance keeps round-off off the list. */
|
|
32
|
+
const SLOPE_TOLERANCE = 0.9;
|
|
33
|
+
/** Horizontal legs shorter than this (meters) are treated as vertical
|
|
34
|
+
* stacks and skipped from the slope check. */
|
|
35
|
+
const VERTICAL_LEG_EPS_M = 0.02;
|
|
36
|
+
function legSlope(a, b) {
|
|
37
|
+
const horizontalM = Math.hypot(b[0] - a[0], b[2] - a[2]);
|
|
38
|
+
if (horizontalM < VERTICAL_LEG_EPS_M)
|
|
39
|
+
return { horizontalM, slope: Infinity };
|
|
40
|
+
return { horizontalM, slope: Math.abs(a[1] - b[1]) / horizontalM };
|
|
41
|
+
}
|
|
42
|
+
function inchLabel(value) {
|
|
43
|
+
return `${value}"`;
|
|
44
|
+
}
|
|
45
|
+
/** Per-foot slope as a readable fraction, e.g. 0.0208 → '¼"/ft'. */
|
|
46
|
+
function slopePerFootLabel(slope) {
|
|
47
|
+
const inchesPerFoot = slope * 12;
|
|
48
|
+
return `${inchesPerFoot.toFixed(2)}"/ft`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Run every DWV rule over the scene and return the findings. Empty
|
|
52
|
+
* array = nothing to flag. Pure: no scene/store access, no rendering.
|
|
53
|
+
*/
|
|
54
|
+
export function validateDwv(nodes) {
|
|
55
|
+
const findings = [];
|
|
56
|
+
// ── Per-segment slope (waste only) ──────────────────────────────
|
|
57
|
+
for (const node of Object.values(nodes)) {
|
|
58
|
+
if (node?.type !== 'pipe-segment' || node.system !== 'waste')
|
|
59
|
+
continue;
|
|
60
|
+
const path = node.path;
|
|
61
|
+
const minSlope = minSlopeFor(node.diameter);
|
|
62
|
+
const maxSlope = node.diameter / 12; // 1 pipe-diameter per foot → siphoning
|
|
63
|
+
let flaggedFlat = false;
|
|
64
|
+
let flaggedSteep = false;
|
|
65
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
66
|
+
const { slope } = legSlope(path[i], path[i + 1]);
|
|
67
|
+
if (slope === Infinity)
|
|
68
|
+
continue; // vertical stack leg
|
|
69
|
+
if (!flaggedFlat && slope < minSlope * SLOPE_TOLERANCE) {
|
|
70
|
+
findings.push({
|
|
71
|
+
severity: 'error',
|
|
72
|
+
code: 'slope-too-flat',
|
|
73
|
+
message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — IPC 704.1 requires at least ${slopePerFootLabel(minSlope)}.`,
|
|
74
|
+
nodeIds: [node.id],
|
|
75
|
+
});
|
|
76
|
+
flaggedFlat = true;
|
|
77
|
+
}
|
|
78
|
+
if (!flaggedSteep && slope > maxSlope) {
|
|
79
|
+
findings.push({
|
|
80
|
+
severity: 'warning',
|
|
81
|
+
code: 'slope-too-steep',
|
|
82
|
+
message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — over one pipe-diameter per foot risks siphoning the traps.`,
|
|
83
|
+
nodeIds: [node.id],
|
|
84
|
+
});
|
|
85
|
+
flaggedSteep = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ── Component-scoped trap rules ──────────────────────────────────
|
|
90
|
+
for (const component of buildPortComponents(nodes)) {
|
|
91
|
+
const traps = [];
|
|
92
|
+
for (const id of component) {
|
|
93
|
+
const node = nodes[id];
|
|
94
|
+
if (!node)
|
|
95
|
+
continue;
|
|
96
|
+
if (node.type === 'pipe-trap') {
|
|
97
|
+
traps.push(node);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Trap-arm developed length: trap outlet → its vent, capped by size.
|
|
101
|
+
// Independent of waste segments — a trap on its own can already be
|
|
102
|
+
// over-armed.
|
|
103
|
+
for (const trap of traps) {
|
|
104
|
+
const t = trap;
|
|
105
|
+
const armLengthM = t.armLengthM ?? 0;
|
|
106
|
+
const maxArm = trapArmMaxFor(t.diameter);
|
|
107
|
+
if (armLengthM > maxArm + 1e-6) {
|
|
108
|
+
findings.push({
|
|
109
|
+
severity: 'error',
|
|
110
|
+
code: 'trap-arm-too-long',
|
|
111
|
+
message: `${inchLabel(t.diameter)} trap arm runs ${(armLengthM / 0.0254).toFixed(0)}" to its vent — IPC 909.1 caps it at ${(maxArm / 0.0254).toFixed(0)}".`,
|
|
112
|
+
nodeIds: [t.id],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-dwv.test.d.ts","sourceRoot":"","sources":["../../src/services/validate-dwv.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { registerNode } from '../registry';
|
|
3
|
+
import { validateDwv } from './validate-dwv';
|
|
4
|
+
// The validator reads node fields directly + buildPortComponents (which
|
|
5
|
+
// consults def.ports), so register stub port-providers for the DWV kinds
|
|
6
|
+
// it groups by. Mirrors the system-graph test's approach.
|
|
7
|
+
function stubDef(kind, ports) {
|
|
8
|
+
registerNode({
|
|
9
|
+
kind,
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
schema: {},
|
|
12
|
+
category: 'utility',
|
|
13
|
+
defaults: () => ({}),
|
|
14
|
+
capabilities: {},
|
|
15
|
+
ports,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
stubDef('pipe-segment', (node) => {
|
|
19
|
+
const path = node.path;
|
|
20
|
+
const diameter = node.diameter;
|
|
21
|
+
const system = node.system;
|
|
22
|
+
return [
|
|
23
|
+
{ id: 'start', position: path[0], direction: [-1, 0, 0], diameter, system },
|
|
24
|
+
{
|
|
25
|
+
id: 'end',
|
|
26
|
+
position: path[path.length - 1],
|
|
27
|
+
direction: [1, 0, 0],
|
|
28
|
+
diameter,
|
|
29
|
+
system,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
});
|
|
33
|
+
stubDef('pipe-trap', (node) => {
|
|
34
|
+
const position = node.position;
|
|
35
|
+
return [{ id: 'inlet', position, direction: [0, 1, 0], diameter: 1.5, system: 'waste' }];
|
|
36
|
+
});
|
|
37
|
+
let nextId = 0;
|
|
38
|
+
function makeNode(type, fields) {
|
|
39
|
+
nextId += 1;
|
|
40
|
+
return { id: `${type}_${nextId}`, type, object: 'node', parentId: null, ...fields };
|
|
41
|
+
}
|
|
42
|
+
function sceneOf(...nodes) {
|
|
43
|
+
return Object.fromEntries(nodes.map((n) => [n.id, n]));
|
|
44
|
+
}
|
|
45
|
+
/** A waste run from a→b. Drop the end Y to slope it. */
|
|
46
|
+
function waste(path, diameter = 2) {
|
|
47
|
+
return makeNode('pipe-segment', { path, diameter, system: 'waste' });
|
|
48
|
+
}
|
|
49
|
+
const QUARTER_PER_FOOT = 1 / 48;
|
|
50
|
+
describe('validateDwv — slope', () => {
|
|
51
|
+
test('flags a flat waste run', () => {
|
|
52
|
+
const run = waste([
|
|
53
|
+
[0, 0, 0],
|
|
54
|
+
[3, 0, 0], // dead level
|
|
55
|
+
]);
|
|
56
|
+
const findings = validateDwv(sceneOf(run));
|
|
57
|
+
expect(findings.some((f) => f.code === 'slope-too-flat')).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
test('passes a run sloped at quarter-inch per foot', () => {
|
|
60
|
+
const drop = 3 * QUARTER_PER_FOOT;
|
|
61
|
+
const run = waste([
|
|
62
|
+
[0, 0, 0],
|
|
63
|
+
[3, -drop, 0],
|
|
64
|
+
]);
|
|
65
|
+
const findings = validateDwv(sceneOf(run));
|
|
66
|
+
expect(findings.some((f) => f.code === 'slope-too-flat')).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
test('flags an over-steep run (siphoning risk)', () => {
|
|
69
|
+
// 2" pipe, max slope = 2/12 ≈ 0.167; drop 2m over 1m horizontal.
|
|
70
|
+
const run = waste([
|
|
71
|
+
[0, 0, 0],
|
|
72
|
+
[1, -2, 0],
|
|
73
|
+
]);
|
|
74
|
+
const findings = validateDwv(sceneOf(run));
|
|
75
|
+
expect(findings.some((f) => f.code === 'slope-too-steep')).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
test('ignores vents (level is fine)', () => {
|
|
78
|
+
const vent = makeNode('pipe-segment', {
|
|
79
|
+
path: [
|
|
80
|
+
[0, 0, 0],
|
|
81
|
+
[0, 3, 0],
|
|
82
|
+
],
|
|
83
|
+
diameter: 2,
|
|
84
|
+
system: 'vent',
|
|
85
|
+
});
|
|
86
|
+
const findings = validateDwv(sceneOf(vent));
|
|
87
|
+
expect(findings.length).toBe(0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('validateDwv — trap arm', () => {
|
|
91
|
+
test('flags an over-long trap arm', () => {
|
|
92
|
+
const trap = makeNode('pipe-trap', {
|
|
93
|
+
position: [0, 0, 0],
|
|
94
|
+
diameter: 1.5, // max arm 42in = 1.067m
|
|
95
|
+
armLengthM: 2, // way over
|
|
96
|
+
});
|
|
97
|
+
const findings = validateDwv(sceneOf(trap));
|
|
98
|
+
expect(findings.some((f) => f.code === 'trap-arm-too-long')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
test('passes a trap arm within the limit', () => {
|
|
101
|
+
const trap = makeNode('pipe-trap', {
|
|
102
|
+
position: [0, 0, 0],
|
|
103
|
+
diameter: 2, // max arm 60in = 1.524m
|
|
104
|
+
armLengthM: 1,
|
|
105
|
+
});
|
|
106
|
+
const findings = validateDwv(sceneOf(trap));
|
|
107
|
+
expect(findings.some((f) => f.code === 'trap-arm-too-long')).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EASf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC3D,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAC7D,KAAK,YAAY,GAAG,SAAS,CAAA;AA0uB7B,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,YAAY,EAAE,SAkDpB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,SAyIvF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAoFrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SAqIjB,CAAA"}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { nodeRegistry } from '../../registry/registry';
|
|
2
|
+
import { AnyNode as AnyNodeSchema, createDefaultRidgeVentsForSegment, getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, isAutoRidgeVentEnabled, isDefaultRidgeVentNode, } from '../../schema';
|
|
3
|
+
const DEFAULT_RIDGE_VENT_REFRESH_FIELDS = new Set([
|
|
4
|
+
'roofType',
|
|
5
|
+
'width',
|
|
6
|
+
'depth',
|
|
7
|
+
'pitch',
|
|
8
|
+
'overhang',
|
|
9
|
+
'wallThickness',
|
|
10
|
+
'shingleThickness',
|
|
11
|
+
'gambrelLowerWidthRatio',
|
|
12
|
+
'mansardSteepWidthRatio',
|
|
13
|
+
'dutchHipWidthRatio',
|
|
14
|
+
'dutchWaistLengthRatio',
|
|
15
|
+
'dutchGabletRake',
|
|
16
|
+
]);
|
|
2
17
|
const NUMBER_FORMAT_BOUNDS = {
|
|
3
18
|
safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
|
|
4
19
|
int32: [-2147483648, 2147483647],
|
|
@@ -341,6 +356,34 @@ function parseUpdatedNode(currentNode, data) {
|
|
|
341
356
|
warnSanitizedNodeMutation('update', currentNode.id, sanitized.issues);
|
|
342
357
|
return { ...currentNode, ...sanitized.value };
|
|
343
358
|
}
|
|
359
|
+
function shouldRefreshDefaultRidgeVents(data) {
|
|
360
|
+
return Object.keys(data).some((key) => DEFAULT_RIDGE_VENT_REFRESH_FIELDS.has(key));
|
|
361
|
+
}
|
|
362
|
+
function refreshDefaultRidgeVentsForSegment(nextNodes, segment) {
|
|
363
|
+
const childIds = Array.isArray(segment.children) ? segment.children : [];
|
|
364
|
+
if (!isAutoRidgeVentEnabled(segment, nextNodes))
|
|
365
|
+
return [];
|
|
366
|
+
const defaultIds = childIds.filter((childId) => isDefaultRidgeVentNode(nextNodes[childId], segment.id));
|
|
367
|
+
const defaultIdSet = new Set(defaultIds);
|
|
368
|
+
for (const id of defaultIds) {
|
|
369
|
+
delete nextNodes[id];
|
|
370
|
+
}
|
|
371
|
+
const nextVents = createDefaultRidgeVentsForSegment(segment);
|
|
372
|
+
for (const vent of nextVents) {
|
|
373
|
+
nextNodes[vent.id] = {
|
|
374
|
+
...vent,
|
|
375
|
+
parentId: segment.id,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
nextNodes[segment.id] = {
|
|
379
|
+
...segment,
|
|
380
|
+
children: [
|
|
381
|
+
...childIds.filter((childId) => !defaultIdSet.has(childId)),
|
|
382
|
+
...nextVents.map((vent) => vent.id),
|
|
383
|
+
],
|
|
384
|
+
};
|
|
385
|
+
return nextVents.map((vent) => vent.id);
|
|
386
|
+
}
|
|
344
387
|
// Track pending RAF for updateNodesAction to prevent multiple queued callbacks
|
|
345
388
|
let pendingRafId = null;
|
|
346
389
|
let pendingUpdates = new Set();
|
|
@@ -575,6 +618,11 @@ export const applyNodeChangesAction = (set, get, changes) => {
|
|
|
575
618
|
}
|
|
576
619
|
}
|
|
577
620
|
nextNodes[id] = updatedNode;
|
|
621
|
+
if (updatedNode.type === 'roof-segment' && shouldRefreshDefaultRidgeVents(data)) {
|
|
622
|
+
for (const ventId of refreshDefaultRidgeVentsForSegment(nextNodes, updatedNode)) {
|
|
623
|
+
nodesToMarkDirty.add(ventId);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
578
626
|
nodesToMarkDirty.add(id);
|
|
579
627
|
}
|
|
580
628
|
for (const { node, parentId } of createOps) {
|
|
@@ -659,6 +707,7 @@ export const updateNodesAction = (set, get, updates) => {
|
|
|
659
707
|
if (get().readOnly)
|
|
660
708
|
return;
|
|
661
709
|
const parentsToUpdate = new Set();
|
|
710
|
+
const extraNodesToUpdate = new Set();
|
|
662
711
|
set((state) => {
|
|
663
712
|
const nextNodes = { ...state.nodes };
|
|
664
713
|
for (const { id, data } of updates) {
|
|
@@ -702,6 +751,11 @@ export const updateNodesAction = (set, get, updates) => {
|
|
|
702
751
|
}
|
|
703
752
|
// Apply the update
|
|
704
753
|
nextNodes[id] = updatedNode;
|
|
754
|
+
if (updatedNode.type === 'roof-segment' && shouldRefreshDefaultRidgeVents(data)) {
|
|
755
|
+
for (const ventId of refreshDefaultRidgeVentsForSegment(nextNodes, updatedNode)) {
|
|
756
|
+
extraNodesToUpdate.add(ventId);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
705
759
|
}
|
|
706
760
|
return { nodes: nextNodes };
|
|
707
761
|
});
|
|
@@ -712,6 +766,9 @@ export const updateNodesAction = (set, get, updates) => {
|
|
|
712
766
|
for (const pId of parentsToUpdate) {
|
|
713
767
|
pendingUpdates.add(pId);
|
|
714
768
|
}
|
|
769
|
+
for (const id of extraNodesToUpdate) {
|
|
770
|
+
pendingUpdates.add(id);
|
|
771
|
+
}
|
|
715
772
|
if (pendingRafId !== null) {
|
|
716
773
|
cancelAnimationFrame(pendingRafId);
|
|
717
774
|
}
|
|
@@ -728,6 +785,7 @@ export const deleteNodesAction = (set, get, ids) => {
|
|
|
728
785
|
return;
|
|
729
786
|
const parentsToMarkDirty = new Set();
|
|
730
787
|
const nodesToMarkDirty = new Set();
|
|
788
|
+
const deletedIds = new Set();
|
|
731
789
|
const mergePlans = buildWallMergePlans(get().nodes, ids);
|
|
732
790
|
set((state) => {
|
|
733
791
|
const nextNodes = { ...state.nodes };
|
|
@@ -741,6 +799,13 @@ export const deleteNodesAction = (set, get, ids) => {
|
|
|
741
799
|
return;
|
|
742
800
|
allIds.add(id);
|
|
743
801
|
const node = nextNodes[id];
|
|
802
|
+
const cascadeDeletes = node
|
|
803
|
+
? nodeRegistry.get(node.type)?.parametrics?.onDeleteCascade?.(node, nextNodes, allIds)
|
|
804
|
+
: null;
|
|
805
|
+
if (cascadeDeletes) {
|
|
806
|
+
for (const companionId of cascadeDeletes)
|
|
807
|
+
collect(companionId);
|
|
808
|
+
}
|
|
744
809
|
if (node && 'children' in node) {
|
|
745
810
|
for (const cid of node.children)
|
|
746
811
|
collect(cid);
|
|
@@ -751,6 +816,29 @@ export const deleteNodesAction = (set, get, ids) => {
|
|
|
751
816
|
for (const plan of mergePlans) {
|
|
752
817
|
allIds.add(plan.secondaryWallId);
|
|
753
818
|
}
|
|
819
|
+
for (const id of allIds)
|
|
820
|
+
deletedIds.add(id);
|
|
821
|
+
// Let each deleted kind undo what it imposed on its neighbours (e.g. an
|
|
822
|
+
// auto-inserted elbow re-extends the duct runs it trimmed back onto the
|
|
823
|
+
// corner it replaced). Read against pre-deletion `nextNodes`; skip
|
|
824
|
+
// patches that target a node also being deleted.
|
|
825
|
+
for (const id of allIds) {
|
|
826
|
+
const node = nextNodes[id];
|
|
827
|
+
if (!node)
|
|
828
|
+
continue;
|
|
829
|
+
const onDelete = nodeRegistry.get(node.type)?.parametrics?.onDelete;
|
|
830
|
+
if (!onDelete)
|
|
831
|
+
continue;
|
|
832
|
+
for (const { id: targetId, data } of onDelete(node, nextNodes)) {
|
|
833
|
+
if (allIds.has(targetId))
|
|
834
|
+
continue;
|
|
835
|
+
const target = nextNodes[targetId];
|
|
836
|
+
if (!target)
|
|
837
|
+
continue;
|
|
838
|
+
nextNodes[targetId] = { ...target, ...data };
|
|
839
|
+
nodesToMarkDirty.add(targetId);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
754
842
|
for (const plan of mergePlans) {
|
|
755
843
|
const primaryWall = nextNodes[plan.primaryWallId];
|
|
756
844
|
if (!(primaryWall && primaryWall.type === 'wall') || allIds.has(plan.primaryWallId)) {
|
|
@@ -805,6 +893,11 @@ export const deleteNodesAction = (set, get, ids) => {
|
|
|
805
893
|
}
|
|
806
894
|
return { nodes: nextNodes, rootNodeIds: nextRootIds, collections: nextCollections };
|
|
807
895
|
});
|
|
896
|
+
// Deleted ids must leave the dirty set: every consumer skips missing
|
|
897
|
+
// nodes without clearing them, so a mark on a deleted node would sit in
|
|
898
|
+
// the set (and defeat the consumers' empty-set early exit) forever.
|
|
899
|
+
for (const id of deletedIds)
|
|
900
|
+
get().clearDirty(id);
|
|
808
901
|
// Mark affected nodes dirty: parents of deleted nodes and their remaining children
|
|
809
902
|
// (e.g. deleting a slab affects sibling walls via level elevation changes)
|
|
810
903
|
parentsToMarkDirty.forEach((parentId) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ridge-vent-update.test.d.ts","sourceRoot":"","sources":["../../../src/store/actions/ridge-vent-update.test.ts"],"names":[],"mappings":""}
|