@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.
Files changed (136) hide show
  1. package/package.json +7 -7
  2. package/src/components/editor/custom-camera-controls.tsx +90 -34
  3. package/src/components/editor/duplicate-node.ts +2 -1
  4. package/src/components/editor/first-person-controls.tsx +5 -7
  5. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  6. package/src/components/editor/fit-scene-framing.ts +142 -0
  7. package/src/components/editor/floating-action-menu.tsx +4 -2
  8. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  9. package/src/components/editor/floor-region-controls.tsx +2 -2
  10. package/src/components/editor/floorplan-panel.tsx +173 -8
  11. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  12. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  13. package/src/components/editor/footprint-height-handle.tsx +1 -1
  14. package/src/components/editor/group-transform-shared.ts +3 -1
  15. package/src/components/editor/index.tsx +20 -0
  16. package/src/components/editor/measurement-pill.tsx +1 -1
  17. package/src/components/editor/node-arrow-handles.tsx +1 -1
  18. package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -2
  19. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  20. package/src/components/editor/quick-measurement-card.tsx +1 -1
  21. package/src/components/editor/room-controls.tsx +3 -2
  22. package/src/components/editor/room-handles-2d.tsx +2 -1
  23. package/src/components/editor/room-handles.tsx +3 -3
  24. package/src/components/editor/room-labels.tsx +4 -2
  25. package/src/components/editor/site-edge-labels.tsx +6 -2
  26. package/src/components/editor/wall-measurement-label.tsx +30 -14
  27. package/src/components/editor/wall-move-side-handles.tsx +10 -6
  28. package/src/components/editor/wall-region-handles.tsx +2 -2
  29. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  30. package/src/components/editor-2d/floorplan-group-move.tsx +1 -1
  31. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +12 -12
  32. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +1 -2
  33. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  34. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +22 -4
  35. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  36. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
  37. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  38. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  39. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +22 -0
  40. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +14 -2
  41. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  42. package/src/components/systems/roof/roof-edit-system.tsx +30 -19
  43. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  44. package/src/components/tools/distribution/distribution-run-tool.tsx +150 -32
  45. package/src/components/tools/item/move-tool.tsx +1 -6
  46. package/src/components/tools/item/use-placement-coordinator.tsx +1 -1
  47. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
  48. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  49. package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
  50. package/src/components/tools/shared/placement-box.tsx +1 -5
  51. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  52. package/src/components/tools/shared/segment-angle.ts +0 -4
  53. package/src/components/tools/tool-manager.tsx +1 -24
  54. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  55. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  56. package/src/components/ui/controls/slider-control.tsx +18 -6
  57. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  58. package/src/components/ui/helpers/helper-manager.test.tsx +53 -0
  59. package/src/components/ui/helpers/helper-manager.tsx +54 -0
  60. package/src/components/ui/panels/floor-foundation-panel.tsx +1 -1
  61. package/src/components/ui/panels/floor-opening-panel.tsx +1 -1
  62. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  63. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  64. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  65. package/src/components/ui/panels/panel-manager.tsx +4 -2
  66. package/src/components/ui/panels/panel-wrapper.tsx +41 -18
  67. package/src/components/ui/panels/room-construction-rows.tsx +1 -1
  68. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  69. package/src/components/ui/panels/room-panel.tsx +1 -1
  70. package/src/components/ui/right-stack.test.ts +13 -6
  71. package/src/components/ui/right-stack.tsx +21 -10
  72. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
  73. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  74. package/src/components/ui/sidebar/panels/site-panel/index.tsx +1 -8
  75. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  76. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  77. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
  78. package/src/components/viewer/viewer-rooms.tsx +1 -1
  79. package/src/i18n/locales/en/messages.po +856 -124
  80. package/src/i18n/locales/en/messages.ts +1 -1
  81. package/src/i18n/locales/en-XA/messages.ts +1 -1
  82. package/src/i18n/locales/es/messages.po +2087 -257
  83. package/src/i18n/locales/fr/messages.po +2087 -257
  84. package/src/i18n/locales/ja/messages.po +2087 -257
  85. package/src/i18n/locales/zh-CN/messages.po +2087 -257
  86. package/src/index.tsx +74 -47
  87. package/src/lib/contextual-help.ts +6 -0
  88. package/src/lib/elevation-guides.ts +21 -6
  89. package/src/lib/elevator-support.ts +1 -72
  90. package/src/lib/floor-footprints.test.ts +1 -1
  91. package/src/lib/floor-footprints.ts +1 -9
  92. package/src/lib/floor-foundation-support.test.ts +8 -3
  93. package/src/lib/floor-opening-draft.ts +1 -1
  94. package/src/lib/floor-region-session.ts +1 -1
  95. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  96. package/src/lib/gesture-lifecycle.ts +3 -1
  97. package/src/lib/interaction/scope.ts +1 -0
  98. package/src/lib/layer-thickness.ts +1 -1
  99. package/src/lib/level-selection.test.ts +18 -2
  100. package/src/lib/level-selection.ts +31 -8
  101. package/src/lib/linear-display.ts +1 -1
  102. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  103. package/src/lib/measurement-label.test.ts +1 -1
  104. package/src/lib/measurements.test.ts +8 -7
  105. package/src/lib/measurements.ts +1 -218
  106. package/src/lib/node-action-movement.test.tsx +115 -0
  107. package/src/lib/node-action-movement.ts +2 -0
  108. package/src/lib/placement-metadata.ts +4 -0
  109. package/src/lib/point-ask/describe-target.ts +6 -8
  110. package/src/lib/point-ask/region.ts +1 -1
  111. package/src/lib/print-shell-compiler.ts +12 -1
  112. package/src/lib/print-wall-solids.test.ts +11 -0
  113. package/src/lib/print-wall-solids.ts +9 -0
  114. package/src/lib/room-assembly-overlay.ts +1 -1
  115. package/src/lib/room-built-on.test.ts +2 -5
  116. package/src/lib/room-built-on.ts +1 -15
  117. package/src/lib/room-construction-commands.ts +3 -2
  118. package/src/lib/room-controls-phase6.test.ts +1 -1
  119. package/src/lib/room-handle-drag.ts +6 -25
  120. package/src/lib/room-push-dimensions.test.ts +1 -1
  121. package/src/lib/scene-bounds.test.ts +144 -0
  122. package/src/lib/scene-bounds.ts +79 -15
  123. package/src/lib/scene-clipboard.ts +2 -1
  124. package/src/lib/snapping-mode.test.ts +23 -1
  125. package/src/lib/viewer-selection.ts +1 -1
  126. package/src/lib/wall-region-list.ts +3 -1
  127. package/src/lib/window-interaction.test.ts +4 -2
  128. package/src/store/use-editor.tsx +5 -0
  129. package/src/store/use-placement-typing.test.ts +165 -0
  130. package/src/store/use-placement-typing.ts +186 -0
  131. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  132. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  133. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  134. package/src/lib/door-interaction.ts +0 -102
  135. package/src/lib/measurement-label.ts +0 -68
  136. package/src/lib/window-interaction.ts +0 -142
