@pascal-app/editor 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +63 -25
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +36 -3
- package/src/components/editor/first-person/build-collider-world.ts +32 -5
- package/src/components/editor/first-person-controls.tsx +118 -7
- package/src/components/editor/floating-action-menu.tsx +410 -33
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +1804 -849
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/use-handle-drag.ts +37 -4
- package/src/components/editor/index.tsx +248 -27
- package/src/components/editor/measurement-pill.tsx +62 -22
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +366 -173
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +812 -466
- package/src/components/editor/site-edge-labels.tsx +4 -4
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +255 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +136 -43
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +3 -13
- package/src/components/editor/wall-move-side-handles.tsx +36 -21
- package/src/components/editor/wall-snap-beacon-layer.tsx +160 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +1 -1
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +169 -4
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +300 -59
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1704 -342
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +419 -60
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +80 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +31 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +761 -121
- package/src/components/tools/registry/move-registry-node-tool.tsx +561 -99
- package/src/components/tools/roof/roof-tool.tsx +320 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +88 -36
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +82 -11
- package/src/components/tools/tool-manager.tsx +86 -25
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +180 -103
- package/src/components/tools/wall/wall-snap-geometry.test.ts +44 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +67 -6
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +82 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -93
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +2 -2
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +86 -17
- package/src/components/ui/controls/material-picker.tsx +83 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +1 -1
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +227 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +22 -13
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +15 -2
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +71 -40
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +60 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +40 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +85 -10
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-keyboard.ts +385 -40
- package/src/index.tsx +219 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +240 -3
- package/src/lib/fresh-planar-placement.ts +68 -1
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +9 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +73 -8
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +50 -13
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +111 -66
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +484 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/level-name.ts +0 -11
|
@@ -6,24 +6,45 @@ import { furnishTools } from '../../../components/ui/action-menu/furnish-tools'
|
|
|
6
6
|
import { tools } from '../../../components/ui/action-menu/structure-tools'
|
|
7
7
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
8
8
|
import useEditor from '../../../store/use-editor'
|
|
9
|
+
import useWallSnapIndicator from '../../../store/use-wall-snap-indicator'
|
|
9
10
|
|
|
10
11
|
interface CursorSphereProps extends Omit<ThreeElements['group'], 'ref'> {
|
|
11
12
|
color?: string
|
|
12
13
|
depthWrite?: boolean
|
|
13
14
|
showTooltip?: boolean
|
|
14
15
|
height?: number
|
|
16
|
+
/**
|
|
17
|
+
* Put the bright marker dot at the TIP of the vertical line (y = height)
|
|
18
|
+
* instead of on the ground ring. Used when the point being placed hangs
|
|
19
|
+
* above the floor (e.g. duct drawn against the ceiling): the dot rides at
|
|
20
|
+
* the cursor / placement point while the line drops to a floor ring that
|
|
21
|
+
* keeps the plan position readable.
|
|
22
|
+
*/
|
|
23
|
+
dotAtTip?: boolean
|
|
15
24
|
/** Custom tooltip content — overrides the auto-detected build tool icon */
|
|
16
25
|
tooltipContent?: React.ReactNode
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
export const CursorSphere = forwardRef<Group, CursorSphereProps>(function CursorSphere(
|
|
20
|
-
{
|
|
29
|
+
{
|
|
30
|
+
color = '#818cf8',
|
|
31
|
+
showTooltip = true,
|
|
32
|
+
height = 2.5,
|
|
33
|
+
dotAtTip = false,
|
|
34
|
+
visible = true,
|
|
35
|
+
tooltipContent,
|
|
36
|
+
...props
|
|
37
|
+
},
|
|
21
38
|
ref,
|
|
22
39
|
) {
|
|
23
40
|
const tool = useEditor((s) => s.tool)
|
|
24
41
|
const mode = useEditor((s) => s.mode)
|
|
25
42
|
const catalogCategory = useEditor((s) => s.catalogCategory)
|
|
26
43
|
const isFloorplanHovered = useEditor((s) => s.isFloorplanHovered)
|
|
44
|
+
// While a wall snap is active the beacon layer already marks the point
|
|
45
|
+
// (green square at a corner); hide the cursor's ground dot/ring so it
|
|
46
|
+
// doesn't sit on top of that glyph.
|
|
47
|
+
const isSnapping = useWallSnapIndicator((s) => s.point !== null)
|
|
27
48
|
|
|
28
49
|
// Find the icon for the current tool
|
|
29
50
|
let activeToolConfig = null
|
|
@@ -39,32 +60,38 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
|
|
|
39
60
|
|
|
40
61
|
return (
|
|
41
62
|
<group ref={ref} {...props} visible={isVisible}>
|
|
42
|
-
{/* Flat marker on the ground
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
{/* Flat marker on the ground. The bright center dot moves to the tip
|
|
64
|
+
of the line in `dotAtTip` mode (the placement point hangs above the
|
|
65
|
+
floor), leaving a faint ring here so the plan position stays read. */}
|
|
66
|
+
{!isSnapping && (
|
|
67
|
+
<group rotation={[-Math.PI / 2, 0, 0]}>
|
|
68
|
+
{/* Center dot — at the ground unless the placement point is elevated */}
|
|
69
|
+
{!dotAtTip && (
|
|
70
|
+
<mesh layers={EDITOR_LAYER} renderOrder={2}>
|
|
71
|
+
<circleGeometry args={[0.06, 32]} />
|
|
72
|
+
<meshBasicMaterial
|
|
73
|
+
color={color}
|
|
74
|
+
depthTest={false}
|
|
75
|
+
depthWrite={false}
|
|
76
|
+
opacity={0.9}
|
|
77
|
+
transparent
|
|
78
|
+
/>
|
|
79
|
+
</mesh>
|
|
80
|
+
)}
|
|
55
81
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
82
|
+
{/* Outer ring / glow */}
|
|
83
|
+
<mesh layers={EDITOR_LAYER} renderOrder={2}>
|
|
84
|
+
<circleGeometry args={[0.2, 32]} />
|
|
85
|
+
<meshBasicMaterial
|
|
86
|
+
color={color}
|
|
87
|
+
depthTest={false}
|
|
88
|
+
depthWrite={false}
|
|
89
|
+
opacity={dotAtTip ? 0.2 : 0.25}
|
|
90
|
+
transparent
|
|
91
|
+
/>
|
|
92
|
+
</mesh>
|
|
93
|
+
</group>
|
|
94
|
+
)}
|
|
68
95
|
|
|
69
96
|
{/* Vertical line */}
|
|
70
97
|
{height > 0 && (
|
|
@@ -80,6 +107,15 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
|
|
|
80
107
|
</mesh>
|
|
81
108
|
)}
|
|
82
109
|
|
|
110
|
+
{/* Bright marker dot at the tip of the line — the actual placement
|
|
111
|
+
point, riding at the cursor while the line drops to the floor. */}
|
|
112
|
+
{dotAtTip && height > 0 && (
|
|
113
|
+
<mesh layers={EDITOR_LAYER} position={[0, height, 0]} renderOrder={2}>
|
|
114
|
+
<sphereGeometry args={[0.08, 20, 14]} />
|
|
115
|
+
<meshBasicMaterial color={color} depthTest={false} depthWrite={false} />
|
|
116
|
+
</mesh>
|
|
117
|
+
)}
|
|
118
|
+
|
|
83
119
|
{/* Tool Icon Tooltip at the top of the line */}
|
|
84
120
|
{isVisible && showTooltip && (activeToolConfig || tooltipContent) && (
|
|
85
121
|
<Html
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { sceneRegistry } from '@pascal-app/core'
|
|
3
|
+
import { type AnyNodeId, resolveFacingIndicator, sceneRegistry, useScene } from '@pascal-app/core'
|
|
4
4
|
import { useEffect, useMemo } from 'react'
|
|
5
5
|
import {
|
|
6
6
|
Box3,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { distance, smoothstep, uv, vec2 } from 'three/tsl'
|
|
16
16
|
import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
|
|
17
17
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
18
|
+
import useFacingPose from '../../../store/use-facing-pose'
|
|
18
19
|
|
|
19
20
|
const NO_RAYCAST = () => null
|
|
20
21
|
|
|
@@ -71,6 +72,8 @@ interface DragBoundingBoxProps {
|
|
|
71
72
|
* includes per-level landings outside the shaft footprint).
|
|
72
73
|
*/
|
|
73
74
|
size?: [number, number, number]
|
|
75
|
+
/** Local center of `size`. Defaults to the node origin plus half-height. */
|
|
76
|
+
center?: [number, number, number]
|
|
74
77
|
/** Y center of the box in the node's local frame. Defaults to `size[1] / 2`. */
|
|
75
78
|
centerY?: number
|
|
76
79
|
color?: number
|
|
@@ -90,9 +93,13 @@ export function DragBoundingBox({
|
|
|
90
93
|
rotationY = 0,
|
|
91
94
|
fallbackSize = [0, 0, 0],
|
|
92
95
|
size,
|
|
96
|
+
center,
|
|
93
97
|
centerY,
|
|
94
98
|
color = DEFAULT_COLOR,
|
|
95
99
|
}: DragBoundingBoxProps) {
|
|
100
|
+
const nodeType = useScene((state) => state.nodes[nodeId as AnyNodeId]?.type)
|
|
101
|
+
const facing = nodeType ? resolveFacingIndicator(nodeType) : null
|
|
102
|
+
|
|
96
103
|
const measured = useMemo(() => {
|
|
97
104
|
if (size) return null
|
|
98
105
|
const obj = sceneRegistry.nodes.get(nodeId)
|
|
@@ -101,9 +108,10 @@ export function DragBoundingBox({
|
|
|
101
108
|
|
|
102
109
|
const [w, h, d] = size ?? measured?.size ?? fallbackSize
|
|
103
110
|
const [cx, cy, cz] = size
|
|
104
|
-
? [0, centerY ?? size[1] / 2, 0]
|
|
111
|
+
? (center ?? [0, centerY ?? size[1] / 2, 0])
|
|
105
112
|
: (measured?.center ?? [0, fallbackSize[1] / 2, 0])
|
|
106
113
|
const minY = cy - h / 2
|
|
114
|
+
const groundY = minY + 0.01
|
|
107
115
|
|
|
108
116
|
const edgeGeometry = useMemo(() => {
|
|
109
117
|
const box = new BoxGeometry(w, h, d)
|
|
@@ -117,9 +125,9 @@ export function DragBoundingBox({
|
|
|
117
125
|
const planeGeometry = useMemo(() => {
|
|
118
126
|
const plane = new PlaneGeometry(w, d)
|
|
119
127
|
plane.rotateX(-Math.PI / 2)
|
|
120
|
-
plane.translate(cx,
|
|
128
|
+
plane.translate(cx, groundY, cz)
|
|
121
129
|
return plane
|
|
122
|
-
}, [w, d, cx,
|
|
130
|
+
}, [w, d, cx, groundY, cz])
|
|
123
131
|
|
|
124
132
|
const edgeMaterial = useMemo(
|
|
125
133
|
() => new LineBasicNodeMaterial({ color, linewidth: 3, depthTest: false, depthWrite: false }),
|
|
@@ -147,6 +155,22 @@ export function DragBoundingBox({
|
|
|
147
155
|
[edgeGeometry, planeGeometry, edgeMaterial, planeMaterial],
|
|
148
156
|
)
|
|
149
157
|
|
|
158
|
+
// Publish the facing pose to the editor-side overlay (the single triangle
|
|
159
|
+
// renderer) rather than drawing it here. The node origin is `position`; the
|
|
160
|
+
// footprint centre is `[cx, cz]` in the node's local frame. Runs each drag
|
|
161
|
+
// frame so the triangle follows; a separate mount/unmount effect clears it.
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!facing || d <= 0) return
|
|
164
|
+
useFacingPose.getState().set({
|
|
165
|
+
position: [position[0], position[1] + groundY, position[2]],
|
|
166
|
+
rotationY,
|
|
167
|
+
depth: d,
|
|
168
|
+
center: [cx, cz],
|
|
169
|
+
reversed: facing.reversed,
|
|
170
|
+
})
|
|
171
|
+
}, [facing, position, rotationY, d, cx, cz, groundY])
|
|
172
|
+
useEffect(() => () => useFacingPose.getState().clear(), [])
|
|
173
|
+
|
|
150
174
|
if (w <= 0 || h <= 0 || d <= 0) return null
|
|
151
175
|
|
|
152
176
|
return (
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react'
|
|
2
|
+
import { BufferGeometry, DoubleSide, Float32BufferAttribute } from 'three'
|
|
3
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
4
|
+
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
5
|
+
|
|
6
|
+
// A flat forward-pointing triangle drawn on the floor just in front of a
|
|
7
|
+
// placement ghost, so the direction the node will face is obvious. Tip at local
|
|
8
|
+
// +Z (every kind's forward face); render it inside the ghost's rotated group so
|
|
9
|
+
// it inherits the node's yaw. Matches the item coordinator / drag-bounding-box
|
|
10
|
+
// indicators (same size, colour, double-sided so it shows from above).
|
|
11
|
+
const FACING_INDICATOR_WIDTH = 0.4
|
|
12
|
+
const FACING_INDICATOR_LENGTH = 0.46
|
|
13
|
+
const FACING_INDICATOR_GAP = 0.45
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param depth bbox depth (along local Z) of the ghost — positions the
|
|
17
|
+
* triangle just past the front edge.
|
|
18
|
+
* @param center optional [x, z] of the bbox centre in the ghost's local frame.
|
|
19
|
+
* @param reversed point along local -Z (the front is the -Z side, e.g. a stair
|
|
20
|
+
* entry) instead of +Z.
|
|
21
|
+
* @param y small lift off the floor to avoid z-fighting.
|
|
22
|
+
*/
|
|
23
|
+
export function FacingIndicator({
|
|
24
|
+
depth,
|
|
25
|
+
center = [0, 0],
|
|
26
|
+
reversed = false,
|
|
27
|
+
y = 0.02,
|
|
28
|
+
}: {
|
|
29
|
+
depth: number
|
|
30
|
+
center?: [number, number]
|
|
31
|
+
reversed?: boolean
|
|
32
|
+
y?: number
|
|
33
|
+
}) {
|
|
34
|
+
const dir = reversed ? -1 : 1
|
|
35
|
+
// Per-instance geometry/material (not module singletons) so this works no
|
|
36
|
+
// matter which package mounts it (the tools live in `nodes`, imported via
|
|
37
|
+
// `@pascal-app/editor`). Disposed on unmount.
|
|
38
|
+
const geometry = useMemo(() => {
|
|
39
|
+
const g = new BufferGeometry()
|
|
40
|
+
g.setAttribute(
|
|
41
|
+
'position',
|
|
42
|
+
new Float32BufferAttribute(
|
|
43
|
+
[
|
|
44
|
+
0,
|
|
45
|
+
0,
|
|
46
|
+
dir * FACING_INDICATOR_LENGTH,
|
|
47
|
+
FACING_INDICATOR_WIDTH / 2,
|
|
48
|
+
0,
|
|
49
|
+
0,
|
|
50
|
+
-FACING_INDICATOR_WIDTH / 2,
|
|
51
|
+
0,
|
|
52
|
+
0,
|
|
53
|
+
],
|
|
54
|
+
3,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
return g
|
|
58
|
+
}, [dir])
|
|
59
|
+
const material = useMemo(
|
|
60
|
+
() =>
|
|
61
|
+
new MeshBasicNodeMaterial({
|
|
62
|
+
color: 0x22_c5_5e, // green-500 (forward)
|
|
63
|
+
depthTest: false,
|
|
64
|
+
depthWrite: false,
|
|
65
|
+
side: DoubleSide,
|
|
66
|
+
}),
|
|
67
|
+
[],
|
|
68
|
+
)
|
|
69
|
+
useEffect(
|
|
70
|
+
() => () => {
|
|
71
|
+
geometry.dispose()
|
|
72
|
+
material.dispose()
|
|
73
|
+
},
|
|
74
|
+
[geometry, material],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<mesh
|
|
79
|
+
frustumCulled={false}
|
|
80
|
+
geometry={geometry}
|
|
81
|
+
layers={EDITOR_LAYER}
|
|
82
|
+
material={material}
|
|
83
|
+
position={[center[0], y, center[1] + dir * (depth / 2 + FACING_INDICATOR_GAP)]}
|
|
84
|
+
renderOrder={1001}
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
import type { Group } from 'three'
|
|
3
|
+
import useFacingPose, { type FacingPose } from '../../../store/use-facing-pose'
|
|
4
|
+
import { FacingIndicator } from './facing-indicator'
|
|
5
|
+
|
|
6
|
+
// The single editor-side renderer for the placement/move facing triangle.
|
|
7
|
+
// Mounted once inside ToolManager's building-local group; every tool publishes
|
|
8
|
+
// its ghost pose to `useFacingPose` and this draws the triangle. The pose
|
|
9
|
+
// (position/yaw) is applied imperatively to a ref so the per-frame cursor
|
|
10
|
+
// updates don't re-render React — only a change in footprint shape (depth /
|
|
11
|
+
// centre), which is constant per tool session, triggers a re-render.
|
|
12
|
+
export function FacingPoseIndicator() {
|
|
13
|
+
const groupRef = useRef<Group>(null)
|
|
14
|
+
const [shape, setShape] = useState<Pick<FacingPose, 'depth' | 'center' | 'reversed'> | null>(null)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const apply = (pose: FacingPose | null) => {
|
|
18
|
+
const group = groupRef.current
|
|
19
|
+
if (group) {
|
|
20
|
+
if (pose) {
|
|
21
|
+
group.visible = true
|
|
22
|
+
group.position.set(...pose.position)
|
|
23
|
+
group.rotation.y = pose.rotationY
|
|
24
|
+
} else {
|
|
25
|
+
group.visible = false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
setShape((prev) => {
|
|
29
|
+
if (!pose) return null
|
|
30
|
+
const center = pose.center ?? [0, 0]
|
|
31
|
+
if (
|
|
32
|
+
prev &&
|
|
33
|
+
prev.depth === pose.depth &&
|
|
34
|
+
prev.reversed === pose.reversed &&
|
|
35
|
+
(prev.center ?? [0, 0])[0] === center[0] &&
|
|
36
|
+
(prev.center ?? [0, 0])[1] === center[1]
|
|
37
|
+
) {
|
|
38
|
+
return prev
|
|
39
|
+
}
|
|
40
|
+
return { depth: pose.depth, center, reversed: pose.reversed }
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
apply(useFacingPose.getState().pose)
|
|
44
|
+
return useFacingPose.subscribe((state) => apply(state.pose))
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<group ref={groupRef} visible={false}>
|
|
49
|
+
{shape ? (
|
|
50
|
+
<FacingIndicator center={shape.center} depth={shape.depth} reversed={shape.reversed} />
|
|
51
|
+
) : null}
|
|
52
|
+
</group>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -2,15 +2,131 @@
|
|
|
2
2
|
|
|
3
3
|
import '../../../three-types'
|
|
4
4
|
|
|
5
|
+
import { Html } from '@react-three/drei'
|
|
5
6
|
import { useEffect, useMemo } from 'react'
|
|
6
7
|
import { PlaneGeometry } from 'three'
|
|
7
8
|
import { distance, smoothstep, uv, vec2 } from 'three/tsl'
|
|
8
9
|
import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
|
|
9
10
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
11
|
+
import { formatLinearMeasurement, type LinearUnit } from '../../../lib/measurements'
|
|
10
12
|
import { createLineGeometry, getBoxEdgePoints } from './placement-box-geometry'
|
|
11
13
|
|
|
12
14
|
const VALID_COLOR = 0x22_c5_5e // green-500
|
|
13
15
|
const INVALID_COLOR = 0xef_44_44 // red-500
|
|
16
|
+
const MEASUREMENT_COLOR = 0x0f_17_2a
|
|
17
|
+
|
|
18
|
+
type PlacementBoxMeasurements = {
|
|
19
|
+
unit: LinearUnit
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getMeasurementGuidePoints(width: number, height: number, depth: number) {
|
|
23
|
+
const minX = -width / 2
|
|
24
|
+
const maxX = width / 2
|
|
25
|
+
const minZ = -depth / 2
|
|
26
|
+
const maxZ = depth / 2
|
|
27
|
+
const guideOffset = 0.18
|
|
28
|
+
const tick = 0.08
|
|
29
|
+
const y = 0.02
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
depth: [
|
|
33
|
+
maxX + guideOffset,
|
|
34
|
+
y,
|
|
35
|
+
minZ,
|
|
36
|
+
maxX + guideOffset,
|
|
37
|
+
y,
|
|
38
|
+
maxZ,
|
|
39
|
+
|
|
40
|
+
maxX + guideOffset - tick,
|
|
41
|
+
y,
|
|
42
|
+
minZ,
|
|
43
|
+
maxX + guideOffset + tick,
|
|
44
|
+
y,
|
|
45
|
+
minZ,
|
|
46
|
+
|
|
47
|
+
maxX + guideOffset - tick,
|
|
48
|
+
y,
|
|
49
|
+
maxZ,
|
|
50
|
+
maxX + guideOffset + tick,
|
|
51
|
+
y,
|
|
52
|
+
maxZ,
|
|
53
|
+
],
|
|
54
|
+
height: [
|
|
55
|
+
minX - guideOffset,
|
|
56
|
+
0,
|
|
57
|
+
minZ,
|
|
58
|
+
minX - guideOffset,
|
|
59
|
+
height,
|
|
60
|
+
minZ,
|
|
61
|
+
|
|
62
|
+
minX - guideOffset - tick,
|
|
63
|
+
0,
|
|
64
|
+
minZ,
|
|
65
|
+
minX - guideOffset + tick,
|
|
66
|
+
0,
|
|
67
|
+
minZ,
|
|
68
|
+
|
|
69
|
+
minX - guideOffset - tick,
|
|
70
|
+
height,
|
|
71
|
+
minZ,
|
|
72
|
+
minX - guideOffset + tick,
|
|
73
|
+
height,
|
|
74
|
+
minZ,
|
|
75
|
+
],
|
|
76
|
+
width: [
|
|
77
|
+
minX,
|
|
78
|
+
y,
|
|
79
|
+
maxZ + guideOffset,
|
|
80
|
+
maxX,
|
|
81
|
+
y,
|
|
82
|
+
maxZ + guideOffset,
|
|
83
|
+
|
|
84
|
+
minX,
|
|
85
|
+
y,
|
|
86
|
+
maxZ + guideOffset - tick,
|
|
87
|
+
minX,
|
|
88
|
+
y,
|
|
89
|
+
maxZ + guideOffset + tick,
|
|
90
|
+
|
|
91
|
+
maxX,
|
|
92
|
+
y,
|
|
93
|
+
maxZ + guideOffset - tick,
|
|
94
|
+
maxX,
|
|
95
|
+
y,
|
|
96
|
+
maxZ + guideOffset + tick,
|
|
97
|
+
],
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function MeasurementPill({
|
|
102
|
+
label,
|
|
103
|
+
position,
|
|
104
|
+
}: {
|
|
105
|
+
label: string
|
|
106
|
+
position: [number, number, number]
|
|
107
|
+
}) {
|
|
108
|
+
return (
|
|
109
|
+
<Html center position={position} style={{ pointerEvents: 'none' }}>
|
|
110
|
+
<div
|
|
111
|
+
style={{
|
|
112
|
+
background: 'rgba(15, 23, 42, 0.86)',
|
|
113
|
+
border: '1px solid rgba(15, 23, 42, 0.65)',
|
|
114
|
+
borderRadius: '999px',
|
|
115
|
+
color: '#f8fafc',
|
|
116
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
117
|
+
fontSize: '11px',
|
|
118
|
+
fontWeight: 600,
|
|
119
|
+
lineHeight: 1,
|
|
120
|
+
padding: '4px 8px',
|
|
121
|
+
pointerEvents: 'none',
|
|
122
|
+
whiteSpace: 'nowrap',
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
{label}
|
|
126
|
+
</div>
|
|
127
|
+
</Html>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
14
130
|
|
|
15
131
|
/**
|
|
16
132
|
* Green/red placement footprint shown while a node follows the cursor — the
|
|
@@ -27,12 +143,15 @@ const INVALID_COLOR = 0xef_44_44 // red-500
|
|
|
27
143
|
*/
|
|
28
144
|
export function PlacementBox({
|
|
29
145
|
dimensions,
|
|
146
|
+
measurements,
|
|
30
147
|
position,
|
|
31
148
|
rotationY = 0,
|
|
32
149
|
valid,
|
|
33
150
|
}: {
|
|
34
151
|
/** Footprint extent `[width, height, depth]` (unrotated). */
|
|
35
152
|
dimensions: [number, number, number]
|
|
153
|
+
/** Optional dimension guide labels matching the GLB item placement cursor. */
|
|
154
|
+
measurements?: PlacementBoxMeasurements
|
|
36
155
|
/** World-plan position of the footprint centre (floor level). */
|
|
37
156
|
position: [number, number, number]
|
|
38
157
|
/** Y-rotation in radians, applied to the whole box. */
|
|
@@ -61,6 +180,14 @@ export function PlacementBox({
|
|
|
61
180
|
geometry.translate(0, 0.01, 0)
|
|
62
181
|
return geometry
|
|
63
182
|
}, [width, depth])
|
|
183
|
+
const measurementGuideGeometries = useMemo(() => {
|
|
184
|
+
const points = getMeasurementGuidePoints(width, height, depth)
|
|
185
|
+
return {
|
|
186
|
+
depth: createLineGeometry(points.depth),
|
|
187
|
+
height: createLineGeometry(points.height),
|
|
188
|
+
width: createLineGeometry(points.width),
|
|
189
|
+
}
|
|
190
|
+
}, [width, height, depth])
|
|
64
191
|
|
|
65
192
|
const edgeMaterial = useMemo(
|
|
66
193
|
() => new LineBasicNodeMaterial({ linewidth: 3, depthTest: false, depthWrite: false }),
|
|
@@ -77,6 +204,16 @@ export function PlacementBox({
|
|
|
77
204
|
material.opacityNode = smoothstep(0, 0.7, distance(uv(), vec2(0.5, 0.5))).mul(0.6)
|
|
78
205
|
return material
|
|
79
206
|
}, [])
|
|
207
|
+
const measurementMaterial = useMemo(
|
|
208
|
+
() =>
|
|
209
|
+
new LineBasicNodeMaterial({
|
|
210
|
+
color: MEASUREMENT_COLOR,
|
|
211
|
+
linewidth: 2,
|
|
212
|
+
depthTest: false,
|
|
213
|
+
depthWrite: false,
|
|
214
|
+
}),
|
|
215
|
+
[],
|
|
216
|
+
)
|
|
80
217
|
|
|
81
218
|
useEffect(() => {
|
|
82
219
|
const color = valid ? VALID_COLOR : INVALID_COLOR
|
|
@@ -90,6 +227,14 @@ export function PlacementBox({
|
|
|
90
227
|
},
|
|
91
228
|
[edgeGeometry],
|
|
92
229
|
)
|
|
230
|
+
useEffect(
|
|
231
|
+
() => () => {
|
|
232
|
+
measurementGuideGeometries.depth.dispose()
|
|
233
|
+
measurementGuideGeometries.height.dispose()
|
|
234
|
+
measurementGuideGeometries.width.dispose()
|
|
235
|
+
},
|
|
236
|
+
[measurementGuideGeometries],
|
|
237
|
+
)
|
|
93
238
|
useEffect(
|
|
94
239
|
() => () => {
|
|
95
240
|
basePlaneGeometry.dispose()
|
|
@@ -100,10 +245,46 @@ export function PlacementBox({
|
|
|
100
245
|
() => () => {
|
|
101
246
|
edgeMaterial.dispose()
|
|
102
247
|
basePlaneMaterial.dispose()
|
|
248
|
+
measurementMaterial.dispose()
|
|
103
249
|
},
|
|
104
|
-
[edgeMaterial, basePlaneMaterial],
|
|
250
|
+
[edgeMaterial, basePlaneMaterial, measurementMaterial],
|
|
105
251
|
)
|
|
106
252
|
|
|
253
|
+
const measurementContent = measurements ? (
|
|
254
|
+
<>
|
|
255
|
+
<lineSegments
|
|
256
|
+
geometry={measurementGuideGeometries.width}
|
|
257
|
+
layers={EDITOR_LAYER}
|
|
258
|
+
material={measurementMaterial}
|
|
259
|
+
renderOrder={998}
|
|
260
|
+
/>
|
|
261
|
+
<lineSegments
|
|
262
|
+
geometry={measurementGuideGeometries.depth}
|
|
263
|
+
layers={EDITOR_LAYER}
|
|
264
|
+
material={measurementMaterial}
|
|
265
|
+
renderOrder={998}
|
|
266
|
+
/>
|
|
267
|
+
<lineSegments
|
|
268
|
+
geometry={measurementGuideGeometries.height}
|
|
269
|
+
layers={EDITOR_LAYER}
|
|
270
|
+
material={measurementMaterial}
|
|
271
|
+
renderOrder={998}
|
|
272
|
+
/>
|
|
273
|
+
<MeasurementPill
|
|
274
|
+
label={formatLinearMeasurement(width, measurements.unit)}
|
|
275
|
+
position={[0, 0.04, depth / 2 + 0.24]}
|
|
276
|
+
/>
|
|
277
|
+
<MeasurementPill
|
|
278
|
+
label={formatLinearMeasurement(depth, measurements.unit)}
|
|
279
|
+
position={[width / 2 + 0.24, 0.04, 0]}
|
|
280
|
+
/>
|
|
281
|
+
<MeasurementPill
|
|
282
|
+
label={formatLinearMeasurement(height, measurements.unit)}
|
|
283
|
+
position={[-width / 2 - 0.24, height / 2, -depth / 2]}
|
|
284
|
+
/>
|
|
285
|
+
</>
|
|
286
|
+
) : null
|
|
287
|
+
|
|
107
288
|
return (
|
|
108
289
|
<group position={position} rotation={[0, rotationY, 0]}>
|
|
109
290
|
<lineSegments
|
|
@@ -112,6 +293,7 @@ export function PlacementBox({
|
|
|
112
293
|
material={edgeMaterial}
|
|
113
294
|
renderOrder={999}
|
|
114
295
|
/>
|
|
296
|
+
{measurementContent}
|
|
115
297
|
<mesh
|
|
116
298
|
geometry={basePlaneGeometry}
|
|
117
299
|
layers={EDITOR_LAYER}
|