@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
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
type NodeEvent,
|
|
16
16
|
nodeRegistry,
|
|
17
17
|
type RoofEvent,
|
|
18
|
-
type RoofNode,
|
|
19
18
|
type RoofSegmentEvent,
|
|
20
19
|
type RoofSegmentNode,
|
|
21
20
|
resolveLevelId,
|
|
@@ -29,7 +28,13 @@ import {
|
|
|
29
28
|
useScene,
|
|
30
29
|
wallFaceScriptPaintRefusal,
|
|
31
30
|
} from '@pascal-app/core'
|
|
32
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
markHighlight,
|
|
33
|
+
ROOF_HEEL_MATERIAL_INDEX,
|
|
34
|
+
registerMaterialCacheCleanup,
|
|
35
|
+
tintEmissive,
|
|
36
|
+
useViewer,
|
|
37
|
+
} from '@pascal-app/viewer'
|
|
33
38
|
import { useThree } from '@react-three/fiber'
|
|
34
39
|
import { useCallback, useEffect, useRef } from 'react'
|
|
35
40
|
import { type BufferGeometry, Color, type Material, type Mesh, type Object3D, Vector3 } from 'three'
|
|
@@ -57,7 +62,6 @@ import {
|
|
|
57
62
|
import { selectionEnabled } from '../../lib/interaction/scope'
|
|
58
63
|
import {
|
|
59
64
|
type ActivePaintMaterial,
|
|
60
|
-
buildRoofSurfaceMaterialPatch,
|
|
61
65
|
hasActivePaintMaterial,
|
|
62
66
|
resolveActivePaintMaterialFromSelection,
|
|
63
67
|
} from '../../lib/material-paint'
|
|
@@ -90,7 +94,6 @@ import {
|
|
|
90
94
|
paintScopeRole,
|
|
91
95
|
paintSurfaceLabel,
|
|
92
96
|
resolvePaintScopeTargets,
|
|
93
|
-
slotDisplayLabel,
|
|
94
97
|
type WallPaintHit,
|
|
95
98
|
} from '../../lib/paint-scope'
|
|
96
99
|
import {
|
|
@@ -248,9 +251,10 @@ function resolveStairMaterialTarget(
|
|
|
248
251
|
|
|
249
252
|
function resolveRoofMaterialTarget(
|
|
250
253
|
event: RoofEvent | RoofSegmentEvent,
|
|
251
|
-
): 'top' | 'edge' | 'wall' | null {
|
|
254
|
+
): 'top' | 'edge' | 'wall' | 'heel' | null {
|
|
252
255
|
const materialIndex = getIntersectionMaterialIndex(getEventObject(event), event.faceIndex)
|
|
253
256
|
if (materialIndex === 3) return 'top'
|
|
257
|
+
if (materialIndex === ROOF_HEEL_MATERIAL_INDEX) return 'heel'
|
|
254
258
|
if (materialIndex === 0) return 'edge'
|
|
255
259
|
if (materialIndex === 1 || materialIndex === 2) return 'wall'
|
|
256
260
|
|
|
@@ -451,14 +455,6 @@ function resolveSelectModeNodeTarget(event: NodeEvent, drill = false): AnyNode {
|
|
|
451
455
|
return event.node
|
|
452
456
|
}
|
|
453
457
|
|
|
454
|
-
function previewMeshMaterial(mesh: Mesh, material: Material | Material[]): PaintPreviewCleanup {
|
|
455
|
-
const previousMaterial = mesh.material
|
|
456
|
-
mesh.material = material
|
|
457
|
-
return () => {
|
|
458
|
-
mesh.material = previousMaterial
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
458
|
function previewCursor(cursor: string): PaintPreviewCleanup {
|
|
463
459
|
const previousCursor = document.body.style.cursor
|
|
464
460
|
document.body.style.cursor = cursor
|
|
@@ -467,33 +463,6 @@ function previewCursor(cursor: string): PaintPreviewCleanup {
|
|
|
467
463
|
}
|
|
468
464
|
}
|
|
469
465
|
|
|
470
|
-
function applyRoofPaintPreview(
|
|
471
|
-
node: RoofNode,
|
|
472
|
-
role: 'top' | 'edge' | 'wall',
|
|
473
|
-
material: ActivePaintMaterial,
|
|
474
|
-
): PaintPreviewCleanup | null {
|
|
475
|
-
const root = getRegisteredNodeObject(node.id)
|
|
476
|
-
const mesh = root?.getObjectByName('merged-roof') as Mesh | undefined
|
|
477
|
-
if (!mesh) return null
|
|
478
|
-
|
|
479
|
-
const previewNode = {
|
|
480
|
-
...node,
|
|
481
|
-
...buildRoofSurfaceMaterialPatch(node, role, material.material, material.materialPreset),
|
|
482
|
-
}
|
|
483
|
-
const previewMaterial = getRoofMaterialArray(
|
|
484
|
-
previewNode,
|
|
485
|
-
useViewer.getState().shading,
|
|
486
|
-
useViewer.getState().textures,
|
|
487
|
-
useViewer.getState().colorPreset,
|
|
488
|
-
useViewer.getState().sceneTheme,
|
|
489
|
-
null,
|
|
490
|
-
useScene.getState().materials,
|
|
491
|
-
)
|
|
492
|
-
if (!previewMaterial) return null
|
|
493
|
-
|
|
494
|
-
return previewMeshMaterial(mesh, previewMaterial)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
466
|
// Chimney + dormer paint dispatch lives on their NodeDefinition's
|
|
498
467
|
// `capabilities.paint` (see packages/nodes/src/{chimney,dormer}/
|
|
499
468
|
// paint.ts). The generic registry-driven arm in this file consults
|
|
@@ -518,16 +487,14 @@ const HIGHLIGHT_PROFILES = {
|
|
|
518
487
|
delete: {
|
|
519
488
|
color: new Color('#dc2626'),
|
|
520
489
|
blend: 0.76,
|
|
521
|
-
|
|
522
|
-
emissiveIntensity: 0.46,
|
|
490
|
+
emissive: 0.92,
|
|
523
491
|
},
|
|
524
492
|
selection: {
|
|
525
493
|
// Keep the real material/texture readable: no albedo tint, just a gentle
|
|
526
494
|
// indigo emissive glow so it reads as selected.
|
|
527
495
|
color: new Color('#818cf8'),
|
|
528
496
|
blend: 0,
|
|
529
|
-
|
|
530
|
-
emissiveIntensity: 0.12,
|
|
497
|
+
emissive: 0.12,
|
|
531
498
|
},
|
|
532
499
|
} as const
|
|
533
500
|
|
|
@@ -582,15 +549,7 @@ function createHighlightedMaterial(material: Material, kind: HighlightKind): Mat
|
|
|
582
549
|
highlightedMaterial.color = highlightedMaterial.color.clone().lerp(profile.color, profile.blend)
|
|
583
550
|
}
|
|
584
551
|
|
|
585
|
-
|
|
586
|
-
highlightedMaterial.emissive = highlightedMaterial.emissive
|
|
587
|
-
.clone()
|
|
588
|
-
.lerp(profile.color, profile.emissiveBlend)
|
|
589
|
-
highlightedMaterial.emissiveIntensity = Math.max(
|
|
590
|
-
highlightedMaterial.emissiveIntensity ?? 0,
|
|
591
|
-
profile.emissiveIntensity,
|
|
592
|
-
)
|
|
593
|
-
}
|
|
552
|
+
tintEmissive(highlightedMaterial, profile.color, profile.emissive)
|
|
594
553
|
|
|
595
554
|
if (typeof highlightedMaterial.opacity === 'number' && highlightedMaterial.opacity < 1) {
|
|
596
555
|
highlightedMaterial.transparent = true
|
|
@@ -598,7 +557,7 @@ function createHighlightedMaterial(material: Material, kind: HighlightKind): Mat
|
|
|
598
557
|
}
|
|
599
558
|
|
|
600
559
|
highlightedMaterial.needsUpdate = true
|
|
601
|
-
return highlightedMaterial
|
|
560
|
+
return markHighlight(highlightedMaterial)
|
|
602
561
|
}
|
|
603
562
|
|
|
604
563
|
function createHighlightedMaterials(
|
|
@@ -892,9 +851,7 @@ export const SelectionManager = () => {
|
|
|
892
851
|
|
|
893
852
|
// Registry-driven paint dispatch — kinds that declare
|
|
894
853
|
// `capabilities.paint` route hover / click / preview through
|
|
895
|
-
// their definition
|
|
896
|
-
// roof / stair / single-surface arms below stay until they
|
|
897
|
-
// migrate too.
|
|
854
|
+
// their definition (wall, chimney, dormer, roof, …).
|
|
898
855
|
const paintCap = nodeRegistry.get(node.type)?.capabilities?.paint
|
|
899
856
|
if (paintCap) {
|
|
900
857
|
const materialIndex = getIntersectionMaterialIndex(getEventObject(event), event.faceIndex)
|
|
@@ -1063,51 +1020,7 @@ export const SelectionManager = () => {
|
|
|
1063
1020
|
}
|
|
1064
1021
|
}
|
|
1065
1022
|
|
|
1066
|
-
//
|
|
1067
|
-
// the roof's, and fans to it.
|
|
1068
|
-
if (node.type === 'roof') {
|
|
1069
|
-
const role = resolveRoofMaterialTarget(event as RoofEvent)
|
|
1070
|
-
if (isPaintPicking()) return pickInteraction(node, role, event, null)
|
|
1071
|
-
const compatible = role !== null && paintEnabled
|
|
1072
|
-
return {
|
|
1073
|
-
key: `roof:${node.id}:${role ?? 'unsupported'}:${eraser ? 'erase' : 'paint'}`,
|
|
1074
|
-
hoveredId: node.id as AnyNodeId,
|
|
1075
|
-
hoverMode: compatible ? (eraser ? 'erase-ready' : 'paint-ready') : 'paint-disabled',
|
|
1076
|
-
// Roof isn't on the slot model (role-specific fields, custom commit),
|
|
1077
|
-
// so it offers only the single surface — but still labels it.
|
|
1078
|
-
paintHover:
|
|
1079
|
-
compatible && role
|
|
1080
|
-
? {
|
|
1081
|
-
scopes: ['single'],
|
|
1082
|
-
slotLabel: slotDisplayLabel(node, role),
|
|
1083
|
-
nodeNoun: 'roof',
|
|
1084
|
-
}
|
|
1085
|
-
: null,
|
|
1086
|
-
apply:
|
|
1087
|
-
compatible && role
|
|
1088
|
-
? () => {
|
|
1089
|
-
useScene
|
|
1090
|
-
.getState()
|
|
1091
|
-
.updateNode(
|
|
1092
|
-
node.id as AnyNodeId,
|
|
1093
|
-
buildRoofSurfaceMaterialPatch(
|
|
1094
|
-
node,
|
|
1095
|
-
role,
|
|
1096
|
-
paintSpec.material,
|
|
1097
|
-
paintSpec.materialPreset,
|
|
1098
|
-
),
|
|
1099
|
-
)
|
|
1100
|
-
}
|
|
1101
|
-
: null,
|
|
1102
|
-
preview:
|
|
1103
|
-
compatible && role
|
|
1104
|
-
? () => applyRoofPaintPreview(node, role, paintSpec)
|
|
1105
|
-
: () => previewCursor('not-allowed'),
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Only `roof` reaches a legacy paint arm (above) — every
|
|
1110
|
-
// other paintable kind declares `capabilities.paint` and returns from the
|
|
1023
|
+
// Every paintable kind declares `capabilities.paint` and returns from the
|
|
1111
1024
|
// registry-driven dispatch at the top of this function.
|
|
1112
1025
|
|
|
1113
1026
|
const disabledNodeTypes = ['zone']
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type SeparatorNode, sceneRegistry } from '@pascal-app/core'
|
|
4
|
+
import { type ThreeEvent, useThree } from '@react-three/fiber'
|
|
5
|
+
import { useState } from 'react'
|
|
6
|
+
import { OrthographicCamera, Plane, Vector2, Vector3 } from 'three'
|
|
7
|
+
import { runSeparatorEndDrag } from '../../lib/separator-edit'
|
|
8
|
+
import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
|
|
9
|
+
import { HandleArrow } from './handles/handle-arrow'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An invisible wall's 3D end grip, the twin of its end handle on the plan: a
|
|
13
|
+
* cube at the end, on the floor at `height`. Dragging it moves the end across
|
|
14
|
+
* that floor (`runSeparatorEndDrag`), the invisible walls joined there
|
|
15
|
+
* following. Rendered in the level frame.
|
|
16
|
+
*/
|
|
17
|
+
export function SeparatorEndGrip({
|
|
18
|
+
separator,
|
|
19
|
+
endpoint,
|
|
20
|
+
height,
|
|
21
|
+
levelId,
|
|
22
|
+
}: {
|
|
23
|
+
separator: SeparatorNode
|
|
24
|
+
endpoint: 'start' | 'end'
|
|
25
|
+
height: number
|
|
26
|
+
levelId: string
|
|
27
|
+
}) {
|
|
28
|
+
const [hover, setHover] = useState(false)
|
|
29
|
+
const { camera, raycaster, gl } = useThree()
|
|
30
|
+
const zoom = camera instanceof OrthographicCamera ? 1 / camera.zoom : 1
|
|
31
|
+
const [x, z] = separator[endpoint]
|
|
32
|
+
const onPointerDown = (event: ThreeEvent<PointerEvent>) => {
|
|
33
|
+
if (event.button !== 0) return
|
|
34
|
+
event.stopPropagation()
|
|
35
|
+
suppressBoxSelectForPointer(event)
|
|
36
|
+
const level = sceneRegistry.nodes.get(levelId)
|
|
37
|
+
if (!level) return
|
|
38
|
+
level.updateWorldMatrix(true, false)
|
|
39
|
+
const toLocal = level.matrixWorld.clone().invert()
|
|
40
|
+
const floor = new Plane().setFromNormalAndCoplanarPoint(
|
|
41
|
+
new Vector3(0, 1, 0).transformDirection(level.matrixWorld),
|
|
42
|
+
new Vector3(x, height, z).applyMatrix4(level.matrixWorld),
|
|
43
|
+
)
|
|
44
|
+
runSeparatorEndDrag({
|
|
45
|
+
separatorId: separator.id,
|
|
46
|
+
endpoint,
|
|
47
|
+
levelId,
|
|
48
|
+
toPlan: (clientX, clientY) => {
|
|
49
|
+
const rect = gl.domElement.getBoundingClientRect()
|
|
50
|
+
raycaster.setFromCamera(
|
|
51
|
+
new Vector2(
|
|
52
|
+
((clientX - rect.left) / rect.width) * 2 - 1,
|
|
53
|
+
-((clientY - rect.top) / rect.height) * 2 + 1,
|
|
54
|
+
),
|
|
55
|
+
camera,
|
|
56
|
+
)
|
|
57
|
+
const hit = raycaster.ray.intersectPlane(floor, new Vector3())
|
|
58
|
+
if (!hit) return null
|
|
59
|
+
const local = hit.applyMatrix4(toLocal)
|
|
60
|
+
return [local.x, local.z]
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return (
|
|
65
|
+
<HandleArrow
|
|
66
|
+
cursor="grab"
|
|
67
|
+
hover={hover}
|
|
68
|
+
onHoverChange={setHover}
|
|
69
|
+
onPointerDown={onPointerDown}
|
|
70
|
+
placement={{ position: [x, height, z], baseScale: zoom }}
|
|
71
|
+
shape="tracker"
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import type { SiteNode } from '@pascal-app/core'
|
|
4
4
|
import { sceneRegistry, useLiveNodeOverrides, useScene } from '@pascal-app/core'
|
|
5
5
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer'
|
|
6
|
-
import { Html } from '@react-three/drei'
|
|
7
6
|
import { createPortal, useFrame, useThree } from '@react-three/fiber'
|
|
8
7
|
import { useCallback, useMemo, useRef, useState } from 'react'
|
|
9
8
|
import { type Camera, type Object3D, Vector3 } from 'three'
|
|
@@ -11,6 +10,7 @@ import { groundHeightAt } from '../../lib/ground-surface'
|
|
|
11
10
|
import { formatLinearMeasurement } from '../../lib/measurements'
|
|
12
11
|
import { SITE_BOUNDARY_DRAG_LABEL } from '../../lib/site-boundary'
|
|
13
12
|
import { useActiveHandleDrag } from '../../store/use-interaction-scope'
|
|
13
|
+
import { Html } from './localized-html'
|
|
14
14
|
|
|
15
15
|
type ViewportSize = {
|
|
16
16
|
width: number
|
|
@@ -205,31 +205,38 @@ describe('a sheet capture session', () => {
|
|
|
205
205
|
const viewer = useViewer.getState()
|
|
206
206
|
viewer.setWallMode('down')
|
|
207
207
|
viewer.setLevelMode('exploded')
|
|
208
|
-
viewer.
|
|
208
|
+
viewer.setRenderQuality('low')
|
|
209
209
|
viewer.setTextures(false)
|
|
210
210
|
viewer.setColorPreset('white')
|
|
211
|
+
viewer.setTimeOfDay('night')
|
|
212
|
+
viewer.setWeather('overcast')
|
|
211
213
|
const view = () => {
|
|
212
|
-
const { wallMode, levelMode,
|
|
213
|
-
|
|
214
|
+
const { wallMode, levelMode, renderQuality, textures, colorPreset, timeOfDay, weather } =
|
|
215
|
+
useViewer.getState()
|
|
216
|
+
return { wallMode, levelMode, renderQuality, textures, colorPreset, timeOfDay, weather }
|
|
214
217
|
}
|
|
215
|
-
// the
|
|
218
|
+
// the quality stays the user's: the capture brings its own AO
|
|
216
219
|
|
|
217
220
|
const restore = presentFinished()
|
|
218
221
|
expect(view()).toEqual({
|
|
219
222
|
wallMode: 'up',
|
|
220
223
|
levelMode: 'stacked',
|
|
221
|
-
|
|
224
|
+
renderQuality: 'low',
|
|
222
225
|
textures: true,
|
|
223
226
|
colorPreset: 'clay',
|
|
227
|
+
timeOfDay: 'midday',
|
|
228
|
+
weather: 'fair',
|
|
224
229
|
})
|
|
225
230
|
|
|
226
231
|
restore()
|
|
227
232
|
expect(view()).toEqual({
|
|
228
233
|
wallMode: 'down',
|
|
229
234
|
levelMode: 'exploded',
|
|
230
|
-
|
|
235
|
+
renderQuality: 'low',
|
|
231
236
|
textures: false,
|
|
232
237
|
colorPreset: 'white',
|
|
238
|
+
timeOfDay: 'night',
|
|
239
|
+
weather: 'overcast',
|
|
233
240
|
})
|
|
234
241
|
})
|
|
235
242
|
|
|
@@ -239,6 +246,8 @@ describe('a sheet capture session', () => {
|
|
|
239
246
|
viewer.setLevelMode('stacked')
|
|
240
247
|
viewer.setTextures(true)
|
|
241
248
|
viewer.setColorPreset('clay')
|
|
249
|
+
viewer.setTimeOfDay('midday')
|
|
250
|
+
viewer.setWeather('fair')
|
|
242
251
|
let writes = 0
|
|
243
252
|
const unsubscribe = useViewer.subscribe(() => {
|
|
244
253
|
writes++
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
GRID_LAYER,
|
|
17
17
|
heroCameraPose,
|
|
18
18
|
holdLiveFrame,
|
|
19
|
+
itemLightsForCapture,
|
|
19
20
|
pendingSceneBuildCount,
|
|
20
21
|
presentLevelsForCapture,
|
|
21
22
|
refreshIsolation,
|
|
@@ -437,13 +438,15 @@ const HOLD_MAX_MS = 300_000
|
|
|
437
438
|
* What the sheets' pictures are drawn in, whatever the user's view: full-
|
|
438
439
|
* height walls, the storeys stacked at their true heights, the materials'
|
|
439
440
|
* own textures and colours (a white or blueprint palette is a view, not the
|
|
440
|
-
* house). The shading stays the user's: the capture brings its own AO.
|
|
441
|
+
* house), lit by a fair midday. The shading stays the user's: the capture brings its own AO.
|
|
441
442
|
*/
|
|
442
443
|
const FINISHED_PRESENTATION = {
|
|
443
444
|
wallMode: 'up',
|
|
444
445
|
levelMode: 'stacked',
|
|
445
446
|
textures: true,
|
|
446
447
|
colorPreset: 'clay',
|
|
448
|
+
timeOfDay: 'midday',
|
|
449
|
+
weather: 'fair',
|
|
447
450
|
} as const
|
|
448
451
|
|
|
449
452
|
/** The viewer switched to FINISHED_PRESENTATION; the returned function puts the user's view back. */
|
|
@@ -470,6 +473,25 @@ export function presentFinished(): () => void {
|
|
|
470
473
|
viewer.setColorPreset(FINISHED_PRESENTATION.colorPreset)
|
|
471
474
|
restores.push(() => useViewer.getState().setColorPreset(colorPreset))
|
|
472
475
|
}
|
|
476
|
+
if (
|
|
477
|
+
viewer.timeOfDay !== FINISHED_PRESENTATION.timeOfDay ||
|
|
478
|
+
viewer.timeOfDayMinutes !== null ||
|
|
479
|
+
viewer.skyLive
|
|
480
|
+
) {
|
|
481
|
+
const { timeOfDay, timeOfDayMinutes, skyLive } = viewer
|
|
482
|
+
viewer.setTimeOfDay(FINISHED_PRESENTATION.timeOfDay)
|
|
483
|
+
restores.push(() => {
|
|
484
|
+
const next = useViewer.getState()
|
|
485
|
+
next.setTimeOfDay(timeOfDay)
|
|
486
|
+
if (timeOfDayMinutes !== null) next.setTimeOfDayMinutes(timeOfDayMinutes)
|
|
487
|
+
if (skyLive) next.setSkyLive(true)
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
if (viewer.weather !== FINISHED_PRESENTATION.weather) {
|
|
491
|
+
const weather = viewer.weather
|
|
492
|
+
viewer.setWeather(FINISHED_PRESENTATION.weather)
|
|
493
|
+
restores.push(() => useViewer.getState().setWeather(weather))
|
|
494
|
+
}
|
|
473
495
|
return () => {
|
|
474
496
|
for (const restore of restores.reverse()) restore()
|
|
475
497
|
}
|
|
@@ -995,6 +1017,9 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
995
1017
|
} else if (snapLevels || pose || cameraPose) {
|
|
996
1018
|
exteriorGroundForCapture(restore, Boolean(cameraPose || pose), thumbnailCamera)
|
|
997
1019
|
}
|
|
1020
|
+
// Last, with the camera where the picture is taken from: the item lights that
|
|
1021
|
+
// matter to it, at full strength.
|
|
1022
|
+
restore(itemLightsForCapture(scene, thumbnailCamera))
|
|
998
1023
|
}
|
|
999
1024
|
// The scene pass is a FRAME-updated node: it renders once per node
|
|
1000
1025
|
// frame, and only the animation loop advances that frame. With the
|
|
@@ -19,6 +19,8 @@ import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid-
|
|
|
19
19
|
import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap'
|
|
20
20
|
import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
|
|
21
21
|
import { commitWallPolygonDraft, wallPolygonDraftWalls } from '../../lib/wall-polygon-draft'
|
|
22
|
+
import { wallDraftIsRun } from '../../lib/wall-pull'
|
|
23
|
+
import { isWallRunFree, snapWallRunDraftPoint } from '../../lib/wall-run-snap'
|
|
22
24
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
23
25
|
import useEditor, { isAngleSnapActive, isMagneticSnapActive } from '../../store/use-editor'
|
|
24
26
|
import usePlacementPreview from '../../store/use-placement-preview'
|
|
@@ -52,6 +54,8 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
52
54
|
wall: WallNode
|
|
53
55
|
} | null
|
|
54
56
|
floorplanOpeningLocalY: number
|
|
57
|
+
/** An open wall run's start and the direction of its last wall (null before one). */
|
|
58
|
+
getWallRunContext: () => { runStart: WallPlanPoint | null; reference: WallPlanPoint | null }
|
|
55
59
|
getSnappedFloorplanPoint: (point: WallPlanPoint) => WallPlanPoint
|
|
56
60
|
handleCeilingItemPlacementClick: (
|
|
57
61
|
planPoint: WallPlanPoint,
|
|
@@ -72,12 +76,9 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
72
76
|
isZoneBuildActive: boolean
|
|
73
77
|
levelId: string | null
|
|
74
78
|
registryToolOwnsSnapping: boolean
|
|
75
|
-
roofDraftStart: WallPlanPoint | null
|
|
76
79
|
setCursorPoint: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
77
80
|
setFenceDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
78
81
|
setFenceDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
79
|
-
setRoofDraftEnd: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
80
|
-
setRoofDraftStart: React.Dispatch<React.SetStateAction<WallPlanPoint | null>>
|
|
81
82
|
snapWallDraftPoint: (args: {
|
|
82
83
|
point: WallPlanPoint
|
|
83
84
|
walls: WallNode[]
|
|
@@ -86,6 +87,7 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
86
87
|
bypassSnap?: boolean
|
|
87
88
|
step?: number
|
|
88
89
|
gridSnap?: (point: WallPlanPoint) => WallPlanPoint
|
|
90
|
+
captureWallBodies?: boolean
|
|
89
91
|
}) => WallPlanPoint
|
|
90
92
|
snapPolygonDraftPoint: (args: {
|
|
91
93
|
point: WallPlanPoint
|
|
@@ -95,10 +97,9 @@ type UseFloorplanBackgroundPlacementArgs = {
|
|
|
95
97
|
toPoint2D: (point: WallPlanPoint) => { x: number; y: number }
|
|
96
98
|
walls: WallNode[]
|
|
97
99
|
/**
|
|
98
|
-
* Snap a building-local plan point
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* step.
|
|
100
|
+
* Snap a building-local plan point onto the building's grid lattice at
|
|
101
|
+
* `step` (the lattice the plan draws, which turns with the building).
|
|
102
|
+
* Injected — used by wall / fence branches that snap at variable step.
|
|
102
103
|
*/
|
|
103
104
|
worldGridSnap: (point: WallPlanPoint, step: number) => WallPlanPoint
|
|
104
105
|
}
|
|
@@ -115,6 +116,7 @@ export function useFloorplanBackgroundPlacement({
|
|
|
115
116
|
findClosestWallPoint,
|
|
116
117
|
floorplanOpeningLocalY,
|
|
117
118
|
getSnappedFloorplanPoint,
|
|
119
|
+
getWallRunContext,
|
|
118
120
|
handleCeilingItemPlacementClick,
|
|
119
121
|
handleCeilingPlacementPoint,
|
|
120
122
|
handleSlabPlacementPoint,
|
|
@@ -131,12 +133,9 @@ export function useFloorplanBackgroundPlacement({
|
|
|
131
133
|
isZoneBuildActive,
|
|
132
134
|
levelId,
|
|
133
135
|
registryToolOwnsSnapping,
|
|
134
|
-
roofDraftStart,
|
|
135
136
|
setCursorPoint,
|
|
136
137
|
setFenceDraftEnd,
|
|
137
138
|
setFenceDraftStart,
|
|
138
|
-
setRoofDraftEnd,
|
|
139
|
-
setRoofDraftStart,
|
|
140
139
|
snapWallDraftPoint,
|
|
141
140
|
snapPolygonDraftPoint,
|
|
142
141
|
toPoint2D,
|
|
@@ -219,24 +218,14 @@ export function useFloorplanBackgroundPlacement({
|
|
|
219
218
|
if (isRoofBuildActive) {
|
|
220
219
|
// Footprint placement (polygon context: grid / lines / off, no angle),
|
|
221
220
|
// mode-driven to match the chip. Alt is force/free at commit time;
|
|
222
|
-
// alignment display/pull follows the active magnetic mode.
|
|
221
|
+
// alignment display/pull follows the active magnetic mode. The roof
|
|
222
|
+
// tool snaps the corner it commits and writes the draft rectangle from
|
|
223
|
+
// it, so the plan shows exactly the roof the click makes.
|
|
223
224
|
const snappedPoint = alignFloorplanDraftPoint(getSnappedFloorplanPoint(planPoint), {
|
|
224
225
|
applySnap: isMagneticSnapActive(),
|
|
225
226
|
})
|
|
226
227
|
emitFloorplanGridEvent('click', snappedPoint, event)
|
|
227
228
|
setCursorPoint(snappedPoint)
|
|
228
|
-
|
|
229
|
-
if (roofFootprintSource !== 'draw') {
|
|
230
|
-
clearRoofPlacementDraft()
|
|
231
|
-
return true
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (roofDraftStart) {
|
|
235
|
-
clearRoofPlacementDraft()
|
|
236
|
-
} else {
|
|
237
|
-
setRoofDraftStart(snappedPoint)
|
|
238
|
-
setRoofDraftEnd(snappedPoint)
|
|
239
|
-
}
|
|
240
229
|
return true
|
|
241
230
|
}
|
|
242
231
|
|
|
@@ -254,8 +243,8 @@ export function useFloorplanBackgroundPlacement({
|
|
|
254
243
|
|
|
255
244
|
if (isFenceBuildActive) {
|
|
256
245
|
// Fence draft: mode-driven (matches the chip), same as the move
|
|
257
|
-
// preview. `grid` snaps to the
|
|
258
|
-
//
|
|
246
|
+
// preview. `grid` snaps to the building's grid lattice (the `gridSnap`
|
|
247
|
+
// callback), `angles` locks 15° rays from the start, `lines`
|
|
259
248
|
// pulls onto walls / fences / alignment, `off` is free.
|
|
260
249
|
const fenceStep = getSegmentGridStep()
|
|
261
250
|
const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive()
|
|
@@ -363,30 +352,48 @@ export function useFloorplanBackgroundPlacement({
|
|
|
363
352
|
// / draftEnd state in the floor plan would never update, leaving
|
|
364
353
|
// the dashed-line draft preview invisible.
|
|
365
354
|
if (isWallBuildActive) {
|
|
366
|
-
// Wall draft
|
|
367
|
-
// `grid` snaps to the
|
|
368
|
-
// `angles` locks 15° rays from
|
|
369
|
-
// onto existing wall corners / edges + alignment, `off`
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
355
|
+
// Wall draft, matching the move-preview branch: mode-driven (matches
|
|
356
|
+
// the chip). `grid` snaps to the building's grid lattice (`gridSnap`),
|
|
357
|
+
// `angles` locks a Walls segment to 15° rays from its start, `lines`
|
|
358
|
+
// pulls the point onto existing wall corners / edges + alignment, `off`
|
|
359
|
+
// is free, and in every mode but `lines` an end aimed into a wall's body
|
|
360
|
+
// joins that wall. A run's segment (a Polygon side, a pulled run) lands
|
|
361
|
+
// where the move showed it (`snapWallRunDraftPoint`).
|
|
362
|
+
let snappedPoint: WallPlanPoint
|
|
363
|
+
if (draftStart && wallDraftIsRun()) {
|
|
364
|
+
const run = getWallRunContext()
|
|
365
|
+
snappedPoint = snapWallRunDraftPoint({
|
|
366
|
+
point: planPoint,
|
|
367
|
+
start: draftStart,
|
|
368
|
+
runStart: run.runStart ?? draftStart,
|
|
369
|
+
reference: run.reference,
|
|
370
|
+
walls: [...walls, ...wallPolygonDraftWalls()],
|
|
371
|
+
free: isWallRunFree(event),
|
|
372
|
+
}).point
|
|
384
373
|
} else {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
374
|
+
const wallStep = getSegmentGridStep()
|
|
375
|
+
const wallAngleSnap = draftStart !== null && isAngleSnapActive()
|
|
376
|
+
const wallSnapped = snapWallDraftPoint({
|
|
377
|
+
point: planPoint,
|
|
378
|
+
walls: [...walls, ...wallPolygonDraftWalls()],
|
|
379
|
+
start: draftStart ?? undefined,
|
|
380
|
+
angleSnap: wallAngleSnap,
|
|
381
|
+
gridSnap: (p) => worldGridSnap(p, wallStep),
|
|
382
|
+
captureWallBodies: true,
|
|
389
383
|
})
|
|
384
|
+
const wallGridBase = worldGridSnap(planPoint, wallStep)
|
|
385
|
+
const wallLocked =
|
|
386
|
+
wallSnapped[0] !== wallGridBase[0] || wallSnapped[1] !== wallGridBase[1]
|
|
387
|
+
snappedPoint = wallSnapped
|
|
388
|
+
if (wallLocked) {
|
|
389
|
+
useAlignmentGuides.getState().clear()
|
|
390
|
+
} else {
|
|
391
|
+
// Alignment lines are shown in every mode; the pull applies only when
|
|
392
|
+
// magnetic ('lines') and the segment isn't angle-locked.
|
|
393
|
+
snappedPoint = alignFloorplanDraftPoint(wallSnapped, {
|
|
394
|
+
applySnap: isMagneticSnapActive() && !wallAngleSnap,
|
|
395
|
+
})
|
|
396
|
+
}
|
|
390
397
|
}
|
|
391
398
|
|
|
392
399
|
emitFloorplanGridEvent('click', snappedPoint, event)
|
|
@@ -437,12 +444,12 @@ export function useFloorplanBackgroundPlacement({
|
|
|
437
444
|
activePolygonDraftPoints,
|
|
438
445
|
ceilingDraftPoints,
|
|
439
446
|
clearFencePlacementDraft,
|
|
440
|
-
clearRoofPlacementDraft,
|
|
441
447
|
clearWallPlacementDraft,
|
|
442
448
|
emitFloorplanGridEvent,
|
|
443
449
|
fenceDraftStart,
|
|
444
450
|
fences,
|
|
445
451
|
findClosestWallPoint,
|
|
452
|
+
getWallRunContext,
|
|
446
453
|
floorplanOpeningLocalY,
|
|
447
454
|
getSnappedFloorplanPoint,
|
|
448
455
|
handleCeilingItemPlacementClick,
|
|
@@ -459,14 +466,10 @@ export function useFloorplanBackgroundPlacement({
|
|
|
459
466
|
isWallBuildActive,
|
|
460
467
|
isZoneBuildActive,
|
|
461
468
|
levelId,
|
|
462
|
-
roofDraftStart,
|
|
463
469
|
registryToolOwnsSnapping,
|
|
464
|
-
roofFootprintSource,
|
|
465
470
|
setCursorPoint,
|
|
466
471
|
setFenceDraftEnd,
|
|
467
472
|
setFenceDraftStart,
|
|
468
|
-
setRoofDraftEnd,
|
|
469
|
-
setRoofDraftStart,
|
|
470
473
|
snapWallDraftPoint,
|
|
471
474
|
snapPolygonDraftPoint,
|
|
472
475
|
toPoint2D,
|