@pascal-app/editor 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +20 -17
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +20 -8
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +72 -61
- package/src/components/editor/floating-action-menu.tsx +27 -9
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +285 -132
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +20 -0
- package/src/components/editor/index.tsx +130 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +1 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +1 -1
- package/src/components/editor/room-handles-2d.tsx +156 -6
- package/src/components/editor/room-handles.tsx +241 -51
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +29 -15
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +1 -1
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +5 -1
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +30 -14
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
- package/src/components/ui/helpers/helper-manager.tsx +82 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
- package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +8 -7
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +22 -109
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +464 -65
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +21 -12
- package/src/components/ui/panels/room-panel.tsx +206 -99
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +61 -4
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15117 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +37501 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +37501 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +37501 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +37501 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +156 -4
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +95 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/floor-footprints.test.ts +2 -1
- package/src/lib/floor-foundation-support.test.ts +7 -3
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +17 -0
- package/src/lib/measurements.ts +20 -5
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-built-on.test.ts +26 -12
- package/src/lib/room-built-on.ts +20 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +140 -0
- package/src/lib/room-handle-drag.ts +557 -42
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +37 -0
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +9 -4
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +20 -5
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +14 -5
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +42 -0
- package/src/lib/window-interaction.ts +47 -5
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +48 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-wall-snap-indicator.ts +15 -2
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type AnyNode,
|
|
4
4
|
BuildingNode,
|
|
5
5
|
clearSceneHistory,
|
|
6
|
+
createI18n,
|
|
6
7
|
createZone,
|
|
7
8
|
generateId,
|
|
8
9
|
initSpaceDetectionSync,
|
|
@@ -67,6 +68,7 @@ const scene = (...nodes: AnyNode[]): Record<string, AnyNode> =>
|
|
|
67
68
|
const living = zone('zone_living', 'Living', rect(0, 0, 4, 4))
|
|
68
69
|
const lanai = zone('zone_lanai', 'Lanai', rect(4, 0, 6, 4))
|
|
69
70
|
const keyedLanai = zone('zone_lanai', 'Lanai', rect(4, 0, 6, 4), 'floor_lanai')
|
|
71
|
+
const english = createI18n()
|
|
70
72
|
const length = (meters: number) => `${meters.toFixed(2)} m`
|
|
71
73
|
|
|
72
74
|
describe('Built on: when the picker shows and what it offers', () => {
|
|
@@ -116,7 +118,11 @@ describe('Built on: when the picker shows and what it offers', () => {
|
|
|
116
118
|
expect(model.shared).toBeNull()
|
|
117
119
|
expect(separateFloorOffset(nodes, keyedLanai.id)).toBeNull()
|
|
118
120
|
expect(
|
|
119
|
-
separateFloorSummary(
|
|
121
|
+
separateFloorSummary(
|
|
122
|
+
{ ...model, offset: separateFloorOffset(nodes, keyedLanai.id) },
|
|
123
|
+
length,
|
|
124
|
+
english,
|
|
125
|
+
),
|
|
120
126
|
).toBe('Lanai floor')
|
|
121
127
|
})
|
|
122
128
|
|
|
@@ -159,18 +165,22 @@ describe('Built on: the Floor row summary', () => {
|
|
|
159
165
|
const lanaiFloor = { name: 'Lanai floor' }
|
|
160
166
|
test('says how a separate floor sits against the shared floor', () => {
|
|
161
167
|
const shared = { name: 'Shared floor' }
|
|
162
|
-
expect(separateFloorSummary({ current: lanaiFloor, shared, offset: -0.15 }, length)).toBe(
|
|
163
|
-
'Lanai floor · 0.15 m below Shared floor',
|
|
164
|
-
)
|
|
165
168
|
expect(
|
|
166
|
-
separateFloorSummary({ current: lanaiFloor, shared
|
|
169
|
+
separateFloorSummary({ current: lanaiFloor, shared, offset: -0.15 }, length, english),
|
|
170
|
+
).toBe('Lanai floor · 0.15 m below Shared floor')
|
|
171
|
+
expect(
|
|
172
|
+
separateFloorSummary(
|
|
173
|
+
{ current: lanaiFloor, shared: { name: 'Cabin' }, offset: 0.3 },
|
|
174
|
+
length,
|
|
175
|
+
english,
|
|
176
|
+
),
|
|
167
177
|
).toBe('Lanai floor · 0.30 m above Cabin')
|
|
168
|
-
expect(
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
expect(
|
|
172
|
-
|
|
173
|
-
)
|
|
178
|
+
expect(
|
|
179
|
+
separateFloorSummary({ current: lanaiFloor, shared, offset: 0.002 }, length, english),
|
|
180
|
+
).toBe('Lanai floor · level with Shared floor')
|
|
181
|
+
expect(
|
|
182
|
+
separateFloorSummary({ current: lanaiFloor, shared: null, offset: null }, length, english),
|
|
183
|
+
).toBe('Lanai floor')
|
|
174
184
|
})
|
|
175
185
|
|
|
176
186
|
test("compares the room's walking surface with the shared-floor room beside it, not plate with plate", () => {
|
|
@@ -188,7 +198,11 @@ describe('Built on: the Floor row summary', () => {
|
|
|
188
198
|
expect(separateFloorOffset(nodes, keyedLanai.id)).toBeCloseTo(-0.3)
|
|
189
199
|
const model = roomBuiltOn(nodes, keyedLanai.id)!
|
|
190
200
|
expect(
|
|
191
|
-
separateFloorSummary(
|
|
201
|
+
separateFloorSummary(
|
|
202
|
+
{ ...model, offset: separateFloorOffset(nodes, keyedLanai.id) },
|
|
203
|
+
length,
|
|
204
|
+
english,
|
|
205
|
+
),
|
|
192
206
|
).toBe('Lanai floor · 0.30 m below Living floor')
|
|
193
207
|
// Nothing shared across a wall or open side: the shared plate's top.
|
|
194
208
|
expect(separateFloorOffset(scene(raised, keyedLanai, house, own), keyedLanai.id)).toBeCloseTo(
|
package/src/lib/room-built-on.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AnyNode,
|
|
3
3
|
roomFloorChoices as coreRoomFloorChoices,
|
|
4
|
+
type PascalI18n,
|
|
4
5
|
roomDrawnFloor,
|
|
5
6
|
type SlabNode,
|
|
6
7
|
type ZoneNode,
|
|
@@ -130,9 +131,26 @@ export function roomBuiltOn(nodes: Nodes, zoneId: string): RoomBuiltOnModel | nu
|
|
|
130
131
|
export function separateFloorSummary(
|
|
131
132
|
model: { current: { name: string }; shared: { name: string } | null; offset: number | null },
|
|
132
133
|
length: (meters: number) => string,
|
|
134
|
+
i18n: PascalI18n,
|
|
133
135
|
): string {
|
|
134
136
|
const { current, shared, offset } = model
|
|
135
137
|
if (!shared || offset === null) return current.name
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
const values = { current: current.name, shared: shared.name, distance: length(Math.abs(offset)) }
|
|
139
|
+
if (Math.abs(offset) < 0.005)
|
|
140
|
+
return i18n._({
|
|
141
|
+
id: 'editor.room.builtOn.levelWith',
|
|
142
|
+
message: '{current} · level with {shared}',
|
|
143
|
+
values,
|
|
144
|
+
})
|
|
145
|
+
return offset > 0
|
|
146
|
+
? i18n._({
|
|
147
|
+
id: 'editor.room.builtOn.above',
|
|
148
|
+
message: '{current} · {distance} above {shared}',
|
|
149
|
+
values,
|
|
150
|
+
})
|
|
151
|
+
: i18n._({
|
|
152
|
+
id: 'editor.room.builtOn.below',
|
|
153
|
+
message: '{current} · {distance} below {shared}',
|
|
154
|
+
values,
|
|
155
|
+
})
|
|
138
156
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
area,
|
|
5
5
|
BuildingNode,
|
|
6
6
|
CeilingNode,
|
|
7
|
+
ColumnNode,
|
|
7
8
|
clearSceneHistory,
|
|
8
9
|
createZone,
|
|
9
10
|
DoorNode,
|
|
@@ -25,7 +26,12 @@ import { deleteConfirmationContent } from '../components/editor/delete-confirmat
|
|
|
25
26
|
import { constructionActions, wallsSwitch } from '../components/ui/panels/room-construction-rows'
|
|
26
27
|
import useDeleteConfirmation from '../store/use-delete-confirmation'
|
|
27
28
|
import useEditor from '../store/use-editor'
|
|
28
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
roomClearPolygon,
|
|
31
|
+
roomConstructionState,
|
|
32
|
+
roomFloorContents,
|
|
33
|
+
roomPorchFramingCounts,
|
|
34
|
+
} from './room-construction'
|
|
29
35
|
import {
|
|
30
36
|
addRoomConstruction,
|
|
31
37
|
adoptExistingCeiling,
|
|
@@ -177,6 +183,27 @@ describe('room construction state', () => {
|
|
|
177
183
|
expect(constructionActions(room, 'floor')).toEqual({ add: false, remove: true })
|
|
178
184
|
})
|
|
179
185
|
|
|
186
|
+
test('porch framing counts the room’s generated parts by marker, across node kinds', () => {
|
|
187
|
+
const zone = nodes()[zoneId as AnyNodeId] as ZoneNode
|
|
188
|
+
const parts = [
|
|
189
|
+
ColumnNode.parse({ metadata: { porchOf: zoneId, porchPart: 'corner:0' } }),
|
|
190
|
+
ItemNode.parse({
|
|
191
|
+
...item('item_side_post', 'Post', LEVEL, [0, 0, 0]),
|
|
192
|
+
metadata: { porchOf: zoneId, porchPart: 'side:0:1' },
|
|
193
|
+
}),
|
|
194
|
+
ColumnNode.parse({ metadata: { porchOf: zoneId, porchPart: 'beam:0' } }),
|
|
195
|
+
ColumnNode.parse({ metadata: { porchOf: 'zone_other', porchPart: 'corner:0' } }),
|
|
196
|
+
ColumnNode.parse({}),
|
|
197
|
+
]
|
|
198
|
+
const level = nodes()[LEVEL as AnyNodeId] as LevelNode
|
|
199
|
+
const scene = {
|
|
200
|
+
...nodes(),
|
|
201
|
+
...Object.fromEntries(parts.map((part) => [part.id, part])),
|
|
202
|
+
[level.id]: { ...level, children: [...level.children, ...parts.map((part) => part.id)] },
|
|
203
|
+
}
|
|
204
|
+
expect(roomPorchFramingCounts(scene, zone)).toEqual({ posts: 2, beams: 1 })
|
|
205
|
+
})
|
|
206
|
+
|
|
180
207
|
test('memoized per nodes identity', () => {
|
|
181
208
|
expect(roomConstructionState(nodes(), zoneId)).toBe(roomConstructionState(nodes(), zoneId))
|
|
182
209
|
expect(roomConstructionState(nodes(), 'zone_missing')).toBeNull()
|
|
@@ -383,3 +383,17 @@ export function roomConstructionState(
|
|
|
383
383
|
memo.states.set(zoneId, state)
|
|
384
384
|
return state
|
|
385
385
|
}
|
|
386
|
+
|
|
387
|
+
export function roomPorchFramingCounts(nodes: StructureNodes, zone: ZoneNode) {
|
|
388
|
+
const level = zone.parentId ? nodes[zone.parentId] : undefined
|
|
389
|
+
let posts = 0
|
|
390
|
+
let beams = 0
|
|
391
|
+
for (const id of level?.type === 'level' ? level.children : []) {
|
|
392
|
+
const metadata = nodes[id]?.metadata
|
|
393
|
+
const part = metadata?.porchPart
|
|
394
|
+
if (metadata?.porchOf !== zone.id || typeof part !== 'string') continue
|
|
395
|
+
if (/^(corner|side):/.test(part)) posts++
|
|
396
|
+
else if (part.startsWith('beam:')) beams++
|
|
397
|
+
}
|
|
398
|
+
return { posts, beams }
|
|
399
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { useViewer } from '@pascal-app/viewer'
|
|
18
18
|
import { createElement } from 'react'
|
|
19
19
|
import { roomTransformSource } from '../components/editor/room-controls'
|
|
20
|
+
import { commitDimensionValue } from '../components/editor-2d/renderers/floorplan-dimension-edit-overlay'
|
|
20
21
|
import { getRoomSelectionIndex } from '../hooks/use-selected-room'
|
|
21
22
|
import useEditor from '../store/use-editor'
|
|
22
23
|
import useInteractionScope from '../store/use-interaction-scope'
|
|
@@ -24,6 +25,7 @@ import { installImmediateAnimationFrames } from '../test-utils/immediate-animati
|
|
|
24
25
|
import { withSelectionHarness } from '../test-utils/selection-harness'
|
|
25
26
|
import { useGestureLifecycleOwner } from './gesture-lifecycle'
|
|
26
27
|
import {
|
|
28
|
+
beginWallGroupPush,
|
|
27
29
|
cancelRoomHandleDrag,
|
|
28
30
|
commitRoomElevation,
|
|
29
31
|
commitRoomPush,
|
|
@@ -34,7 +36,10 @@ import {
|
|
|
34
36
|
roomFloorElevation,
|
|
35
37
|
roomPushHandles,
|
|
36
38
|
runRoomHandleDrag,
|
|
39
|
+
selectedWallDimensions,
|
|
40
|
+
useRoomHandleDrag,
|
|
37
41
|
} from './room-handle-drag'
|
|
42
|
+
import { mergeSelectionDimensions } from './room-push-dimensions'
|
|
38
43
|
import { applyRoomPlan, renameRoom } from './room-structure-commands'
|
|
39
44
|
import {
|
|
40
45
|
cancelRoomTransform,
|
|
@@ -376,6 +381,45 @@ describe('room handles (elevation, push/pull)', () => {
|
|
|
376
381
|
expect(useScene.temporal.getState().pastStates).toHaveLength(1)
|
|
377
382
|
})
|
|
378
383
|
|
|
384
|
+
test('a selected wall shows its room width and outside size, and typing either moves the wall', () => {
|
|
385
|
+
const east = () =>
|
|
386
|
+
Object.values(useScene.getState().nodes).find(
|
|
387
|
+
(n): n is WallNode => n.type === 'wall' && n.start[0] === n.end[0] && n.start[0] > 3,
|
|
388
|
+
)!
|
|
389
|
+
const wall = east()
|
|
390
|
+
const thickness = wall.thickness ?? 0.1
|
|
391
|
+
const dimensions = selectedWallDimensions(useScene.getState().nodes, wall, 'both')
|
|
392
|
+
const interior = dimensions.find((d) => d.faces === 'interior')!
|
|
393
|
+
const exterior = dimensions.find((d) => d.faces === 'exterior')!
|
|
394
|
+
// The 6 m room between centrelines: face to face inside, outer face to outer face outside.
|
|
395
|
+
expect(interior.distance).toBeCloseTo(6 - thickness)
|
|
396
|
+
expect(exterior.distance).toBeCloseTo(6 + thickness)
|
|
397
|
+
expect(selectedWallDimensions(useScene.getState().nodes, wall, 'interior')).toEqual([interior])
|
|
398
|
+
expect(selectedWallDimensions(useScene.getState().nodes, wall, 'exterior')).toEqual([exterior])
|
|
399
|
+
|
|
400
|
+
// Type 10' ID: the wall moves out until the room is exactly that wide, one undo step.
|
|
401
|
+
const target = (d: typeof interior) => ({
|
|
402
|
+
ownerNodeId: wall.id as AnyNodeId,
|
|
403
|
+
witnessStart: d.from,
|
|
404
|
+
witnessEnd: d.to,
|
|
405
|
+
value: d.distance,
|
|
406
|
+
})
|
|
407
|
+
expect(commitDimensionValue({ target: target(interior), nextLength: 3.048 })).toEqual({
|
|
408
|
+
ok: true,
|
|
409
|
+
})
|
|
410
|
+
// The west face sits half a thickness inside x = 0; the east centreline half outside its face.
|
|
411
|
+
expect(east().start[0]).toBeCloseTo(3.048 + thickness)
|
|
412
|
+
expect(useScene.temporal.getState().pastStates).toHaveLength(1)
|
|
413
|
+
const after = selectedWallDimensions(useScene.getState().nodes, east(), 'both')
|
|
414
|
+
expect(after.find((d) => d.faces === 'interior')!.distance).toBeCloseTo(3.048)
|
|
415
|
+
|
|
416
|
+
// Type the outside size: the same wall moves so the building reads it.
|
|
417
|
+
const outside = after.find((d) => d.faces === 'exterior')!
|
|
418
|
+
expect(commitDimensionValue({ target: target(outside), nextLength: 5 })).toEqual({ ok: true })
|
|
419
|
+
expect(east().start[0]).toBeCloseTo(5 - thickness)
|
|
420
|
+
expect(useScene.temporal.getState().pastStates).toHaveLength(2)
|
|
421
|
+
})
|
|
422
|
+
|
|
379
423
|
test('a partial span splits off and moves only the room-facing piece, openings stay put', () => {
|
|
380
424
|
// A 10 m north wall, of which this 6 m room faces only 0..0.6; a door
|
|
381
425
|
// on the far stretch must stay where it is.
|
|
@@ -452,6 +496,102 @@ describe('room handles (elevation, push/pull)', () => {
|
|
|
452
496
|
expect(host.start[1]).toBeCloseTo(4)
|
|
453
497
|
expect(useScene.temporal.getState().pastStates).toHaveLength(1)
|
|
454
498
|
})
|
|
499
|
+
|
|
500
|
+
const roomWalls = () => {
|
|
501
|
+
const walls = Object.values(useScene.getState().nodes).filter(
|
|
502
|
+
(n): n is WallNode => n.type === 'wall',
|
|
503
|
+
)
|
|
504
|
+
const at = (axis: 0 | 1, value: number) =>
|
|
505
|
+
walls.find(
|
|
506
|
+
(w) => Math.abs(w.start[axis] - value) < 1e-6 && Math.abs(w.end[axis] - value) < 1e-6,
|
|
507
|
+
)!
|
|
508
|
+
return { east: at(0, 6), west: at(0, 0), north: at(1, 4), south: at(1, 0) }
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
test('two selected walls move as their push arrows: parallel, still joined, one undo step', () => {
|
|
512
|
+
const { east, north, west, south } = roomWalls()
|
|
513
|
+
const push = beginWallGroupPush([east.id, north.id], LEVEL)
|
|
514
|
+
const nodes = useScene.getState().nodes
|
|
515
|
+
// A diagonal drag: each wall takes only the part along its own normal.
|
|
516
|
+
push.preview([1, 0.5])
|
|
517
|
+
expect(useLiveNodeOverrides.getState().overrides.get(east.id as AnyNodeId)).toBeDefined()
|
|
518
|
+
expect(useScene.getState().nodes).toBe(nodes)
|
|
519
|
+
expect(push.commit()).toBe(true)
|
|
520
|
+
const after = useScene.getState().nodes
|
|
521
|
+
const wall = (id: string) => after[id as AnyNodeId] as WallNode
|
|
522
|
+
for (const point of [wall(east.id).start, wall(east.id).end]) expect(point[0]).toBeCloseTo(7)
|
|
523
|
+
for (const point of [wall(north.id).start, wall(north.id).end])
|
|
524
|
+
expect(point[1]).toBeCloseTo(4.5)
|
|
525
|
+
const corner = (a: WallNode, b: WallNode) =>
|
|
526
|
+
[a.start, a.end].some((p) =>
|
|
527
|
+
[b.start, b.end].some((q) => Math.hypot(p[0] - q[0], p[1] - q[1]) < 1e-6),
|
|
528
|
+
)
|
|
529
|
+
// Their own corner, and the walls they meet, stay joined; the others keep their line.
|
|
530
|
+
expect(corner(wall(east.id), wall(north.id))).toBe(true)
|
|
531
|
+
expect(corner(wall(east.id), wall(south.id))).toBe(true)
|
|
532
|
+
expect(corner(wall(north.id), wall(west.id))).toBe(true)
|
|
533
|
+
for (const point of [wall(west.id).start, wall(west.id).end]) expect(point[0]).toBeCloseTo(0)
|
|
534
|
+
for (const point of [wall(south.id).start, wall(south.id).end]) expect(point[1]).toBeCloseTo(0)
|
|
535
|
+
expect(useScene.temporal.getState().pastStates).toHaveLength(1)
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
test("several selected walls show each one's dimensions, a room between two of them once", () => {
|
|
539
|
+
const { east, west, north } = roomWalls()
|
|
540
|
+
const thickness = east.thickness ?? 0.1
|
|
541
|
+
const nodes = useScene.getState().nodes
|
|
542
|
+
const dimensions = mergeSelectionDimensions(
|
|
543
|
+
[east, west, north].map((wall) => ({
|
|
544
|
+
ownerId: wall.id,
|
|
545
|
+
dimensions: selectedWallDimensions(nodes, wall, 'both'),
|
|
546
|
+
})),
|
|
547
|
+
)
|
|
548
|
+
const shown = (faces: 'interior' | 'exterior') =>
|
|
549
|
+
dimensions
|
|
550
|
+
.filter((d) => d.faces === faces)
|
|
551
|
+
.map((d) => [d.ownerId, Number(d.distance.toFixed(4))])
|
|
552
|
+
// East and west both measure the 6 m room and the building across it: once each, on east.
|
|
553
|
+
expect(shown('interior')).toEqual([
|
|
554
|
+
[east.id, Number((6 - thickness).toFixed(4))],
|
|
555
|
+
[north.id, Number((4 - thickness).toFixed(4))],
|
|
556
|
+
])
|
|
557
|
+
expect(shown('exterior')).toEqual([
|
|
558
|
+
[east.id, Number((6 + thickness).toFixed(4))],
|
|
559
|
+
[north.id, Number((4 + thickness).toFixed(4))],
|
|
560
|
+
])
|
|
561
|
+
expect(new Set(dimensions.map((d) => d.key)).size).toBe(dimensions.length)
|
|
562
|
+
|
|
563
|
+
// Typing one moves the wall it runs from.
|
|
564
|
+
const depth = dimensions.find((d) => d.ownerId === north.id && d.faces === 'interior')!
|
|
565
|
+
expect(
|
|
566
|
+
commitDimensionValue({
|
|
567
|
+
target: {
|
|
568
|
+
ownerNodeId: north.id as AnyNodeId,
|
|
569
|
+
witnessStart: depth.from,
|
|
570
|
+
witnessEnd: depth.to,
|
|
571
|
+
value: depth.distance,
|
|
572
|
+
},
|
|
573
|
+
nextLength: 5,
|
|
574
|
+
}),
|
|
575
|
+
).toEqual({ ok: true })
|
|
576
|
+
const moved = useScene.getState().nodes[north.id as AnyNodeId] as WallNode
|
|
577
|
+
expect(moved.start[1]).toBeCloseTo(5 + thickness)
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
test('a multi-wall move shows the room widths live, as a single wall push does', () => {
|
|
581
|
+
const { east, north, west } = roomWalls()
|
|
582
|
+
const thickness = east.thickness ?? 0.1
|
|
583
|
+
const push = beginWallGroupPush([east.id, west.id, north.id], LEVEL)
|
|
584
|
+
push.preview([1, 0.5])
|
|
585
|
+
const drag = useRoomHandleDrag.getState().drag
|
|
586
|
+
expect(drag?.kind).toBe('wall-group-push')
|
|
587
|
+
const widths = (drag?.kind === 'wall-group-push' ? drag.dimensions : []).map((d) => d.distance)
|
|
588
|
+
// East and west both moved 1 m: the 6 m room is still 6 m wide, shown once; north went 0.5 m out.
|
|
589
|
+
expect(widths.filter((w) => Math.abs(w - (6 - thickness)) < 1e-6)).toHaveLength(1)
|
|
590
|
+
expect(widths.some((w) => Math.abs(w - (4.5 - thickness)) < 1e-6)).toBe(true)
|
|
591
|
+
push.cancel()
|
|
592
|
+
expect(useRoomHandleDrag.getState().drag).toBeNull()
|
|
593
|
+
expect(useLiveNodeOverrides.getState().overrides.size).toBe(0)
|
|
594
|
+
})
|
|
455
595
|
})
|
|
456
596
|
|
|
457
597
|
describe('room gesture ownership', () => {
|