@pascal-app/editor 1.0.4 → 1.0.6
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/README.md +106 -0
- package/package.json +40 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/bake-thumbnail.tsx +6 -15
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +81 -8
- package/src/components/editor/editor-layout-mobile.tsx +1 -1
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/export-manager.tsx +14 -19
- package/src/components/editor/first-person/build-collider-world.test.ts +269 -0
- package/src/components/editor/first-person/build-collider-world.ts +22 -5
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +89 -2
- package/src/components/editor/first-person/mechanism-targets.ts +52 -0
- package/src/components/editor/first-person-controls.tsx +19 -11
- package/src/components/editor/floor-aware-camera-controls.test.ts +24 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +72 -23
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/group-actions.ts +2 -0
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- package/src/components/editor/index.tsx +166 -79
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-highlight.tsx +13 -5
- package/src/components/editor/room-labels.test.ts +621 -128
- package/src/components/editor/room-labels.tsx +1090 -190
- package/src/components/editor/selection-manager.tsx +38 -117
- package/src/components/editor/slab-hole-highlights.tsx +2 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +3 -2
- package/src/components/editor/snapshot-capture.test.ts +69 -58
- package/src/components/editor/snapshot-capture.ts +7 -1
- package/src/components/editor/thumbnail-generator.test.ts +350 -4
- package/src/components/editor/thumbnail-generator.tsx +128 -54
- package/src/components/editor/use-floorplan-background-placement.ts +3 -1
- package/src/components/editor/views/plugins-view.tsx +1 -8
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +140 -17
- package/src/components/editor/wall-snap-beacon-layer.tsx +5 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +3 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +1 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +29 -7
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +38 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +3 -6
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +94 -94
- package/src/components/tools/item/use-placement-coordinator.tsx +55 -49
- package/src/components/tools/registry/item-surface-move.test.ts +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +63 -29
- package/src/components/tools/tool-manager.tsx +14 -5
- package/src/components/tools/wall/wall-drafting.test.ts +170 -57
- package/src/components/ui/action-menu/action-button.tsx +6 -1
- package/src/components/ui/action-menu/camera-actions.tsx +7 -3
- package/src/components/ui/action-menu/control-modes.tsx +13 -7
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +11 -9
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +10 -3
- package/src/components/ui/command-palette/editor-commands.tsx +3 -12
- package/src/components/ui/command-palette/index.tsx +45 -15
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +1 -1
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +150 -45
- package/src/components/ui/helpers/contextual-helper-panel.tsx +32 -7
- package/src/components/ui/helpers/helper-manager.test.tsx +54 -2
- package/src/components/ui/helpers/helper-manager.tsx +7 -1
- package/src/components/ui/helpers/item-helper.tsx +9 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +8 -8
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +6 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +120 -3
- package/src/components/ui/panels/panel-wrapper.tsx +53 -27
- package/src/components/ui/panels/parametric-field-control.tsx +2 -2
- package/src/components/ui/panels/parametric-inspector.tsx +3 -3
- package/src/components/ui/panels/room-panel.tsx +65 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/sidebar.tsx +1 -1
- package/src/components/ui/right-stack.tsx +21 -2
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +3 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +107 -23
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +5 -12
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -10
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/tab-bar.tsx +4 -4
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-rooms.tsx +16 -0
- package/src/hooks/use-auto-save.integration.test.tsx +96 -38
- package/src/hooks/use-auto-save.ts +26 -1
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +65 -16
- package/src/hooks/use-keyboard.ts +29 -12
- package/src/hooks/use-placement-notice.test.ts +24 -1
- package/src/hooks/use-placement-notice.ts +37 -6
- package/src/index.tsx +71 -24
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -0
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.ts +9 -2
- package/src/lib/editor-views.ts +35 -1
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-foundation-support.test.ts +1 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +31 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +39 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +4 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/sheet-export.test.ts +24 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +34 -45
- package/src/lib/floorplan/site-plan/coverage.ts +2 -4
- package/src/lib/floorplan/site-plan/front-edge.ts +2 -1
- package/src/lib/floorplan/site-plan/geometry.test.ts +9 -15
- package/src/lib/floorplan/site-plan/geometry.ts +1 -416
- package/src/lib/floorplan/site-plan/index.ts +0 -16
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +117 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -2
- package/src/lib/floorplan/site-plan/site-parts.ts +14 -106
- package/src/lib/floorplan/site-plan/yards.test.ts +2 -1
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +112 -0
- package/src/lib/fresh-planar-placement.ts +57 -3
- package/src/lib/geometry-script/author.ts +6 -1
- package/src/lib/geometry-script/client.ts +8 -1
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +1 -0
- package/src/lib/glb-export.test.ts +187 -162
- package/src/lib/glb-export.ts +4 -1
- package/src/lib/history.test.ts +12 -7
- package/src/lib/interaction/scope.ts +4 -0
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/lot/clean-ring.test.ts +2 -1
- package/src/lib/lot/drop-in.ts +9 -24
- package/src/lib/lot/lot-patch.test.ts +1 -1
- package/src/lib/lot/lot-patch.ts +2 -2
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/orbit-floor-clearance.test.ts +36 -0
- package/src/lib/orbit-floor-clearance.ts +48 -0
- package/src/lib/paint-pass-through.test.ts +11 -0
- package/src/lib/paint-pass-through.ts +4 -2
- package/src/lib/paint-scope.ts +1 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plugin-chat.ts +15 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- package/src/lib/room-assembly-overlay.ts +2 -3
- package/src/lib/room-selection.test.ts +45 -1
- package/src/lib/scene-clipboard.test.ts +246 -1
- package/src/lib/scene-clipboard.ts +44 -14
- package/src/lib/selection-routing.ts +1 -1
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -0
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.ts +2 -1
- package/src/lib/viewer-selection.ts +22 -11
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/window-changes.ts +35 -0
- package/src/store/use-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- package/src/store/use-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-stair-build-preview.ts +25 -5
- package/src/store/view-layout.test.ts +27 -1
- package/src/store/view-layout.ts +4 -1
- package/src/test-utils/render-current-markup.tsx +16 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -15
- package/src/components/tools/stair/stair-tool.tsx +0 -682
|
@@ -6,8 +6,7 @@ import {
|
|
|
6
6
|
emitter,
|
|
7
7
|
floorPlatePaintRefusal,
|
|
8
8
|
type GridEvent,
|
|
9
|
-
|
|
10
|
-
getEffectiveSegmentSurfaceMaterial,
|
|
9
|
+
getRoofSegmentDeckReach,
|
|
11
10
|
getRoofSegmentSurfaceY,
|
|
12
11
|
getSelectableKinds,
|
|
13
12
|
type ItemNode,
|
|
@@ -28,14 +27,9 @@ import {
|
|
|
28
27
|
useLiveNodeOverrides,
|
|
29
28
|
useRegistryVersion,
|
|
30
29
|
useScene,
|
|
30
|
+
wallFaceScriptPaintRefusal,
|
|
31
31
|
} from '@pascal-app/core'
|
|
32
|
-
import {
|
|
33
|
-
createMaterial,
|
|
34
|
-
getRoofMaterialArray,
|
|
35
|
-
registerMaterialCacheCleanup,
|
|
36
|
-
resolveMaterialRef,
|
|
37
|
-
useViewer,
|
|
38
|
-
} from '@pascal-app/viewer'
|
|
32
|
+
import { getRoofMaterialArray, registerMaterialCacheCleanup, useViewer } from '@pascal-app/viewer'
|
|
39
33
|
import { useThree } from '@react-three/fiber'
|
|
40
34
|
import { useCallback, useEffect, useRef } from 'react'
|
|
41
35
|
import { type BufferGeometry, Color, type Material, type Mesh, type Object3D, Vector3 } from 'three'
|
|
@@ -63,7 +57,6 @@ import {
|
|
|
63
57
|
import { selectionEnabled } from '../../lib/interaction/scope'
|
|
64
58
|
import {
|
|
65
59
|
type ActivePaintMaterial,
|
|
66
|
-
buildRoofSegmentSurfaceMaterialPatch,
|
|
67
60
|
buildRoofSurfaceMaterialPatch,
|
|
68
61
|
hasActivePaintMaterial,
|
|
69
62
|
resolveActivePaintMaterialFromSelection,
|
|
@@ -355,7 +348,7 @@ const wallPaintWorldPoint = new Vector3()
|
|
|
355
348
|
|
|
356
349
|
const roomHitPoint = new Vector3()
|
|
357
350
|
const roomHitXZ: [number, number] = [0, 0]
|
|
358
|
-
function roomForEvent(event: NodeEvent) {
|
|
351
|
+
export function roomForEvent(event: NodeEvent) {
|
|
359
352
|
if (event.node.type !== 'wall' && event.node.type !== 'slab' && event.node.type !== 'ceiling')
|
|
360
353
|
return null
|
|
361
354
|
// The floor edge band and the foundation belong to the footprint, not the
|
|
@@ -400,7 +393,7 @@ function resolveWallPaintHit(event: NodeEvent): WallPaintHit | undefined {
|
|
|
400
393
|
}
|
|
401
394
|
}
|
|
402
395
|
|
|
403
|
-
function resolveRoofSegmentSelectionTarget(event: NodeEvent): RoofSegmentNode | null {
|
|
396
|
+
export function resolveRoofSegmentSelectionTarget(event: NodeEvent): RoofSegmentNode | null {
|
|
404
397
|
const roof = event.node
|
|
405
398
|
if (roof.type !== 'roof') return null
|
|
406
399
|
|
|
@@ -420,9 +413,9 @@ function resolveRoofSegmentSelectionTarget(event: NodeEvent): RoofSegmentNode |
|
|
|
420
413
|
|
|
421
414
|
object.updateWorldMatrix(true, false)
|
|
422
415
|
const local = object.worldToLocal(roofSelectionWorldPoint.clone())
|
|
423
|
-
const
|
|
424
|
-
const halfWidth = segment.width / 2 +
|
|
425
|
-
const halfDepth = segment.depth / 2 +
|
|
416
|
+
const reach = getRoofSegmentDeckReach(segment)
|
|
417
|
+
const halfWidth = segment.width / 2 + reach.x
|
|
418
|
+
const halfDepth = segment.depth / 2 + reach.z
|
|
426
419
|
|
|
427
420
|
if (Math.abs(local.x) > halfWidth || Math.abs(local.z) > halfDepth) {
|
|
428
421
|
continue
|
|
@@ -501,47 +494,6 @@ function applyRoofPaintPreview(
|
|
|
501
494
|
return previewMeshMaterial(mesh, previewMaterial)
|
|
502
495
|
}
|
|
503
496
|
|
|
504
|
-
function applyRoofSegmentPaintPreview(
|
|
505
|
-
node: RoofSegmentNode,
|
|
506
|
-
parent: RoofNode | null,
|
|
507
|
-
role: 'top' | 'edge' | 'wall',
|
|
508
|
-
material: ActivePaintMaterial,
|
|
509
|
-
): PaintPreviewCleanup | null {
|
|
510
|
-
const mesh = getRegisteredMesh(node.id)
|
|
511
|
-
if (!mesh) return null
|
|
512
|
-
|
|
513
|
-
// Synthesise the segment node as if the paint had committed, then build
|
|
514
|
-
// the same 4-slot array the renderer would. Mirrors getRoofMaterialArray
|
|
515
|
-
// layout (slot 0 ← edge, 1 ← wall, 2 ← wall, 3 ← top) so the preview
|
|
516
|
-
// material lands on the matching CSG groups.
|
|
517
|
-
const previewNode: RoofSegmentNode = {
|
|
518
|
-
...node,
|
|
519
|
-
...buildRoofSegmentSurfaceMaterialPatch(node, role, material.material, material.materialPreset),
|
|
520
|
-
}
|
|
521
|
-
const sceneMaterials = useScene.getState().materials
|
|
522
|
-
const resolveSlot = (r: 'top' | 'edge' | 'wall'): Material | null => {
|
|
523
|
-
const parentSpec = parent ? getEffectiveRoofSurfaceMaterial(parent, r) : undefined
|
|
524
|
-
const spec = getEffectiveSegmentSurfaceMaterial(previewNode, r, parentSpec)
|
|
525
|
-
const resolved = resolveMaterialRef(spec.materialPreset, sceneMaterials)
|
|
526
|
-
if (resolved) return resolved
|
|
527
|
-
if (spec.material !== undefined) return createMaterial(spec.material)
|
|
528
|
-
return null
|
|
529
|
-
}
|
|
530
|
-
const edge = resolveSlot('edge')
|
|
531
|
-
const wall = resolveSlot('wall')
|
|
532
|
-
const top = resolveSlot('top')
|
|
533
|
-
if (!(edge || wall || top)) return null
|
|
534
|
-
const fallback = parent
|
|
535
|
-
? getRoofMaterialArray(parent, undefined, undefined, undefined, undefined, null, sceneMaterials)
|
|
536
|
-
: null
|
|
537
|
-
const fb = (n: number) => fallback?.[n] ?? null
|
|
538
|
-
// Per-role only, then the parent's themed slot — matches the renderer so the
|
|
539
|
-
// preview never bleeds a painted surface onto the segment's other surfaces.
|
|
540
|
-
const arr: Material[] = [edge ?? fb(0)!, wall ?? fb(1)!, wall ?? fb(2)!, top ?? fb(3)!]
|
|
541
|
-
if (arr.some((m) => !m)) return null
|
|
542
|
-
return previewMeshMaterial(mesh, arr)
|
|
543
|
-
}
|
|
544
|
-
|
|
545
497
|
// Chimney + dormer paint dispatch lives on their NodeDefinition's
|
|
546
498
|
// `capabilities.paint` (see packages/nodes/src/{chimney,dormer}/
|
|
547
499
|
// paint.ts). The generic registry-driven arm in this file consults
|
|
@@ -1031,8 +983,11 @@ export const SelectionManager = () => {
|
|
|
1031
983
|
compatible && role
|
|
1032
984
|
? () => {
|
|
1033
985
|
// A surface with nothing of its own to paint (a footprint's
|
|
1034
|
-
// top where no room stands)
|
|
1035
|
-
|
|
986
|
+
// top where no room stands), or one a script builds, says
|
|
987
|
+
// so instead of painting.
|
|
988
|
+
const refusal =
|
|
989
|
+
floorPlatePaintRefusal(useScene.getState().nodes, node, role) ??
|
|
990
|
+
wallFaceScriptPaintRefusal(node, role)
|
|
1036
991
|
usePaintRegionMode.getState().setNotice(refusal?.message ?? null)
|
|
1037
992
|
if (refusal) return
|
|
1038
993
|
// Spread targets are all the same slot-model kind, so one
|
|
@@ -1108,30 +1063,15 @@ export const SelectionManager = () => {
|
|
|
1108
1063
|
}
|
|
1109
1064
|
}
|
|
1110
1065
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
: node.parentId
|
|
1117
|
-
? useScene.getState().nodes[node.parentId as AnyNodeId]
|
|
1118
|
-
: null
|
|
1119
|
-
if (roofNode?.type !== 'roof') return null
|
|
1120
|
-
|
|
1121
|
-
const role = resolveRoofMaterialTarget(event as RoofEvent | RoofSegmentEvent)
|
|
1122
|
-
if (isPaintPicking())
|
|
1123
|
-
return pickInteraction(isSegmentHit ? node : roofNode, role, event, null)
|
|
1066
|
+
// A segment paints through its definition's `capabilities.paint`; the merged shell's hit is
|
|
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)
|
|
1124
1071
|
const compatible = role !== null && paintEnabled
|
|
1125
|
-
// Painting directly on a segment (only possible in segment edit
|
|
1126
|
-
// mode, where the per-segment mesh is visible) writes to the
|
|
1127
|
-
// segment's own role-specific fields. Painting the merged shell
|
|
1128
|
-
// — or a roof node directly — keeps fanning to the parent roof.
|
|
1129
|
-
const segmentTarget = isSegmentHit ? (node as RoofSegmentNode) : null
|
|
1130
1072
|
return {
|
|
1131
|
-
key:
|
|
1132
|
-
|
|
1133
|
-
}:${role ?? 'unsupported'}:${eraser ? 'erase' : 'paint'}`,
|
|
1134
|
-
hoveredId: (segmentTarget ? segmentTarget.id : roofNode.id) as AnyNodeId,
|
|
1073
|
+
key: `roof:${node.id}:${role ?? 'unsupported'}:${eraser ? 'erase' : 'paint'}`,
|
|
1074
|
+
hoveredId: node.id as AnyNodeId,
|
|
1135
1075
|
hoverMode: compatible ? (eraser ? 'erase-ready' : 'paint-ready') : 'paint-disabled',
|
|
1136
1076
|
// Roof isn't on the slot model (role-specific fields, custom commit),
|
|
1137
1077
|
// so it offers only the single surface — but still labels it.
|
|
@@ -1139,53 +1079,34 @@ export const SelectionManager = () => {
|
|
|
1139
1079
|
compatible && role
|
|
1140
1080
|
? {
|
|
1141
1081
|
scopes: ['single'],
|
|
1142
|
-
slotLabel: slotDisplayLabel(
|
|
1082
|
+
slotLabel: slotDisplayLabel(node, role),
|
|
1143
1083
|
nodeNoun: 'roof',
|
|
1144
1084
|
}
|
|
1145
1085
|
: null,
|
|
1146
1086
|
apply:
|
|
1147
1087
|
compatible && role
|
|
1148
1088
|
? () => {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
buildRoofSegmentSurfaceMaterialPatch(
|
|
1154
|
-
segmentTarget,
|
|
1155
|
-
role,
|
|
1156
|
-
paintSpec.material,
|
|
1157
|
-
paintSpec.materialPreset,
|
|
1158
|
-
),
|
|
1159
|
-
)
|
|
1160
|
-
} else {
|
|
1161
|
-
sceneState.updateNode(
|
|
1162
|
-
roofNode.id as AnyNodeId,
|
|
1089
|
+
useScene
|
|
1090
|
+
.getState()
|
|
1091
|
+
.updateNode(
|
|
1092
|
+
node.id as AnyNodeId,
|
|
1163
1093
|
buildRoofSurfaceMaterialPatch(
|
|
1164
|
-
|
|
1094
|
+
node,
|
|
1165
1095
|
role,
|
|
1166
1096
|
paintSpec.material,
|
|
1167
1097
|
paintSpec.materialPreset,
|
|
1168
1098
|
),
|
|
1169
1099
|
)
|
|
1170
|
-
}
|
|
1171
1100
|
}
|
|
1172
1101
|
: null,
|
|
1173
1102
|
preview:
|
|
1174
1103
|
compatible && role
|
|
1175
|
-
? () =>
|
|
1176
|
-
segmentTarget
|
|
1177
|
-
? applyRoofSegmentPaintPreview(
|
|
1178
|
-
segmentTarget,
|
|
1179
|
-
roofNode as RoofNode,
|
|
1180
|
-
role,
|
|
1181
|
-
paintSpec,
|
|
1182
|
-
)
|
|
1183
|
-
: applyRoofPaintPreview(roofNode as RoofNode, role, paintSpec)
|
|
1104
|
+
? () => applyRoofPaintPreview(node, role, paintSpec)
|
|
1184
1105
|
: () => previewCursor('not-allowed'),
|
|
1185
1106
|
}
|
|
1186
1107
|
}
|
|
1187
1108
|
|
|
1188
|
-
// Only `roof`
|
|
1109
|
+
// Only `roof` reaches a legacy paint arm (above) — every
|
|
1189
1110
|
// other paintable kind declares `capabilities.paint` and returns from the
|
|
1190
1111
|
// registry-driven dispatch at the top of this function.
|
|
1191
1112
|
|
|
@@ -1244,23 +1165,20 @@ export const SelectionManager = () => {
|
|
|
1244
1165
|
|
|
1245
1166
|
const onLeave = (event: NodeEvent) => {
|
|
1246
1167
|
if (isCeilingGridHit(event)) return
|
|
1247
|
-
|
|
1248
|
-
//
|
|
1249
|
-
// the
|
|
1250
|
-
|
|
1168
|
+
// Only the hovered node leaving ends the hover; a surface behind it (under
|
|
1169
|
+
// an opening that never stops the pointer) leaving says nothing about it.
|
|
1170
|
+
// This goes by the node, not by resolving the hit again: a leave event's
|
|
1171
|
+
// ray no longer meets the node, so that resolved to nothing and every
|
|
1172
|
+
// leave was ignored — the last surface stayed previewed, with its HUD.
|
|
1173
|
+
if (event.node.id !== hoveredNodeId) return
|
|
1251
1174
|
|
|
1252
1175
|
// Leaving any surface → the HUD shows the "hover a surface" hint again.
|
|
1253
1176
|
hoveredNodeId = null
|
|
1254
1177
|
lastEnterEvent = null
|
|
1255
1178
|
useEditor.getState().setPaintHover(null)
|
|
1256
1179
|
usePaintOutline.setState({ surfaces: null })
|
|
1257
|
-
|
|
1258
|
-
if (activePreview?.key !== interaction.key) {
|
|
1259
|
-
return
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
1180
|
clearActivePreview()
|
|
1263
|
-
if (useViewer.getState().hoveredId ===
|
|
1181
|
+
if (useViewer.getState().hoveredId === event.node.id) {
|
|
1264
1182
|
useViewer.setState({ hoveredId: null })
|
|
1265
1183
|
}
|
|
1266
1184
|
setHoverHighlightMode('default')
|
|
@@ -1879,6 +1797,9 @@ export const SelectionManager = () => {
|
|
|
1879
1797
|
!getMovingNode() &&
|
|
1880
1798
|
canDirectMoveNode(nodeToSelect)
|
|
1881
1799
|
) {
|
|
1800
|
+
// The move tool mounts before the browser's click that ends this
|
|
1801
|
+
// gesture, which would otherwise commit the node where it stands.
|
|
1802
|
+
swallowNextClick()
|
|
1882
1803
|
sfxEmitter.emit('sfx:item-pick')
|
|
1883
1804
|
const moveTarget = resolveDirectManipulationNode(nodeToSelect, useScene.getState().nodes)
|
|
1884
1805
|
useEditor.getState().setMovingNode(moveTarget as never)
|
|
@@ -139,12 +139,12 @@ function makeHitGeometry(hole: HolePolygon, centerY: number, height: number): Bu
|
|
|
139
139
|
|
|
140
140
|
function getSurfaceY(slab: SlabNode): number {
|
|
141
141
|
const elevation = slab.elevation ?? 0.05
|
|
142
|
-
return (elevation
|
|
142
|
+
return Math.max(elevation, 0) + SURFACE_OFFSET
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function getHitCenterY(slab: SlabNode): number {
|
|
146
146
|
const elevation = slab.elevation ?? 0.05
|
|
147
|
-
const surfaceTop = elevation
|
|
147
|
+
const surfaceTop = Math.max(elevation, 0)
|
|
148
148
|
// Keep the hit box's TOP at the slab surface (never poke above it) so the
|
|
149
149
|
// slab's centre thickness/height handle, which sits on top, always wins the
|
|
150
150
|
// raycast over the hole's selectable area.
|
|
@@ -18,6 +18,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
18
18
|
import { flushSync } from 'react-dom'
|
|
19
19
|
import { useIsMobile } from '../../hooks/use-mobile'
|
|
20
20
|
import { triggerSFX } from '../../lib/sfx-bus'
|
|
21
|
+
import { isTypingTarget } from '../../lib/typing-target'
|
|
21
22
|
import { requestWalkthroughPointerLock } from '../../lib/walkthrough-pointer-lock'
|
|
22
23
|
import useEditor, {
|
|
23
24
|
CAPTURE_FOV_MAX,
|
|
@@ -467,7 +468,7 @@ export function SnapshotCaptureOverlay({ projectId }: { projectId: string }) {
|
|
|
467
468
|
return
|
|
468
469
|
}
|
|
469
470
|
if (e.key !== 'Enter') return
|
|
470
|
-
if (e.target
|
|
471
|
+
if (isTypingTarget(e.target)) return
|
|
471
472
|
e.preventDefault()
|
|
472
473
|
handleCapture()
|
|
473
474
|
}
|
|
@@ -566,7 +567,7 @@ export function SnapshotCaptureOverlay({ projectId }: { projectId: string }) {
|
|
|
566
567
|
const captureDisabled = captureState !== 'idle' || (mode === 'area' && !hasSelection)
|
|
567
568
|
|
|
568
569
|
return (
|
|
569
|
-
<div className="pointer-events-none absolute inset-0 z-40" ref={overlayRef}>
|
|
570
|
+
<div className="dark pointer-events-none absolute inset-0 z-40" ref={overlayRef}>
|
|
570
571
|
{/* Walk / drone keep the walkthrough's centered pointer (the ring means
|
|
571
572
|
E opens the door / window under it) — the capture overlay replaces
|
|
572
573
|
the walkthrough HUD, so the crosshair rides along here. */}
|
|
@@ -240,62 +240,61 @@ describe('snapshot request correlation', () => {
|
|
|
240
240
|
])
|
|
241
241
|
})
|
|
242
242
|
|
|
243
|
-
test.each([
|
|
244
|
-
'
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
events.
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
})
|
|
243
|
+
test.each(['standard', 'viewport', 'area'] as const)(
|
|
244
|
+
'a manual %s shutter waits for a background upload; redundant autosaves are dropped',
|
|
245
|
+
async (captureMode) => {
|
|
246
|
+
const enqueue = createSnapshotQueue()
|
|
247
|
+
const busy = { current: false }
|
|
248
|
+
const failures: SnapshotCaptureFailedEvent[] = []
|
|
249
|
+
const events: string[] = []
|
|
250
|
+
let finishUpload!: () => void
|
|
251
|
+
const uploading = new Promise<void>((resolve) => {
|
|
252
|
+
finishUpload = resolve
|
|
253
|
+
})
|
|
254
|
+
const background = enqueue({ requestId: 'background' }, () =>
|
|
255
|
+
runSnapshotCapture(
|
|
256
|
+
'background',
|
|
257
|
+
busy,
|
|
258
|
+
async () => {
|
|
259
|
+
events.push('background rendered')
|
|
260
|
+
await uploading
|
|
261
|
+
events.push('background saved')
|
|
262
|
+
},
|
|
263
|
+
(failure) => failures.push(failure),
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
await Promise.resolve()
|
|
267
|
+
expect(busy.current).toBe(true)
|
|
268
|
+
const autosave = enqueue({}, async () => {
|
|
269
|
+
events.push('autosave')
|
|
270
|
+
})
|
|
271
|
+
let overlayState = 'capturing'
|
|
272
|
+
const manual = enqueue({ captureMode }, () =>
|
|
273
|
+
runSnapshotCapture(
|
|
274
|
+
undefined,
|
|
275
|
+
busy,
|
|
276
|
+
async () => {
|
|
277
|
+
events.push('manual saved')
|
|
278
|
+
overlayState = 'saved'
|
|
279
|
+
},
|
|
280
|
+
(failure) => failures.push(failure),
|
|
281
|
+
),
|
|
282
|
+
)
|
|
283
|
+
await autosave
|
|
284
|
+
expect(overlayState).toBe('capturing')
|
|
285
|
+
expect(events).toEqual(['background rendered'])
|
|
286
|
+
finishUpload()
|
|
287
|
+
await Promise.all([background, manual])
|
|
288
|
+
expect(events).toEqual(['background rendered', 'background saved', 'manual saved'])
|
|
289
|
+
expect(overlayState).toBe('saved')
|
|
290
|
+
expect(failures).toEqual([])
|
|
291
|
+
expect(busy.current).toBe(false)
|
|
292
|
+
await enqueue({}, async () => {
|
|
293
|
+
events.push('idle autosave')
|
|
294
|
+
})
|
|
295
|
+
expect(events.at(-1)).toBe('idle autosave')
|
|
296
|
+
},
|
|
297
|
+
)
|
|
299
298
|
|
|
300
299
|
test('queues a frame dispatched by a save callback until the previous capture releases its lock', async () => {
|
|
301
300
|
const enqueue = createSnapshotQueue()
|
|
@@ -439,13 +438,25 @@ describe('an ephemeral capture', () => {
|
|
|
439
438
|
await deliverSnapshot(
|
|
440
439
|
{ requestId: 'view-1', ephemeral: true },
|
|
441
440
|
blob,
|
|
442
|
-
{
|
|
441
|
+
{
|
|
442
|
+
...cameraData,
|
|
443
|
+
resolution: { w: 1280, h: 800 },
|
|
444
|
+
presentation: { mode: 'solo', levelId: 'level_upper' },
|
|
445
|
+
},
|
|
443
446
|
(frame) => {
|
|
444
447
|
stored.push(frame)
|
|
445
448
|
},
|
|
446
449
|
)
|
|
447
450
|
expect(stored).toEqual([])
|
|
448
|
-
expect(received).toEqual([
|
|
451
|
+
expect(received).toEqual([
|
|
452
|
+
{
|
|
453
|
+
requestId: 'view-1',
|
|
454
|
+
blob,
|
|
455
|
+
width: 1280,
|
|
456
|
+
height: 800,
|
|
457
|
+
presentation: { mode: 'solo', levelId: 'level_upper' },
|
|
458
|
+
},
|
|
459
|
+
])
|
|
449
460
|
} finally {
|
|
450
461
|
emitter.off('snapshot:captured', onCaptured)
|
|
451
462
|
}
|
|
@@ -149,7 +149,12 @@ export async function runSnapshotCapture(
|
|
|
149
149
|
* Where a captured frame goes: to the host, which stores it (a snapshot, the project's
|
|
150
150
|
* thumbnail), or for an ephemeral capture straight back to its caller, stored nowhere.
|
|
151
151
|
*/
|
|
152
|
-
export function deliverSnapshot<
|
|
152
|
+
export function deliverSnapshot<
|
|
153
|
+
T extends object & {
|
|
154
|
+
resolution?: { w: number; h: number }
|
|
155
|
+
presentation?: ThumbnailGenerateEvent['presentation']
|
|
156
|
+
},
|
|
157
|
+
>(
|
|
153
158
|
event: Pick<ThumbnailGenerateEvent, 'requestId' | 'ephemeral'>,
|
|
154
159
|
blob: Blob,
|
|
155
160
|
cameraData: T,
|
|
@@ -163,6 +168,7 @@ export function deliverSnapshot<T extends object & { resolution?: { w: number; h
|
|
|
163
168
|
blob,
|
|
164
169
|
width: cameraData.resolution?.w ?? 0,
|
|
165
170
|
height: cameraData.resolution?.h ?? 0,
|
|
171
|
+
...(cameraData.presentation ? { presentation: cameraData.presentation } : {}),
|
|
166
172
|
})
|
|
167
173
|
return
|
|
168
174
|
}
|