@pascal-app/editor 1.0.3 → 1.0.4
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/package.json +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { AnyNode, AnyNodeId } from '@pascal-app/core'
|
|
4
|
+
import { useScene } from '@pascal-app/core'
|
|
5
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
7
|
+
import { createPortal } from 'react-dom'
|
|
8
|
+
import {
|
|
9
|
+
parseDimensionInput,
|
|
10
|
+
planDimensionDrive,
|
|
11
|
+
resolveDimensionDrive,
|
|
12
|
+
} from '../../../lib/floorplan/dimension-drive'
|
|
13
|
+
import { formatScheduleLength } from '../../../lib/floorplan/schedules'
|
|
14
|
+
import useEditor from '../../../store/use-editor'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Click-to-type dimensions — WS3.
|
|
18
|
+
*
|
|
19
|
+
* Single-clicking a dimension label opens an inline input over the label
|
|
20
|
+
* plate, pre-filled with the current text. Enter commits, Esc cancels.
|
|
21
|
+
* Committing DRIVES geometry: the anchor the dimension runs toward moves
|
|
22
|
+
* along the dimension direction by the delta, and connected wall junctions
|
|
23
|
+
* follow (`planDimensionDrive`). When nothing bindable is under the far
|
|
24
|
+
* anchor the value is written as `textOverride` on a construction-dimension
|
|
25
|
+
* node instead, and the label renders an "override" badge.
|
|
26
|
+
*
|
|
27
|
+
* The label is found by DOM delegation on the attributes
|
|
28
|
+
* `floorplan-dimension-renderer.tsx` emits, so this works identically for
|
|
29
|
+
* `dimension` and `dimension-string` geometry, for automatic wall dimensions,
|
|
30
|
+
* contextual dimensions and manual construction-dimension nodes — and on
|
|
31
|
+
* sheets, which mount the same renderer.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
type EditGateEditorState = Pick<
|
|
35
|
+
ReturnType<typeof useEditor.getState>,
|
|
36
|
+
'workspaceMode' | 'mode' | 'isPreviewMode' | 'isCaptureMode' | 'isFirstPersonMode'
|
|
37
|
+
>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Typing a dimension moves geometry, so it is only offered on an editable
|
|
41
|
+
* scene in idle select mode — never in studio, sheets, preview, capture or
|
|
42
|
+
* read-only (which is how version preview locks the graph).
|
|
43
|
+
*/
|
|
44
|
+
export function isDimensionEditAllowed(
|
|
45
|
+
editor: EditGateEditorState,
|
|
46
|
+
scene: { readOnly: boolean },
|
|
47
|
+
): boolean {
|
|
48
|
+
return (
|
|
49
|
+
editor.workspaceMode === 'edit' &&
|
|
50
|
+
editor.mode === 'select' &&
|
|
51
|
+
!editor.isPreviewMode &&
|
|
52
|
+
!editor.isCaptureMode &&
|
|
53
|
+
!editor.isFirstPersonMode &&
|
|
54
|
+
!scene.readOnly
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function useDimensionEditAllowed(): boolean {
|
|
59
|
+
const readOnly = useScene((state) => state.readOnly)
|
|
60
|
+
return useEditor((state) => isDimensionEditAllowed(state, { readOnly }))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type EditTarget = {
|
|
64
|
+
ownerNodeId: AnyNodeId
|
|
65
|
+
text: string
|
|
66
|
+
value: number
|
|
67
|
+
witnessStart: [number, number]
|
|
68
|
+
witnessEnd: [number, number]
|
|
69
|
+
rect: { left: number; top: number; width: number; height: number }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const MIN_INPUT_WIDTH_PX = 76
|
|
73
|
+
const CLICK_SLOP_PX = 4
|
|
74
|
+
|
|
75
|
+
export function FloorplanDimensionEditOverlay(): React.ReactElement | null {
|
|
76
|
+
const [target, setTarget] = useState<EditTarget | null>(null)
|
|
77
|
+
const [draft, setDraft] = useState('')
|
|
78
|
+
const [error, setError] = useState<string | null>(null)
|
|
79
|
+
const inputRef = useRef<HTMLInputElement | null>(null)
|
|
80
|
+
const unit = useViewer((state) => state.unit)
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
let down: { x: number; y: number } | null = null
|
|
84
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
85
|
+
down = { x: event.clientX, y: event.clientY }
|
|
86
|
+
}
|
|
87
|
+
const onClick = (event: MouseEvent) => {
|
|
88
|
+
if (event.button !== 0 || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
if (down && Math.hypot(event.clientX - down.x, event.clientY - down.y) > CLICK_SLOP_PX) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (!isDimensionEditAllowed(useEditor.getState(), useScene.getState())) return
|
|
95
|
+
const origin = event.target as Element | null
|
|
96
|
+
const hit = origin?.closest?.('[data-floorplan-dimension-hit]')
|
|
97
|
+
if (!hit) return
|
|
98
|
+
const label = hit.closest('[data-floorplan-annotation-label]')
|
|
99
|
+
const owner = hit.closest('[data-node-id]')
|
|
100
|
+
const ownerNodeId = owner?.getAttribute('data-node-id')
|
|
101
|
+
if (!label || !ownerNodeId) return
|
|
102
|
+
|
|
103
|
+
const value = number(label.getAttribute('data-floorplan-dimension-value'))
|
|
104
|
+
const witnessStart = point(
|
|
105
|
+
label.getAttribute('data-floorplan-dimension-witness-start-x'),
|
|
106
|
+
label.getAttribute('data-floorplan-dimension-witness-start-y'),
|
|
107
|
+
)
|
|
108
|
+
const witnessEnd = point(
|
|
109
|
+
label.getAttribute('data-floorplan-dimension-witness-end-x'),
|
|
110
|
+
label.getAttribute('data-floorplan-dimension-witness-end-y'),
|
|
111
|
+
)
|
|
112
|
+
if (value === null || !witnessStart || !witnessEnd) return
|
|
113
|
+
|
|
114
|
+
event.preventDefault()
|
|
115
|
+
event.stopPropagation()
|
|
116
|
+
const box = label.getBoundingClientRect()
|
|
117
|
+
const text = label.getAttribute('data-floorplan-dimension-text') ?? ''
|
|
118
|
+
setTarget({
|
|
119
|
+
ownerNodeId: ownerNodeId as AnyNodeId,
|
|
120
|
+
text,
|
|
121
|
+
value,
|
|
122
|
+
witnessStart,
|
|
123
|
+
witnessEnd,
|
|
124
|
+
rect: {
|
|
125
|
+
left: box.left + box.width / 2,
|
|
126
|
+
top: box.top + box.height / 2,
|
|
127
|
+
width: Math.max(MIN_INPUT_WIDTH_PX, box.width + 24),
|
|
128
|
+
height: Math.max(22, box.height + 6),
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
setDraft(text)
|
|
132
|
+
setError(null)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Document, not window: drag and marquee handlers swallow the click that
|
|
136
|
+
// ends them with a window capture listener registered after this one, and
|
|
137
|
+
// only a listener further down the capture path is skipped by it.
|
|
138
|
+
document.addEventListener('pointerdown', onPointerDown, true)
|
|
139
|
+
document.addEventListener('click', onClick, true)
|
|
140
|
+
return () => {
|
|
141
|
+
document.removeEventListener('pointerdown', onPointerDown, true)
|
|
142
|
+
document.removeEventListener('click', onClick, true)
|
|
143
|
+
}
|
|
144
|
+
}, [])
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!target) return
|
|
148
|
+
const frame = requestAnimationFrame(() => {
|
|
149
|
+
inputRef.current?.focus()
|
|
150
|
+
inputRef.current?.select()
|
|
151
|
+
})
|
|
152
|
+
return () => cancelAnimationFrame(frame)
|
|
153
|
+
}, [target])
|
|
154
|
+
|
|
155
|
+
const commit = useCallback(() => {
|
|
156
|
+
if (!target) return
|
|
157
|
+
const nextLength = parseDimensionInput(draft, unit)
|
|
158
|
+
if (nextLength === null) {
|
|
159
|
+
setError(`Cannot read "${draft.trim()}"`)
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
const outcome = commitDimensionValue({ target, nextLength, unit })
|
|
163
|
+
if (!outcome.ok) {
|
|
164
|
+
setError(outcome.reason)
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
setTarget(null)
|
|
168
|
+
setError(null)
|
|
169
|
+
}, [draft, target, unit])
|
|
170
|
+
|
|
171
|
+
if (!target) return null
|
|
172
|
+
if (typeof document === 'undefined') return null
|
|
173
|
+
|
|
174
|
+
return createPortal(
|
|
175
|
+
<div
|
|
176
|
+
className="pointer-events-none fixed inset-0 z-[70]"
|
|
177
|
+
data-floorplan-dimension-edit-overlay=""
|
|
178
|
+
>
|
|
179
|
+
<div
|
|
180
|
+
className="pointer-events-auto absolute -translate-x-1/2 -translate-y-1/2"
|
|
181
|
+
style={{ left: target.rect.left, top: target.rect.top, width: target.rect.width }}
|
|
182
|
+
>
|
|
183
|
+
<input
|
|
184
|
+
className="w-full rounded-sm border border-primary bg-card px-1 py-0.5 text-center font-mono text-foreground text-xs shadow-sm outline-none"
|
|
185
|
+
onBlur={() => setTarget(null)}
|
|
186
|
+
onChange={(event) => {
|
|
187
|
+
setDraft(event.target.value)
|
|
188
|
+
setError(null)
|
|
189
|
+
}}
|
|
190
|
+
onKeyDown={(event) => {
|
|
191
|
+
if (event.key === 'Enter') {
|
|
192
|
+
event.preventDefault()
|
|
193
|
+
commit()
|
|
194
|
+
} else if (event.key === 'Escape') {
|
|
195
|
+
event.preventDefault()
|
|
196
|
+
setTarget(null)
|
|
197
|
+
setError(null)
|
|
198
|
+
}
|
|
199
|
+
event.stopPropagation()
|
|
200
|
+
}}
|
|
201
|
+
ref={inputRef}
|
|
202
|
+
spellCheck={false}
|
|
203
|
+
value={draft}
|
|
204
|
+
/>
|
|
205
|
+
{error ? (
|
|
206
|
+
<div className="mt-0.5 whitespace-nowrap rounded-sm bg-card px-1 text-[10px] text-destructive shadow-sm">
|
|
207
|
+
{error}
|
|
208
|
+
</div>
|
|
209
|
+
) : null}
|
|
210
|
+
</div>
|
|
211
|
+
</div>,
|
|
212
|
+
document.body,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
type CommitOutcome = { ok: true } | { ok: false; reason: string }
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Apply a typed dimension value. Exported for tests — it is the seam between
|
|
220
|
+
* the pure drive math and the scene store.
|
|
221
|
+
*/
|
|
222
|
+
export function commitDimensionValue(args: {
|
|
223
|
+
target: Pick<EditTarget, 'ownerNodeId' | 'witnessStart' | 'witnessEnd' | 'value'>
|
|
224
|
+
nextLength: number
|
|
225
|
+
unit?: 'metric' | 'imperial'
|
|
226
|
+
}): CommitOutcome {
|
|
227
|
+
const store = useScene.getState()
|
|
228
|
+
const nodes = store.nodes as Readonly<Record<string, AnyNode>>
|
|
229
|
+
const resolution = resolveDimensionDrive({
|
|
230
|
+
nodes,
|
|
231
|
+
ownerNodeId: args.target.ownerNodeId,
|
|
232
|
+
start: args.target.witnessStart,
|
|
233
|
+
end: args.target.witnessEnd,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
if (!resolution.drivable) {
|
|
237
|
+
// Fallback: annotate rather than move. Only a construction-dimension
|
|
238
|
+
// node can carry an override — anything else has nowhere to put it.
|
|
239
|
+
const owner = nodes[args.target.ownerNodeId]
|
|
240
|
+
if (owner?.type !== 'construction-dimension') {
|
|
241
|
+
return { ok: false, reason: `Not drivable — ${resolution.reason}` }
|
|
242
|
+
}
|
|
243
|
+
store.updateNode(args.target.ownerNodeId, {
|
|
244
|
+
textOverride: formatScheduleLength(args.nextLength, args.unit ?? 'imperial'),
|
|
245
|
+
} as Partial<AnyNode>)
|
|
246
|
+
return { ok: true }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const plan = planDimensionDrive({
|
|
250
|
+
nodes,
|
|
251
|
+
target: resolution.target,
|
|
252
|
+
currentLength: args.target.value,
|
|
253
|
+
nextLength: args.nextLength,
|
|
254
|
+
})
|
|
255
|
+
if (!plan) return { ok: false, reason: 'Value would collapse the geometry' }
|
|
256
|
+
if (plan.updates.length === 0) return { ok: true }
|
|
257
|
+
|
|
258
|
+
const changes = plan.updates.map((update) => ({
|
|
259
|
+
id: update.id,
|
|
260
|
+
data: update.data as Partial<AnyNode>,
|
|
261
|
+
}))
|
|
262
|
+
if (store.applyNodeChanges) store.applyNodeChanges({ update: changes })
|
|
263
|
+
else for (const change of changes) store.updateNode(change.id, change.data)
|
|
264
|
+
for (const change of changes) store.markDirty(change.id)
|
|
265
|
+
return { ok: true }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function number(value: string | null): number | null {
|
|
269
|
+
if (value === null) return null
|
|
270
|
+
const parsed = Number.parseFloat(value)
|
|
271
|
+
return Number.isFinite(parsed) ? parsed : null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function point(x: string | null, y: string | null): [number, number] | null {
|
|
275
|
+
const parsedX = number(x)
|
|
276
|
+
const parsedY = number(y)
|
|
277
|
+
return parsedX === null || parsedY === null ? null : [parsedX, parsedY]
|
|
278
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FloorplanGeometry, FloorplanPoint } from '@pascal-app/core'
|
|
2
|
+
import { useDimensionEditAllowed } from './floorplan-dimension-edit-overlay'
|
|
2
3
|
import { resolveFloorplanLabelAngle } from './floorplan-label-angle'
|
|
3
4
|
|
|
4
5
|
const EXTENSION_START_GAP = 0.075
|
|
@@ -21,6 +22,22 @@ const SQRT_ONE_HALF = Math.SQRT1_2
|
|
|
21
22
|
|
|
22
23
|
type FloorplanDimensionRenderMode = 'screen' | 'pdf'
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Click-to-type dimensions (WS3).
|
|
27
|
+
*
|
|
28
|
+
* On screen the label plate carries an invisible hit rect plus the data the
|
|
29
|
+
* editing layer needs to drive geometry from a typed value. The layer picks
|
|
30
|
+
* it up by DOM delegation (`FloorplanDimensionEditOverlay` below), so every
|
|
31
|
+
* producer of `dimension` / `dimension-string` geometry — wall automatic
|
|
32
|
+
* dimensions, contextual dimensions, manual construction-dimension nodes —
|
|
33
|
+
* becomes editable without each one wiring up a callback.
|
|
34
|
+
*
|
|
35
|
+
* Witness points are the measured feature points in level-plan metres; the
|
|
36
|
+
* value is the length the label actually reads (dimension-line points when
|
|
37
|
+
* the producer supplied them, witness points otherwise).
|
|
38
|
+
*/
|
|
39
|
+
export const FLOORPLAN_DIMENSION_HIT_ATTRIBUTE = 'data-floorplan-dimension-hit'
|
|
40
|
+
|
|
24
41
|
type DimensionGeometry = Extract<FloorplanGeometry, { kind: 'dimension' }>
|
|
25
42
|
type DimensionStringGeometry = Extract<FloorplanGeometry, { kind: 'dimension-string' }>
|
|
26
43
|
type DimensionTerminator = NonNullable<DimensionGeometry['terminator']>
|
|
@@ -289,10 +306,20 @@ export function FloorplanDimensionRenderer({
|
|
|
289
306
|
data-floorplan-dimension-start-y={layout.dimensionStart[1]}
|
|
290
307
|
data-floorplan-dimension-end-x={layout.dimensionEnd[0]}
|
|
291
308
|
data-floorplan-dimension-end-y={layout.dimensionEnd[1]}
|
|
309
|
+
data-floorplan-dimension-text={geometry.text}
|
|
310
|
+
data-floorplan-dimension-value={dimensionLabelValue(layout)}
|
|
311
|
+
data-floorplan-dimension-witness-start-x={geometry.start[0]}
|
|
312
|
+
data-floorplan-dimension-witness-start-y={geometry.start[1]}
|
|
313
|
+
data-floorplan-dimension-witness-end-x={geometry.end[0]}
|
|
314
|
+
data-floorplan-dimension-witness-end-y={geometry.end[1]}
|
|
292
315
|
transform={labelTransform}
|
|
293
316
|
>
|
|
294
317
|
<DimensionLabel
|
|
295
318
|
fontSize={labelFontSize}
|
|
319
|
+
// `overridden` is declared on the geometry union in
|
|
320
|
+
// packages/core/src/registry/types.ts; the cast keeps this file
|
|
321
|
+
// compiling against a core dist built before that field landed.
|
|
322
|
+
overridden={(geometry as { overridden?: boolean }).overridden === true}
|
|
296
323
|
renderMode={renderMode}
|
|
297
324
|
stroke={stroke}
|
|
298
325
|
text={geometry.text}
|
|
@@ -464,6 +491,12 @@ export function FloorplanDimensionStringRenderer({
|
|
|
464
491
|
data-floorplan-dimension-start-y={layout.dimensionStart[1]}
|
|
465
492
|
data-floorplan-dimension-end-x={layout.dimensionEnd[0]}
|
|
466
493
|
data-floorplan-dimension-end-y={layout.dimensionEnd[1]}
|
|
494
|
+
data-floorplan-dimension-text={segment.text}
|
|
495
|
+
data-floorplan-dimension-value={dimensionLabelValue(layout)}
|
|
496
|
+
data-floorplan-dimension-witness-start-x={segment.start[0]}
|
|
497
|
+
data-floorplan-dimension-witness-start-y={segment.start[1]}
|
|
498
|
+
data-floorplan-dimension-witness-end-x={segment.end[0]}
|
|
499
|
+
data-floorplan-dimension-witness-end-y={segment.end[1]}
|
|
467
500
|
transform={labelTransform}
|
|
468
501
|
>
|
|
469
502
|
<DimensionLabel
|
|
@@ -481,6 +514,14 @@ export function FloorplanDimensionStringRenderer({
|
|
|
481
514
|
)
|
|
482
515
|
}
|
|
483
516
|
|
|
517
|
+
/** The length the label reads — the dimension-line span, offset cancels out. */
|
|
518
|
+
function dimensionLabelValue(layout: ArchitecturalDimensionLayout): number {
|
|
519
|
+
return Math.hypot(
|
|
520
|
+
layout.dimensionEnd[0] - layout.dimensionStart[0],
|
|
521
|
+
layout.dimensionEnd[1] - layout.dimensionStart[1],
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
484
525
|
function rotateVector(vector: FloorplanPoint, radians: number): FloorplanPoint {
|
|
485
526
|
const cosine = Math.cos(radians)
|
|
486
527
|
const sine = Math.sin(radians)
|
|
@@ -493,17 +534,21 @@ function DimensionLabel({
|
|
|
493
534
|
fontSize,
|
|
494
535
|
stroke,
|
|
495
536
|
renderMode,
|
|
537
|
+
overridden = false,
|
|
496
538
|
}: {
|
|
497
539
|
text: string
|
|
498
540
|
y: number
|
|
499
541
|
fontSize: number
|
|
500
542
|
stroke: string
|
|
501
543
|
renderMode: FloorplanDimensionRenderMode
|
|
544
|
+
/** Draws the "override" badge when the value could not drive geometry. */
|
|
545
|
+
overridden?: boolean
|
|
502
546
|
}) {
|
|
503
547
|
const width = Math.max(fontSize, text.length * fontSize * LABEL_CHARACTER_WIDTH_RATIO)
|
|
504
548
|
const plateWidth = width + fontSize * 0.5
|
|
505
549
|
const plateHeight = fontSize * 1.2
|
|
506
550
|
const plateY = y - fontSize * 0.82
|
|
551
|
+
const editable = useDimensionEditAllowed()
|
|
507
552
|
|
|
508
553
|
return (
|
|
509
554
|
<>
|
|
@@ -535,6 +580,38 @@ function DimensionLabel({
|
|
|
535
580
|
>
|
|
536
581
|
{text}
|
|
537
582
|
</text>
|
|
583
|
+
{overridden && renderMode !== 'pdf' ? (
|
|
584
|
+
<text
|
|
585
|
+
fill={stroke}
|
|
586
|
+
fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
587
|
+
fontSize={fontSize * 0.62}
|
|
588
|
+
opacity={0.75}
|
|
589
|
+
paintOrder="stroke"
|
|
590
|
+
stroke="#ffffff"
|
|
591
|
+
strokeLinejoin="round"
|
|
592
|
+
strokeWidth={2}
|
|
593
|
+
textAnchor="middle"
|
|
594
|
+
vectorEffect="non-scaling-stroke"
|
|
595
|
+
x={0}
|
|
596
|
+
y={y + fontSize * 0.86}
|
|
597
|
+
>
|
|
598
|
+
override
|
|
599
|
+
</text>
|
|
600
|
+
) : null}
|
|
601
|
+
{renderMode === 'pdf' || !editable ? null : (
|
|
602
|
+
// Invisible click target on the plate. `pointer-events` is inherited
|
|
603
|
+
// from the dimension group (`none`), so it is re-enabled here only.
|
|
604
|
+
<rect
|
|
605
|
+
{...{ [FLOORPLAN_DIMENSION_HIT_ATTRIBUTE]: '' }}
|
|
606
|
+
cursor="text"
|
|
607
|
+
fill="transparent"
|
|
608
|
+
height={plateHeight}
|
|
609
|
+
pointerEvents="all"
|
|
610
|
+
width={plateWidth}
|
|
611
|
+
x={-plateWidth / 2}
|
|
612
|
+
y={plateY}
|
|
613
|
+
/>
|
|
614
|
+
)}
|
|
538
615
|
</>
|
|
539
616
|
)
|
|
540
617
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** Length plate + angle arcs for a drafted segment, in plan space. */
|
|
4
|
+
type PlanPoint = readonly [number, number]
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
export type DraftWallMeasurement = {
|
|
6
|
+
export type DraftMeasurement = {
|
|
7
7
|
lengthLabel: string
|
|
8
|
-
midpoint:
|
|
9
|
-
direction:
|
|
8
|
+
midpoint: PlanPoint
|
|
9
|
+
direction: PlanPoint
|
|
10
10
|
angleLabels: {
|
|
11
11
|
id: string
|
|
12
12
|
label: string
|
|
13
|
-
center:
|
|
13
|
+
center: PlanPoint
|
|
14
14
|
radius: number
|
|
15
15
|
startAngle: number
|
|
16
16
|
endAngle: number
|
|
@@ -18,7 +18,7 @@ export type DraftWallMeasurement = {
|
|
|
18
18
|
}[]
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function
|
|
21
|
+
export function FloorplanDraftMeasurement({
|
|
22
22
|
measurement,
|
|
23
23
|
measurementStroke,
|
|
24
24
|
labelBackground,
|
|
@@ -26,7 +26,7 @@ export function FloorplanDraftWallMeasurement({
|
|
|
26
26
|
sceneRotationDeg,
|
|
27
27
|
unitsPerPixel,
|
|
28
28
|
}: {
|
|
29
|
-
measurement:
|
|
29
|
+
measurement: DraftMeasurement
|
|
30
30
|
measurementStroke: string
|
|
31
31
|
labelBackground: string
|
|
32
32
|
labelText: string
|
|
@@ -172,7 +172,10 @@ function documentTextFontSize(
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
function documentTextSizePt(geometry: Extract<FloorplanGeometry, { kind: 'text' }>): number {
|
|
175
|
-
|
|
175
|
+
const metadata = readFloorplanGeometryMetadata(geometry)
|
|
176
|
+
// an explicit paper size wins over the role's (a sheet's room tag sets its own)
|
|
177
|
+
if (typeof metadata.textSizePt === 'number' && metadata.textSizePt > 0) return metadata.textSizePt
|
|
178
|
+
switch (metadata.annotationRole) {
|
|
176
179
|
case 'room-label':
|
|
177
180
|
if (geometry.fontSize >= 0.18) return DOCUMENT_ROOM_NAME_TEXT_SIZE_PT
|
|
178
181
|
if (geometry.fontSize >= 0.145) return DOCUMENT_ROOM_NUMBER_TEXT_SIZE_PT
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
FLOORPLAN_NODE_EXTENSION_KEY,
|
|
16
16
|
floorplanGeometryMetadata,
|
|
17
17
|
} from '../../../lib/floorplan/floorplan-extension'
|
|
18
|
+
import type { SessionWrites } from '../../../lib/session-writes'
|
|
18
19
|
import {
|
|
19
20
|
buildFloorplanEntryGeometry,
|
|
20
21
|
cancelFloorplanAffordanceDrag,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
isFloorplanOpeningPlacementState,
|
|
31
32
|
resolveFloorplanHandleUnitsPerPixel,
|
|
32
33
|
siteToFloorplanTransform,
|
|
34
|
+
splitFloorplanHandles,
|
|
33
35
|
splitFloorplanOverlay,
|
|
34
36
|
subscribeFloorplanAffordanceToolCancel,
|
|
35
37
|
} from './floorplan-registry-layer'
|
|
@@ -385,7 +387,13 @@ describe('floorplan affordance cancellation', () => {
|
|
|
385
387
|
canCommit: () => true,
|
|
386
388
|
commit,
|
|
387
389
|
}
|
|
388
|
-
const
|
|
390
|
+
const revert = mock(() => {})
|
|
391
|
+
const writes = {
|
|
392
|
+
record: (run: () => unknown) => run(),
|
|
393
|
+
revert,
|
|
394
|
+
changes: () => ({ create: [], update: [], delete: [] }),
|
|
395
|
+
size: 0,
|
|
396
|
+
} as unknown as SessionWrites
|
|
389
397
|
const drag = {
|
|
390
398
|
pointerId: 7,
|
|
391
399
|
captureTarget: {
|
|
@@ -394,12 +402,11 @@ describe('floorplan affordance cancellation', () => {
|
|
|
394
402
|
} as unknown as Element,
|
|
395
403
|
handleId: 'wall_a:endpoint',
|
|
396
404
|
session,
|
|
397
|
-
|
|
405
|
+
writes,
|
|
398
406
|
historyPaused: true,
|
|
399
407
|
lastPlanPoint: [0, 0] as [number, number],
|
|
400
408
|
}
|
|
401
409
|
const dragRef = { current: drag }
|
|
402
|
-
const restoreSnapshots = mock(() => {})
|
|
403
410
|
const resumeHistory = mock(() => {})
|
|
404
411
|
const clearPreview = mock(() => {})
|
|
405
412
|
const clearSnapFeedback = mock(() => {})
|
|
@@ -410,7 +417,6 @@ describe('floorplan affordance cancellation', () => {
|
|
|
410
417
|
const unsubscribe = subscribeFloorplanAffordanceToolCancel(
|
|
411
418
|
() =>
|
|
412
419
|
cancelFloorplanAffordanceDrag(dragRef, {
|
|
413
|
-
restoreSnapshots,
|
|
414
420
|
resumeHistory,
|
|
415
421
|
clearPreview,
|
|
416
422
|
clearSnapFeedback,
|
|
@@ -429,7 +435,7 @@ describe('floorplan affordance cancellation', () => {
|
|
|
429
435
|
|
|
430
436
|
expect(dragRef.current).toBeNull()
|
|
431
437
|
expect(releasePointerCapture).toHaveBeenCalledWith(7)
|
|
432
|
-
expect(
|
|
438
|
+
expect(revert).toHaveBeenCalledTimes(1)
|
|
433
439
|
expect(resumeHistory).toHaveBeenCalledTimes(1)
|
|
434
440
|
expect(clearPreview).toHaveBeenCalledTimes(2)
|
|
435
441
|
expect(clearPreview).toHaveBeenNthCalledWith(1, 'wall_a')
|
|
@@ -450,7 +456,6 @@ describe('floorplan opening placement interaction routing', () => {
|
|
|
450
456
|
test('passes entries through only while an opening tool or moving opening is active', () => {
|
|
451
457
|
expect(
|
|
452
458
|
isFloorplanOpeningPlacementState({
|
|
453
|
-
phase: 'structure',
|
|
454
459
|
mode: 'build',
|
|
455
460
|
tool: 'window',
|
|
456
461
|
movingNodeHasWallOpeningPlacement: false,
|
|
@@ -458,7 +463,6 @@ describe('floorplan opening placement interaction routing', () => {
|
|
|
458
463
|
).toBe(true)
|
|
459
464
|
expect(
|
|
460
465
|
isFloorplanOpeningPlacementState({
|
|
461
|
-
phase: 'structure',
|
|
462
466
|
mode: 'select',
|
|
463
467
|
tool: null,
|
|
464
468
|
movingNodeHasWallOpeningPlacement: true,
|
|
@@ -466,7 +470,6 @@ describe('floorplan opening placement interaction routing', () => {
|
|
|
466
470
|
).toBe(true)
|
|
467
471
|
expect(
|
|
468
472
|
isFloorplanOpeningPlacementState({
|
|
469
|
-
phase: 'structure',
|
|
470
473
|
mode: 'select',
|
|
471
474
|
tool: null,
|
|
472
475
|
movingNodeHasWallOpeningPlacement: false,
|
|
@@ -829,3 +832,30 @@ describe('isFloorplanHierarchyVisible', () => {
|
|
|
829
832
|
expect(visibleUnder(nodes, 'level_a', 'wall_a')).toBe(true)
|
|
830
833
|
})
|
|
831
834
|
})
|
|
835
|
+
|
|
836
|
+
describe('the handles pass', () => {
|
|
837
|
+
test('grabbable handles leave the overlay for a pass of their own, under the same transform', () => {
|
|
838
|
+
const label = { kind: 'text', x: 0, y: 0, text: 'Wall 1' } as FloorplanGeometry
|
|
839
|
+
const corner = {
|
|
840
|
+
kind: 'endpoint-handle',
|
|
841
|
+
point: [1, 0],
|
|
842
|
+
state: 'idle',
|
|
843
|
+
affordance: 'move-endpoint',
|
|
844
|
+
payload: { wallId: 'wall_a', endpoint: 'end' },
|
|
845
|
+
} as FloorplanGeometry
|
|
846
|
+
const tree = {
|
|
847
|
+
kind: 'group',
|
|
848
|
+
transform: 'rotate(30)',
|
|
849
|
+
children: [label, { kind: 'group', children: [corner] }],
|
|
850
|
+
} as FloorplanGeometry
|
|
851
|
+
expect(splitFloorplanHandles(tree)).toEqual({
|
|
852
|
+
rest: { kind: 'group', transform: 'rotate(30)', children: [label] },
|
|
853
|
+
handles: {
|
|
854
|
+
kind: 'group',
|
|
855
|
+
transform: 'rotate(30)',
|
|
856
|
+
children: [{ kind: 'group', children: [corner] }],
|
|
857
|
+
},
|
|
858
|
+
} as never)
|
|
859
|
+
expect(splitFloorplanHandles(label)).toEqual({ rest: label, handles: null })
|
|
860
|
+
})
|
|
861
|
+
})
|