@pascal-app/editor 0.9.1 → 1.0.0-beta.1
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 +14 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +49 -0
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +332 -199
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -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 +102 -5
- package/src/components/editor/first-person/build-collider-world.ts +52 -5
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +410 -136
- package/src/components/editor/floating-action-menu.tsx +435 -64
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +72 -0
- package/src/components/editor/floorplan-background-selection.ts +7 -20
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +2517 -1492
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +597 -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/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +49 -6
- package/src/components/editor/index.tsx +289 -35
- package/src/components/editor/measurement-pill.tsx +68 -23
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +409 -180
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +76 -0
- package/src/components/editor/quick-measurement-hud.tsx +34 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +814 -466
- package/src/components/editor/site-edge-labels.tsx +28 -6
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +118 -253
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
- package/src/components/editor/wall-measurement-label.tsx +10 -28
- package/src/components/editor/wall-move-side-handles.tsx +229 -39
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
- 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 +107 -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 +146 -57
- package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
- package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
- package/src/components/tools/roof/roof-tool.tsx +336 -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/floor-stack-preview.ts +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +101 -42
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +164 -42
- package/src/components/tools/tool-manager.tsx +149 -30
- package/src/components/tools/wall/wall-drafting.test.ts +447 -0
- package/src/components/tools/wall/wall-drafting.ts +304 -108
- package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +98 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +44 -94
- 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 +267 -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 +16 -3
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +93 -18
- package/src/components/ui/controls/material-picker.tsx +162 -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/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +74 -20
- 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 +273 -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/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +36 -14
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +19 -4
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- 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 +82 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- 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 +42 -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/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +36 -596
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/components/walkthrough-hud.tsx +111 -0
- 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-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +438 -70
- package/src/index.tsx +355 -9
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +42 -0
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -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 +275 -0
- package/src/lib/direct-manipulation.ts +118 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +236 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +978 -0
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +52 -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 +283 -3
- package/src/lib/fresh-planar-placement.ts +85 -1
- package/src/lib/glb-export.test.ts +556 -0
- package/src/lib/glb-export.ts +1071 -0
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +31 -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 +52 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +243 -0
- package/src/lib/level-duplication.test.ts +2 -1
- 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 +188 -0
- package/src/lib/measurements.ts +210 -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-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -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 +325 -0
- package/src/lib/scene-clipboard.ts +310 -21
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +313 -0
- package/src/lib/selection-routing.ts +180 -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 +82 -14
- package/src/lib/sfx-player.test.ts +266 -0
- package/src/lib/sfx-player.ts +262 -69
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -0
- 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 +165 -0
- package/src/lib/snapping-mode.ts +191 -0
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +58 -86
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +665 -126
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +51 -0
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +151 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +206 -0
- package/src/store/use-interaction-scope.ts +140 -0
- package/src/store/use-measurement-draft.test.ts +559 -0
- package/src/store/use-measurement-draft.ts +570 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-path-draft-preview.ts +90 -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 +53 -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/floorplan/selection-tool.ts +0 -271
- package/src/lib/level-name.ts +0 -11
|
@@ -1,34 +1,25 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { emitter
|
|
4
|
-
import {
|
|
3
|
+
import { emitter } from '@pascal-app/core'
|
|
4
|
+
import {
|
|
5
|
+
computeHeroFraming,
|
|
6
|
+
createSnapshotPipeline,
|
|
7
|
+
GRID_LAYER,
|
|
8
|
+
heroCameraPose,
|
|
9
|
+
type SnapshotPipeline,
|
|
10
|
+
snapLevelsToTruePositions,
|
|
11
|
+
THUMBNAIL_HEIGHT,
|
|
12
|
+
THUMBNAIL_WIDTH,
|
|
13
|
+
temporarilyHideNodeTypes,
|
|
14
|
+
useViewer,
|
|
15
|
+
} from '@pascal-app/viewer'
|
|
5
16
|
import type { CameraControls } from '@react-three/drei'
|
|
6
17
|
import { useThree } from '@react-three/fiber'
|
|
7
18
|
import { useCallback, useEffect, useRef } from 'react'
|
|
8
19
|
import * as THREE from 'three'
|
|
9
|
-
import {
|
|
10
|
-
import { ssgi } from 'three/addons/tsl/display/SSGINode.js'
|
|
11
|
-
import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js'
|
|
12
|
-
import { fxaa } from 'three/examples/jsm/tsl/display/FXAANode.js'
|
|
13
|
-
import {
|
|
14
|
-
colorToDirection,
|
|
15
|
-
convertToTexture,
|
|
16
|
-
diffuseColor,
|
|
17
|
-
directionToColor,
|
|
18
|
-
float,
|
|
19
|
-
mrt,
|
|
20
|
-
normalView,
|
|
21
|
-
output,
|
|
22
|
-
pass,
|
|
23
|
-
sample,
|
|
24
|
-
vec4,
|
|
25
|
-
} from 'three/tsl'
|
|
26
|
-
import { RenderPipeline, RenderTarget, type WebGPURenderer } from 'three/webgpu'
|
|
20
|
+
import type { WebGPURenderer } from 'three/webgpu'
|
|
27
21
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
28
22
|
|
|
29
|
-
const THUMBNAIL_WIDTH = 1920
|
|
30
|
-
const THUMBNAIL_HEIGHT = 1080
|
|
31
|
-
|
|
32
23
|
export interface SnapshotCameraData {
|
|
33
24
|
position: [number, number, number]
|
|
34
25
|
target: [number, number, number] | null
|
|
@@ -51,8 +42,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
51
42
|
const onThumbnailCaptureRef = useRef(onThumbnailCapture)
|
|
52
43
|
|
|
53
44
|
const thumbnailCameraRef = useRef<THREE.PerspectiveCamera | null>(null)
|
|
54
|
-
const pipelineRef = useRef<
|
|
55
|
-
const renderTargetRef = useRef<RenderTarget | null>(null)
|
|
45
|
+
const pipelineRef = useRef<SnapshotPipeline | null>(null)
|
|
56
46
|
|
|
57
47
|
useEffect(() => {
|
|
58
48
|
onThumbnailCaptureRef.current = onThumbnailCapture
|
|
@@ -68,81 +58,24 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
68
58
|
let mounted = true
|
|
69
59
|
|
|
70
60
|
const buildPipeline = async () => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
scenePass.setMRT(
|
|
80
|
-
mrt({
|
|
81
|
-
output,
|
|
82
|
-
diffuseColor,
|
|
83
|
-
normal: directionToColor(normalView),
|
|
84
|
-
}),
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
const scenePassColor = scenePass.getTextureNode('output')
|
|
88
|
-
const scenePassDepth = scenePass.getTextureNode('depth')
|
|
89
|
-
const scenePassNormal = scenePass.getTextureNode('normal')
|
|
90
|
-
|
|
91
|
-
scenePass.getTexture('diffuseColor').type = UnsignedByteType
|
|
92
|
-
scenePass.getTexture('normal').type = UnsignedByteType
|
|
93
|
-
|
|
94
|
-
const sceneNormal = sample((uv) => colorToDirection(scenePassNormal.sample(uv)))
|
|
95
|
-
|
|
96
|
-
const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, cam as any)
|
|
97
|
-
giPass.sliceCount.value = SSGI_PARAMS.sliceCount
|
|
98
|
-
giPass.stepCount.value = SSGI_PARAMS.stepCount
|
|
99
|
-
giPass.radius.value = SSGI_PARAMS.radius
|
|
100
|
-
giPass.expFactor.value = SSGI_PARAMS.expFactor
|
|
101
|
-
giPass.thickness.value = SSGI_PARAMS.thickness
|
|
102
|
-
giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting
|
|
103
|
-
giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity
|
|
104
|
-
giPass.giIntensity.value = SSGI_PARAMS.giIntensity
|
|
105
|
-
giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness
|
|
106
|
-
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling
|
|
107
|
-
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering
|
|
108
|
-
|
|
109
|
-
const giTexture = (giPass as any).getTextureNode()
|
|
110
|
-
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1))
|
|
111
|
-
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, cam)
|
|
112
|
-
denoisePass.index.value = 0
|
|
113
|
-
denoisePass.radius.value = 4
|
|
114
|
-
|
|
115
|
-
const ao = (denoisePass as any).r
|
|
116
|
-
const finalOutput = vec4(scenePassColor.rgb.mul(ao), scenePassColor.a)
|
|
117
|
-
|
|
118
|
-
// FXAA requires a texture node as input; convertToTexture renders finalOutput
|
|
119
|
-
// into an intermediate RT so FXAA can sample it with neighbour UV offsets.
|
|
120
|
-
const aaOutput = fxaa(convertToTexture(finalOutput))
|
|
121
|
-
|
|
122
|
-
const pipeline = new RenderPipeline(gl as unknown as WebGPURenderer)
|
|
123
|
-
pipeline.outputNode = aaOutput
|
|
124
|
-
pipelineRef.current = pipeline
|
|
125
|
-
|
|
126
|
-
// Dedicated render target — pipeline outputs here instead of the canvas,
|
|
127
|
-
// so R3F's main render loop can never overwrite our capture.
|
|
128
|
-
const { width, height } = gl.domElement
|
|
129
|
-
renderTargetRef.current = new RenderTarget(width, height, { depthBuffer: true })
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error(
|
|
132
|
-
'[thumbnail] Failed to build post-processing pipeline, will use fallback render.',
|
|
133
|
-
error,
|
|
134
|
-
)
|
|
61
|
+
const pipeline = await createSnapshotPipeline({
|
|
62
|
+
renderer: gl as unknown as WebGPURenderer,
|
|
63
|
+
scene,
|
|
64
|
+
camera: cam,
|
|
65
|
+
})
|
|
66
|
+
if (!mounted) {
|
|
67
|
+
pipeline?.dispose()
|
|
68
|
+
return
|
|
135
69
|
}
|
|
70
|
+
pipelineRef.current = pipeline
|
|
136
71
|
}
|
|
137
72
|
|
|
138
|
-
buildPipeline()
|
|
73
|
+
void buildPipeline()
|
|
139
74
|
|
|
140
75
|
return () => {
|
|
141
76
|
mounted = false
|
|
142
77
|
pipelineRef.current?.dispose()
|
|
143
78
|
pipelineRef.current = null
|
|
144
|
-
renderTargetRef.current?.dispose()
|
|
145
|
-
renderTargetRef.current = null
|
|
146
79
|
}
|
|
147
80
|
}, [gl, scene])
|
|
148
81
|
|
|
@@ -151,7 +84,11 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
151
84
|
snapLevels: boolean,
|
|
152
85
|
captureMode?: 'standard' | 'viewport' | 'area',
|
|
153
86
|
cropRegion?: { x: number; y: number; width: number; height: number },
|
|
87
|
+
standardSize?: { w: number; h: number },
|
|
88
|
+
transparent = false,
|
|
154
89
|
) => {
|
|
90
|
+
const standardW = standardSize?.w ?? THUMBNAIL_WIDTH
|
|
91
|
+
const standardH = standardSize?.h ?? THUMBNAIL_HEIGHT
|
|
155
92
|
if (isGenerating.current) return
|
|
156
93
|
if (!onThumbnailCaptureRef.current) return
|
|
157
94
|
|
|
@@ -173,6 +110,20 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
173
110
|
const { width, height } = gl.domElement
|
|
174
111
|
thumbnailCamera.aspect = width / height
|
|
175
112
|
thumbnailCamera.updateProjectionMatrix()
|
|
113
|
+
// The capture camera never joins the scene graph, so its matrixWorld
|
|
114
|
+
// is only refreshed by the render itself — too late for the backdrop
|
|
115
|
+
// uniforms below.
|
|
116
|
+
thumbnailCamera.updateMatrixWorld()
|
|
117
|
+
|
|
118
|
+
const pipeline = pipelineRef.current
|
|
119
|
+
pipeline?.applyEnvironment({
|
|
120
|
+
theme: useViewer.getState().sceneTheme,
|
|
121
|
+
transparent,
|
|
122
|
+
grade: useViewer.getState().shading === 'rendered',
|
|
123
|
+
// Preset/item captures stay clean; scene captures mirror the canvas.
|
|
124
|
+
edges: transparent ? 'off' : useViewer.getState().edges,
|
|
125
|
+
camera: thumbnailCamera,
|
|
126
|
+
})
|
|
176
127
|
|
|
177
128
|
// Capture camera data for snapshot storage
|
|
178
129
|
const pos = mainCamera.position
|
|
@@ -202,174 +153,82 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
202
153
|
restoreLevels = snapLevelsToTruePositions()
|
|
203
154
|
}
|
|
204
155
|
|
|
205
|
-
// Hide scan and
|
|
206
|
-
// thumbnail regardless of whether ScanSystem/GuideSystem listeners
|
|
207
|
-
// registered.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
156
|
+
// Hide scan, guide, and spawn nodes directly so they are excluded from
|
|
157
|
+
// the thumbnail regardless of whether ScanSystem/GuideSystem listeners
|
|
158
|
+
// are registered. Spawn renders on SCENE_LAYER for occlusion, so the
|
|
159
|
+
// thumbnail camera's layer mask can't filter it either. Returns a
|
|
160
|
+
// function that restores the original visibility.
|
|
161
|
+
const restoreNodeVisibility = temporarilyHideNodeTypes(['scan', 'guide', 'spawn'])
|
|
162
|
+
|
|
163
|
+
// Auto-save shots don't copy the user's mid-edit camera — they re-pose
|
|
164
|
+
// onto the same computed hero angle the published thumbnail uses, so a
|
|
165
|
+
// project's card never shows a half-zoomed working view. Measured after
|
|
166
|
+
// the level snap so stacked positions frame correctly. User-driven
|
|
167
|
+
// captures (captureMode set) keep the exact viewport pose.
|
|
168
|
+
if (snapLevels) {
|
|
169
|
+
const framing = computeHeroFraming()
|
|
170
|
+
if (framing) {
|
|
171
|
+
const pose = heroCameraPose({
|
|
172
|
+
boxes: framing.boxes,
|
|
173
|
+
aim: framing.aim,
|
|
174
|
+
azimuthRad: framing.azimuthRad,
|
|
175
|
+
aspect: width / height,
|
|
218
176
|
})
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
177
|
+
thumbnailCamera.position.set(pose.position[0], pose.position[1], pose.position[2])
|
|
178
|
+
thumbnailCamera.lookAt(pose.target[0], pose.target[1], pose.target[2])
|
|
179
|
+
thumbnailCamera.updateMatrixWorld()
|
|
180
|
+
pipeline?.applyEnvironment({
|
|
181
|
+
theme: useViewer.getState().sceneTheme,
|
|
182
|
+
transparent,
|
|
183
|
+
grade: useViewer.getState().shading === 'rendered',
|
|
184
|
+
edges: transparent ? 'off' : useViewer.getState().edges,
|
|
185
|
+
camera: thumbnailCamera,
|
|
223
186
|
})
|
|
187
|
+
cameraData.position = pose.position
|
|
188
|
+
cameraData.target = pose.target
|
|
224
189
|
}
|
|
225
|
-
}
|
|
190
|
+
}
|
|
226
191
|
|
|
227
192
|
let blob: Blob
|
|
228
193
|
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// Resize RT if the canvas dimensions changed
|
|
233
|
-
if (rt.width !== width || rt.height !== height) {
|
|
234
|
-
rt.setSize(width, height)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const renderer = gl as unknown as WebGPURenderer
|
|
194
|
+
if (pipeline) {
|
|
195
|
+
let capturePromise: ReturnType<SnapshotPipeline['capture']>
|
|
238
196
|
|
|
239
197
|
// Notify other systems (wall cutouts, selection manager) to restore
|
|
240
198
|
// their overrides before capture and re-apply them after.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// depad the rows before constructing ImageData.
|
|
257
|
-
const pixels = (await (renderer as any).readRenderTargetPixelsAsync(
|
|
258
|
-
rt,
|
|
259
|
-
0,
|
|
260
|
-
0,
|
|
261
|
-
width,
|
|
262
|
-
height,
|
|
263
|
-
)) as Uint8Array
|
|
264
|
-
|
|
265
|
-
const actualBytesPerRow = width * 4
|
|
266
|
-
const tightTotal = actualBytesPerRow * height
|
|
267
|
-
const paddedBytesPerRow = Math.ceil(actualBytesPerRow / 256) * 256
|
|
268
|
-
// Two readback shapes to handle:
|
|
269
|
-
// - WebGPU (`copyTextureToBuffer`): top-down + 256-byte row padding
|
|
270
|
-
// when width*4 isn't already a multiple of 256.
|
|
271
|
-
// - WebGL2 fallback (iOS Chrome, etc.): tightly-packed but bottom-up
|
|
272
|
-
// (OpenGL framebuffer convention).
|
|
273
|
-
// `isWebGPURenderer` lies — it stays true even when the renderer
|
|
274
|
-
// falls back to the WebGL backend. Inspect the actual backend
|
|
275
|
-
// instead (presence of a GPU device, or backend constructor name).
|
|
276
|
-
const backend = (renderer as any).backend
|
|
277
|
-
const isWebGPU =
|
|
278
|
-
!!backend?.device ||
|
|
279
|
-
backend?.isWebGPUBackend === true ||
|
|
280
|
-
backend?.constructor?.name === 'WebGPUBackend'
|
|
281
|
-
let tightPixels: Uint8ClampedArray
|
|
282
|
-
if (isWebGPU) {
|
|
283
|
-
// WebGPU: depad rows if needed; orientation is already top-down.
|
|
284
|
-
if (paddedBytesPerRow === actualBytesPerRow) {
|
|
285
|
-
tightPixels = new Uint8ClampedArray(
|
|
286
|
-
pixels.buffer,
|
|
287
|
-
pixels.byteOffset,
|
|
288
|
-
Math.min(pixels.byteLength, tightTotal),
|
|
289
|
-
)
|
|
290
|
-
} else {
|
|
291
|
-
tightPixels = new Uint8ClampedArray(tightTotal)
|
|
292
|
-
for (let row = 0; row < height; row++) {
|
|
293
|
-
tightPixels.set(
|
|
294
|
-
pixels.subarray(
|
|
295
|
-
row * paddedBytesPerRow,
|
|
296
|
-
row * paddedBytesPerRow + actualBytesPerRow,
|
|
297
|
-
),
|
|
298
|
-
row * actualBytesPerRow,
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
} else {
|
|
303
|
-
// WebGL2: tight buffer in bottom-up order — flip rows.
|
|
304
|
-
tightPixels = new Uint8ClampedArray(tightTotal)
|
|
305
|
-
for (let row = 0; row < height; row++) {
|
|
306
|
-
const srcStart = (height - 1 - row) * actualBytesPerRow
|
|
307
|
-
tightPixels.set(
|
|
308
|
-
pixels.subarray(srcStart, srcStart + actualBytesPerRow),
|
|
309
|
-
row * actualBytesPerRow,
|
|
310
|
-
)
|
|
311
|
-
}
|
|
199
|
+
try {
|
|
200
|
+
emitter.emit('thumbnail:before-capture', undefined)
|
|
201
|
+
capturePromise = pipeline.capture({
|
|
202
|
+
captureMode,
|
|
203
|
+
cropRegion,
|
|
204
|
+
standardSize,
|
|
205
|
+
})
|
|
206
|
+
} finally {
|
|
207
|
+
// Restore level positions, levelMode, and node visibility immediately
|
|
208
|
+
// after the render — before the async GPU readback. Runs in `finally`
|
|
209
|
+
// so a render failure can't leave helpers permanently hidden.
|
|
210
|
+
emitter.emit('thumbnail:after-capture', undefined)
|
|
211
|
+
restoreLevels()
|
|
212
|
+
restoreLevelMode?.()
|
|
213
|
+
restoreNodeVisibility()
|
|
312
214
|
}
|
|
313
215
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
width,
|
|
317
|
-
height,
|
|
318
|
-
)
|
|
319
|
-
const srcCanvas = new OffscreenCanvas(width, height)
|
|
320
|
-
srcCanvas.getContext('2d')!.putImageData(imageData, 0, 0)
|
|
321
|
-
|
|
322
|
-
let outW: number
|
|
323
|
-
let outH: number
|
|
324
|
-
|
|
325
|
-
if (captureMode === 'viewport') {
|
|
326
|
-
outW = width
|
|
327
|
-
outH = height
|
|
328
|
-
const offscreen = new OffscreenCanvas(outW, outH)
|
|
329
|
-
offscreen.getContext('2d')!.drawImage(srcCanvas, 0, 0)
|
|
330
|
-
blob = await offscreen.convertToBlob({ type: 'image/png' })
|
|
331
|
-
} else if (captureMode === 'area' && cropRegion) {
|
|
332
|
-
const sx = Math.round(cropRegion.x * width)
|
|
333
|
-
const sy = Math.round(cropRegion.y * height)
|
|
334
|
-
outW = Math.round(cropRegion.width * width)
|
|
335
|
-
outH = Math.round(cropRegion.height * height)
|
|
336
|
-
const offscreen = new OffscreenCanvas(outW, outH)
|
|
337
|
-
offscreen.getContext('2d')!.drawImage(srcCanvas, sx, sy, outW, outH, 0, 0, outW, outH)
|
|
338
|
-
blob = await offscreen.convertToBlob({ type: 'image/png' })
|
|
339
|
-
} else {
|
|
340
|
-
// Standard: center-crop to 1920×1080 aspect ratio
|
|
341
|
-
const srcAspect = width / height
|
|
342
|
-
const dstAspect = THUMBNAIL_WIDTH / THUMBNAIL_HEIGHT
|
|
343
|
-
let sx = 0,
|
|
344
|
-
sy = 0,
|
|
345
|
-
sWidth = width,
|
|
346
|
-
sHeight = height
|
|
347
|
-
if (srcAspect > dstAspect) {
|
|
348
|
-
sWidth = Math.round(height * dstAspect)
|
|
349
|
-
sx = Math.round((width - sWidth) / 2)
|
|
350
|
-
} else if (srcAspect < dstAspect) {
|
|
351
|
-
sHeight = Math.round(width / dstAspect)
|
|
352
|
-
sy = Math.round((height - sHeight) / 2)
|
|
353
|
-
}
|
|
354
|
-
outW = THUMBNAIL_WIDTH
|
|
355
|
-
outH = THUMBNAIL_HEIGHT
|
|
356
|
-
const offscreen = new OffscreenCanvas(outW, outH)
|
|
357
|
-
offscreen
|
|
358
|
-
.getContext('2d')!
|
|
359
|
-
.drawImage(srcCanvas, sx, sy, sWidth, sHeight, 0, 0, outW, outH)
|
|
360
|
-
blob = await offscreen.convertToBlob({ type: 'image/png' })
|
|
361
|
-
}
|
|
216
|
+
const result = await capturePromise
|
|
217
|
+
blob = result.blob
|
|
362
218
|
|
|
363
219
|
if (captureMode !== undefined) cameraData.captureMode = captureMode
|
|
364
|
-
cameraData.resolution = { w: outW, h: outH }
|
|
220
|
+
cameraData.resolution = { w: result.outW, h: result.outH }
|
|
365
221
|
} else {
|
|
366
222
|
// Fallback: plain render directly to the canvas
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
223
|
+
try {
|
|
224
|
+
emitter.emit('thumbnail:before-capture', undefined)
|
|
225
|
+
gl.render(scene, thumbnailCamera)
|
|
226
|
+
} finally {
|
|
227
|
+
emitter.emit('thumbnail:after-capture', undefined)
|
|
228
|
+
restoreLevels()
|
|
229
|
+
restoreLevelMode?.()
|
|
230
|
+
restoreNodeVisibility()
|
|
231
|
+
}
|
|
373
232
|
|
|
374
233
|
let outW: number
|
|
375
234
|
let outH: number
|
|
@@ -406,7 +265,7 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
406
265
|
)
|
|
407
266
|
} else {
|
|
408
267
|
const srcAspect = width / height
|
|
409
|
-
const dstAspect =
|
|
268
|
+
const dstAspect = standardW / standardH
|
|
410
269
|
let sx = 0,
|
|
411
270
|
sy = 0,
|
|
412
271
|
sWidth = width,
|
|
@@ -418,8 +277,8 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
418
277
|
sHeight = Math.round(width / dstAspect)
|
|
419
278
|
sy = Math.round((height - sHeight) / 2)
|
|
420
279
|
}
|
|
421
|
-
outW =
|
|
422
|
-
outH =
|
|
280
|
+
outW = standardW
|
|
281
|
+
outH = standardH
|
|
423
282
|
const offscreen = document.createElement('canvas')
|
|
424
283
|
offscreen.width = outW
|
|
425
284
|
offscreen.height = outH
|
|
@@ -460,14 +319,20 @@ export const ThumbnailGenerator = ({ onThumbnailCapture }: ThumbnailGeneratorPro
|
|
|
460
319
|
const handleGenerateThumbnail = async (event: {
|
|
461
320
|
captureMode?: 'standard' | 'viewport' | 'area'
|
|
462
321
|
cropRegion?: { x: number; y: number; width: number; height: number }
|
|
322
|
+
standardSize?: { w: number; h: number }
|
|
463
323
|
snapLevels?: boolean
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
// background bits) can branch on it without touching the emitter.
|
|
324
|
+
// Preset/item captures keep the alpha channel (their thumbnails compose
|
|
325
|
+
// onto arbitrary palette backgrounds); scene snapshots — studio renders
|
|
326
|
+
// and project thumbnails — composite the theme backdrop + sky.
|
|
468
327
|
transparent?: boolean
|
|
469
328
|
}) => {
|
|
470
|
-
await generate(
|
|
329
|
+
await generate(
|
|
330
|
+
event.snapLevels === true,
|
|
331
|
+
event.captureMode,
|
|
332
|
+
event.cropRegion,
|
|
333
|
+
event.standardSize,
|
|
334
|
+
event.transparent === true,
|
|
335
|
+
)
|
|
471
336
|
}
|
|
472
337
|
|
|
473
338
|
emitter.on('camera-controls:generate-thumbnail', handleGenerateThumbnail)
|