@pascal-app/core 0.8.0 → 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 +67 -13
- 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 +14 -23
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +59 -26
- 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.d.ts +14 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
- 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 +36 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -6
- 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/polygon-relations.d.ts +10 -0
- package/dist/lib/polygon-relations.d.ts.map +1 -0
- package/dist/lib/polygon-relations.js +73 -0
- 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-pause.test.d.ts +2 -0
- package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
- package/dist/lib/space-detection-pause.test.js +42 -0
- package/dist/lib/space-detection.d.ts +54 -2
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +599 -124
- package/dist/lib/space-detection.test.d.ts +2 -0
- package/dist/lib/space-detection.test.d.ts.map +1 -0
- package/dist/lib/space-detection.test.js +376 -0
- 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 +3737 -200
- package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
- package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
- package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
- package/dist/registry/handles.d.ts +394 -0
- package/dist/registry/handles.d.ts.map +1 -0
- package/dist/registry/handles.js +24 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/registry.d.ts +136 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +282 -0
- package/dist/registry/registry.test.d.ts +2 -0
- package/dist/registry/registry.test.d.ts.map +1 -0
- package/dist/registry/registry.test.js +196 -0
- package/dist/registry/relations-resolver.d.ts +42 -0
- package/dist/registry/relations-resolver.d.ts.map +1 -0
- package/dist/registry/relations-resolver.js +77 -0
- package/dist/registry/relations-resolver.test.d.ts +2 -0
- package/dist/registry/relations-resolver.test.d.ts.map +1 -0
- package/dist/registry/relations-resolver.test.js +183 -0
- package/dist/registry/scene-api.d.ts +44 -0
- package/dist/registry/scene-api.d.ts.map +1 -0
- package/dist/registry/scene-api.js +111 -0
- package/dist/registry/scene-api.test.d.ts +2 -0
- package/dist/registry/scene-api.test.d.ts.map +1 -0
- package/dist/registry/scene-api.test.js +183 -0
- package/dist/registry/subtree.d.ts +67 -0
- package/dist/registry/subtree.d.ts.map +1 -0
- package/dist/registry/subtree.js +123 -0
- package/dist/registry/subtree.test.d.ts +2 -0
- package/dist/registry/subtree.test.d.ts.map +1 -0
- package/dist/registry/subtree.test.js +163 -0
- package/dist/registry/types.d.ts +2099 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +1 -0
- package/dist/schema/index.d.ts +35 -4
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +32 -4
- package/dist/schema/material.d.ts +13 -2
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +17 -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 +74 -0
- package/dist/schema/nodes/box-vent.d.ts.map +1 -0
- package/dist/schema/nodes/box-vent.js +63 -0
- package/dist/schema/nodes/building.d.ts +1 -1
- package/dist/schema/nodes/building.d.ts.map +1 -1
- package/dist/schema/nodes/building.js +3 -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 +5 -2
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +5 -1
- package/dist/schema/nodes/chimney.d.ts +158 -0
- package/dist/schema/nodes/chimney.d.ts.map +1 -0
- package/dist/schema/nodes/chimney.js +67 -0
- package/dist/schema/nodes/column.d.ts +600 -7
- package/dist/schema/nodes/column.d.ts.map +1 -1
- package/dist/schema/nodes/column.js +589 -6
- package/dist/schema/nodes/cupola.d.ts +66 -0
- package/dist/schema/nodes/cupola.d.ts.map +1 -0
- package/dist/schema/nodes/cupola.js +28 -0
- 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 +228 -0
- package/dist/schema/nodes/dormer.d.ts.map +1 -0
- package/dist/schema/nodes/dormer.js +119 -0
- package/dist/schema/nodes/downspout.d.ts +75 -0
- package/dist/schema/nodes/downspout.d.ts.map +1 -0
- package/dist/schema/nodes/downspout.js +63 -0
- 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 +106 -0
- package/dist/schema/nodes/elevator.d.ts.map +1 -0
- package/dist/schema/nodes/elevator.js +57 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
- package/dist/schema/nodes/eyebrow-vent.js +40 -0
- package/dist/schema/nodes/fence.d.ts +19 -2
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +27 -2
- package/dist/schema/nodes/gutter.d.ts +84 -0
- package/dist/schema/nodes/gutter.d.ts.map +1 -0
- package/dist/schema/nodes/gutter.js +76 -0
- 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 +12 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +20 -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 +4 -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 +82 -0
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
- package/dist/schema/nodes/ridge-vent.js +248 -0
- 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 +273 -3
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +539 -3
- package/dist/schema/nodes/roof.d.ts +8 -8
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +4 -16
- package/dist/schema/nodes/shelf.d.ts +106 -0
- package/dist/schema/nodes/shelf.d.ts.map +1 -0
- package/dist/schema/nodes/shelf.js +90 -0
- package/dist/schema/nodes/site.d.ts +1 -122
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/site.js +2 -6
- package/dist/schema/nodes/skylight.d.ts +279 -0
- package/dist/schema/nodes/skylight.d.ts.map +1 -0
- package/dist/schema/nodes/skylight.js +152 -0
- package/dist/schema/nodes/slab.d.ts +5 -2
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +5 -1
- package/dist/schema/nodes/solar-panel.d.ts +117 -0
- package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
- package/dist/schema/nodes/solar-panel.js +47 -0
- 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/surface-hole-metadata.d.ts +2 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
- package/dist/schema/nodes/surface-hole-metadata.js +3 -1
- package/dist/schema/nodes/turbine-vent.d.ts +68 -0
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
- package/dist/schema/nodes/turbine-vent.js +41 -0
- 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 +2850 -655
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +49 -0
- package/dist/services/alignment-anchors.d.ts +100 -0
- package/dist/services/alignment-anchors.d.ts.map +1 -0
- package/dist/services/alignment-anchors.js +312 -0
- package/dist/services/alignment-anchors.test.d.ts +2 -0
- package/dist/services/alignment-anchors.test.d.ts.map +1 -0
- package/dist/services/alignment-anchors.test.js +383 -0
- package/dist/services/alignment.d.ts +131 -0
- package/dist/services/alignment.d.ts.map +1 -0
- package/dist/services/alignment.js +171 -0
- package/dist/services/alignment.test.d.ts +2 -0
- package/dist/services/alignment.test.d.ts.map +1 -0
- package/dist/services/alignment.test.js +92 -0
- package/dist/services/drag-session.d.ts +47 -0
- package/dist/services/drag-session.d.ts.map +1 -0
- package/dist/services/drag-session.js +100 -0
- package/dist/services/drag-session.test.d.ts +2 -0
- package/dist/services/drag-session.test.d.ts.map +1 -0
- package/dist/services/drag-session.test.js +219 -0
- package/dist/services/hosting.d.ts +88 -0
- package/dist/services/hosting.d.ts.map +1 -0
- package/dist/services/hosting.js +131 -0
- package/dist/services/hosting.test.d.ts +2 -0
- package/dist/services/hosting.test.d.ts.map +1 -0
- package/dist/services/hosting.test.js +230 -0
- package/dist/services/index.d.ts +13 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +12 -0
- 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/movement.d.ts +48 -0
- package/dist/services/movement.d.ts.map +1 -0
- package/dist/services/movement.js +65 -0
- package/dist/services/movement.test.d.ts +2 -0
- package/dist/services/movement.test.d.ts.map +1 -0
- package/dist/services/movement.test.js +133 -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/single-undo-dance.test.d.ts +2 -0
- package/dist/services/single-undo-dance.test.d.ts.map +1 -0
- package/dist/services/single-undo-dance.test.js +210 -0
- package/dist/services/snap.d.ts +80 -0
- package/dist/services/snap.d.ts.map +1 -0
- package/dist/services/snap.js +140 -0
- package/dist/services/snap.test.d.ts +2 -0
- package/dist/services/snap.test.d.ts.map +1 -0
- package/dist/services/snap.test.js +137 -0
- 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/solar-panel-presets.d.ts +17 -0
- package/dist/solar-panel-presets.d.ts.map +1 -0
- package/dist/solar-panel-presets.js +43 -0
- package/dist/store/actions/node-actions.d.ts +14 -2
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +585 -15
- package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
- package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
- package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
- package/dist/store/actions/reparent.test.d.ts +2 -0
- package/dist/store/actions/reparent.test.d.ts.map +1 -0
- package/dist/store/actions/reparent.test.js +212 -0
- 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-interactive.d.ts +39 -0
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +75 -0
- package/dist/store/use-live-node-overrides.d.ts +22 -0
- package/dist/store/use-live-node-overrides.d.ts.map +1 -0
- package/dist/store/use-live-node-overrides.js +59 -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-elevator-migration.test.d.ts +2 -0
- package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
- package/dist/store/use-scene-elevator-migration.test.js +109 -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 +28 -2
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +546 -49
- package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
- package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-dispatch.js +57 -0
- package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
- package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-geometry.js +50 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
- package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.js +218 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-opening-system.js +48 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime-system.js +8 -0
- package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
- package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.js +225 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
- package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-runtime.test.js +64 -0
- package/dist/systems/elevator/elevator-service.d.ts +19 -0
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-service.js +111 -0
- 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 +44 -0
- package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
- package/dist/systems/stair/stair-footprint.js +178 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.js +93 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
- package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-preview.test.js +80 -0
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +155 -147
- package/dist/systems/stair/stair-opening-sync.test.js +424 -2
- package/dist/systems/stair/stair-opening-system.d.ts +2 -0
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-system.js +104 -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/systems/wall/wall-move.d.ts +48 -0
- package/dist/systems/wall/wall-move.d.ts.map +1 -0
- package/dist/systems/wall/wall-move.js +193 -0
- package/dist/utils/clone-scene-graph.d.ts +7 -4
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +29 -7
- package/dist/utils/clone-scene-graph.test.d.ts +2 -0
- package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
- package/dist/utils/clone-scene-graph.test.js +62 -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 +44 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -0
- package/dist/validation/validate-build-json.js +256 -0
- package/package.json +11 -4
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { nodeRegistry } from '../registry';
|
|
2
|
+
/**
|
|
3
|
+
* The "System" primitive: connected components over the port graph.
|
|
4
|
+
*
|
|
5
|
+
* Two nodes are joined when a port of one coincides in space with a port
|
|
6
|
+
* of the other — the same mated-joint relationship `port-connectivity`
|
|
7
|
+
* uses for drag propagation, read here at whole-scene scope. A component
|
|
8
|
+
* is one distribution system: a furnace, its trunk, the tees, branches,
|
|
9
|
+
* and registers hanging off it.
|
|
10
|
+
*
|
|
11
|
+
* Pure logic (def.ports + arithmetic), no rendering — lives in core so
|
|
12
|
+
* the editor (badges, schedules) and analyses (sizing, code checks) can
|
|
13
|
+
* share it.
|
|
14
|
+
*/
|
|
15
|
+
/** Distance (meters) under which two ports count as the same joint —
|
|
16
|
+
* matches port-connectivity's tolerance for hand-placed joints. */
|
|
17
|
+
const COINCIDENT_EPS_M = 0.05;
|
|
18
|
+
function collectPorts(nodes) {
|
|
19
|
+
const result = [];
|
|
20
|
+
for (const node of Object.values(nodes)) {
|
|
21
|
+
if (!node)
|
|
22
|
+
continue;
|
|
23
|
+
const ports = nodeRegistry.get(node.type)?.ports?.(node);
|
|
24
|
+
if (!ports)
|
|
25
|
+
continue;
|
|
26
|
+
for (const port of ports) {
|
|
27
|
+
result.push({
|
|
28
|
+
nodeId: node.id,
|
|
29
|
+
x: port.position[0],
|
|
30
|
+
y: port.position[1],
|
|
31
|
+
z: port.position[2],
|
|
32
|
+
system: port.system,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/** Union-find over node ids. */
|
|
39
|
+
class Components {
|
|
40
|
+
parent = new Map();
|
|
41
|
+
find(id) {
|
|
42
|
+
let root = this.parent.get(id) ?? id;
|
|
43
|
+
if (root !== id) {
|
|
44
|
+
root = this.find(root);
|
|
45
|
+
this.parent.set(id, root);
|
|
46
|
+
}
|
|
47
|
+
return root;
|
|
48
|
+
}
|
|
49
|
+
union(a, b) {
|
|
50
|
+
const ra = this.find(a);
|
|
51
|
+
const rb = this.find(b);
|
|
52
|
+
if (ra !== rb)
|
|
53
|
+
this.parent.set(rb, ra);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function pathLength(path) {
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
59
|
+
const a = path[i];
|
|
60
|
+
const b = path[i + 1];
|
|
61
|
+
total += Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
62
|
+
}
|
|
63
|
+
return total;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Group every port-bearing node into connected components via coinciding
|
|
67
|
+
* ports. Nodes with ports but no joints form singleton components; nodes
|
|
68
|
+
* without `def.ports` don't participate at all.
|
|
69
|
+
*/
|
|
70
|
+
export function buildPortComponents(nodes) {
|
|
71
|
+
const ports = collectPorts(nodes);
|
|
72
|
+
const components = new Components();
|
|
73
|
+
const epsSq = COINCIDENT_EPS_M * COINCIDENT_EPS_M;
|
|
74
|
+
for (let i = 0; i < ports.length; i++) {
|
|
75
|
+
const a = ports[i];
|
|
76
|
+
for (let j = i + 1; j < ports.length; j++) {
|
|
77
|
+
const b = ports[j];
|
|
78
|
+
if (a.nodeId === b.nodeId)
|
|
79
|
+
continue;
|
|
80
|
+
const dx = a.x - b.x;
|
|
81
|
+
const dy = a.y - b.y;
|
|
82
|
+
const dz = a.z - b.z;
|
|
83
|
+
if (dx * dx + dy * dy + dz * dz <= epsSq)
|
|
84
|
+
components.union(a.nodeId, b.nodeId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const grouped = new Map();
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
for (const port of ports) {
|
|
90
|
+
if (seen.has(port.nodeId))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(port.nodeId);
|
|
93
|
+
const root = components.find(port.nodeId);
|
|
94
|
+
const group = grouped.get(root);
|
|
95
|
+
if (group)
|
|
96
|
+
group.push(port.nodeId);
|
|
97
|
+
else
|
|
98
|
+
grouped.set(root, [port.nodeId]);
|
|
99
|
+
}
|
|
100
|
+
return [...grouped.values()];
|
|
101
|
+
}
|
|
102
|
+
function summarize(nodeIds, nodes) {
|
|
103
|
+
const systems = new Set();
|
|
104
|
+
let runCount = 0;
|
|
105
|
+
let runLengthM = 0;
|
|
106
|
+
let fittingCount = 0;
|
|
107
|
+
let terminalCount = 0;
|
|
108
|
+
let equipmentCount = 0;
|
|
109
|
+
for (const id of nodeIds) {
|
|
110
|
+
const node = nodes[id];
|
|
111
|
+
if (!node)
|
|
112
|
+
continue;
|
|
113
|
+
const role = nodeRegistry.get(node.type)?.distributionRole;
|
|
114
|
+
const fields = node;
|
|
115
|
+
if (role === 'run') {
|
|
116
|
+
runCount += 1;
|
|
117
|
+
if (fields.path)
|
|
118
|
+
runLengthM += pathLength(fields.path);
|
|
119
|
+
// Linesets carry refrigerant; duct / pipe runs name their own loop.
|
|
120
|
+
systems.add(fields.system ?? 'refrigerant');
|
|
121
|
+
}
|
|
122
|
+
else if (role === 'fitting') {
|
|
123
|
+
fittingCount += 1;
|
|
124
|
+
if (fields.system)
|
|
125
|
+
systems.add(fields.system);
|
|
126
|
+
}
|
|
127
|
+
else if (role === 'terminal') {
|
|
128
|
+
terminalCount += 1;
|
|
129
|
+
systems.add(fields.terminalType === 'return-grille' ? 'return' : 'supply');
|
|
130
|
+
}
|
|
131
|
+
else if (role === 'equipment') {
|
|
132
|
+
equipmentCount += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
nodeIds,
|
|
137
|
+
systems: [...systems].sort(),
|
|
138
|
+
runCount,
|
|
139
|
+
runLengthM,
|
|
140
|
+
fittingCount,
|
|
141
|
+
terminalCount,
|
|
142
|
+
equipmentCount,
|
|
143
|
+
connectedToEquipment: equipmentCount > 0,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Summary of the system the given node belongs to, or null when the node
|
|
148
|
+
* has no ports (not a distribution kind). A node with ports but no
|
|
149
|
+
* joints yet still gets a (singleton) summary — `connectedToEquipment:
|
|
150
|
+
* false` is the interesting signal there.
|
|
151
|
+
*/
|
|
152
|
+
export function summarizeSystemFor(nodeId, nodes) {
|
|
153
|
+
const node = nodes[nodeId];
|
|
154
|
+
if (!node)
|
|
155
|
+
return null;
|
|
156
|
+
const ports = nodeRegistry.get(node.type)?.ports?.(node);
|
|
157
|
+
if (!ports || ports.length === 0)
|
|
158
|
+
return null;
|
|
159
|
+
for (const component of buildPortComponents(nodes)) {
|
|
160
|
+
if (component.includes(nodeId))
|
|
161
|
+
return summarize(component, nodes);
|
|
162
|
+
}
|
|
163
|
+
return summarize([nodeId], nodes);
|
|
164
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-graph.test.d.ts","sourceRoot":"","sources":["../../src/services/system-graph.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { registerNode } from '../registry';
|
|
3
|
+
import { buildPortComponents, summarizeSystemFor } from './system-graph';
|
|
4
|
+
// Stub registrations: the graph consults `def.ports` for the connectivity
|
|
5
|
+
// graph and `def.distributionRole` to classify each node. Mirrors the real
|
|
6
|
+
// kinds' port + role conventions (duct runs expose start/end, equipment a
|
|
7
|
+
// supply collar, terminals one collar) without importing the nodes package.
|
|
8
|
+
function stubDef(kind, distributionRole, ports) {
|
|
9
|
+
registerNode({
|
|
10
|
+
kind,
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
schema: {},
|
|
13
|
+
category: 'utility',
|
|
14
|
+
distributionRole,
|
|
15
|
+
defaults: () => ({}),
|
|
16
|
+
capabilities: {},
|
|
17
|
+
ports,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
stubDef('duct-segment', 'run', (node) => {
|
|
21
|
+
const path = node.path;
|
|
22
|
+
const system = node.system;
|
|
23
|
+
return [
|
|
24
|
+
{ id: 'start', position: path[0], direction: [-1, 0, 0], diameter: 6, system },
|
|
25
|
+
{
|
|
26
|
+
id: 'end',
|
|
27
|
+
position: path[path.length - 1],
|
|
28
|
+
direction: [1, 0, 0],
|
|
29
|
+
diameter: 6,
|
|
30
|
+
system,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
});
|
|
34
|
+
stubDef('hvac-equipment', 'equipment', (node) => {
|
|
35
|
+
const position = node.position;
|
|
36
|
+
return [{ id: 'supply', position, direction: [0, 1, 0], diameter: 12, system: 'supply' }];
|
|
37
|
+
});
|
|
38
|
+
stubDef('duct-terminal', 'terminal', (node) => {
|
|
39
|
+
const position = node.position;
|
|
40
|
+
return [{ id: 'collar', position, direction: [0, -1, 0], diameter: 6, system: 'supply' }];
|
|
41
|
+
});
|
|
42
|
+
let nextId = 0;
|
|
43
|
+
function makeNode(type, fields) {
|
|
44
|
+
nextId += 1;
|
|
45
|
+
return { id: `${type}_${nextId}`, type, object: 'node', parentId: null, ...fields };
|
|
46
|
+
}
|
|
47
|
+
function sceneOf(...nodes) {
|
|
48
|
+
return Object.fromEntries(nodes.map((n) => [n.id, n]));
|
|
49
|
+
}
|
|
50
|
+
function run(path, system = 'supply') {
|
|
51
|
+
return makeNode('duct-segment', { path, system, diameter: 6 });
|
|
52
|
+
}
|
|
53
|
+
describe('buildPortComponents', () => {
|
|
54
|
+
test('chained runs land in one component; a distant run is separate', () => {
|
|
55
|
+
const a = run([
|
|
56
|
+
[0, 0, 0],
|
|
57
|
+
[3, 0, 0],
|
|
58
|
+
]);
|
|
59
|
+
const b = run([
|
|
60
|
+
[3, 0, 0],
|
|
61
|
+
[3, 0, 4],
|
|
62
|
+
]); // shares a's end
|
|
63
|
+
const c = run([
|
|
64
|
+
[20, 0, 0],
|
|
65
|
+
[24, 0, 0],
|
|
66
|
+
]); // far away
|
|
67
|
+
const components = buildPortComponents(sceneOf(a, b, c));
|
|
68
|
+
expect(components.length).toBe(2);
|
|
69
|
+
const joined = components.find((g) => g.length === 2);
|
|
70
|
+
expect(new Set(joined)).toEqual(new Set([a.id, b.id]));
|
|
71
|
+
});
|
|
72
|
+
test('joints within tolerance still join; outside do not', () => {
|
|
73
|
+
const a = run([
|
|
74
|
+
[0, 0, 0],
|
|
75
|
+
[3, 0, 0],
|
|
76
|
+
]);
|
|
77
|
+
const near = run([
|
|
78
|
+
[3.03, 0, 0],
|
|
79
|
+
[6, 0, 0],
|
|
80
|
+
]); // 3 cm — joined
|
|
81
|
+
const far = run([
|
|
82
|
+
[3.2, 0, 4],
|
|
83
|
+
[6, 0, 4],
|
|
84
|
+
]); // 20 cm in another row — separate
|
|
85
|
+
const components = buildPortComponents(sceneOf(a, near, far));
|
|
86
|
+
expect(components.length).toBe(2);
|
|
87
|
+
});
|
|
88
|
+
test('nodes without ports do not participate', () => {
|
|
89
|
+
const wall = makeNode('wall', {});
|
|
90
|
+
const a = run([
|
|
91
|
+
[0, 0, 0],
|
|
92
|
+
[3, 0, 0],
|
|
93
|
+
]);
|
|
94
|
+
const components = buildPortComponents(sceneOf(wall, a));
|
|
95
|
+
expect(components.length).toBe(1);
|
|
96
|
+
expect(components[0]).toEqual([a.id]);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('summarizeSystemFor', () => {
|
|
100
|
+
test('full tree: equipment → run → terminal, stats add up', () => {
|
|
101
|
+
const furnace = makeNode('hvac-equipment', { position: [0, 0, 0] });
|
|
102
|
+
const trunk = run([
|
|
103
|
+
[0, 0, 0],
|
|
104
|
+
[4, 0, 0],
|
|
105
|
+
]);
|
|
106
|
+
const branch = run([
|
|
107
|
+
[4, 0, 0],
|
|
108
|
+
[4, 0, 3],
|
|
109
|
+
]);
|
|
110
|
+
const register = makeNode('duct-terminal', {
|
|
111
|
+
position: [4, 0, 3],
|
|
112
|
+
terminalType: 'supply-register',
|
|
113
|
+
});
|
|
114
|
+
const scene = sceneOf(furnace, trunk, branch, register);
|
|
115
|
+
const summary = summarizeSystemFor(register.id, scene);
|
|
116
|
+
expect(summary.nodeIds.length).toBe(4);
|
|
117
|
+
expect(summary.connectedToEquipment).toBe(true);
|
|
118
|
+
expect(summary.runCount).toBe(2);
|
|
119
|
+
expect(summary.runLengthM).toBeCloseTo(7, 6);
|
|
120
|
+
expect(summary.terminalCount).toBe(1);
|
|
121
|
+
expect(summary.equipmentCount).toBe(1);
|
|
122
|
+
expect(summary.systems).toEqual(['supply']);
|
|
123
|
+
});
|
|
124
|
+
test('orphaned run reports no equipment', () => {
|
|
125
|
+
const lonely = run([
|
|
126
|
+
[10, 0, 10],
|
|
127
|
+
[14, 0, 10],
|
|
128
|
+
]);
|
|
129
|
+
const summary = summarizeSystemFor(lonely.id, sceneOf(lonely));
|
|
130
|
+
expect(summary.connectedToEquipment).toBe(false);
|
|
131
|
+
expect(summary.runCount).toBe(1);
|
|
132
|
+
expect(summary.runLengthM).toBeCloseTo(4, 6);
|
|
133
|
+
});
|
|
134
|
+
test('port-less node → null', () => {
|
|
135
|
+
const wall = makeNode('wall', {});
|
|
136
|
+
expect(summarizeSystemFor(wall.id, sceneOf(wall))).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const SolarPanelPresetKey: z.ZodEnum<{
|
|
3
|
+
residential: "residential";
|
|
4
|
+
"residential-large": "residential-large";
|
|
5
|
+
compact: "compact";
|
|
6
|
+
frameless: "frameless";
|
|
7
|
+
}>;
|
|
8
|
+
export type SolarPanelPresetKey = z.infer<typeof SolarPanelPresetKey>;
|
|
9
|
+
export type SolarPanelPresetDims = {
|
|
10
|
+
panelWidth: number;
|
|
11
|
+
panelHeight: number;
|
|
12
|
+
frameThickness: number;
|
|
13
|
+
frameDepth: number;
|
|
14
|
+
};
|
|
15
|
+
export declare const SOLAR_PANEL_PRESETS: Record<SolarPanelPresetKey, SolarPanelPresetDims>;
|
|
16
|
+
export declare const SOLAR_PANEL_PRESET_LABELS: Record<SolarPanelPresetKey, string>;
|
|
17
|
+
//# sourceMappingURL=solar-panel-presets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solar-panel-presets.d.ts","sourceRoot":"","sources":["../src/solar-panel-presets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAOvB,eAAO,MAAM,mBAAmB;;;;;EAK9B,CAAA;AACF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAErE,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,EAAE,oBAAoB,CAyBjF,CAAA;AAED,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAKzE,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Vendor-neutral visual presets. Picking one writes the four `panelWidth`,
|
|
3
|
+
// `panelHeight`, `frameThickness`, `frameDepth` fields onto a SolarPanelNode.
|
|
4
|
+
// Manually editing any of those four fields clears the preset back to undefined
|
|
5
|
+
// ("Custom") so the schema's `panelTypePreset` always matches the dims.
|
|
6
|
+
export const SolarPanelPresetKey = z.enum([
|
|
7
|
+
'residential',
|
|
8
|
+
'residential-large',
|
|
9
|
+
'compact',
|
|
10
|
+
'frameless',
|
|
11
|
+
]);
|
|
12
|
+
export const SOLAR_PANEL_PRESETS = {
|
|
13
|
+
residential: {
|
|
14
|
+
panelWidth: 1.0,
|
|
15
|
+
panelHeight: 1.65,
|
|
16
|
+
frameThickness: 0.04,
|
|
17
|
+
frameDepth: 0.04,
|
|
18
|
+
},
|
|
19
|
+
'residential-large': {
|
|
20
|
+
panelWidth: 1.0,
|
|
21
|
+
panelHeight: 2.0,
|
|
22
|
+
frameThickness: 0.04,
|
|
23
|
+
frameDepth: 0.04,
|
|
24
|
+
},
|
|
25
|
+
compact: {
|
|
26
|
+
panelWidth: 0.83,
|
|
27
|
+
panelHeight: 1.0,
|
|
28
|
+
frameThickness: 0.03,
|
|
29
|
+
frameDepth: 0.035,
|
|
30
|
+
},
|
|
31
|
+
frameless: {
|
|
32
|
+
panelWidth: 1.0,
|
|
33
|
+
panelHeight: 1.65,
|
|
34
|
+
frameThickness: 0.005,
|
|
35
|
+
frameDepth: 0.025,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const SOLAR_PANEL_PRESET_LABELS = {
|
|
39
|
+
residential: 'Residential',
|
|
40
|
+
'residential-large': 'Residential Large',
|
|
41
|
+
compact: 'Compact',
|
|
42
|
+
frameless: 'Frameless',
|
|
43
|
+
};
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { type AnyNode, type AnyNodeId } from '../../schema';
|
|
2
2
|
import type { SceneState } from '../use-scene';
|
|
3
|
-
|
|
3
|
+
type NodeCreateOp = {
|
|
4
4
|
node: AnyNode;
|
|
5
5
|
parentId?: AnyNodeId;
|
|
6
|
-
}
|
|
6
|
+
};
|
|
7
|
+
type NodeUpdateOp = {
|
|
8
|
+
id: AnyNodeId;
|
|
9
|
+
data: Partial<AnyNode>;
|
|
10
|
+
};
|
|
11
|
+
type NodeDeleteOp = AnyNodeId;
|
|
12
|
+
export declare const createNodesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, ops: NodeCreateOp[]) => void;
|
|
13
|
+
export declare const applyNodeChangesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, changes: {
|
|
14
|
+
create?: NodeCreateOp[];
|
|
15
|
+
update?: NodeUpdateOp[];
|
|
16
|
+
delete?: NodeDeleteOp[];
|
|
17
|
+
}) => void;
|
|
7
18
|
export declare const updateNodesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, updates: {
|
|
8
19
|
id: AnyNodeId;
|
|
9
20
|
data: Partial<AnyNode>;
|
|
10
21
|
}[]) => void;
|
|
11
22
|
export declare const deleteNodesAction: (set: (fn: (state: SceneState) => Partial<SceneState>) => void, get: () => SceneState, ids: AnyNodeId[]) => void;
|
|
23
|
+
export {};
|
|
12
24
|
//# sourceMappingURL=node-actions.d.ts.map
|
|
@@ -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"}
|