@@ -1,102 +0,0 @@
1
- import {
2
- type AnyNodeId,
3
- type DoorInteractiveState,
4
- isOperationDoorType,
5
- useInteractive,
6
- useScene,
7
- } from '@pascal-app/core'
8
-
9
- export const DOOR_SWING_OPEN_ANGLE = Math.PI / 2
10
- export const DOOR_TOGGLE_ANIMATION_MS = 520
11
-
12
- export { isOperationDoorType }
13
-
14
- type DoorOpenAnimationOptions = {
15
- persist?: boolean
16
- }
17
-
18
- export function getDisplayedDoorValue(
19
- doorId: AnyNodeId,
20
- field: keyof DoorInteractiveState,
21
- nodeValue: number | undefined,
22
- ) {
23
- const interactive = useInteractive.getState()
24
- const runtimeValue = interactive.doors[doorId]?.[field]
25
- if (runtimeValue !== undefined) return runtimeValue
26
-
27
- const queuedValue = interactive.doorAnimations[doorId]?.from
28
- if (queuedValue !== undefined) return queuedValue
29
-
30
- return nodeValue ?? 0
31
- }
32
-
33
- function startDoorOpenAnimation(
34
- doorId: AnyNodeId,
35
- field: keyof DoorInteractiveState,
36
- from: number,
37
- to: number,
38
- options?: DoorOpenAnimationOptions,
39
- ) {
40
- useInteractive.getState().startDoorAnimation(doorId, {
41
- field,
42
- from,
43
- to,
44
- startedAt: null,
45
- durationMs: DOOR_TOGGLE_ANIMATION_MS,
46
- persist: options?.persist ?? true,
47
- })
48
- }
49
-
50
- export function toggleDoorOpenState(doorId: AnyNodeId, options?: DoorOpenAnimationOptions) {
51
- const node = useScene.getState().nodes[doorId]
52
- if (node?.type !== 'door' || node.openingKind === 'opening') return
53
-
54
- if (isOperationDoorType(node.doorType)) {
55
- const currentOpenAmount = getDisplayedDoorValue(doorId, 'operationState', node.operationState)
56
- startDoorOpenAnimation(
57
- doorId,
58
- 'operationState',
59
- currentOpenAmount,
60
- currentOpenAmount >= 0.5 ? 0 : 1,
61
- options,
62
- )
63
- return
64
- }
65
-
66
- const currentSwingAngle = getDisplayedDoorValue(doorId, 'swingAngle', node.swingAngle)
67
- startDoorOpenAnimation(
68
- doorId,
69
- 'swingAngle',
70
- currentSwingAngle,
71
- currentSwingAngle >= DOOR_SWING_OPEN_ANGLE / 2 ? 0 : DOOR_SWING_OPEN_ANGLE,
72
- options,
73
- )
74
- }
75
-
76
- export function closeDoorOpenState(doorId: AnyNodeId, options?: DoorOpenAnimationOptions) {
77
- const node = useScene.getState().nodes[doorId]
78
- if (node?.type !== 'door' || node.openingKind === 'opening') return
79
-
80
- if (isOperationDoorType(node.doorType)) {
81
- const currentOpenAmount = getDisplayedDoorValue(doorId, 'operationState', node.operationState)
82
- startDoorOpenAnimation(doorId, 'operationState', currentOpenAmount, 0, options)
83
- return
84
- }
85
-
86
- const currentSwingAngle = getDisplayedDoorValue(doorId, 'swingAngle', node.swingAngle)
87
- startDoorOpenAnimation(doorId, 'swingAngle', currentSwingAngle, 0, options)
88
- }
89
-
90
- export function openDoorOpenState(doorId: AnyNodeId, options?: DoorOpenAnimationOptions) {
91
- const node = useScene.getState().nodes[doorId]
92
- if (node?.type !== 'door' || node.openingKind === 'opening') return
93
-
94
- if (isOperationDoorType(node.doorType)) {
95
- const currentOpenAmount = getDisplayedDoorValue(doorId, 'operationState', node.operationState)
96
- startDoorOpenAnimation(doorId, 'operationState', currentOpenAmount, 1, options)
97
- return
98
- }
99
-
100
- const currentSwingAngle = getDisplayedDoorValue(doorId, 'swingAngle', node.swingAngle)
101
- startDoorOpenAnimation(doorId, 'swingAngle', currentSwingAngle, DOOR_SWING_OPEN_ANGLE, options)
102
- }
@@ -1,68 +0,0 @@
1
- import { type MeasurementPoint, measurementCentroid, measurementNormal } from '@pascal-app/core'
2
- import { ShapeUtils, Vector2, Vector3 } from 'three'
3
-
4
- type MeasurementPolygonProjection = {
5
- contour: Vector2[]
6
- triangles: number[][]
7
- }
8
-
9
- function projectMeasurementPolygon(
10
- points: readonly MeasurementPoint[],
11
- ): MeasurementPolygonProjection | null {
12
- const normalValue = measurementNormal(points)
13
- const originValue = points[0]
14
- if (!(normalValue && originValue)) return null
15
-
16
- const normal = new Vector3(...normalValue)
17
- const reference = Math.abs(normal.y) < 0.9 ? new Vector3(0, 1, 0) : new Vector3(1, 0, 0)
18
- const tangent = new Vector3().crossVectors(reference, normal).normalize()
19
- const bitangent = new Vector3().crossVectors(normal, tangent).normalize()
20
- const origin = new Vector3(...originValue)
21
- const contour = points.map((point) => {
22
- const relative = new Vector3(...point).sub(origin)
23
- return new Vector2(relative.dot(tangent), relative.dot(bitangent))
24
- })
25
-
26
- return { contour, triangles: ShapeUtils.triangulateShape(contour, []) }
27
- }
28
-
29
- export function triangulateMeasurementPolygon(points: readonly MeasurementPoint[]): number[][] {
30
- return projectMeasurementPolygon(points)?.triangles ?? []
31
- }
32
-
33
- export function measurementPolygonLabelAnchor(
34
- points: readonly MeasurementPoint[],
35
- ): MeasurementPoint | null {
36
- const projection = projectMeasurementPolygon(points)
37
- if (!projection || projection.triangles.length === 0) return measurementCentroid(points)
38
-
39
- let largestTriangle: number[] | null = null
40
- let largestArea = Number.NEGATIVE_INFINITY
41
- for (const triangle of projection.triangles) {
42
- const [firstIndex, secondIndex, thirdIndex] = triangle
43
- const first = firstIndex === undefined ? null : projection.contour[firstIndex]
44
- const second = secondIndex === undefined ? null : projection.contour[secondIndex]
45
- const third = thirdIndex === undefined ? null : projection.contour[thirdIndex]
46
- if (!(first && second && third)) continue
47
- const area = Math.abs(
48
- (second.x - first.x) * (third.y - first.y) - (second.y - first.y) * (third.x - first.x),
49
- )
50
- if (area > largestArea) {
51
- largestArea = area
52
- largestTriangle = triangle
53
- }
54
- }
55
-
56
- if (!largestTriangle) return measurementCentroid(points)
57
- const [firstIndex, secondIndex, thirdIndex] = largestTriangle
58
- const first = firstIndex === undefined ? null : points[firstIndex]
59
- const second = secondIndex === undefined ? null : points[secondIndex]
60
- const third = thirdIndex === undefined ? null : points[thirdIndex]
61
- if (!(first && second && third)) return measurementCentroid(points)
62
-
63
- return [
64
- (first[0] + second[0] + third[0]) / 3,
65
- (first[1] + second[1] + third[1]) / 3,
66
- (first[2] + second[2] + third[2]) / 3,
67
- ]
68
- }
@@ -1,142 +0,0 @@
1
- import {
2
- type AnyNodeId,
3
- type InteractiveState,
4
- useInteractive,
5
- useScene,
6
- type WindowInteractiveState,
7
- } from '@pascal-app/core'
8
-
9
- export const WINDOW_TOGGLE_ANIMATION_MS = 520
10
-
11
- export type WindowOpenAnimationOptions = {
12
- persist?: boolean
13
- }
14
-
15
- export function isOperableWindowType(windowType: string | undefined) {
16
- return (
17
- windowType === 'sliding' ||
18
- windowType === 'casement' ||
19
- windowType === 'awning' ||
20
- windowType === 'hopper' ||
21
- windowType === 'tilt-turn' ||
22
- windowType === 'single-hung' ||
23
- windowType === 'double-hung' ||
24
- windowType === 'louvered'
25
- )
26
- }
27
-
28
- export function getDisplayedWindowValue(
29
- windowId: AnyNodeId,
30
- nodeValue: number | undefined,
31
- field: keyof WindowInteractiveState = 'operationState',
32
- interactive: InteractiveState = useInteractive.getState(),
33
- ) {
34
- const runtimeValue = interactive.windows[windowId]?.[field]
35
- if (runtimeValue !== undefined) return runtimeValue
36
-
37
- const queuedValue = interactive.windowAnimations[windowId]?.[field]?.from
38
- if (queuedValue !== undefined) return queuedValue
39
-
40
- return nodeValue ?? 0
41
- }
42
-
43
- function startWindowOpenAnimation(
44
- windowId: AnyNodeId,
45
- field: keyof WindowInteractiveState,
46
- from: number,
47
- to: number,
48
- options?: WindowOpenAnimationOptions,
49
- ) {
50
- useInteractive.getState().startWindowAnimation(windowId, {
51
- field,
52
- from,
53
- to,
54
- startedAt: null,
55
- durationMs: WINDOW_TOGGLE_ANIMATION_MS,
56
- persist: options?.persist ?? true,
57
- })
58
- }
59
-
60
- export function toggleWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
61
- const node = useScene.getState().nodes[windowId]
62
- if (
63
- node?.type !== 'window' ||
64
- node.openingKind === 'opening' ||
65
- !isOperableWindowType(node.windowType)
66
- ) {
67
- return
68
- }
69
-
70
- const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
71
- startWindowOpenAnimation(
72
- windowId,
73
- 'operationState',
74
- currentOpenAmount,
75
- currentOpenAmount >= 0.5 ? 0 : 1,
76
- options,
77
- )
78
- }
79
-
80
- export function closeWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
81
- const node = useScene.getState().nodes[windowId]
82
- if (
83
- node?.type !== 'window' ||
84
- node.openingKind === 'opening' ||
85
- !isOperableWindowType(node.windowType)
86
- ) {
87
- return
88
- }
89
-
90
- const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
91
- startWindowOpenAnimation(windowId, 'operationState', currentOpenAmount, 0, options)
92
- }
93
-
94
- export function openWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
95
- const node = useScene.getState().nodes[windowId]
96
- if (
97
- node?.type !== 'window' ||
98
- node.openingKind === 'opening' ||
99
- !isOperableWindowType(node.windowType)
100
- ) {
101
- return
102
- }
103
-
104
- const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
105
- startWindowOpenAnimation(windowId, 'operationState', currentOpenAmount, 1, options)
106
- }
107
-
108
- export type WindowShutterField = 'shutterOpen' | 'rollerShutterOpen'
109
-
110
- export function getDisplayedWindowShutterValue(
111
- windowId: AnyNodeId,
112
- field: WindowShutterField,
113
- nodeValue: number | undefined,
114
- state: InteractiveState = useInteractive.getState(),
115
- ): number {
116
- return getDisplayedWindowValue(windowId, nodeValue ?? 1, field, state)
117
- }
118
-
119
- /** Preview both shutter styles together, independently of the sash. */
120
- export function setWindowShuttersOpenState(
121
- windowId: AnyNodeId,
122
- on: boolean,
123
- options?: WindowOpenAnimationOptions,
124
- ): void {
125
- const node = useScene.getState().nodes[windowId]
126
- if (node?.type !== 'window' || node.source) return
127
- const fields: WindowShutterField[] = []
128
- if (node.shutters && node.shutters !== 'none') fields.push('shutterOpen')
129
- if (node.rollerShutter === 'box' || node.rollerShutter === 'concealed')
130
- fields.push('rollerShutterOpen')
131
- if (!fields.length) return
132
- useInteractive.getState().setMechanismPart(windowId, 'shutters', on)
133
- for (const field of fields) {
134
- startWindowOpenAnimation(
135
- windowId,
136
- field,
137
- getDisplayedWindowShutterValue(windowId, field, node[field]),
138
- on ? 1 : 0,
139
- options,
140
- )
141
- }
142
- }