@pascal-app/editor 1.0.4 → 1.0.5
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 +39 -37
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyNodeId, BuildingNode, LevelNode } from '@pascal-app/core'
|
|
2
|
-
import { useScene } from '@pascal-app/core'
|
|
2
|
+
import { findLevelAncestorId, useScene } from '@pascal-app/core'
|
|
3
3
|
import { useViewer } from '@pascal-app/viewer'
|
|
4
4
|
|
|
5
5
|
function getAdjacentLevelIdForDeletion(levelId: AnyNodeId): LevelNode['id'] | null {
|
|
@@ -29,3 +29,18 @@ export function deleteLevelWithFallbackSelection(levelId: AnyNodeId) {
|
|
|
29
29
|
useViewer.getState().setSelection({ levelId: nextLevelId })
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Selects a node a tool just made, on its own level when that is not the one in view: a roof drawn
|
|
35
|
+
* over a storey's walls lands on the level above, which the editor hides while the storey is
|
|
36
|
+
* selected, so selecting it there alone would leave it out of sight.
|
|
37
|
+
*/
|
|
38
|
+
export function selectPlacedNode(nodeId: AnyNodeId) {
|
|
39
|
+
const levelId = findLevelAncestorId(nodeId, useScene.getState().nodes)
|
|
40
|
+
const { selection, setSelection } = useViewer.getState()
|
|
41
|
+
setSelection(
|
|
42
|
+
levelId && levelId !== selection.levelId
|
|
43
|
+
? { levelId: levelId as LevelNode['id'], selectedIds: [nodeId] }
|
|
44
|
+
: { selectedIds: [nodeId] },
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { classifyEdges, setbackEnvelope } from '@pascal-app/core'
|
|
2
3
|
import type { Pt } from '../floorplan/site-plan/geometry'
|
|
3
|
-
import {
|
|
4
|
+
import { polygonArea } from '../floorplan/site-plan/geometry'
|
|
4
5
|
import {
|
|
5
6
|
cleanLotRing,
|
|
6
7
|
describeRingCleanup,
|
package/src/lib/lot/drop-in.ts
CHANGED
|
@@ -24,15 +24,12 @@
|
|
|
24
24
|
import {
|
|
25
25
|
type AnyNode,
|
|
26
26
|
type AnyNodeId,
|
|
27
|
-
type BuildingNode,
|
|
28
27
|
generateId,
|
|
28
|
+
placeBuildingPose,
|
|
29
29
|
SiteNode,
|
|
30
|
+
siteBrief,
|
|
30
31
|
useScene,
|
|
31
32
|
} from '@pascal-app/core'
|
|
32
|
-
import {
|
|
33
|
-
buildingRecentreOffset,
|
|
34
|
-
buildSitePlanDrawing,
|
|
35
|
-
} from '../floorplan/site-plan/build-site-plan-drawing'
|
|
36
33
|
import {
|
|
37
34
|
answered,
|
|
38
35
|
type CodeBasisData,
|
|
@@ -339,27 +336,15 @@ export async function dropInLot(
|
|
|
339
336
|
terrainFailure = 'skipped'
|
|
340
337
|
}
|
|
341
338
|
|
|
342
|
-
//
|
|
339
|
+
// A house the new lot leaves past its lines stands centred on the buildable area again, facing
|
|
340
|
+
// as it did.
|
|
343
341
|
let recentred = false
|
|
344
342
|
const after = useScene.getState()
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
rootNodeIds: after.rootNodeIds,
|
|
351
|
-
})
|
|
352
|
-
const offset = buildingRecentreOffset(drawing.meta.lot, drawing.meta.footprintBounds)
|
|
353
|
-
if (offset && drawing.meta.buildingId) {
|
|
354
|
-
const building = after.nodes[drawing.meta.buildingId as AnyNodeId] as BuildingNode | undefined
|
|
355
|
-
if (building) {
|
|
356
|
-
after.updateNode(building.id, {
|
|
357
|
-
position: [
|
|
358
|
-
building.position[0] + offset[0],
|
|
359
|
-
building.position[1],
|
|
360
|
-
building.position[2] + offset[1],
|
|
361
|
-
],
|
|
362
|
-
})
|
|
343
|
+
const brief = siteBrief(after.nodes)
|
|
344
|
+
if (brief?.buildingId && brief.footprint?.fit === 'outside') {
|
|
345
|
+
const placed = placeBuildingPose(after.nodes, { buildingId: brief.buildingId })
|
|
346
|
+
if (placed.ok && placed.moved) {
|
|
347
|
+
after.updateNode(placed.buildingId as AnyNodeId, { position: placed.position })
|
|
363
348
|
recentred = true
|
|
364
349
|
}
|
|
365
350
|
}
|
package/src/lib/lot/lot-patch.ts
CHANGED
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
* - `northRotation` is 0: the parcel frame is x east / z south, so plan up
|
|
23
23
|
* is true north.
|
|
24
24
|
*/
|
|
25
|
-
import type
|
|
25
|
+
import { edgeLength, type SiteNode, type SiteSetbacks } from '@pascal-app/core'
|
|
26
26
|
import {
|
|
27
27
|
detectFrontEdgeFromRoads,
|
|
28
28
|
type FrontEdgeMatch,
|
|
29
29
|
type RoadCenterline,
|
|
30
30
|
} from '../floorplan/site-plan/front-edge'
|
|
31
|
-
import {
|
|
31
|
+
import { METRES_PER_FOOT, type Pt } from '../floorplan/site-plan/geometry'
|
|
32
32
|
import { sameStreet } from '../floorplan/site-plan/street-name'
|
|
33
33
|
import { cleanLotRing, describeRingCleanup } from './clean-ring'
|
|
34
34
|
import { detectFrontEdgeFromFrontage, type FrontageMatch } from './dossier'
|
|
@@ -19,12 +19,10 @@ describe('creatable measurement kinds', () => {
|
|
|
19
19
|
}
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
test.each([
|
|
23
|
-
'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
expect(normalizeCreatableMeasurementKind(value)).toBe(DEFAULT_CREATABLE_MEASUREMENT_KIND)
|
|
29
|
-
})
|
|
22
|
+
test.each(['smart', 'unknown', null, undefined])(
|
|
23
|
+
'falls back from a non-creatable persisted value: %s',
|
|
24
|
+
(value) => {
|
|
25
|
+
expect(normalizeCreatableMeasurementKind(value)).toBe(DEFAULT_CREATABLE_MEASUREMENT_KIND)
|
|
26
|
+
},
|
|
27
|
+
)
|
|
30
28
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { measurementHint, parseMeasurement } from '../index'
|
|
2
3
|
import {
|
|
3
4
|
buildMeasurementAngleArcPoints,
|
|
4
5
|
cubicMetersToVolumeUnit,
|
|
@@ -190,3 +191,25 @@ describe('volume measurements', () => {
|
|
|
190
191
|
expect(formatVolumeLabel(Number.NEGATIVE_INFINITY, 'metric')).toBe('--')
|
|
191
192
|
})
|
|
192
193
|
})
|
|
194
|
+
|
|
195
|
+
describe('typed measurement fields', () => {
|
|
196
|
+
test('canonicalizes explicit, compound, and scientific dimensions', () => {
|
|
197
|
+
const length = { kind: 'length', unitId: 'm' } as const
|
|
198
|
+
expect(parseMeasurement('180cm', length)).toBeCloseTo(1.8)
|
|
199
|
+
expect(parseMeasurement('1m80', length)).toBeCloseTo(1.8)
|
|
200
|
+
expect(parseMeasurement(`5'11"`, length)).toBeCloseTo(1.8034)
|
|
201
|
+
expect(parseMeasurement('1.234e-4 m', length)).toBeCloseTo(0.0001234, 10)
|
|
202
|
+
expect(parseMeasurement('6', length, { bareUnit: 'ft' })).toBeCloseTo(1.8288)
|
|
203
|
+
expect(parseMeasurement('180deg', { kind: 'angle', unitId: 'rad' })).toBeCloseTo(Math.PI)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('rejects ranges, incomplete arithmetic, and nonfinite input', () => {
|
|
207
|
+
const length = { kind: 'length', unitId: 'm' } as const
|
|
208
|
+
for (const input of ['2+3 m', '2-3 m', 'between 2 and 3 m', '1e999 m']) {
|
|
209
|
+
expect(parseMeasurement(input, length)).toBeNull()
|
|
210
|
+
expect(measurementHint(input, length)).toBeNull()
|
|
211
|
+
}
|
|
212
|
+
expect(measurementHint('180cm', length)).toBe('= 1.80 m')
|
|
213
|
+
expect(measurementHint('6', length)).toBeNull()
|
|
214
|
+
})
|
|
215
|
+
})
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { expect, test } from 'bun:test'
|
|
2
2
|
import {
|
|
3
|
+
DIVE_DEPTH,
|
|
4
|
+
DIVE_MARGIN,
|
|
5
|
+
diveFloorY,
|
|
3
6
|
floorLimitAt,
|
|
4
7
|
glideFloorLimit,
|
|
5
8
|
LOOK_UP_PITCH_GAIN,
|
|
@@ -7,8 +10,11 @@ import {
|
|
|
7
10
|
MAX_ORBIT_POLAR_ANGLE,
|
|
8
11
|
ORBIT_FLOOR_CLEARANCE,
|
|
9
12
|
ORBIT_TARGET_HEIGHT,
|
|
13
|
+
OVERVIEW_GROUND_CLEARANCE,
|
|
10
14
|
orbitAngleBelowFloor,
|
|
11
15
|
orbitCameraAboveFloor,
|
|
16
|
+
orbitFloorMinY,
|
|
17
|
+
overviewEyeY,
|
|
12
18
|
} from './orbit-floor-clearance'
|
|
13
19
|
|
|
14
20
|
type Vec3 = { x: number; y: number; z: number }
|
|
@@ -163,3 +169,33 @@ test('a change in floor height is glided over, never popped', () => {
|
|
|
163
169
|
}
|
|
164
170
|
expect(limit).toBeCloseTo(0.75, 6)
|
|
165
171
|
})
|
|
172
|
+
|
|
173
|
+
test('a pivot sent four storeys down keeps the eye where it was, over the ground; a diver stays under', () => {
|
|
174
|
+
// Eye 12 m up over a pivot at 1.1; the pivot goes to -12: the move alone would put the eye at -0.1.
|
|
175
|
+
expect(overviewEyeY(12 - 13.1, 12, -0.05, false)).toBe(12)
|
|
176
|
+
// An eye that was already low is brought up to the clearance.
|
|
177
|
+
expect(overviewEyeY(-9, 0.5, -0.05, false)).toBe(-0.05 + OVERVIEW_GROUND_CLEARANCE)
|
|
178
|
+
// The move leaves it high enough: it goes with the pivot, as always.
|
|
179
|
+
expect(overviewEyeY(20, 25, -0.05, false)).toBe(20)
|
|
180
|
+
// The person is under the ground: the move is theirs.
|
|
181
|
+
expect(overviewEyeY(-8, 12, -0.05, true)).toBe(-8)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('the dive floor sits 3 m under the lowest ground, or 1 m under the deepest drawn thing when that is deeper', () => {
|
|
185
|
+
// A house at grade: nothing drawn is deeper than the ground's 3 m.
|
|
186
|
+
expect(diveFloorY(-0.05, -0.36)).toBeCloseTo(-0.05 - DIVE_DEPTH, 9)
|
|
187
|
+
expect(diveFloorY(-0.05, null)).toBeCloseTo(-0.05 - DIVE_DEPTH, 9)
|
|
188
|
+
// The villa's basement footing at -3.83 goes a metre deeper.
|
|
189
|
+
expect(diveFloorY(-0.05, -3.83)).toBeCloseTo(-3.83 - DIVE_MARGIN, 9)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('the eye may sit the clearance over the dive floor from any storey, over the level floor otherwise', () => {
|
|
193
|
+
const dive = diveFloorY(-0.05, null)
|
|
194
|
+
// The ground floor, and a storey four metres up: both dive to the same floor.
|
|
195
|
+
expect(orbitFloorMinY(0, dive)).toBeCloseTo(dive + ORBIT_FLOOR_CLEARANCE, 9)
|
|
196
|
+
expect(orbitFloorMinY(4, dive)).toBeCloseTo(dive + ORBIT_FLOOR_CLEARANCE, 9)
|
|
197
|
+
// No dive (Solid): the level's floor, as before.
|
|
198
|
+
expect(orbitFloorMinY(4, null)).toBeCloseTo(4 + ORBIT_FLOOR_CLEARANCE, 9)
|
|
199
|
+
// A basement deeper than the dive floor keeps its own floor: the dive never lifts the limit.
|
|
200
|
+
expect(orbitFloorMinY(-12, dive)).toBeCloseTo(-12 + ORBIT_FLOOR_CLEARANCE, 9)
|
|
201
|
+
})
|
|
@@ -11,8 +11,31 @@ export const ORBIT_FLOOR_CLEARANCE = 0.45
|
|
|
11
11
|
*/
|
|
12
12
|
export const ORBIT_TARGET_HEIGHT = 1.1
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The dive under the ground: with the ground on Auto the orbit's eye may leave the ground for the
|
|
16
|
+
* earth under any building, down to a floor this far under the lowest ground, or this far under
|
|
17
|
+
* the deepest thing drawn (a basement's footing), whichever is deeper.
|
|
18
|
+
*/
|
|
19
|
+
export const DIVE_DEPTH = 3
|
|
20
|
+
export const DIVE_MARGIN = 1
|
|
21
|
+
|
|
14
22
|
const DEGREE = Math.PI / 180
|
|
15
23
|
|
|
24
|
+
/** The floor of the dive, world Y: the eye's clearance over it is {@link ORBIT_FLOOR_CLEARANCE}. */
|
|
25
|
+
export function diveFloorY(lowestGroundY: number, deepestDrawnY: number | null): number {
|
|
26
|
+
const underGround = lowestGroundY - DIVE_DEPTH
|
|
27
|
+
return deepestDrawnY === null ? underGround : Math.min(underGround, deepestDrawnY - DIVE_MARGIN)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The lowest the orbit's eye may sit: the clearance over the selected level's floor, or over the
|
|
32
|
+
* dive's floor when the person may dive and it lies deeper (`diveFloor` null: no dive). Selecting a
|
|
33
|
+
* level never lifts the limit above the dive floor, so a dive works from any storey.
|
|
34
|
+
*/
|
|
35
|
+
export function orbitFloorMinY(levelFloorY: number, diveFloor: number | null): number {
|
|
36
|
+
return Math.min(levelFloorY, diveFloor ?? levelFloorY) + ORBIT_FLOOR_CLEARANCE
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
/** The steepest an orbit camera looks: straight down is 0, straight up π. */
|
|
17
40
|
export const MAX_ORBIT_POLAR_ANGLE = Math.PI - 0.05
|
|
18
41
|
|
|
@@ -104,3 +127,28 @@ export function glideFloorLimit(current: number | null, target: number, delta: n
|
|
|
104
127
|
if (current === null || !(delta > 0)) return current ?? target
|
|
105
128
|
return target + (current - target) * Math.exp(-delta / FLOOR_GLIDE_SECONDS)
|
|
106
129
|
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* How far over the ground the overview's eye stays when the pivot goes down to a deep level: the
|
|
133
|
+
* ground is a medium, the eye above it sees the level through the hole and the dive is the
|
|
134
|
+
* person's own act, so picking a level four storeys under grade does not sink the eye with it.
|
|
135
|
+
*/
|
|
136
|
+
export const OVERVIEW_GROUND_CLEARANCE = 2
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The height an overview eye is kept at when its pivot moves to another level: `movedEyeY` as the
|
|
140
|
+
* move leaves it (the eye following the pivot down), unless that would sink the eye under the
|
|
141
|
+
* ground (the eye was above it, `wasUnder` false): then the eye stays where it was, over the ground
|
|
142
|
+
* (at least {@link OVERVIEW_GROUND_CLEARANCE} over it), and looks down at the new pivot. An eye
|
|
143
|
+
* already under the ground (the person dived) goes where the move puts it.
|
|
144
|
+
*/
|
|
145
|
+
export function overviewEyeY(
|
|
146
|
+
movedEyeY: number,
|
|
147
|
+
previousEyeY: number,
|
|
148
|
+
groundY: number,
|
|
149
|
+
wasUnder: boolean,
|
|
150
|
+
): number {
|
|
151
|
+
const lowest = groundY + OVERVIEW_GROUND_CLEARANCE
|
|
152
|
+
if (wasUnder || movedEyeY >= lowest) return movedEyeY
|
|
153
|
+
return Math.max(previousEyeY, lowest)
|
|
154
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Group,
|
|
8
8
|
type Intersection,
|
|
9
9
|
Mesh,
|
|
10
|
+
MeshBasicMaterial,
|
|
10
11
|
Path,
|
|
11
12
|
Ray,
|
|
12
13
|
Shape,
|
|
@@ -74,6 +75,16 @@ describe('paint passes through an empty opening', () => {
|
|
|
74
75
|
it('without a drawn mesh the hit is the wall', () => {
|
|
75
76
|
expect(wallHitInOpening(null, new Ray(), 1)).toBe(false)
|
|
76
77
|
})
|
|
78
|
+
|
|
79
|
+
it('a drawn mesh with a group its material array has no entry for is the wall, without throwing', () => {
|
|
80
|
+
const torn = new Mesh(drawn.geometry.clone(), [new MeshBasicMaterial()])
|
|
81
|
+
torn.geometry.clearGroups()
|
|
82
|
+
torn.geometry.addGroup(0, Number.POSITIVE_INFINITY, 3)
|
|
83
|
+
torn.updateMatrixWorld(true)
|
|
84
|
+
const ray = new Ray(new Vector3(2, 1, 5), new Vector3(0, 0, -1))
|
|
85
|
+
expect(() => wallHitInOpening(torn, ray, 5 - 0.2)).not.toThrow()
|
|
86
|
+
expect(wallHitInOpening(torn, ray, 5 - 0.2)).toBe(false)
|
|
87
|
+
})
|
|
77
88
|
})
|
|
78
89
|
|
|
79
90
|
describe('a door part found behind the floor seen through its opening', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyNode } from '@pascal-app/core'
|
|
2
|
-
import { setSurfaceRaycastLayers } from '@pascal-app/viewer'
|
|
2
|
+
import { hasMaterialsForGroups, setSurfaceRaycastLayers } from '@pascal-app/viewer'
|
|
3
3
|
import { type Intersection, Mesh, type Object3D, type Ray, Raycaster } from 'three'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -62,7 +62,9 @@ export function wallHitInOpening(
|
|
|
62
62
|
ray: Ray,
|
|
63
63
|
hitDistance: number,
|
|
64
64
|
): boolean {
|
|
65
|
-
|
|
65
|
+
// A mesh caught between a palette and the groups a preview gave it cannot be
|
|
66
|
+
// raycast (three reads each group's material); the hit stands this move.
|
|
67
|
+
if (!(drawn instanceof Mesh && hasMaterialsForGroups(drawn))) return false
|
|
66
68
|
drawnRaycaster.ray.copy(ray)
|
|
67
69
|
drawnRaycaster.far = Number.POSITIVE_INFINITY
|
|
68
70
|
const hits: Intersection[] = []
|
package/src/lib/paint-scope.ts
CHANGED
|
@@ -337,6 +337,7 @@ function boundaryPointKey(point: readonly [number, number]): string {
|
|
|
337
337
|
function boundarySegmentKey(boundary: WallBoundaryFace): string {
|
|
338
338
|
const forward = boundary.points.map(boundaryPointKey).join('|')
|
|
339
339
|
const reverse = [...boundary.points].reverse().map(boundaryPointKey).join('|')
|
|
340
|
+
// biome-ignore lint/complexity/useMathMinMax: lexicographic string comparison
|
|
340
341
|
return `${boundary.wallId}:${forward < reverse ? forward : reverse}`
|
|
341
342
|
}
|
|
342
343
|
|
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
useScene,
|
|
7
7
|
} from '@pascal-app/core'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** A field edit with what its kind's parametrics derive and reconcile from it, as updates. */
|
|
10
|
+
export function parametricNodeUpdates(
|
|
10
11
|
nodeId: AnyNodeId,
|
|
11
12
|
requestedPatch: Partial<AnyNode>,
|
|
12
|
-
):
|
|
13
|
-
const
|
|
14
|
-
const node =
|
|
15
|
-
if (!node) return
|
|
13
|
+
): { id: AnyNodeId; data: Partial<AnyNode> }[] {
|
|
14
|
+
const nodes = useScene.getState().nodes
|
|
15
|
+
const node = nodes[nodeId]
|
|
16
|
+
if (!node) return []
|
|
16
17
|
|
|
17
18
|
const parametrics = nodeRegistry.get(node.type)?.parametrics as
|
|
18
19
|
| ParametricDescriptor<AnyNode>
|
|
@@ -31,7 +32,15 @@ export function commitParametricNodeFields(
|
|
|
31
32
|
]
|
|
32
33
|
if (parametrics?.reconcile) {
|
|
33
34
|
const next = { ...node, ...patch } as AnyNode
|
|
34
|
-
updates.push(...parametrics.reconcile(node, next, { ...
|
|
35
|
+
updates.push(...parametrics.reconcile(node, next, { ...nodes, [nodeId]: next }))
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
return updates
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function commitParametricNodeFields(
|
|
41
|
+
nodeId: AnyNodeId,
|
|
42
|
+
requestedPatch: Partial<AnyNode>,
|
|
43
|
+
): void {
|
|
44
|
+
const updates = parametricNodeUpdates(nodeId, requestedPatch)
|
|
45
|
+
if (updates.length) useScene.getState().updateNodes(updates)
|
|
37
46
|
}
|
package/src/lib/plugin-chat.ts
CHANGED
|
@@ -28,6 +28,19 @@ export type ChatToolCardProps = {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* What a plugin's start screen does through the host's chat, before anything is said. `send` sends
|
|
33
|
+
* a message now, its pictures by reference (a host-stored image), and `attach` puts files on the
|
|
34
|
+
* next message, in the composer: both at the level that runs the plugin's tools. `requirePro` only
|
|
35
|
+
* asks whether the person's plan includes that level ("Pro" is the hosted product's name for it):
|
|
36
|
+
* it answers false after offering the upgrade, and changes nothing in the chat.
|
|
37
|
+
*/
|
|
38
|
+
export type ChatStartProps = {
|
|
39
|
+
send(text: string, images?: readonly { url: string; name: string }[]): void
|
|
40
|
+
attach(files: readonly File[]): void
|
|
41
|
+
requirePro(): boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
/**
|
|
32
45
|
* A plugin's contribution to a host's chat, exported from its browser entry beside its panels and
|
|
33
46
|
* views; its tool contracts are its `./agent-tools` export. Which tools a turn offers, and to whom,
|
|
@@ -47,4 +60,6 @@ export type PluginChatExtension = {
|
|
|
47
60
|
actions?: readonly { id: string; label: string; hint: string; prompt: string }[]
|
|
48
61
|
/** File extensions or MIME types the plugin's tools read, which the composer then accepts. */
|
|
49
62
|
accepts?: readonly string[]
|
|
63
|
+
/** What the chat shows while its thread is empty, loaded lazily. */
|
|
64
|
+
start?: () => Promise<{ default: ComponentType<ChatStartProps> }>
|
|
50
65
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useScene } from '@pascal-app/core'
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { PerspectiveCamera, Raycaster } from 'three'
|
|
5
|
+
import { setEditorThreeContext } from '../../../components/editor/three-context-bridge'
|
|
6
|
+
import { PointAskLayer } from '../../../components/point-ask/point-ask-layer'
|
|
7
|
+
import { useAutoSave } from '../../../hooks/use-auto-save'
|
|
8
|
+
import useEditor from '../../../store/use-editor'
|
|
9
|
+
import { type PointBubble, usePointAsk } from '../../../store/use-point-ask'
|
|
10
|
+
import { changeMore, endCompare, redoAsk, sendBubble, startCompare, undoAsk } from '../session'
|
|
11
|
+
|
|
12
|
+
// Same-package entry: the private fixture uses this window boundary, never Editor src imports.
|
|
13
|
+
const saves: unknown[] = []
|
|
14
|
+
let saveControls: ReturnType<typeof useAutoSave>
|
|
15
|
+
function Host() {
|
|
16
|
+
saveControls = useAutoSave({
|
|
17
|
+
onSave: async (scene) => {
|
|
18
|
+
saves.push(structuredClone(scene))
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
return <PointAskLayer />
|
|
22
|
+
}
|
|
23
|
+
const element = document.createElement('div')
|
|
24
|
+
element.id = 'point-consumer-editor'
|
|
25
|
+
document.body.append(element)
|
|
26
|
+
const root = createRoot(element)
|
|
27
|
+
// Only the DOM mode precondition and CPU projection: no renderer or GPU is created.
|
|
28
|
+
const canvas = document.createElement('canvas')
|
|
29
|
+
canvas.width = 800
|
|
30
|
+
canvas.height = 600
|
|
31
|
+
document.body.append(canvas)
|
|
32
|
+
const camera = new PerspectiveCamera(50, 4 / 3, 0.1, 100)
|
|
33
|
+
camera.position.set(2, 5, 8)
|
|
34
|
+
camera.lookAt(2, 0, 2)
|
|
35
|
+
setEditorThreeContext({ camera, raycaster: new Raycaster(), domElement: canvas })
|
|
36
|
+
useEditor.setState({
|
|
37
|
+
viewMode: '3d',
|
|
38
|
+
workspaceMode: 'edit',
|
|
39
|
+
isPreviewMode: false,
|
|
40
|
+
isFirstPersonMode: false,
|
|
41
|
+
})
|
|
42
|
+
;(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
43
|
+
await act(async () => {
|
|
44
|
+
root.render(<Host />)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const fixture = {
|
|
48
|
+
scene: useScene,
|
|
49
|
+
point: usePointAsk,
|
|
50
|
+
act,
|
|
51
|
+
saves,
|
|
52
|
+
open: (bubble: PointBubble) =>
|
|
53
|
+
act(async () => {
|
|
54
|
+
usePointAsk.getState().openBubble(bubble)
|
|
55
|
+
}),
|
|
56
|
+
send: (text: string) =>
|
|
57
|
+
act(async () => {
|
|
58
|
+
await sendBubble(text, { x: 0, y: 0, width: 100, height: 20 })
|
|
59
|
+
}),
|
|
60
|
+
changeMore,
|
|
61
|
+
undo: undoAsk,
|
|
62
|
+
redo: redoAsk,
|
|
63
|
+
startCompare,
|
|
64
|
+
endCompare,
|
|
65
|
+
beginLoad: () => saveControls.beginSceneLoad(),
|
|
66
|
+
completeLoad: () => saveControls.completeSceneLoad(),
|
|
67
|
+
saveNow: () => saveControls.saveNow(),
|
|
68
|
+
unmount: () =>
|
|
69
|
+
act(async () => {
|
|
70
|
+
root.unmount()
|
|
71
|
+
setEditorThreeContext(null)
|
|
72
|
+
canvas.remove()
|
|
73
|
+
}),
|
|
74
|
+
}
|
|
75
|
+
;(window as unknown as { pointEditorFixture: typeof fixture }).pointEditorFixture = fixture
|
|
76
|
+
window.dispatchEvent(new Event('point-editor-fixture-ready'))
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyNode,
|
|
3
|
+
ItemNode,
|
|
4
|
+
LevelNode,
|
|
5
|
+
WallNode,
|
|
6
|
+
WindowNode,
|
|
7
|
+
ZoneNode,
|
|
8
|
+
DoorNode,
|
|
9
|
+
} from '@pascal-app/core'
|
|
10
|
+
|
|
11
|
+
// One 4 × 3 m kitchen on a ground level, as the point-ask tests share it: four walls, the room they
|
|
12
|
+
// make, a window in the north wall, a door in the south wall and a sofa on the floor.
|
|
13
|
+
export const LEVEL_ID = 'level_0'
|
|
14
|
+
|
|
15
|
+
const wall = (id: string, start: [number, number], end: [number, number]) =>
|
|
16
|
+
WallNode.parse({ id, parentId: LEVEL_ID, start, end, height: 2.6 })
|
|
17
|
+
|
|
18
|
+
const walls = [
|
|
19
|
+
wall('wall_north', [0, 0], [4, 0]),
|
|
20
|
+
wall('wall_east', [4, 0], [4, 3]),
|
|
21
|
+
wall('wall_south', [4, 3], [0, 3]),
|
|
22
|
+
wall('wall_west', [0, 3], [0, 0]),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const zone = ZoneNode.parse({
|
|
26
|
+
id: 'zone_kitchen',
|
|
27
|
+
parentId: LEVEL_ID,
|
|
28
|
+
name: 'Kitchen',
|
|
29
|
+
polygon: [
|
|
30
|
+
[0, 0],
|
|
31
|
+
[4, 0],
|
|
32
|
+
[4, 3],
|
|
33
|
+
[0, 3],
|
|
34
|
+
],
|
|
35
|
+
boundaryWallIds: walls.map((w) => w.id),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const window = WindowNode.parse({
|
|
39
|
+
id: 'window_north',
|
|
40
|
+
parentId: 'wall_north',
|
|
41
|
+
wallId: 'wall_north',
|
|
42
|
+
position: [2, 1.4, 0],
|
|
43
|
+
width: 1.2,
|
|
44
|
+
height: 1.4,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const door = DoorNode.parse({
|
|
48
|
+
id: 'door_front',
|
|
49
|
+
parentId: 'wall_south',
|
|
50
|
+
wallId: 'wall_south',
|
|
51
|
+
position: [2, 1.05, 0],
|
|
52
|
+
width: 0.9,
|
|
53
|
+
height: 2.1,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const sofa = ItemNode.parse({
|
|
57
|
+
id: 'item_sofa',
|
|
58
|
+
parentId: LEVEL_ID,
|
|
59
|
+
name: 'Sofa',
|
|
60
|
+
position: [1.5, 0, 1.5],
|
|
61
|
+
asset: {
|
|
62
|
+
id: 'sofa',
|
|
63
|
+
category: 'furniture',
|
|
64
|
+
name: 'Oslo 3-seat',
|
|
65
|
+
thumbnail: '',
|
|
66
|
+
src: 'asset://sofa.glb',
|
|
67
|
+
dimensions: [2.1, 0.85, 0.95],
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const level = LevelNode.parse({
|
|
72
|
+
id: LEVEL_ID,
|
|
73
|
+
children: [...walls.map((w) => w.id), zone.id, sofa.id],
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
/** The scene as `useScene` holds it: nodes by id. */
|
|
77
|
+
export function house(extra: AnyNode[] = []): Record<string, AnyNode> {
|
|
78
|
+
return Object.fromEntries(
|
|
79
|
+
[level, ...walls, zone, window, door, sofa, ...extra].map((node) => [node.id, node as AnyNode]),
|
|
80
|
+
)
|
|
81
|
+
}
|