@pascal-app/editor 1.0.7 → 1.1.0
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 +7 -7
- package/src/components/editor/custom-camera-controls.tsx +90 -34
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/first-person-controls.tsx +5 -7
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +4 -2
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +173 -8
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/group-transform-shared.ts +3 -1
- package/src/components/editor/index.tsx +20 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-arrow-handles.tsx +1 -1
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -2
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/room-controls.tsx +3 -2
- package/src/components/editor/room-handles-2d.tsx +2 -1
- package/src/components/editor/room-handles.tsx +3 -3
- package/src/components/editor/room-labels.tsx +4 -2
- package/src/components/editor/site-edge-labels.tsx +6 -2
- package/src/components/editor/wall-measurement-label.tsx +30 -14
- package/src/components/editor/wall-move-side-handles.tsx +10 -6
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
- package/src/components/editor-2d/floorplan-group-move.tsx +1 -1
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +12 -12
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +1 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +22 -4
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +22 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +14 -2
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +150 -32
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/use-placement-coordinator.tsx +1 -1
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/shared/placement-box.tsx +1 -5
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/ui/controls/slider-control.tsx +18 -6
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/helpers/helper-manager.test.tsx +53 -0
- package/src/components/ui/helpers/helper-manager.tsx +54 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +1 -1
- package/src/components/ui/panels/floor-opening-panel.tsx +1 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
- package/src/components/ui/panels/multi-height-mode.tsx +3 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
- package/src/components/ui/panels/panel-manager.tsx +4 -2
- package/src/components/ui/panels/panel-wrapper.tsx +41 -18
- package/src/components/ui/panels/room-construction-rows.tsx +1 -1
- package/src/components/ui/panels/room-openings-field.tsx +1 -1
- package/src/components/ui/panels/room-panel.tsx +1 -1
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +1 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
- package/src/components/viewer/viewer-rooms.tsx +1 -1
- package/src/i18n/locales/en/messages.po +856 -124
- package/src/i18n/locales/en/messages.ts +1 -1
- package/src/i18n/locales/en-XA/messages.ts +1 -1
- package/src/i18n/locales/es/messages.po +2087 -257
- package/src/i18n/locales/fr/messages.po +2087 -257
- package/src/i18n/locales/ja/messages.po +2087 -257
- package/src/i18n/locales/zh-CN/messages.po +2087 -257
- package/src/index.tsx +74 -47
- package/src/lib/contextual-help.ts +6 -0
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +1 -1
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +8 -3
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/layer-thickness.ts +1 -1
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurements.test.ts +8 -7
- package/src/lib/measurements.ts +1 -218
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +2 -5
- package/src/lib/room-built-on.ts +1 -15
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-controls-phase6.test.ts +1 -1
- package/src/lib/room-handle-drag.ts +6 -25
- package/src/lib/room-push-dimensions.test.ts +1 -1
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.ts +2 -1
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/viewer-selection.ts +1 -1
- package/src/lib/wall-region-list.ts +3 -1
- package/src/lib/window-interaction.test.ts +4 -2
- package/src/store/use-editor.tsx +5 -0
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -142
package/src/index.tsx
CHANGED
|
@@ -15,8 +15,12 @@ export {
|
|
|
15
15
|
acquireSceneReadOnlyLease,
|
|
16
16
|
applyScenePatch,
|
|
17
17
|
applySceneSnapshot,
|
|
18
|
+
buildMeasurementAngleArcPoints,
|
|
18
19
|
type CompiledTranslationMessages,
|
|
20
|
+
closeDoorOpenState,
|
|
21
|
+
closeWindowOpenState,
|
|
19
22
|
createI18n,
|
|
23
|
+
cubicMetersToVolumeUnit,
|
|
20
24
|
DEFAULT_CURVED_STAIR_INNER_RADIUS,
|
|
21
25
|
DEFAULT_CURVED_STAIR_SWEEP_ANGLE,
|
|
22
26
|
DEFAULT_SPIRAL_SHOW_CENTER_COLUMN,
|
|
@@ -33,15 +37,36 @@ export {
|
|
|
33
37
|
DEFAULT_STAIR_TYPE,
|
|
34
38
|
DEFAULT_STAIR_WIDTH,
|
|
35
39
|
defaultI18n,
|
|
40
|
+
formatAngleRadians,
|
|
41
|
+
formatAreaLabel,
|
|
36
42
|
formatLength,
|
|
43
|
+
formatLinearMeasurement,
|
|
37
44
|
formatNumber,
|
|
45
|
+
formatVolumeLabel,
|
|
46
|
+
getAreaUnitLabel,
|
|
47
|
+
getDisplayedDoorValue,
|
|
48
|
+
getDisplayedWindowShutterValue,
|
|
49
|
+
getDisplayedWindowValue,
|
|
50
|
+
getLinearUnitLabel,
|
|
38
51
|
getLocale,
|
|
52
|
+
getVolumeUnitLabel,
|
|
53
|
+
isOperableWindowType,
|
|
54
|
+
type LinearUnit,
|
|
55
|
+
linearControlValueToMeters,
|
|
56
|
+
linearUnitToMeters,
|
|
57
|
+
metersToLinearUnit,
|
|
39
58
|
normalizeDecimalInput,
|
|
59
|
+
openDoorOpenState,
|
|
60
|
+
openWindowOpenState,
|
|
40
61
|
type PascalI18n,
|
|
41
62
|
PascalI18nProvider,
|
|
63
|
+
parseLinearMeasurementInput,
|
|
42
64
|
parseLocalizedNumber,
|
|
43
65
|
registerCompiledMessages,
|
|
44
66
|
registerMessages,
|
|
67
|
+
resolveElevatorNodeSupportY,
|
|
68
|
+
resolveElevatorSupportLevelId,
|
|
69
|
+
resolveElevatorSupportY,
|
|
45
70
|
type SceneCommit,
|
|
46
71
|
type SceneCommitListener,
|
|
47
72
|
type SceneCommitOrigin,
|
|
@@ -49,7 +74,10 @@ export {
|
|
|
49
74
|
type SceneNodePatch,
|
|
50
75
|
type ScenePatch,
|
|
51
76
|
type SceneSnapshot,
|
|
77
|
+
separatorFloorElevation,
|
|
52
78
|
setLocale,
|
|
79
|
+
setWindowShuttersOpenState,
|
|
80
|
+
squareMetersToAreaUnit,
|
|
53
81
|
subscribe,
|
|
54
82
|
subscribeSceneCommits,
|
|
55
83
|
type TranslationMessage,
|
|
@@ -59,8 +87,18 @@ export {
|
|
|
59
87
|
useLocale,
|
|
60
88
|
usePascalI18n,
|
|
61
89
|
useScene,
|
|
90
|
+
type WindowOpenAnimationOptions,
|
|
91
|
+
type WindowShutterField,
|
|
62
92
|
} from '@pascal-app/core'
|
|
63
|
-
export {
|
|
93
|
+
export {
|
|
94
|
+
MEASUREMENT_ACTIVE_COLOR,
|
|
95
|
+
MEASUREMENT_DANGLING_COLOR,
|
|
96
|
+
MEASUREMENT_PERSISTENT_COLOR,
|
|
97
|
+
measurementPolygonLabelAnchor,
|
|
98
|
+
measurementPresentationColor,
|
|
99
|
+
triangulateMeasurementPolygon,
|
|
100
|
+
useViewer,
|
|
101
|
+
} from '@pascal-app/viewer'
|
|
64
102
|
export type { EditorProps } from './components/editor'
|
|
65
103
|
export { default as Editor } from './components/editor'
|
|
66
104
|
export { ActionMenuButton } from './components/editor/action-menu-button'
|
|
@@ -188,6 +226,7 @@ export {
|
|
|
188
226
|
} from './components/tools/item/use-placement-coordinator'
|
|
189
227
|
export { MoveRegistryNodeTool } from './components/tools/registry/move-registry-node-tool'
|
|
190
228
|
export {
|
|
229
|
+
type RegistryToolContextValue,
|
|
191
230
|
RegistryToolProvider,
|
|
192
231
|
useRegistryToolContext,
|
|
193
232
|
} from './components/tools/registry-tool-context'
|
|
@@ -207,6 +246,7 @@ export {
|
|
|
207
246
|
resolveLevelConstructionPlane,
|
|
208
247
|
} from './components/tools/shared/horizontal-construction-plane'
|
|
209
248
|
export { PlacementBox } from './components/tools/shared/placement-box'
|
|
249
|
+
export { PlacementCoordinateInput } from './components/tools/shared/placement-coordinate-input'
|
|
210
250
|
export { PlacementDimensionGuides } from './components/tools/shared/placement-dimension-guides'
|
|
211
251
|
// Pointer-decided support surface (deck top vs floor underneath) — the
|
|
212
252
|
// draw tools (wall / fence) ride their grid plane and commit cap on it.
|
|
@@ -222,7 +262,6 @@ export {
|
|
|
222
262
|
type PolygonEditorProps,
|
|
223
263
|
} from './components/tools/shared/polygon-editor'
|
|
224
264
|
export {
|
|
225
|
-
formatAngleRadians,
|
|
226
265
|
getAngleArcToSegmentReference,
|
|
227
266
|
getAngleToSegmentReference,
|
|
228
267
|
getSegmentAngleReferenceAtPoint,
|
|
@@ -264,6 +303,11 @@ export {
|
|
|
264
303
|
type WallPullSnapRadii,
|
|
265
304
|
type WallPullSnapResult,
|
|
266
305
|
} from './components/tools/wall/wall-pull-snap'
|
|
306
|
+
export {
|
|
307
|
+
formatWallDraftHeading,
|
|
308
|
+
resolveTypedWallEnd,
|
|
309
|
+
wallDraftHeadingDegrees,
|
|
310
|
+
} from './components/tools/wall/wall-typed-entry'
|
|
267
311
|
// `ToolbarLeft` / `ToolbarRight` alias `ViewerToolbarLeft` / `ViewerToolbarRight`
|
|
268
312
|
// (same components) under the names headless hosts compose their toolbars with.
|
|
269
313
|
export {
|
|
@@ -476,7 +520,12 @@ export {
|
|
|
476
520
|
resolveCeilingPlanPointSnap,
|
|
477
521
|
} from './lib/ceiling-plan-snap'
|
|
478
522
|
export { EDITOR_LAYER } from './lib/constants'
|
|
479
|
-
export
|
|
523
|
+
export {
|
|
524
|
+
AXIS_MOVE_DRAG_LABEL,
|
|
525
|
+
type ContextualShortcutHint,
|
|
526
|
+
PATH_POINT_DRAG_LABEL,
|
|
527
|
+
ROTATE_HANDLE_DRAG_LABEL,
|
|
528
|
+
} from './lib/contextual-help'
|
|
480
529
|
export {
|
|
481
530
|
CONTEXTUAL_HELP_NODE_EXTENSION_KEY,
|
|
482
531
|
type ContextualHelpNodeExtension,
|
|
@@ -491,11 +540,6 @@ export {
|
|
|
491
540
|
nextContinuation,
|
|
492
541
|
} from './lib/continuation'
|
|
493
542
|
export { canDirectMoveNode } from './lib/direct-manipulation'
|
|
494
|
-
export {
|
|
495
|
-
closeDoorOpenState,
|
|
496
|
-
getDisplayedDoorValue,
|
|
497
|
-
openDoorOpenState,
|
|
498
|
-
} from './lib/door-interaction'
|
|
499
543
|
export { downloadProjectFile } from './lib/download-project-file'
|
|
500
544
|
export { createEditorApi } from './lib/editor-api'
|
|
501
545
|
export {
|
|
@@ -517,12 +561,7 @@ export {
|
|
|
517
561
|
resolveElevationSnapMatch,
|
|
518
562
|
resolveStructuralElevationSnap,
|
|
519
563
|
} from './lib/elevation-guides'
|
|
520
|
-
export {
|
|
521
|
-
resolveCurrentBuildingId,
|
|
522
|
-
resolveElevatorNodeSupportY,
|
|
523
|
-
resolveElevatorSupportLevelId,
|
|
524
|
-
resolveElevatorSupportY,
|
|
525
|
-
} from './lib/elevator-support'
|
|
564
|
+
export { resolveCurrentBuildingId } from './lib/elevator-support'
|
|
526
565
|
export type { ExportTextureUtils } from './lib/export-texture-utils'
|
|
527
566
|
export { getFloatingMenuScale } from './lib/floating-menu-scale'
|
|
528
567
|
export { startOpeningDraft } from './lib/floor-opening-draft'
|
|
@@ -665,6 +704,11 @@ export {
|
|
|
665
704
|
editorFinishScriptHost,
|
|
666
705
|
finishScriptCode,
|
|
667
706
|
} from './lib/geometry-script/finish-scripts'
|
|
707
|
+
export {
|
|
708
|
+
beginGesture,
|
|
709
|
+
type GestureCancelReason,
|
|
710
|
+
type GestureHandle,
|
|
711
|
+
} from './lib/gesture-lifecycle'
|
|
668
712
|
export { exportSceneToGlb, type GlbExportOptions } from './lib/glb-export'
|
|
669
713
|
export {
|
|
670
714
|
type EditorGridEvent,
|
|
@@ -750,10 +794,6 @@ export {
|
|
|
750
794
|
isCreatableMeasurementKind,
|
|
751
795
|
normalizeCreatableMeasurementKind,
|
|
752
796
|
} from './lib/measurement-kind'
|
|
753
|
-
export {
|
|
754
|
-
measurementPolygonLabelAnchor,
|
|
755
|
-
triangulateMeasurementPolygon,
|
|
756
|
-
} from './lib/measurement-label'
|
|
757
797
|
export {
|
|
758
798
|
type LingoUnitSpec,
|
|
759
799
|
lingoUnitSpec,
|
|
@@ -763,26 +803,8 @@ export {
|
|
|
763
803
|
parseMeasurement,
|
|
764
804
|
} from './lib/measurement-parser'
|
|
765
805
|
export {
|
|
766
|
-
buildMeasurementAngleArcPoints,
|
|
767
|
-
cubicMetersToVolumeUnit,
|
|
768
|
-
formatAreaLabel,
|
|
769
|
-
formatLinearMeasurement,
|
|
770
|
-
formatVolumeLabel,
|
|
771
|
-
getAreaUnitLabel,
|
|
772
|
-
getLinearUnitLabel,
|
|
773
|
-
getVolumeUnitLabel,
|
|
774
|
-
type LinearUnit,
|
|
775
|
-
linearControlValueToMeters,
|
|
776
|
-
linearUnitToMeters,
|
|
777
|
-
MEASUREMENT_ACTIVE_COLOR,
|
|
778
|
-
MEASUREMENT_DANGLING_COLOR,
|
|
779
806
|
MEASUREMENT_FLOORPLAN_COLOR,
|
|
780
|
-
MEASUREMENT_PERSISTENT_COLOR,
|
|
781
807
|
measurementFloorplanPresentationColor,
|
|
782
|
-
measurementPresentationColor,
|
|
783
|
-
metersToLinearUnit,
|
|
784
|
-
parseLinearMeasurementInput,
|
|
785
|
-
squareMetersToAreaUnit,
|
|
786
808
|
} from './lib/measurements'
|
|
787
809
|
export type {
|
|
788
810
|
ModelExport,
|
|
@@ -863,7 +885,6 @@ export { clearRoofDuplicateMetadata, duplicateRoofSubtree } from './lib/roof-dup
|
|
|
863
885
|
export { hasRoofFaceChildOverlap, type RoofWallHit, resolveRoofWallHit } from './lib/roof-wall-hit'
|
|
864
886
|
export {
|
|
865
887
|
selectedWallDimensions,
|
|
866
|
-
separatorFloorElevation,
|
|
867
888
|
separatorPushHandles,
|
|
868
889
|
WALL_PUSH_AFFORDANCE,
|
|
869
890
|
type WallPushArrowPayload,
|
|
@@ -1029,16 +1050,6 @@ export {
|
|
|
1029
1050
|
wallTypePlacementOf,
|
|
1030
1051
|
} from './lib/wall-type'
|
|
1031
1052
|
export { writeWindowChanges } from './lib/window-changes'
|
|
1032
|
-
export {
|
|
1033
|
-
closeWindowOpenState,
|
|
1034
|
-
getDisplayedWindowShutterValue,
|
|
1035
|
-
getDisplayedWindowValue,
|
|
1036
|
-
isOperableWindowType,
|
|
1037
|
-
openWindowOpenState,
|
|
1038
|
-
setWindowShuttersOpenState,
|
|
1039
|
-
type WindowOpenAnimationOptions,
|
|
1040
|
-
type WindowShutterField,
|
|
1041
|
-
} from './lib/window-interaction'
|
|
1042
1053
|
export {
|
|
1043
1054
|
getActiveBuildingPose,
|
|
1044
1055
|
projectAlignmentGuidesWorldToActiveBuildingLocal,
|
|
@@ -1161,6 +1172,22 @@ export {
|
|
|
1161
1172
|
default as usePlacementPreview,
|
|
1162
1173
|
type PlacementPreviewDimension,
|
|
1163
1174
|
} from './store/use-placement-preview'
|
|
1175
|
+
export {
|
|
1176
|
+
isPlacementTypingKey,
|
|
1177
|
+
isPlacementTypingStartKey,
|
|
1178
|
+
PLACEMENT_TYPING_DISTANCE_OFFSET,
|
|
1179
|
+
PLACEMENT_TYPING_LENGTH_ANGLE,
|
|
1180
|
+
type PlacementTypingField,
|
|
1181
|
+
type PlacementTypingKeyOptions,
|
|
1182
|
+
type PlacementTypingKeyOutcome,
|
|
1183
|
+
type PlacementTypingProjection,
|
|
1184
|
+
type PlacementTypingSession,
|
|
1185
|
+
type PlacementTypingState,
|
|
1186
|
+
type PlacementTypingValues,
|
|
1187
|
+
placementTypingLengthOptions,
|
|
1188
|
+
routePlacementTypingKey,
|
|
1189
|
+
usePlacementTyping,
|
|
1190
|
+
} from './store/use-placement-typing'
|
|
1164
1191
|
export { usePointAsk } from './store/use-point-ask'
|
|
1165
1192
|
export {
|
|
1166
1193
|
activateQuickMeasurementHudSource,
|
|
@@ -31,6 +31,12 @@ export const RESIZE_HANDLE_DRAG_LABEL = 'resize-handle'
|
|
|
31
31
|
export const GROUP_MOVE_DRAG_LABEL = 'group-move-handle'
|
|
32
32
|
export const GROUP_ROTATE_DRAG_LABEL = 'group-rotate-handle'
|
|
33
33
|
|
|
34
|
+
// `activeHandleDrag.label`s for the MEP handles, in 3D and on the plan: one
|
|
35
|
+
// point of a run, where Alt detaches the joint, and a fitting, a run or one
|
|
36
|
+
// run segment slid along an axis. Their snap context is the kind's own.
|
|
37
|
+
export const PATH_POINT_DRAG_LABEL = 'path-point'
|
|
38
|
+
export const AXIS_MOVE_DRAG_LABEL = 'axis-move'
|
|
39
|
+
|
|
34
40
|
export function resolveRotateHandleHelpHints(altPressed: boolean): ContextualShortcutHint[] {
|
|
35
41
|
return [
|
|
36
42
|
{
|
|
@@ -154,12 +154,27 @@ export function collectElevationSnapTargets(
|
|
|
154
154
|
anchor: center,
|
|
155
155
|
label: 'Wall base',
|
|
156
156
|
})
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
if (node.endHeightOffset) {
|
|
158
|
+
targets.push({
|
|
159
|
+
id: `${node.id}:top-start`,
|
|
160
|
+
elevation: base + getWallEffectiveHeightForNodes(node, nodes, 0),
|
|
161
|
+
anchor: node.start,
|
|
162
|
+
label: 'Wall top start',
|
|
163
|
+
})
|
|
164
|
+
targets.push({
|
|
165
|
+
id: `${node.id}:top-end`,
|
|
166
|
+
elevation: base + getWallEffectiveHeightForNodes(node, nodes, 1),
|
|
167
|
+
anchor: node.end,
|
|
168
|
+
label: 'Wall top end',
|
|
169
|
+
})
|
|
170
|
+
} else {
|
|
171
|
+
targets.push({
|
|
172
|
+
id: `${node.id}:top`,
|
|
173
|
+
elevation: base + getWallEffectiveHeightForNodes(node, nodes, 0.5),
|
|
174
|
+
anchor: center,
|
|
175
|
+
label: 'Wall top',
|
|
176
|
+
})
|
|
177
|
+
}
|
|
163
178
|
continue
|
|
164
179
|
}
|
|
165
180
|
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AnyNode,
|
|
3
|
-
type AnyNodeId,
|
|
4
|
-
type BuildingNode,
|
|
5
|
-
type ElevatorNode,
|
|
6
|
-
type LevelNode,
|
|
7
|
-
spatialGridManager,
|
|
8
|
-
useScene,
|
|
9
|
-
} from '@pascal-app/core'
|
|
10
|
-
|
|
11
|
-
function getBuildingLevels(
|
|
12
|
-
buildingId: string | null | undefined,
|
|
13
|
-
nodes: Record<string, AnyNode>,
|
|
14
|
-
): LevelNode[] {
|
|
15
|
-
if (!buildingId) return []
|
|
16
|
-
const building = nodes[buildingId as AnyNodeId]
|
|
17
|
-
if (building?.type !== 'building') return []
|
|
18
|
-
|
|
19
|
-
return building.children
|
|
20
|
-
.map((childId) => nodes[childId as AnyNodeId])
|
|
21
|
-
.filter((entry): entry is LevelNode => entry?.type === 'level')
|
|
22
|
-
.sort((left, right) => left.level - right.level)
|
|
23
|
-
}
|
|
1
|
+
import type { AnyNode, AnyNodeId, BuildingNode, LevelNode } from '@pascal-app/core'
|
|
24
2
|
|
|
25
3
|
export function resolveCurrentBuildingId({
|
|
26
4
|
buildingId,
|
|
@@ -45,52 +23,3 @@ export function resolveCurrentBuildingId({
|
|
|
45
23
|
|
|
46
24
|
return null
|
|
47
25
|
}
|
|
48
|
-
|
|
49
|
-
export function resolveElevatorSupportLevelId({
|
|
50
|
-
buildingId,
|
|
51
|
-
preferredLevelId,
|
|
52
|
-
}: {
|
|
53
|
-
buildingId: string | null | undefined
|
|
54
|
-
preferredLevelId?: string | null
|
|
55
|
-
}): LevelNode['id'] | null {
|
|
56
|
-
const nodes = useScene.getState().nodes
|
|
57
|
-
const preferred = preferredLevelId ? nodes[preferredLevelId as AnyNodeId] : undefined
|
|
58
|
-
const levels = getBuildingLevels(buildingId, nodes)
|
|
59
|
-
const preferredInBuilding = preferredLevelId
|
|
60
|
-
? levels.find((level) => level.id === preferredLevelId)
|
|
61
|
-
: undefined
|
|
62
|
-
|
|
63
|
-
if (preferredInBuilding) return preferredInBuilding.id
|
|
64
|
-
if (levels.length === 0) return preferred?.type === 'level' ? preferred.id : null
|
|
65
|
-
|
|
66
|
-
return levels[0]?.id ?? null
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function resolveElevatorSupportY({
|
|
70
|
-
buildingId,
|
|
71
|
-
preferredLevelId,
|
|
72
|
-
x,
|
|
73
|
-
z,
|
|
74
|
-
}: {
|
|
75
|
-
buildingId: string | null | undefined
|
|
76
|
-
preferredLevelId?: string | null
|
|
77
|
-
x: number
|
|
78
|
-
z: number
|
|
79
|
-
}): number {
|
|
80
|
-
const levelId = resolveElevatorSupportLevelId({ buildingId, preferredLevelId })
|
|
81
|
-
if (!levelId) return 0
|
|
82
|
-
|
|
83
|
-
return Math.max(0, spatialGridManager.getSlabElevationAt(levelId, x, z))
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function resolveElevatorNodeSupportY(
|
|
87
|
-
node: ElevatorNode,
|
|
88
|
-
position: [number, number, number] = node.position,
|
|
89
|
-
): number {
|
|
90
|
-
return resolveElevatorSupportY({
|
|
91
|
-
buildingId: node.parentId,
|
|
92
|
-
preferredLevelId: node.fromLevelId ?? node.defaultLevelId,
|
|
93
|
-
x: position[0],
|
|
94
|
-
z: position[2],
|
|
95
|
-
})
|
|
96
|
-
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ItemNode,
|
|
12
12
|
initSpaceDetectionSync,
|
|
13
13
|
LevelNode,
|
|
14
|
+
levelFootprints,
|
|
14
15
|
type SceneCommit,
|
|
15
16
|
type SlabNode,
|
|
16
17
|
SlabNode as SlabSchema,
|
|
@@ -29,7 +30,6 @@ import {
|
|
|
29
30
|
footprintsToMoveTogether,
|
|
30
31
|
foundationHeight,
|
|
31
32
|
isFootprintConstructionHit,
|
|
32
|
-
levelFootprints,
|
|
33
33
|
presetPatch,
|
|
34
34
|
roomFloorOwner,
|
|
35
35
|
roomFootprint,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getRoomBaseElevation,
|
|
11
11
|
groundFloorConstruction,
|
|
12
12
|
intersection,
|
|
13
|
+
levelFootprints,
|
|
13
14
|
roomDrawnFloor,
|
|
14
15
|
type SlabNode,
|
|
15
16
|
setFloorFoundation,
|
|
@@ -39,15 +40,6 @@ const ON_GROUND = 0.0005
|
|
|
39
40
|
const isBase = (node: AnyNode | undefined): node is SlabNode =>
|
|
40
41
|
node?.type === 'slab' && node.plateRole === 'base'
|
|
41
42
|
|
|
42
|
-
const footprintArea = (plate: SlabNode) => area([{ outer: plate.polygon, holes: plate.holes }])
|
|
43
|
-
|
|
44
|
-
/** The level's footprints, largest first (ties by id, so the order is stable). */
|
|
45
|
-
export function levelFootprints(nodes: Record<string, AnyNode>, levelId: string): SlabNode[] {
|
|
46
|
-
return Object.values(nodes)
|
|
47
|
-
.filter((node): node is SlabNode => isBase(node) && node.parentId === levelId)
|
|
48
|
-
.sort((a, b) => footprintArea(b) - footprintArea(a) || a.id.localeCompare(b.id))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
43
|
/**
|
|
52
44
|
* The drawn slab a room's floor is: the one it visibly stands on (a legacy
|
|
53
45
|
* floor can show a hand-drawn slab over the one it was taken from), else the
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getLevelElevations,
|
|
12
12
|
initSpaceDetectionSync,
|
|
13
13
|
LevelNode,
|
|
14
|
+
levelFootprints,
|
|
14
15
|
type SlabNode,
|
|
15
16
|
upperFloorHeightControl,
|
|
16
17
|
useScene,
|
|
@@ -30,7 +31,6 @@ import {
|
|
|
30
31
|
footprintHeightMinimum,
|
|
31
32
|
footprintHeightPatch,
|
|
32
33
|
footprintHeightValue,
|
|
33
|
-
levelFootprints,
|
|
34
34
|
presetPatch,
|
|
35
35
|
setFootprintHeight,
|
|
36
36
|
thickFloorAdvice,
|
|
@@ -169,7 +169,6 @@ describe('Upper-floor height drag release (round-final item 6)', () => {
|
|
|
169
169
|
test(`a drag of ${delta} m lands on release`, () => {
|
|
170
170
|
const bedroom = plateOf(UPPER, 'Bedroom')
|
|
171
171
|
const start = footprintHeightValue(nodes(), bedroom)
|
|
172
|
-
const past = useScene.temporal.getState().pastStates.length
|
|
173
172
|
const preview = beginFootprintHeightPreview(bedroom.id)
|
|
174
173
|
expect(preview.preview(start + delta / 2)).toBeNull()
|
|
175
174
|
expect(preview.preview(start + delta)).toBeNull()
|
|
@@ -177,7 +176,13 @@ describe('Upper-floor height drag release (round-final item 6)', () => {
|
|
|
177
176
|
const after = nodes()[bedroom.id] as SlabNode
|
|
178
177
|
expect(after.thickness).toBeCloseTo(bedroom.thickness + delta, 6)
|
|
179
178
|
expect(footprintHeightValue(nodes(), after)).toBeCloseTo(start + delta, 6)
|
|
180
|
-
|
|
179
|
+
useScene.temporal.getState().undo()
|
|
180
|
+
expect(footprintHeightValue(nodes(), nodes()[bedroom.id] as SlabNode)).toBeCloseTo(start, 6)
|
|
181
|
+
useScene.temporal.getState().redo()
|
|
182
|
+
expect(footprintHeightValue(nodes(), nodes()[bedroom.id] as SlabNode)).toBeCloseTo(
|
|
183
|
+
start + delta,
|
|
184
|
+
6,
|
|
185
|
+
)
|
|
181
186
|
})
|
|
182
187
|
}
|
|
183
188
|
})
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
generateId,
|
|
8
8
|
type LevelNode,
|
|
9
9
|
type MultiPolygon,
|
|
10
|
+
roomFloorElevation,
|
|
10
11
|
type StructurePlan,
|
|
11
12
|
structureChangeBatch,
|
|
12
13
|
useScene,
|
|
@@ -31,7 +32,6 @@ import {
|
|
|
31
32
|
import { type FloorRegionPoint, floorRegionSnapTargets } from './floor-region-snap'
|
|
32
33
|
import { beginGesture, type GestureHandle } from './gesture-lifecycle'
|
|
33
34
|
import { resolveRoomAssemblyHeights } from './room-assembly-overlay'
|
|
34
|
-
import { roomFloorElevation } from './room-handle-drag'
|
|
35
35
|
import { sfxEmitter } from './sfx-bus'
|
|
36
36
|
|
|
37
37
|
// "Cut opening": draws an opening on a room's floor (or its ceiling) with the
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ceilingRegionsOwner,
|
|
8
8
|
type MultiPolygon,
|
|
9
9
|
resolveCeilingHeight,
|
|
10
|
+
roomFloorElevation,
|
|
10
11
|
union,
|
|
11
12
|
useScene,
|
|
12
13
|
} from '@pascal-app/core'
|
|
@@ -32,7 +33,6 @@ import { beginGesture, type GestureHandle } from './gesture-lifecycle'
|
|
|
32
33
|
import { hasActivePaintMaterial } from './material-paint'
|
|
33
34
|
import { isPaintErasing, usePaintRegionMode } from './paint-region-mode'
|
|
34
35
|
import { addCeilingRegion, addFloorRegion } from './paint-regions'
|
|
35
|
-
import { roomFloorElevation } from './room-handle-drag'
|
|
36
36
|
|
|
37
37
|
// An outline drawn on one room's floor: a rectangle (press, drag, release; a
|
|
38
38
|
// persistent tool also takes two clicks) or a polygon (click points, close on
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
formatLinearMeasurement,
|
|
3
|
+
type LinearUnit,
|
|
2
4
|
type OpenWallEnd,
|
|
3
5
|
planJoinOpenWallEnd,
|
|
4
6
|
runAsSingleSceneHistoryStep,
|
|
5
7
|
useScene,
|
|
6
8
|
type WallJoinResult,
|
|
7
9
|
} from '@pascal-app/core'
|
|
8
|
-
import { formatLinearMeasurement, type LinearUnit } from '../measurements'
|
|
9
10
|
|
|
10
11
|
const JOIN_REFUSALS: Record<Extract<WallJoinResult, { ok: false }>['reason'], string> = {
|
|
11
12
|
'covered-existing-wall': 'An existing wall already covers this span',
|
|
@@ -33,7 +33,9 @@ export type GestureSpec = {
|
|
|
33
33
|
kind: string
|
|
34
34
|
/**
|
|
35
35
|
* Drops the gesture's own in-flight state (draft, preview, listeners,
|
|
36
|
-
* pending frames).
|
|
36
|
+
* pending frames). Runs once, on cancel only. Undo and redo cancel before
|
|
37
|
+
* they jump, so this must record no history: a gesture that previews
|
|
38
|
+
* through scene writes under paused history reverts them here, then resumes.
|
|
37
39
|
*/
|
|
38
40
|
onCancel: (reason: GestureCancelReason) => void
|
|
39
41
|
/** A scope the owner begins for the gesture and releases only while it is still this one. */
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type AnyNode,
|
|
4
4
|
type AnyNodeId,
|
|
5
5
|
BuildingNode,
|
|
6
|
+
ElevatorNode,
|
|
6
7
|
LevelNode,
|
|
7
8
|
RoofNode,
|
|
8
9
|
useScene,
|
|
@@ -27,10 +28,14 @@ beforeEach(() => {
|
|
|
27
28
|
metadata: { role: 'roof' },
|
|
28
29
|
})
|
|
29
30
|
const roof = RoofNode.parse({ id: 'roof_main', parentId: roofLevel.id })
|
|
30
|
-
const
|
|
31
|
+
const elevator = ElevatorNode.parse({ id: 'elevator_lift', parentId: BUILDING })
|
|
32
|
+
const building = BuildingNode.parse({
|
|
33
|
+
id: BUILDING,
|
|
34
|
+
children: [GROUND, roofLevel.id, elevator.id],
|
|
35
|
+
})
|
|
31
36
|
useScene.setState({
|
|
32
37
|
nodes: Object.fromEntries(
|
|
33
|
-
[building, ground, wall, roofLevel, roof].map((node) => [node.id, node as AnyNode]),
|
|
38
|
+
[building, ground, wall, roofLevel, roof, elevator].map((node) => [node.id, node as AnyNode]),
|
|
34
39
|
),
|
|
35
40
|
rootNodeIds: [BUILDING],
|
|
36
41
|
} as never)
|
|
@@ -54,4 +59,15 @@ describe('selectPlacedNode', () => {
|
|
|
54
59
|
selectedIds: ['wall_a'],
|
|
55
60
|
})
|
|
56
61
|
})
|
|
62
|
+
|
|
63
|
+
test('a building-parented node keeps the floor in view and adopts its building', () => {
|
|
64
|
+
useViewer.getState().resetSelection()
|
|
65
|
+
useViewer.getState().setSelection({ levelId: GROUND })
|
|
66
|
+
selectPlacedNode('elevator_lift' as AnyNodeId)
|
|
67
|
+
expect(useViewer.getState().selection).toMatchObject({
|
|
68
|
+
buildingId: BUILDING,
|
|
69
|
+
levelId: GROUND,
|
|
70
|
+
selectedIds: ['elevator_lift'],
|
|
71
|
+
})
|
|
72
|
+
})
|
|
57
73
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyNodeId, BuildingNode, LevelNode } from '@pascal-app/core'
|
|
1
|
+
import type { AnyNode, AnyNodeId, BuildingNode, LevelNode } from '@pascal-app/core'
|
|
2
2
|
import { findLevelAncestorId, useScene } from '@pascal-app/core'
|
|
3
3
|
import { useViewer } from '@pascal-app/viewer'
|
|
4
4
|
|
|
@@ -30,17 +30,40 @@ export function deleteLevelWithFallbackSelection(levelId: AnyNodeId) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function findBuildingAncestorId(
|
|
34
|
+
nodeId: AnyNodeId,
|
|
35
|
+
nodes: Record<string, AnyNode>,
|
|
36
|
+
): BuildingNode['id'] | null {
|
|
37
|
+
let current: AnyNode | undefined = nodes[nodeId]
|
|
38
|
+
for (let guard = 0; current && guard < 16; guard++) {
|
|
39
|
+
if (current.type === 'building') return current.id
|
|
40
|
+
current = current.parentId ? nodes[current.parentId as AnyNodeId] : undefined
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
/**
|
|
34
46
|
* Selects a node a tool just made, on its own level when that is not the one in view: a roof drawn
|
|
35
47
|
* 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.
|
|
48
|
+
* selected, so selecting it there alone would leave it out of sight. A node parented to the
|
|
49
|
+
* building itself (an elevator) has no level, so the floor in view stays active when it belongs
|
|
50
|
+
* to that building.
|
|
37
51
|
*/
|
|
38
52
|
export function selectPlacedNode(nodeId: AnyNodeId) {
|
|
39
|
-
const
|
|
53
|
+
const { nodes } = useScene.getState()
|
|
54
|
+
const levelId = findLevelAncestorId(nodeId, nodes) as LevelNode['id'] | null
|
|
55
|
+
const buildingId = findBuildingAncestorId(nodeId, nodes)
|
|
40
56
|
const { selection, setSelection } = useViewer.getState()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
const updates: Partial<typeof selection> = { selectedIds: [nodeId] }
|
|
58
|
+
if (buildingId && buildingId !== selection.buildingId) {
|
|
59
|
+
updates.buildingId = buildingId
|
|
60
|
+
updates.levelId =
|
|
61
|
+
levelId ??
|
|
62
|
+
(selection.levelId && findBuildingAncestorId(selection.levelId, nodes) === buildingId
|
|
63
|
+
? selection.levelId
|
|
64
|
+
: null)
|
|
65
|
+
} else if (levelId && levelId !== selection.levelId) {
|
|
66
|
+
updates.levelId = levelId
|
|
67
|
+
}
|
|
68
|
+
setSelection(updates)
|
|
46
69
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createZoneDivisionContext,
|
|
7
7
|
generateId,
|
|
8
8
|
nodeRegistry,
|
|
9
|
+
roomFloorElevation,
|
|
9
10
|
type SlabNode,
|
|
10
11
|
useLiveNodeOverrides,
|
|
11
12
|
useScene,
|
|
@@ -29,7 +30,6 @@ import { roomRelativeFloorHeight, stepRoomFloorElevation } from './room-construc
|
|
|
29
30
|
import {
|
|
30
31
|
commitRoomElevation,
|
|
31
32
|
mezzanineEdgeHandles,
|
|
32
|
-
roomFloorElevation,
|
|
33
33
|
roomPushHandles,
|
|
34
34
|
runFloorplanMezzanineEdge,
|
|
35
35
|
runFloorplanRoomPush,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import { measurementCentroid, pointInPolygon2D } from '@pascal-app/core'
|
|
3
|
-
import { measurementPolygonLabelAnchor, triangulateMeasurementPolygon } from '
|
|
3
|
+
import { measurementPolygonLabelAnchor, triangulateMeasurementPolygon } from '@pascal-app/viewer'
|
|
4
4
|
|
|
5
5
|
describe('measurementPolygonLabelAnchor', () => {
|
|
6
6
|
test('keeps a concave polygon label inside its visible fill', () => {
|