@pascal-app/editor 1.0.5 → 1.0.7
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 +20 -17
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +20 -8
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +72 -61
- package/src/components/editor/floating-action-menu.tsx +27 -9
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +285 -132
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +20 -0
- package/src/components/editor/index.tsx +130 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +1 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +1 -1
- package/src/components/editor/room-handles-2d.tsx +156 -6
- package/src/components/editor/room-handles.tsx +241 -51
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +29 -15
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +1 -1
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +5 -1
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +30 -14
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
- package/src/components/ui/helpers/helper-manager.tsx +82 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
- package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +8 -7
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +22 -109
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +464 -65
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +21 -12
- package/src/components/ui/panels/room-panel.tsx +206 -99
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +61 -4
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15117 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +37501 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +37501 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +37501 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +37501 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +156 -4
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +95 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/floor-footprints.test.ts +2 -1
- package/src/lib/floor-foundation-support.test.ts +7 -3
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +17 -0
- package/src/lib/measurements.ts +20 -5
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-built-on.test.ts +26 -12
- package/src/lib/room-built-on.ts +20 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +140 -0
- package/src/lib/room-handle-drag.ts +557 -42
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +37 -0
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +9 -4
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +20 -5
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +14 -5
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +42 -0
- package/src/lib/window-interaction.ts +47 -5
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +48 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-wall-snap-indicator.ts +15 -2
|
@@ -78,7 +78,7 @@ import { resolveNodeForDrawingType } from '../../../lib/floorplan/drawing-coordi
|
|
|
78
78
|
import {
|
|
79
79
|
createFloorplanContextExtensions,
|
|
80
80
|
type FloorplanAnnotationRole,
|
|
81
|
-
type
|
|
81
|
+
type FloorplanWallDimensionFaces,
|
|
82
82
|
getFloorplanNodeExtension,
|
|
83
83
|
readFloorplanGeometryMetadata,
|
|
84
84
|
withFloorplanGeometryMetadata,
|
|
@@ -86,7 +86,6 @@ import {
|
|
|
86
86
|
import {
|
|
87
87
|
type FloorplanMode,
|
|
88
88
|
resolveFloorplanAnnotationVisibility,
|
|
89
|
-
resolveFloorplanWallDimensionReference,
|
|
90
89
|
} from '../../../lib/floorplan/floorplan-mode'
|
|
91
90
|
import {
|
|
92
91
|
buildFloorplanContext,
|
|
@@ -115,6 +114,11 @@ import { createSessionWrites, type SessionWrites } from '../../../lib/session-wr
|
|
|
115
114
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
116
115
|
import { clearSurfacePlanSnapFeedback } from '../../../lib/surface-plan-snap'
|
|
117
116
|
import { paintZoneMembership } from '../../../lib/units'
|
|
117
|
+
import {
|
|
118
|
+
startWallPull,
|
|
119
|
+
WALL_PULL_AFFORDANCE,
|
|
120
|
+
type WallPullGripPayload,
|
|
121
|
+
} from '../../../lib/wall-pull'
|
|
118
122
|
import useDirectManipulationFeedback from '../../../store/use-direct-manipulation-feedback'
|
|
119
123
|
import useDrawingView from '../../../store/use-drawing-view'
|
|
120
124
|
import useEditor, { isAngleSnapActive, type Mode } from '../../../store/use-editor'
|
|
@@ -130,7 +134,12 @@ import { isViewVisible, VIEW_2D } from '../../../store/view-layout'
|
|
|
130
134
|
import { FloorRegionControls2D } from '../../editor/floor-region-controls'
|
|
131
135
|
import { startGroupPickUp } from '../../editor/group-actions'
|
|
132
136
|
import { classifyParticipant } from '../../editor/group-transform-shared'
|
|
133
|
-
import {
|
|
137
|
+
import {
|
|
138
|
+
RoomHandleDragPreview2D,
|
|
139
|
+
RoomHandles2D,
|
|
140
|
+
SelectedWallDimensions2D,
|
|
141
|
+
useSelectionDimensionNodes,
|
|
142
|
+
} from '../../editor/room-handles-2d'
|
|
134
143
|
import { RoomHighlight2D } from '../../editor/room-highlight'
|
|
135
144
|
import { RoomLabels2D } from '../../editor/room-labels'
|
|
136
145
|
import { suppressBoxSelectForPointer } from '../../tools/select/box-select-state'
|
|
@@ -150,7 +159,10 @@ import {
|
|
|
150
159
|
resolveSvgAnnotationCollisions,
|
|
151
160
|
svgAnnotationLabelId,
|
|
152
161
|
} from './floorplan-annotation-layout'
|
|
153
|
-
import {
|
|
162
|
+
import {
|
|
163
|
+
FloorplanDimensionEditOverlay,
|
|
164
|
+
typeableDimensionAt,
|
|
165
|
+
} from './floorplan-dimension-edit-overlay'
|
|
154
166
|
import { FloorplanDimensionRenderer } from './floorplan-dimension-renderer'
|
|
155
167
|
import { FloorplanGeometryRenderer } from './floorplan-geometry-renderer'
|
|
156
168
|
import {
|
|
@@ -199,6 +211,15 @@ const DIRECT_DRAG_THRESHOLD_PX = 4
|
|
|
199
211
|
const DIRECT_ROTATE_EPSILON = 1e-6
|
|
200
212
|
const DIRECT_ROTATE_RADIANS_PER_PIXEL = Math.PI / 180
|
|
201
213
|
const MAX_HANDLE_UNITS_PER_PIXEL = 0.015
|
|
214
|
+
// A wall end's pull grip, in screen pixels out from the end along the wall:
|
|
215
|
+
// clear of the end's move grip (its hit ring reaches ~17 px), a short dashed
|
|
216
|
+
// leader, then a round button with an outward arrow.
|
|
217
|
+
const PULL_GRIP_LEADER_START_PX = 9
|
|
218
|
+
const PULL_GRIP_CENTER_PX = 30
|
|
219
|
+
const PULL_GRIP_RADIUS_PX = 10
|
|
220
|
+
const PULL_GRIP_HIT_RADIUS_PX = 13
|
|
221
|
+
const PULL_GRIP_FILL = '#10b981' // emerald-500
|
|
222
|
+
const PULL_GRIP_HOVER_FILL = '#059669' // emerald-600
|
|
202
223
|
|
|
203
224
|
export function resolveFloorplanHandleUnitsPerPixel(unitsPerPixel: number): number {
|
|
204
225
|
return Math.min(unitsPerPixel, MAX_HANDLE_UNITS_PER_PIXEL)
|
|
@@ -387,7 +408,7 @@ type NodeDeps = {
|
|
|
387
408
|
live: LiveTransform | undefined
|
|
388
409
|
unit: 'metric' | 'imperial'
|
|
389
410
|
metricNotation: 'meters' | 'millimeters'
|
|
390
|
-
|
|
411
|
+
wallDimensionFaces?: FloorplanWallDimensionFaces
|
|
391
412
|
selected: boolean
|
|
392
413
|
highlighted: boolean
|
|
393
414
|
hovered: boolean
|
|
@@ -497,6 +518,20 @@ export function floorplanEntryYieldsToTool(state: {
|
|
|
497
518
|
return state.mode === 'build' || state.openingPlacement
|
|
498
519
|
}
|
|
499
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Whether an entry draws its node without edit handles or dimension chrome: a member of a
|
|
523
|
+
* multi-selection, a ceiling outside its edit session, or anything while a build tool is armed (a
|
|
524
|
+
* selected roof keeps its selection for a new plane to join it, but a press near its eave must
|
|
525
|
+
* draw, not resize it).
|
|
526
|
+
*/
|
|
527
|
+
export function floorplanEntryStripsHandles(state: {
|
|
528
|
+
mode: Mode
|
|
529
|
+
multiSelected: boolean
|
|
530
|
+
ceilingWithoutSession: boolean
|
|
531
|
+
}): boolean {
|
|
532
|
+
return state.multiSelected || state.ceilingWithoutSession || state.mode === 'build'
|
|
533
|
+
}
|
|
534
|
+
|
|
500
535
|
function floorplanEntryYieldsToToolNow(): boolean {
|
|
501
536
|
const { mode, tool } = useEditor.getState()
|
|
502
537
|
const movingNode = getMovingNode()
|
|
@@ -596,6 +631,11 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
596
631
|
// `handleClickStop` / `handleSelect`, so the tool never sees it. Pass
|
|
597
632
|
// clicks through in that case (`floorplanEntryYieldsToToolNow`).
|
|
598
633
|
const editorMode = useEditor((s) => s.mode)
|
|
634
|
+
const selectionDimensionNodes = useSelectionDimensionNodes()
|
|
635
|
+
const selectionDimensionIds = useMemo(
|
|
636
|
+
() => new Set(selectionDimensionNodes.map((node) => node.id)),
|
|
637
|
+
[selectionDimensionNodes],
|
|
638
|
+
)
|
|
599
639
|
const floorplanSelectionTool = useEditor((s) => s.floorplanSelectionTool)
|
|
600
640
|
const endpointReshape = useEndpointReshape()
|
|
601
641
|
const isMarqueeSelectionActive =
|
|
@@ -609,12 +649,9 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
609
649
|
const floorplanVisible = useEditor((s) => isViewVisible(s, VIEW_2D))
|
|
610
650
|
const drawingType = useDrawingView((s) => s.drawingType)
|
|
611
651
|
const annotationVisibility = useFloorplanAnnotationVisibility((s) => s.visibility)
|
|
612
|
-
const
|
|
652
|
+
const showDimensions = useFloorplanAnnotationVisibility((s) => s.showDimensions)
|
|
653
|
+
const wallDimensionFaces = useFloorplanAnnotationVisibility((s) => s.wallDimensionFaces)
|
|
613
654
|
const floorplanMode = useFloorplanMode((s) => s.mode)
|
|
614
|
-
const effectiveWallDimensionReference = resolveFloorplanWallDimensionReference(
|
|
615
|
-
floorplanMode,
|
|
616
|
-
wallDimensionReference,
|
|
617
|
-
)
|
|
618
655
|
// Elevator builders read runtime state imperatively, so entries include this
|
|
619
656
|
// rare-changing ref in their cache deps.
|
|
620
657
|
const interactiveElevators = useInteractive((s) => s.elevators)
|
|
@@ -981,6 +1018,12 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
981
1018
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return false
|
|
982
1019
|
if (movingNode) return false
|
|
983
1020
|
if (useEditor.getState().mode === 'delete') return false
|
|
1021
|
+
// A press on a member's dimension value is the type-a-value box's: its click opens it.
|
|
1022
|
+
if (!immediate && typeableDimensionAt(event.target, event.clientX, event.clientY)) {
|
|
1023
|
+
event.stopPropagation()
|
|
1024
|
+
suppressBoxSelectForPointer(event)
|
|
1025
|
+
return true
|
|
1026
|
+
}
|
|
984
1027
|
const started = startFloorplanGroupMove(id, event, {
|
|
985
1028
|
immediate,
|
|
986
1029
|
onClickFallthrough: immediate ? undefined : () => startGroupPickUp(),
|
|
@@ -1299,6 +1342,16 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1299
1342
|
runFloorplanWallPush(payload, event.clientX, event.clientY)
|
|
1300
1343
|
return
|
|
1301
1344
|
}
|
|
1345
|
+
// A wall end's pull grip opens the Walls run at that end; the plan's
|
|
1346
|
+
// wall draft then follows the pointer and the release places the wall.
|
|
1347
|
+
if (affordance === WALL_PULL_AFFORDANCE) {
|
|
1348
|
+
event.preventDefault()
|
|
1349
|
+
event.stopPropagation()
|
|
1350
|
+
suppressBoxSelectForPointer(event)
|
|
1351
|
+
const grip = payload as WallPullGripPayload
|
|
1352
|
+
startWallPull({ wallId: grip.wallId, endpoint: grip.endpoint, origin: '2d' })
|
|
1353
|
+
return
|
|
1354
|
+
}
|
|
1302
1355
|
|
|
1303
1356
|
const sceneNodes = useScene.getState().nodes
|
|
1304
1357
|
const node = sceneNodes[nodeId]
|
|
@@ -1590,6 +1643,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1590
1643
|
<FloorplanRegistryEntry
|
|
1591
1644
|
activeDragId={handleIdForNode(activeDragId, entry.id)}
|
|
1592
1645
|
annotationVisibility={annotationVisibility}
|
|
1646
|
+
showDimensions={showDimensions}
|
|
1593
1647
|
floorplanMode={floorplanMode}
|
|
1594
1648
|
floorplanVisible={floorplanVisible}
|
|
1595
1649
|
geometryCacheRef={geometryCacheRef}
|
|
@@ -1597,6 +1651,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1597
1651
|
hoveredHandleId={handleIdForNode(hoveredHandleId, entry.id)}
|
|
1598
1652
|
interactiveElevators={interactiveElevators}
|
|
1599
1653
|
isMarqueeSelectionActive={isMarqueeSelectionActive}
|
|
1654
|
+
keepsSelectionDimensions={selectionDimensionIds.has(entry.id)}
|
|
1600
1655
|
key={`${pass}-${entry.id}`}
|
|
1601
1656
|
levelDataCacheRef={levelDataCacheRef}
|
|
1602
1657
|
levelNodeIdsByType={floorplanData.levelNodeIdsByType}
|
|
@@ -1619,7 +1674,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1619
1674
|
siblingEpoch={entry.dependsOnSiblingInputs ? (siblingEpochs.get(entry.id) ?? 0) : 0}
|
|
1620
1675
|
unit={unit}
|
|
1621
1676
|
metricNotation={metricNotation}
|
|
1622
|
-
|
|
1677
|
+
wallDimensionFaces={wallDimensionFaces}
|
|
1623
1678
|
visibilityRootId={
|
|
1624
1679
|
entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId))
|
|
1625
1680
|
}
|
|
@@ -1669,6 +1724,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
|
|
|
1669
1724
|
<g className="floorplan-registry-handles">{renderEntries('handles')}</g>
|
|
1670
1725
|
<RoomHandles2D levelId={levelId} />
|
|
1671
1726
|
<RoomHandleDragPreview2D levelId={levelId} />
|
|
1727
|
+
<SelectedWallDimensions2D levelId={levelId} />
|
|
1672
1728
|
<FloorplanAnnotationLayoutResolver active={floorplanVisible} />
|
|
1673
1729
|
{/* Click-to-type dimension labels. Portals its HTML input to
|
|
1674
1730
|
document.body, so mounting it inside the SVG tree is safe. */}
|
|
@@ -1707,9 +1763,8 @@ function FloorplanAnnotationLayoutResolver({ active }: { active: boolean }) {
|
|
|
1707
1763
|
const drawingType = useDrawingView((state) => state.drawingType)
|
|
1708
1764
|
const annotationLayoutOverrides = useDrawingView((state) => state.annotationLayoutOverrides)
|
|
1709
1765
|
const annotationVisibility = useFloorplanAnnotationVisibility((state) => state.visibility)
|
|
1710
|
-
const
|
|
1711
|
-
|
|
1712
|
-
)
|
|
1766
|
+
const showDimensions = useFloorplanAnnotationVisibility((state) => state.showDimensions)
|
|
1767
|
+
const wallDimensionFaces = useFloorplanAnnotationVisibility((state) => state.wallDimensionFaces)
|
|
1713
1768
|
const floorplanMode = useFloorplanMode((state) => state.mode)
|
|
1714
1769
|
const layoutInputs = useMemo(
|
|
1715
1770
|
() => ({
|
|
@@ -1718,7 +1773,8 @@ function FloorplanAnnotationLayoutResolver({ active }: { active: boolean }) {
|
|
|
1718
1773
|
drawingType,
|
|
1719
1774
|
interactionIdle,
|
|
1720
1775
|
settledLayoutEpoch,
|
|
1721
|
-
|
|
1776
|
+
showDimensions,
|
|
1777
|
+
wallDimensionFaces,
|
|
1722
1778
|
floorplanMode,
|
|
1723
1779
|
}),
|
|
1724
1780
|
[
|
|
@@ -1727,7 +1783,8 @@ function FloorplanAnnotationLayoutResolver({ active }: { active: boolean }) {
|
|
|
1727
1783
|
drawingType,
|
|
1728
1784
|
interactionIdle,
|
|
1729
1785
|
settledLayoutEpoch,
|
|
1730
|
-
|
|
1786
|
+
showDimensions,
|
|
1787
|
+
wallDimensionFaces,
|
|
1731
1788
|
floorplanMode,
|
|
1732
1789
|
],
|
|
1733
1790
|
)
|
|
@@ -1955,6 +2012,7 @@ function readFloorplanAnnotationLayoutOffset(label: SVGGElement) {
|
|
|
1955
2012
|
type FloorplanRegistryEntryProps = {
|
|
1956
2013
|
activeDragId: string | null
|
|
1957
2014
|
annotationVisibility: FloorplanAnnotationVisibility
|
|
2015
|
+
showDimensions: boolean
|
|
1958
2016
|
floorplanMode: FloorplanMode
|
|
1959
2017
|
ctxOverrides: FloorplanContextOverrides | undefined
|
|
1960
2018
|
floorplanVisible: boolean
|
|
@@ -1963,6 +2021,8 @@ type FloorplanRegistryEntryProps = {
|
|
|
1963
2021
|
hoveredHandleId: string | null
|
|
1964
2022
|
interactiveElevators: unknown
|
|
1965
2023
|
isMarqueeSelectionActive: boolean
|
|
2024
|
+
/** A wall in a multi-selection still shows its own length (`useSelectionDimensionNodes`). */
|
|
2025
|
+
keepsSelectionDimensions: boolean
|
|
1966
2026
|
levelDataCacheRef: { current: Map<string, LevelDataCacheEntry> }
|
|
1967
2027
|
levelNodeIdsByType: ReadonlyMap<string, readonly AnyNodeId[]>
|
|
1968
2028
|
moving: boolean
|
|
@@ -1996,13 +2056,14 @@ type FloorplanRegistryEntryProps = {
|
|
|
1996
2056
|
siblingEpoch: number
|
|
1997
2057
|
unit: 'metric' | 'imperial'
|
|
1998
2058
|
metricNotation: 'meters' | 'millimeters'
|
|
1999
|
-
|
|
2059
|
+
wallDimensionFaces: FloorplanWallDimensionFaces
|
|
2000
2060
|
visibilityRootId: AnyNodeId | undefined
|
|
2001
2061
|
}
|
|
2002
2062
|
|
|
2003
2063
|
const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
2004
2064
|
activeDragId,
|
|
2005
2065
|
annotationVisibility,
|
|
2066
|
+
showDimensions,
|
|
2006
2067
|
floorplanMode,
|
|
2007
2068
|
ctxOverrides,
|
|
2008
2069
|
floorplanVisible,
|
|
@@ -2011,6 +2072,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2011
2072
|
hoveredHandleId,
|
|
2012
2073
|
interactiveElevators,
|
|
2013
2074
|
isMarqueeSelectionActive,
|
|
2075
|
+
keepsSelectionDimensions,
|
|
2014
2076
|
levelDataCacheRef,
|
|
2015
2077
|
levelNodeIdsByType,
|
|
2016
2078
|
moving,
|
|
@@ -2032,7 +2094,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2032
2094
|
siblingEpoch,
|
|
2033
2095
|
unit,
|
|
2034
2096
|
metricNotation,
|
|
2035
|
-
|
|
2097
|
+
wallDimensionFaces,
|
|
2036
2098
|
visibilityRootId,
|
|
2037
2099
|
}: FloorplanRegistryEntryProps): React.ReactElement | null {
|
|
2038
2100
|
// A node an agent just built is drawn on its construction-reveal turn, as in 3D.
|
|
@@ -2049,7 +2111,12 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2049
2111
|
const ceilingWithoutSession = useCeilingEditSession(
|
|
2050
2112
|
(state) => node.type === 'ceiling' && state.session?.ceilingId !== nodeId,
|
|
2051
2113
|
)
|
|
2052
|
-
const
|
|
2114
|
+
const mode = useEditor((state) => state.mode)
|
|
2115
|
+
const suppressHandles = floorplanEntryStripsHandles({
|
|
2116
|
+
mode,
|
|
2117
|
+
multiSelected,
|
|
2118
|
+
ceilingWithoutSession,
|
|
2119
|
+
})
|
|
2053
2120
|
const selectedLevelId = useViewer((state) => state.selection.levelId)
|
|
2054
2121
|
const selectionProxyId = resolveSelectionProxyId(
|
|
2055
2122
|
node,
|
|
@@ -2089,6 +2156,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2089
2156
|
referencedAnnotationRole,
|
|
2090
2157
|
selected,
|
|
2091
2158
|
target: 'editor',
|
|
2159
|
+
dimensions: showDimensions,
|
|
2092
2160
|
},
|
|
2093
2161
|
)
|
|
2094
2162
|
|
|
@@ -2220,7 +2288,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2220
2288
|
siblingEpoch,
|
|
2221
2289
|
unit,
|
|
2222
2290
|
metricNotation,
|
|
2223
|
-
|
|
2291
|
+
wallDimensionFaces,
|
|
2224
2292
|
visibilityRootId,
|
|
2225
2293
|
})
|
|
2226
2294
|
const rawGeometry = cacheEntry ? cacheEntry[pass] : null
|
|
@@ -2229,10 +2297,10 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2229
2297
|
: null
|
|
2230
2298
|
// Multi-selection shows highlight only: strip this member's edit handles /
|
|
2231
2299
|
// dimension chrome (all of which live in the overlay pass) while keeping
|
|
2232
|
-
// its highlighted body geometry.
|
|
2300
|
+
// its highlighted body geometry and, for a wall, its own length.
|
|
2233
2301
|
const geometry =
|
|
2234
2302
|
visibleGeometry && suppressHandles && pass !== 'base'
|
|
2235
|
-
? stripHandleChrome(visibleGeometry)
|
|
2303
|
+
? stripHandleChrome(visibleGeometry, keepsSelectionDimensions)
|
|
2236
2304
|
: visibleGeometry
|
|
2237
2305
|
useFloorplanReveal(nodeId, entryRef, cacheEntry, revealWaiting, revealActive)
|
|
2238
2306
|
if (revealWaiting || !geometry) return null
|
|
@@ -2293,7 +2361,7 @@ type BuildFloorplanEntryGeometryArgs = {
|
|
|
2293
2361
|
siblingEpoch: number
|
|
2294
2362
|
unit: 'metric' | 'imperial'
|
|
2295
2363
|
metricNotation: 'meters' | 'millimeters'
|
|
2296
|
-
|
|
2364
|
+
wallDimensionFaces?: FloorplanWallDimensionFaces
|
|
2297
2365
|
visibilityRootId: AnyNodeId | undefined
|
|
2298
2366
|
}
|
|
2299
2367
|
|
|
@@ -2353,7 +2421,7 @@ export function buildFloorplanEntryGeometry({
|
|
|
2353
2421
|
siblingEpoch,
|
|
2354
2422
|
unit,
|
|
2355
2423
|
metricNotation,
|
|
2356
|
-
|
|
2424
|
+
wallDimensionFaces,
|
|
2357
2425
|
visibilityRootId,
|
|
2358
2426
|
}: BuildFloorplanEntryGeometryArgs): CacheEntry | null {
|
|
2359
2427
|
const def = nodeRegistry.get(node.type)
|
|
@@ -2381,7 +2449,7 @@ export function buildFloorplanEntryGeometry({
|
|
|
2381
2449
|
live,
|
|
2382
2450
|
unit,
|
|
2383
2451
|
metricNotation,
|
|
2384
|
-
|
|
2452
|
+
wallDimensionFaces,
|
|
2385
2453
|
selected,
|
|
2386
2454
|
highlighted,
|
|
2387
2455
|
hovered,
|
|
@@ -2468,7 +2536,7 @@ export function buildFloorplanEntryGeometry({
|
|
|
2468
2536
|
unit,
|
|
2469
2537
|
metricNotation,
|
|
2470
2538
|
roomLabelOverlay: true,
|
|
2471
|
-
|
|
2539
|
+
wallDimensionFaces,
|
|
2472
2540
|
highlighted,
|
|
2473
2541
|
hovered,
|
|
2474
2542
|
moving,
|
|
@@ -2494,7 +2562,7 @@ export function buildFloorplanEntryGeometry({
|
|
|
2494
2562
|
metricNotation,
|
|
2495
2563
|
purpose: 'edit',
|
|
2496
2564
|
roomLabelOverlay: true,
|
|
2497
|
-
|
|
2565
|
+
wallDimensionFaces,
|
|
2498
2566
|
}),
|
|
2499
2567
|
viewState: palette
|
|
2500
2568
|
? {
|
|
@@ -2642,6 +2710,87 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2642
2710
|
|
|
2643
2711
|
return renderInteractive(geometry, 0)
|
|
2644
2712
|
|
|
2713
|
+
/**
|
|
2714
|
+
* A wall end's "pull to continue" grip: screen-sized, just past the end
|
|
2715
|
+
* along the wall, emerald so it reads apart from the end's move grip.
|
|
2716
|
+
*/
|
|
2717
|
+
function renderPullGrip(
|
|
2718
|
+
g: Extract<FloorplanGeometry, { kind: 'move-arrow' }>,
|
|
2719
|
+
keyHint: number,
|
|
2720
|
+
): React.ReactElement {
|
|
2721
|
+
const handleId = `${makeHandleId(nodeId, g.payload)}:pull`
|
|
2722
|
+
const isHovered = hoveredHandleId === handleId
|
|
2723
|
+
const k = handleUnitsPerPixel
|
|
2724
|
+
const c = PULL_GRIP_CENTER_PX * k
|
|
2725
|
+
const r = PULL_GRIP_RADIUS_PX * (isHovered ? 1.12 : 1) * k
|
|
2726
|
+
// Outward arrow inside the button: shaft then head, tip along +x.
|
|
2727
|
+
const arrowD = [
|
|
2728
|
+
`M ${c - 5.5 * k},${-1.4 * k}`,
|
|
2729
|
+
`L ${c + 0.5 * k},${-1.4 * k}`,
|
|
2730
|
+
`L ${c + 0.5 * k},${-4.6 * k}`,
|
|
2731
|
+
`L ${c + 6 * k},0`,
|
|
2732
|
+
`L ${c + 0.5 * k},${4.6 * k}`,
|
|
2733
|
+
`L ${c + 0.5 * k},${1.4 * k}`,
|
|
2734
|
+
`L ${c - 5.5 * k},${1.4 * k}`,
|
|
2735
|
+
'Z',
|
|
2736
|
+
].join(' ')
|
|
2737
|
+
const affordance = g.affordance
|
|
2738
|
+
const payload = g.payload
|
|
2739
|
+
return (
|
|
2740
|
+
<g
|
|
2741
|
+
data-wall-pull-grip={
|
|
2742
|
+
typeof payload === 'object' && payload && 'endpoint' in payload
|
|
2743
|
+
? String((payload as { endpoint: unknown }).endpoint)
|
|
2744
|
+
: ''
|
|
2745
|
+
}
|
|
2746
|
+
key={keyHint}
|
|
2747
|
+
onClick={(e) => e.stopPropagation()}
|
|
2748
|
+
transform={`translate(${g.point[0]} ${g.point[1]}) rotate(${(g.angle * 180) / Math.PI})`}
|
|
2749
|
+
>
|
|
2750
|
+
<line
|
|
2751
|
+
pointerEvents="none"
|
|
2752
|
+
stroke={PULL_GRIP_FILL}
|
|
2753
|
+
strokeDasharray={`${3 * k} ${2.5 * k}`}
|
|
2754
|
+
strokeLinecap="round"
|
|
2755
|
+
strokeWidth={1.5 * k}
|
|
2756
|
+
x1={PULL_GRIP_LEADER_START_PX * k}
|
|
2757
|
+
x2={c - r}
|
|
2758
|
+
y1={0}
|
|
2759
|
+
y2={0}
|
|
2760
|
+
/>
|
|
2761
|
+
<circle
|
|
2762
|
+
cx={c}
|
|
2763
|
+
cy={0}
|
|
2764
|
+
fill={isHovered ? PULL_GRIP_HOVER_FILL : PULL_GRIP_FILL}
|
|
2765
|
+
pointerEvents="none"
|
|
2766
|
+
r={r}
|
|
2767
|
+
stroke="#ffffff"
|
|
2768
|
+
strokeWidth={1.5 * k}
|
|
2769
|
+
/>
|
|
2770
|
+
<path d={arrowD} fill="#ffffff" pointerEvents="none" />
|
|
2771
|
+
<circle
|
|
2772
|
+
cx={c}
|
|
2773
|
+
cy={0}
|
|
2774
|
+
fill="transparent"
|
|
2775
|
+
onPointerDown={(e) => {
|
|
2776
|
+
if (affordance) {
|
|
2777
|
+
onHandlePointerDown(affordance, payload, e as ReactPointerEvent<SVGGElement>)
|
|
2778
|
+
}
|
|
2779
|
+
}}
|
|
2780
|
+
onPointerEnter={() => onHandleHoverChange(handleId)}
|
|
2781
|
+
onPointerLeave={() => onHandleHoverChange(null)}
|
|
2782
|
+
// Fill only: the default stroke is a plan metre wide, and would take
|
|
2783
|
+
// the press meant for the end's move grip.
|
|
2784
|
+
pointerEvents="fill"
|
|
2785
|
+
r={PULL_GRIP_HIT_RADIUS_PX * k}
|
|
2786
|
+
style={{ cursor: 'grab' }}
|
|
2787
|
+
>
|
|
2788
|
+
{g.title ? <title>{g.title}</title> : null}
|
|
2789
|
+
</circle>
|
|
2790
|
+
</g>
|
|
2791
|
+
)
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2645
2794
|
function renderInteractive(g: FloorplanGeometry, keyHint: number): React.ReactElement {
|
|
2646
2795
|
switch (g.kind) {
|
|
2647
2796
|
case 'group': {
|
|
@@ -2914,7 +3063,8 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2914
3063
|
const head1 = `M ${t1x} ${t1y} L ${b1ax} ${b1ay} L ${b1bx} ${b1by} Z`
|
|
2915
3064
|
const head2 = `M ${t2x} ${t2y} L ${b2ax} ${b2ay} L ${b2bx} ${b2by} Z`
|
|
2916
3065
|
const fill = isHovered ? '#a5b4fc' : '#8381ed'
|
|
2917
|
-
const
|
|
3066
|
+
const scale = g.scale ?? 1
|
|
3067
|
+
const strokeWidthPx = (isHovered ? 2.4 : 1.8) * Math.sqrt(scale)
|
|
2918
3068
|
const angleDeg = (g.angle * 180) / Math.PI
|
|
2919
3069
|
const affordance = g.affordance
|
|
2920
3070
|
const payload = g.payload
|
|
@@ -2923,7 +3073,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2923
3073
|
<g
|
|
2924
3074
|
key={keyHint}
|
|
2925
3075
|
onClick={(e) => e.stopPropagation()}
|
|
2926
|
-
transform={`translate(${g.point[0]} ${g.point[1]}) rotate(${angleDeg})`}
|
|
3076
|
+
transform={`translate(${g.point[0]} ${g.point[1]}) rotate(${angleDeg}) scale(${scale})`}
|
|
2927
3077
|
>
|
|
2928
3078
|
<path
|
|
2929
3079
|
d={arcPath}
|
|
@@ -2978,6 +3128,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2978
3128
|
}
|
|
2979
3129
|
case 'move-arrow': {
|
|
2980
3130
|
if (!palette) return <></>
|
|
3131
|
+
if (g.variant === 'pull') return renderPullGrip(g, keyHint)
|
|
2981
3132
|
// Affordance-routed arrows (door width-resize) get a per-payload
|
|
2982
3133
|
// handle id so each side can hover independently; default
|
|
2983
3134
|
// (move-flow) arrows share the node's :move id like the dot.
|
|
@@ -2987,13 +3138,14 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2987
3138
|
// so it shrinks on zoom-out and grows on zoom-in, matching the
|
|
2988
3139
|
// wall it accompanies. Composed of a rectangular shaft + triangular
|
|
2989
3140
|
// head, drawn as a single path for a clean fill + stroke outline.
|
|
2990
|
-
const
|
|
2991
|
-
const
|
|
2992
|
-
const
|
|
2993
|
-
const
|
|
3141
|
+
const k = g.scale ?? 1
|
|
3142
|
+
const sl = 0.1 * k // shaft length (shortened body)
|
|
3143
|
+
const hl = 0.12 * k // head length
|
|
3144
|
+
const sh = 0.04 * k // shaft half-height
|
|
3145
|
+
const hh = 0.1 * k // head half-height
|
|
2994
3146
|
// Inset the shaft start so the arrow sits a little off the wall
|
|
2995
3147
|
// body (matches the 3D `HANDLE_OFFSET`).
|
|
2996
|
-
const bi = 0.03 // base inset
|
|
3148
|
+
const bi = 0.03 * k // base inset
|
|
2997
3149
|
const arrowD = `M ${bi},${-sh} L ${bi + sl},${-sh} L ${bi + sl},${-hh} L ${bi + sl + hl},0 L ${bi + sl},${hh} L ${bi + sl},${sh} L ${bi},${sh} Z`
|
|
2998
3150
|
// Indigo palette to match the 3D wall push arrows
|
|
2999
3151
|
// (`ARROW_COLOR` / `ARROW_HOVER_COLOR`) and the corner-sphere
|
|
@@ -3642,11 +3794,19 @@ const HANDLE_CHROME_KINDS = new Set<FloorplanGeometry['kind']>([
|
|
|
3642
3794
|
'equal-spacing-badge',
|
|
3643
3795
|
])
|
|
3644
3796
|
|
|
3645
|
-
function stripHandleChrome(
|
|
3797
|
+
function stripHandleChrome(
|
|
3798
|
+
g: FloorplanGeometry,
|
|
3799
|
+
keepContextualDimensions = false,
|
|
3800
|
+
): FloorplanGeometry | null {
|
|
3801
|
+
if (
|
|
3802
|
+
keepContextualDimensions &&
|
|
3803
|
+
readFloorplanGeometryMetadata(g).annotationRole === 'contextual-dimension'
|
|
3804
|
+
)
|
|
3805
|
+
return g
|
|
3646
3806
|
if (HANDLE_CHROME_KINDS.has(g.kind)) return null
|
|
3647
3807
|
if (g.kind === 'group') {
|
|
3648
3808
|
const children = g.children
|
|
3649
|
-
.map(stripHandleChrome)
|
|
3809
|
+
.map((child) => stripHandleChrome(child, keepContextualDimensions))
|
|
3650
3810
|
.filter((c): c is FloorplanGeometry => c !== null)
|
|
3651
3811
|
if (children.length === 0) return null
|
|
3652
3812
|
return { kind: 'group', children, transform: g.transform }
|
|
@@ -3763,7 +3923,7 @@ function nodeDepsEqual(a: NodeDeps, b: NodeDeps): boolean {
|
|
|
3763
3923
|
'live',
|
|
3764
3924
|
'unit',
|
|
3765
3925
|
'metricNotation',
|
|
3766
|
-
'
|
|
3926
|
+
'wallDimensionFaces',
|
|
3767
3927
|
'selected',
|
|
3768
3928
|
'highlighted',
|
|
3769
3929
|
'hovered',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import { type AnyNodeId, useLiveNodeOverrides, useScene } from '@pascal-app/core'
|
|
2
|
+
import { type AnyNodeId, useLiveNodeOverrides, usePascalI18n, useScene } from '@pascal-app/core'
|
|
3
3
|
import {
|
|
4
4
|
ProceduralItemNode,
|
|
5
5
|
snapParameters,
|
|
@@ -22,6 +22,7 @@ export function ProceduralInspector({
|
|
|
22
22
|
partId: string | null
|
|
23
23
|
onPartChange: (id: string | null) => void
|
|
24
24
|
}) {
|
|
25
|
+
const i18n = usePascalI18n()
|
|
25
26
|
const committed = useScene((s) => s.nodes[nodeId as AnyNodeId]) as unknown as
|
|
26
27
|
| ProceduralItemNode
|
|
27
28
|
| undefined
|
|
@@ -42,14 +43,24 @@ export function ProceduralInspector({
|
|
|
42
43
|
const parameters = commit ? snapParameters(node.recipe, values) : values
|
|
43
44
|
const parsed = ProceduralItemNode.safeParse({ ...node, parameters })
|
|
44
45
|
if (!parsed.success) {
|
|
45
|
-
setError(
|
|
46
|
+
setError(
|
|
47
|
+
parsed.error.issues[0]?.message ??
|
|
48
|
+
i18n._({ id: 'editor.proceduralItems.inspector.invalidEdit', message: 'Invalid edit' }),
|
|
49
|
+
)
|
|
46
50
|
if (commit) useLiveNodeOverrides.getState().clear(nodeId as AnyNodeId)
|
|
47
51
|
return
|
|
48
52
|
}
|
|
49
53
|
try {
|
|
50
54
|
validateProceduralRelations({ ...node, parameters }, useScene.getState().nodes)
|
|
51
55
|
} catch (e) {
|
|
52
|
-
setError(
|
|
56
|
+
setError(
|
|
57
|
+
e instanceof Error
|
|
58
|
+
? e.message
|
|
59
|
+
: i18n._({
|
|
60
|
+
id: 'editor.proceduralItems.inspector.invalidAttachment',
|
|
61
|
+
message: 'Invalid attachment',
|
|
62
|
+
}),
|
|
63
|
+
)
|
|
53
64
|
if (commit) useLiveNodeOverrides.getState().clear(nodeId as AnyNodeId)
|
|
54
65
|
return
|
|
55
66
|
}
|
|
@@ -64,7 +75,12 @@ export function ProceduralInspector({
|
|
|
64
75
|
<div className="space-y-5">
|
|
65
76
|
<div className="flex items-center justify-between">
|
|
66
77
|
<h2 className="font-semibold text-lg">
|
|
67
|
-
{partId
|
|
78
|
+
{partId
|
|
79
|
+
? node.recipe.parts.find((p) => p.id === partId)?.label
|
|
80
|
+
: i18n._({
|
|
81
|
+
id: 'editor.proceduralItems.inspector.adjustDesign',
|
|
82
|
+
message: 'Adjust design',
|
|
83
|
+
})}
|
|
68
84
|
</h2>
|
|
69
85
|
{partId && (
|
|
70
86
|
<button
|
|
@@ -72,7 +88,7 @@ export function ProceduralInspector({
|
|
|
72
88
|
onClick={() => onPartChange(null)}
|
|
73
89
|
type="button"
|
|
74
90
|
>
|
|
75
|
-
Back
|
|
91
|
+
{i18n._({ id: 'editor.proceduralItems.inspector.back', message: 'Back' })}
|
|
76
92
|
</button>
|
|
77
93
|
)}
|
|
78
94
|
</div>
|
|
@@ -128,11 +144,21 @@ export function ProceduralInspector({
|
|
|
128
144
|
useScene.getState().updateNode(nodeId as AnyNodeId, { parameters } as never)
|
|
129
145
|
setError('')
|
|
130
146
|
} catch (e) {
|
|
131
|
-
setError(
|
|
147
|
+
setError(
|
|
148
|
+
e instanceof Error
|
|
149
|
+
? e.message
|
|
150
|
+
: i18n._({
|
|
151
|
+
id: 'editor.proceduralItems.inspector.invalidReset',
|
|
152
|
+
message: 'Invalid reset',
|
|
153
|
+
}),
|
|
154
|
+
)
|
|
132
155
|
}
|
|
133
156
|
}}
|
|
134
157
|
>
|
|
135
|
-
|
|
158
|
+
{i18n._({
|
|
159
|
+
id: 'editor.proceduralItems.inspector.resetThisPart',
|
|
160
|
+
message: 'Reset this part',
|
|
161
|
+
})}
|
|
136
162
|
</button>
|
|
137
163
|
)}
|
|
138
164
|
{error && (
|
|
@@ -142,7 +168,9 @@ export function ProceduralInspector({
|
|
|
142
168
|
)}
|
|
143
169
|
{!partId && node.recipe.parameters.some((p) => p.part) && (
|
|
144
170
|
<div>
|
|
145
|
-
<h3 className="mb-2 font-medium text-sm">
|
|
171
|
+
<h3 className="mb-2 font-medium text-sm">
|
|
172
|
+
{i18n._({ id: 'editor.proceduralItems.inspector.editParts', message: 'Edit parts' })}
|
|
173
|
+
</h3>
|
|
146
174
|
<div className="flex flex-wrap gap-2">
|
|
147
175
|
{node.recipe.parts
|
|
148
176
|
.filter((part) => node.recipe.parameters.some((p) => p.part === part.id))
|
|
@@ -188,13 +216,21 @@ function ProceduralPanel({ node }: { node: ProceduralItemNode }) {
|
|
|
188
216
|
}
|
|
189
217
|
|
|
190
218
|
function ProceduralPlacementControls({ node }: { node: ProceduralItemNode }) {
|
|
219
|
+
const i18n = usePascalI18n()
|
|
191
220
|
const [error, setError] = useState('')
|
|
192
221
|
const update = (patch: Partial<ProceduralItemNode>) => {
|
|
193
222
|
try {
|
|
194
223
|
useScene.getState().updateNode(node.id as AnyNodeId, patch as never)
|
|
195
224
|
setError('')
|
|
196
225
|
} catch (e) {
|
|
197
|
-
setError(
|
|
226
|
+
setError(
|
|
227
|
+
e instanceof Error
|
|
228
|
+
? e.message
|
|
229
|
+
: i18n._({
|
|
230
|
+
id: 'editor.proceduralItems.inspector.invalidPlacement',
|
|
231
|
+
message: 'Invalid placement',
|
|
232
|
+
}),
|
|
233
|
+
)
|
|
198
234
|
}
|
|
199
235
|
}
|
|
200
236
|
const changePosition = (index: number, value: number, commit: boolean) => {
|
|
@@ -213,18 +249,29 @@ function ProceduralPlacementControls({ node }: { node: ProceduralItemNode }) {
|
|
|
213
249
|
setError('')
|
|
214
250
|
} catch (e) {
|
|
215
251
|
if (commit) useLiveNodeOverrides.getState().clear(node.id as AnyNodeId)
|
|
216
|
-
setError(
|
|
252
|
+
setError(
|
|
253
|
+
e instanceof Error
|
|
254
|
+
? e.message
|
|
255
|
+
: i18n._({
|
|
256
|
+
id: 'editor.proceduralItems.inspector.invalidPosition',
|
|
257
|
+
message: 'Invalid position',
|
|
258
|
+
}),
|
|
259
|
+
)
|
|
217
260
|
}
|
|
218
261
|
}
|
|
219
262
|
return (
|
|
220
|
-
<PanelSection
|
|
263
|
+
<PanelSection
|
|
264
|
+
title={i18n._({ id: 'editor.proceduralItems.inspector.position', message: 'Position' })}
|
|
265
|
+
>
|
|
221
266
|
{(['X', 'Y', 'Z'] as const).map((axis, index) => (
|
|
222
267
|
<SliderControl
|
|
223
268
|
key={axis}
|
|
224
269
|
label={
|
|
225
270
|
<>
|
|
226
271
|
{axis}
|
|
227
|
-
<sub className="ml-[1px] text-[11px] opacity-70">
|
|
272
|
+
<sub className="ml-[1px] text-[11px] opacity-70">
|
|
273
|
+
{i18n._({ id: 'editor.proceduralItems.inspector.pos', message: 'pos' })}
|
|
274
|
+
</sub>
|
|
228
275
|
</>
|
|
229
276
|
}
|
|
230
277
|
value={node.position[index]!}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type AttributesBackend = {
|
|
30
30
|
createAttribute(): void
|
|
31
31
|
updateAttribute(attribute: BufferAttribute): void
|
|
32
|
+
destroyAttribute(attribute: BufferAttribute): void
|
|
32
33
|
}
|
|
33
34
|
const Attributes = UntypedAttributes as new (
|
|
34
35
|
backend: AttributesBackend,
|
|
@@ -371,6 +372,7 @@ test('WebGPU attribute updates skip resting frames and upload only written slots
|
|
|
371
372
|
const attributes = new Attributes(
|
|
372
373
|
{
|
|
373
374
|
createAttribute() {},
|
|
375
|
+
destroyAttribute() {},
|
|
374
376
|
updateAttribute(attribute: BufferAttribute) {
|
|
375
377
|
uploads.push({ attribute, ranges: attribute.updateRanges.map((range) => ({ ...range })) })
|
|
376
378
|
attribute.clearUpdateRanges()
|