@pascal-app/nodes 1.0.1 → 1.0.3
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/dist/block/definition.d.ts.map +1 -1
- package/dist/block/definition.js +4 -1
- package/dist/block/face-host.d.ts.map +1 -1
- package/dist/block/face-host.js +19 -2
- package/dist/block/hosted-edit.d.ts +13 -0
- package/dist/block/hosted-edit.d.ts.map +1 -0
- package/dist/block/hosted-edit.js +205 -0
- package/dist/block/last-operation.d.ts +1 -1
- package/dist/block/last-operation.d.ts.map +1 -1
- package/dist/block/last-operation.js +6 -4
- package/dist/block/modal-session.d.ts +2 -1
- package/dist/block/modal-session.d.ts.map +1 -1
- package/dist/block/modal-session.js +2 -2
- package/dist/block/selection.d.ts.map +1 -1
- package/dist/block/selection.js +82 -24
- package/dist/block/surface.d.ts +4 -0
- package/dist/block/surface.d.ts.map +1 -0
- package/dist/block/surface.js +54 -0
- package/dist/block/use-block-face-operation.d.ts +2 -2
- package/dist/block/use-block-face-operation.d.ts.map +1 -1
- package/dist/block/use-block-face-operation.js +25 -11
- package/dist/box-vent/definition.d.ts.map +1 -1
- package/dist/box-vent/definition.js +1 -0
- package/dist/building/renderer.d.ts.map +1 -1
- package/dist/building/renderer.js +1 -1
- package/dist/cabinet/definition.d.ts.map +1 -1
- package/dist/cabinet/definition.js +3 -1
- package/dist/cabinet/hosted-resize.d.ts +3 -6
- package/dist/cabinet/hosted-resize.d.ts.map +1 -1
- package/dist/cabinet/hosted-resize.js +9 -153
- package/dist/cabinet/placement-snap.d.ts.map +1 -1
- package/dist/cabinet/placement-snap.js +4 -2
- package/dist/cabinet/surfaces.d.ts +1 -0
- package/dist/cabinet/surfaces.d.ts.map +1 -1
- package/dist/cabinet/surfaces.js +13 -1
- package/dist/cabinet/tool.d.ts.map +1 -1
- package/dist/cabinet/tool.js +8 -4
- package/dist/ceiling/definition.d.ts.map +1 -1
- package/dist/ceiling/definition.js +5 -0
- package/dist/ceiling/renderer.d.ts.map +1 -1
- package/dist/ceiling/renderer.js +3 -2
- package/dist/chimney/definition.d.ts.map +1 -1
- package/dist/chimney/definition.js +1 -0
- package/dist/column/definition.d.ts.map +1 -1
- package/dist/column/definition.js +10 -3
- package/dist/column/floorplan-affordances.d.ts.map +1 -1
- package/dist/column/floorplan-affordances.js +6 -1
- package/dist/column/floorplan.d.ts.map +1 -1
- package/dist/column/floorplan.js +23 -11
- package/dist/column/hosted-resize.d.ts +116 -0
- package/dist/column/hosted-resize.d.ts.map +1 -0
- package/dist/column/hosted-resize.js +9 -0
- package/dist/column/panel.d.ts.map +1 -1
- package/dist/column/panel.js +6 -3
- package/dist/column/renderer.d.ts.map +1 -1
- package/dist/column/renderer.js +25 -103
- package/dist/column/shape.d.ts +22 -0
- package/dist/column/shape.d.ts.map +1 -0
- package/dist/column/shape.js +120 -0
- package/dist/column/surface.d.ts +4 -0
- package/dist/column/surface.d.ts.map +1 -0
- package/dist/column/surface.js +66 -0
- package/dist/cupola/definition.d.ts.map +1 -1
- package/dist/cupola/definition.js +1 -0
- package/dist/door/definition.d.ts.map +1 -1
- package/dist/door/definition.js +40 -1
- package/dist/door/door-math.d.ts +1 -1
- package/dist/door/door-math.d.ts.map +1 -1
- package/dist/door/door-math.js +7 -10
- package/dist/door/floorplan-affordances.d.ts.map +1 -1
- package/dist/door/floorplan-affordances.js +8 -12
- package/dist/door/move-tool.d.ts.map +1 -1
- package/dist/door/move-tool.js +3 -4
- package/dist/door/panel.d.ts.map +1 -1
- package/dist/door/panel.js +23 -52
- package/dist/door/tool.d.ts.map +1 -1
- package/dist/door/tool.js +20 -2
- package/dist/dormer/definition.d.ts.map +1 -1
- package/dist/dormer/definition.js +1 -0
- package/dist/downspout/definition.d.ts.map +1 -1
- package/dist/downspout/definition.js +1 -0
- package/dist/duct-segment/tool.d.ts.map +1 -1
- package/dist/duct-segment/tool.js +11 -0
- package/dist/duct-terminal/tool.d.ts.map +1 -1
- package/dist/duct-terminal/tool.js +77 -8
- package/dist/elevator/definition.d.ts.map +1 -1
- package/dist/elevator/definition.js +2 -0
- package/dist/elevator/parametrics.d.ts.map +1 -1
- package/dist/elevator/parametrics.js +98 -1
- package/dist/eyebrow-vent/definition.d.ts.map +1 -1
- package/dist/eyebrow-vent/definition.js +1 -0
- package/dist/fence/definition.d.ts.map +1 -1
- package/dist/fence/definition.js +1 -0
- package/dist/fence/tool.js +2 -2
- package/dist/guide/definition.d.ts.map +1 -1
- package/dist/guide/definition.js +1 -0
- package/dist/gutter/definition.d.ts.map +1 -1
- package/dist/gutter/definition.js +1 -0
- package/dist/hvac-equipment/tool.d.ts.map +1 -1
- package/dist/hvac-equipment/tool.js +8 -2
- package/dist/item/__fixtures__/main-floorplan.d.ts +26 -0
- package/dist/item/__fixtures__/main-floorplan.d.ts.map +1 -0
- package/dist/item/__fixtures__/main-floorplan.js +297 -0
- package/dist/item/definition.d.ts.map +1 -1
- package/dist/item/definition.js +3 -1
- package/dist/item/floorplan-move.d.ts.map +1 -1
- package/dist/item/floorplan-move.js +209 -130
- package/dist/item/floorplan.d.ts +22 -0
- package/dist/item/floorplan.d.ts.map +1 -1
- package/dist/item/floorplan.js +61 -2
- package/dist/item/move-tool.d.ts +1 -1
- package/dist/item/move-tool.d.ts.map +1 -1
- package/dist/item/move-tool.js +15 -7
- package/dist/item/renderer.d.ts.map +1 -1
- package/dist/item/renderer.js +8 -2
- package/dist/level/renderer.js +1 -1
- package/dist/lineset/tool.d.ts.map +1 -1
- package/dist/lineset/tool.js +1 -0
- package/dist/liquid-line/tool.d.ts.map +1 -1
- package/dist/liquid-line/tool.js +5 -1
- package/dist/measurement/definition.d.ts.map +1 -1
- package/dist/measurement/definition.js +1 -0
- package/dist/pipe-segment/tool.d.ts.map +1 -1
- package/dist/pipe-segment/tool.js +14 -3
- package/dist/pipe-trap/tool.d.ts.map +1 -1
- package/dist/pipe-trap/tool.js +7 -8
- package/dist/procedural-item/definition.d.ts.map +1 -1
- package/dist/procedural-item/definition.js +4 -1
- package/dist/ridge-vent/definition.d.ts.map +1 -1
- package/dist/ridge-vent/definition.js +1 -0
- package/dist/roof/definition.d.ts.map +1 -1
- package/dist/roof/definition.js +4 -1
- package/dist/roof/panel-model.d.ts +4 -0
- package/dist/roof/panel-model.d.ts.map +1 -0
- package/dist/roof/panel-model.js +151 -0
- package/dist/roof/panel.d.ts.map +1 -1
- package/dist/roof/panel.js +3 -4
- package/dist/roof/parametrics.d.ts.map +1 -1
- package/dist/roof/parametrics.js +16 -1
- package/dist/roof-segment/definition.d.ts.map +1 -1
- package/dist/roof-segment/definition.js +1 -0
- package/dist/roof-segment/parametrics.d.ts.map +1 -1
- package/dist/roof-segment/parametrics.js +176 -1
- package/dist/scan/definition.d.ts.map +1 -1
- package/dist/scan/definition.js +1 -0
- package/dist/shared/commit-opening-move.d.ts +4 -0
- package/dist/shared/commit-opening-move.d.ts.map +1 -0
- package/dist/shared/commit-opening-move.js +9 -0
- package/dist/shared/curtain-opening-limits.d.ts +14 -0
- package/dist/shared/curtain-opening-limits.d.ts.map +1 -0
- package/dist/shared/curtain-opening-limits.js +53 -0
- package/dist/shared/draft-axis-guides.d.ts +0 -6
- package/dist/shared/draft-axis-guides.d.ts.map +1 -1
- package/dist/shared/draft-axis-guides.js +1 -8
- package/dist/shared/hosted-resize.d.ts +12 -0
- package/dist/shared/hosted-resize.d.ts.map +1 -0
- package/dist/shared/hosted-resize.js +166 -0
- package/dist/shared/opening-grid-follow.d.ts +8 -0
- package/dist/shared/opening-grid-follow.d.ts.map +1 -0
- package/dist/shared/opening-grid-follow.js +12 -0
- package/dist/shared/opening-property-preview-host.d.ts +3 -0
- package/dist/shared/opening-property-preview-host.d.ts.map +1 -0
- package/dist/shared/opening-property-preview-host.js +21 -0
- package/dist/shared/opening-property-preview.d.ts +19 -0
- package/dist/shared/opening-property-preview.d.ts.map +1 -0
- package/dist/shared/opening-property-preview.js +99 -0
- package/dist/shared/resting-surface-plan.d.ts +2 -1
- package/dist/shared/resting-surface-plan.d.ts.map +1 -1
- package/dist/shared/resting-surface-plan.js +29 -4
- package/dist/shared/surface-counterparts.d.ts +12 -0
- package/dist/shared/surface-counterparts.d.ts.map +1 -0
- package/dist/shared/surface-counterparts.js +27 -0
- package/dist/shared/wall-attach-target.d.ts +1 -0
- package/dist/shared/wall-attach-target.d.ts.map +1 -1
- package/dist/shared/wall-attach-target.js +4 -1
- package/dist/shared/wall-event-from-grid.d.ts +5 -0
- package/dist/shared/wall-event-from-grid.d.ts.map +1 -0
- package/dist/shared/wall-event-from-grid.js +36 -0
- package/dist/shelf/definition.d.ts.map +1 -1
- package/dist/shelf/definition.js +3 -1
- package/dist/shelf/floorplan.d.ts.map +1 -1
- package/dist/shelf/floorplan.js +9 -2
- package/dist/site/renderer.d.ts.map +1 -1
- package/dist/site/renderer.js +9 -2
- package/dist/skylight/definition.d.ts.map +1 -1
- package/dist/skylight/definition.js +1 -0
- package/dist/slab/definition.d.ts.map +1 -1
- package/dist/slab/definition.js +4 -0
- package/dist/solar-panel/definition.d.ts.map +1 -1
- package/dist/solar-panel/definition.js +1 -0
- package/dist/spawn/definition.d.ts.map +1 -1
- package/dist/spawn/definition.js +1 -0
- package/dist/stair/definition.d.ts.map +1 -1
- package/dist/stair/definition.js +2 -0
- package/dist/stair/parametrics.d.ts.map +1 -1
- package/dist/stair/parametrics.js +164 -1
- package/dist/stair-segment/definition.d.ts.map +1 -1
- package/dist/stair-segment/definition.js +1 -0
- package/dist/stair-segment/parametrics.d.ts.map +1 -1
- package/dist/stair-segment/parametrics.js +81 -1
- package/dist/turbine-vent/definition.d.ts.map +1 -1
- package/dist/turbine-vent/definition.js +1 -0
- package/dist/wall/actions.d.ts +3 -0
- package/dist/wall/actions.d.ts.map +1 -0
- package/dist/wall/actions.js +56 -0
- package/dist/wall/curtain-opening-frame.d.ts +14 -0
- package/dist/wall/curtain-opening-frame.d.ts.map +1 -0
- package/dist/wall/curtain-opening-frame.js +111 -0
- package/dist/wall/curtain-panel-highlight.d.ts +3 -0
- package/dist/wall/curtain-panel-highlight.d.ts.map +1 -0
- package/dist/wall/curtain-panel-highlight.js +113 -0
- package/dist/wall/curtain-wall-adapter.d.ts +3 -0
- package/dist/wall/curtain-wall-adapter.d.ts.map +1 -0
- package/dist/wall/curtain-wall-adapter.js +55 -0
- package/dist/wall/curtain-wall-floorplan.d.ts +3 -0
- package/dist/wall/curtain-wall-floorplan.d.ts.map +1 -0
- package/dist/wall/curtain-wall-floorplan.js +91 -0
- package/dist/wall/curtain-wall-geometry.d.ts +4 -0
- package/dist/wall/curtain-wall-geometry.d.ts.map +1 -0
- package/dist/wall/curtain-wall-geometry.js +226 -0
- package/dist/wall/curtain-wall-layout.d.ts +16 -0
- package/dist/wall/curtain-wall-layout.d.ts.map +1 -0
- package/dist/wall/curtain-wall-layout.js +176 -0
- package/dist/wall/curtain-wall-materials.d.ts +5 -0
- package/dist/wall/curtain-wall-materials.d.ts.map +1 -0
- package/dist/wall/curtain-wall-materials.js +55 -0
- package/dist/wall/curtain-wall-panel.d.ts +14 -0
- package/dist/wall/curtain-wall-panel.d.ts.map +1 -0
- package/dist/wall/curtain-wall-panel.js +167 -0
- package/dist/wall/curtain-wall-piece-geometry.d.ts +5 -0
- package/dist/wall/curtain-wall-piece-geometry.d.ts.map +1 -0
- package/dist/wall/curtain-wall-piece-geometry.js +220 -0
- package/dist/wall/curtain-wall-shadow.d.ts +4 -0
- package/dist/wall/curtain-wall-shadow.d.ts.map +1 -0
- package/dist/wall/curtain-wall-shadow.js +15 -0
- package/dist/wall/definition.d.ts +0 -15
- package/dist/wall/definition.d.ts.map +1 -1
- package/dist/wall/definition.js +65 -4
- package/dist/wall/drawing-mode.d.ts +12 -0
- package/dist/wall/drawing-mode.d.ts.map +1 -0
- package/dist/wall/drawing-mode.js +38 -0
- package/dist/wall/floorplan-tool.d.ts +4 -0
- package/dist/wall/floorplan-tool.d.ts.map +1 -0
- package/dist/wall/floorplan-tool.js +107 -0
- package/dist/wall/floorplan.d.ts.map +1 -1
- package/dist/wall/floorplan.js +6 -2
- package/dist/wall/length-patch.d.ts +3 -0
- package/dist/wall/length-patch.d.ts.map +1 -0
- package/dist/wall/length-patch.js +14 -0
- package/dist/wall/move-tool.d.ts.map +1 -1
- package/dist/wall/move-tool.js +23 -1
- package/dist/wall/paint.d.ts.map +1 -1
- package/dist/wall/paint.js +39 -4
- package/dist/wall/panel-model.d.ts +4 -0
- package/dist/wall/panel-model.d.ts.map +1 -0
- package/dist/wall/panel-model.js +117 -0
- package/dist/wall/panel.d.ts.map +1 -1
- package/dist/wall/panel.js +20 -24
- package/dist/wall/property-preview.d.ts +7 -0
- package/dist/wall/property-preview.d.ts.map +1 -0
- package/dist/wall/property-preview.js +31 -0
- package/dist/wall/rectangle-command.d.ts +149 -0
- package/dist/wall/rectangle-command.d.ts.map +1 -0
- package/dist/wall/rectangle-command.js +22 -0
- package/dist/wall/rectangle-tool.d.ts +2 -0
- package/dist/wall/rectangle-tool.d.ts.map +1 -0
- package/dist/wall/rectangle-tool.js +147 -0
- package/dist/wall/renderer.d.ts.map +1 -1
- package/dist/wall/renderer.js +7 -4
- package/dist/wall/slots.d.ts +2 -2
- package/dist/wall/slots.d.ts.map +1 -1
- package/dist/wall/slots.js +10 -2
- package/dist/wall/split-floorplan-layer.d.ts +4 -0
- package/dist/wall/split-floorplan-layer.d.ts.map +1 -0
- package/dist/wall/split-floorplan-layer.js +71 -0
- package/dist/wall/split-pointer.d.ts +3 -0
- package/dist/wall/split-pointer.d.ts.map +1 -0
- package/dist/wall/split-pointer.js +95 -0
- package/dist/wall/split-preview.d.ts +55 -0
- package/dist/wall/split-preview.d.ts.map +1 -0
- package/dist/wall/split-preview.js +106 -0
- package/dist/wall/split-session.d.ts +16 -0
- package/dist/wall/split-session.d.ts.map +1 -0
- package/dist/wall/split-session.js +138 -0
- package/dist/wall/split-store.d.ts +16 -0
- package/dist/wall/split-store.d.ts.map +1 -0
- package/dist/wall/split-store.js +6 -0
- package/dist/wall/split-tool.d.ts +10 -0
- package/dist/wall/split-tool.d.ts.map +1 -0
- package/dist/wall/split-tool.js +95 -0
- package/dist/wall/system.d.ts.map +1 -1
- package/dist/wall/system.js +3 -1
- package/dist/wall/tool.d.ts.map +1 -1
- package/dist/wall/tool.js +14 -6
- package/dist/wall/wall-batch-system.d.ts.map +1 -1
- package/dist/wall/wall-batch-system.js +3 -0
- package/dist/window/definition.d.ts.map +1 -1
- package/dist/window/definition.js +48 -1
- package/dist/window/floorplan-affordances.d.ts.map +1 -1
- package/dist/window/floorplan-affordances.js +8 -10
- package/dist/window/move-tool.d.ts.map +1 -1
- package/dist/window/move-tool.js +4 -6
- package/dist/window/panel.d.ts.map +1 -1
- package/dist/window/panel.js +21 -59
- package/dist/window/tool.d.ts.map +1 -1
- package/dist/window/tool.js +34 -2
- package/dist/window/window-math.d.ts +1 -1
- package/dist/window/window-math.d.ts.map +1 -1
- package/dist/window/window-math.js +8 -11
- package/dist/zone/definition.d.ts.map +1 -1
- package/dist/zone/definition.js +1 -0
- package/dist/zone/quantities-panel.d.ts.map +1 -1
- package/dist/zone/quantities-panel.js +3 -19
- package/dist/zone/renderer.d.ts.map +1 -1
- package/dist/zone/renderer.js +1 -1
- package/package.json +8 -8
- package/dist/shared/opening-documentation-fields.d.ts +0 -16
- package/dist/shared/opening-documentation-fields.d.ts.map +0 -1
- package/dist/shared/opening-documentation-fields.js +0 -54
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { getWallCurveLength, planWallDivisions, runAsSingleSceneHistoryStep, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { isGridSnapActive, isMagneticSnapActive, triggerSFX, useEditor, useInteractionScope, } from '@pascal-app/editor';
|
|
3
|
+
import { snapWallSplitDistance, WALL_SPLIT_MAX_CUTS, wallSplitAnchors, wallSplitDistances, wallSplitPreview, } from './split-preview';
|
|
4
|
+
import { useWallSplit } from './split-store';
|
|
5
|
+
// Pointer distance (m) within which a 'lines' alignment target catches the cut.
|
|
6
|
+
const ALIGNMENT_TOLERANCE = 0.2;
|
|
7
|
+
const ownsSplit = () => {
|
|
8
|
+
const { scope } = useInteractionScope.getState();
|
|
9
|
+
return scope.kind === 'reshaping' && scope.reshape === 'split';
|
|
10
|
+
};
|
|
11
|
+
const currentWall = (draft) => {
|
|
12
|
+
const wall = useScene.getState().nodes[draft.wallId];
|
|
13
|
+
return wall?.type === 'wall' ? wall : null;
|
|
14
|
+
};
|
|
15
|
+
const withPreview = (draft, wall) => ({
|
|
16
|
+
...draft,
|
|
17
|
+
preview: wallSplitPreview(useScene.getState().nodes, wall, wallSplitDistances(getWallCurveLength(wall), draft.cuts, draft.distance)),
|
|
18
|
+
});
|
|
19
|
+
let teardown = null;
|
|
20
|
+
/**
|
|
21
|
+
* A split session: the wall's `reshaping` scope (so the snapping chip and the
|
|
22
|
+
* kind's HUD hints resolve), one centred cut, and the watchers that end it —
|
|
23
|
+
* Esc, the wall or its edit rights going away, another interaction, leaving
|
|
24
|
+
* select mode. Both views render the same draft; either can commit.
|
|
25
|
+
*/
|
|
26
|
+
export function openWallSplit(wall) {
|
|
27
|
+
if (useScene.getState().readOnly)
|
|
28
|
+
return;
|
|
29
|
+
closeWallSplit();
|
|
30
|
+
useEditor.getState().setMode('select');
|
|
31
|
+
useInteractionScope
|
|
32
|
+
.getState()
|
|
33
|
+
.begin({ kind: 'reshaping', nodeId: wall.id, reshape: 'split', driver: 'tool' });
|
|
34
|
+
useWallSplit
|
|
35
|
+
.getState()
|
|
36
|
+
.setDraft(withPreview({ wallId: wall.id, cuts: 1, distance: getWallCurveLength(wall) / 2, snap: null }, wall));
|
|
37
|
+
const onKey = (event) => {
|
|
38
|
+
if (event.key !== 'Escape')
|
|
39
|
+
return;
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
event.stopImmediatePropagation();
|
|
42
|
+
closeWallSplit();
|
|
43
|
+
};
|
|
44
|
+
const stopScene = useScene.subscribe((state, previous) => {
|
|
45
|
+
if (state.readOnly || state.nodes[wall.id]?.type !== 'wall')
|
|
46
|
+
closeWallSplit();
|
|
47
|
+
else if (state.nodes !== previous.nodes)
|
|
48
|
+
refreshWallSplit();
|
|
49
|
+
});
|
|
50
|
+
const stopScope = useInteractionScope.subscribe(({ scope }) => {
|
|
51
|
+
if (!(scope.kind === 'reshaping' && scope.reshape === 'split'))
|
|
52
|
+
closeWallSplit();
|
|
53
|
+
});
|
|
54
|
+
const stopEditor = useEditor.subscribe((state) => {
|
|
55
|
+
if (state.mode !== 'select' ||
|
|
56
|
+
state.isCaptureMode ||
|
|
57
|
+
state.isPreviewMode ||
|
|
58
|
+
state.isFirstPersonMode)
|
|
59
|
+
closeWallSplit();
|
|
60
|
+
});
|
|
61
|
+
const hasWindow = typeof window !== 'undefined';
|
|
62
|
+
if (hasWindow)
|
|
63
|
+
window.addEventListener('keydown', onKey, true);
|
|
64
|
+
teardown = () => {
|
|
65
|
+
if (hasWindow)
|
|
66
|
+
window.removeEventListener('keydown', onKey, true);
|
|
67
|
+
stopScene();
|
|
68
|
+
stopScope();
|
|
69
|
+
stopEditor();
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function closeWallSplit() {
|
|
73
|
+
const stop = teardown;
|
|
74
|
+
teardown = null;
|
|
75
|
+
stop?.();
|
|
76
|
+
useWallSplit.getState().setDraft(null);
|
|
77
|
+
useInteractionScope
|
|
78
|
+
.getState()
|
|
79
|
+
.endIf((scope) => scope.kind === 'reshaping' && scope.reshape === 'split');
|
|
80
|
+
}
|
|
81
|
+
/** Pointer projected onto the wall; Alt (`free`) skips snapping. */
|
|
82
|
+
export function hoverWallSplit(raw, free = false) {
|
|
83
|
+
const draft = useWallSplit.getState().draft;
|
|
84
|
+
// Several cuts stay evenly spaced, so the pointer has nothing to move.
|
|
85
|
+
if (!draft || draft.cuts > 1 || !ownsSplit())
|
|
86
|
+
return;
|
|
87
|
+
const wall = currentWall(draft);
|
|
88
|
+
if (!wall)
|
|
89
|
+
return closeWallSplit();
|
|
90
|
+
const { distance, snap } = free
|
|
91
|
+
? { distance: raw, snap: null }
|
|
92
|
+
: snapWallSplitDistance(wall, raw, {
|
|
93
|
+
gridStep: isGridSnapActive() ? useEditor.getState().gridSnapStep : null,
|
|
94
|
+
anchors: isMagneticSnapActive() ? wallSplitAnchors(useScene.getState().nodes, wall) : null,
|
|
95
|
+
tolerance: ALIGNMENT_TOLERANCE,
|
|
96
|
+
});
|
|
97
|
+
if (distance === draft.distance && snap?.kind === draft.snap?.kind)
|
|
98
|
+
return;
|
|
99
|
+
useWallSplit.getState().setDraft(withPreview({ ...draft, distance, snap }, wall));
|
|
100
|
+
}
|
|
101
|
+
export function setWallSplitCuts(cuts) {
|
|
102
|
+
const draft = useWallSplit.getState().draft;
|
|
103
|
+
if (!draft || !ownsSplit())
|
|
104
|
+
return;
|
|
105
|
+
const next = Math.min(WALL_SPLIT_MAX_CUTS, Math.max(1, Math.round(cuts)));
|
|
106
|
+
if (next === draft.cuts)
|
|
107
|
+
return;
|
|
108
|
+
const wall = currentWall(draft);
|
|
109
|
+
if (!wall)
|
|
110
|
+
return closeWallSplit();
|
|
111
|
+
useWallSplit.getState().setDraft(withPreview({ ...draft, cuts: next }, wall));
|
|
112
|
+
}
|
|
113
|
+
/** Re-plan against the current scene (after an external edit). */
|
|
114
|
+
export function refreshWallSplit() {
|
|
115
|
+
const draft = useWallSplit.getState().draft;
|
|
116
|
+
if (!draft)
|
|
117
|
+
return;
|
|
118
|
+
const wall = currentWall(draft);
|
|
119
|
+
if (!wall)
|
|
120
|
+
return closeWallSplit();
|
|
121
|
+
useWallSplit.getState().setDraft(withPreview(draft, wall));
|
|
122
|
+
}
|
|
123
|
+
export function commitWallSplit() {
|
|
124
|
+
const draft = useWallSplit.getState().draft;
|
|
125
|
+
const current = useScene.getState();
|
|
126
|
+
if (!draft || current.readOnly || !ownsSplit())
|
|
127
|
+
return;
|
|
128
|
+
const wall = currentWall(draft);
|
|
129
|
+
if (!wall)
|
|
130
|
+
return closeWallSplit();
|
|
131
|
+
const checked = withPreview(draft, wall);
|
|
132
|
+
if (!checked.preview.valid)
|
|
133
|
+
return useWallSplit.getState().setDraft(checked);
|
|
134
|
+
const plan = planWallDivisions(current.nodes, wall.id, checked.preview.distances);
|
|
135
|
+
runAsSingleSceneHistoryStep(useScene, () => current.applyNodeChanges(plan.changes));
|
|
136
|
+
closeWallSplit();
|
|
137
|
+
triggerSFX('sfx:structure-build');
|
|
138
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WallNode } from '@pascal-app/core';
|
|
2
|
+
import type { WallSplitPreview, WallSplitSnap } from './split-preview';
|
|
3
|
+
export type WallSplitDraft = {
|
|
4
|
+
wallId: WallNode['id'];
|
|
5
|
+
cuts: number;
|
|
6
|
+
/** The single cut's snapped distance from the wall start; more cuts are evenly spaced. */
|
|
7
|
+
distance: number;
|
|
8
|
+
snap: WallSplitSnap;
|
|
9
|
+
preview: WallSplitPreview;
|
|
10
|
+
};
|
|
11
|
+
/** The open split's cut preview. Every transition lives in `split-session.ts`. */
|
|
12
|
+
export declare const useWallSplit: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
13
|
+
draft: WallSplitDraft | null;
|
|
14
|
+
setDraft: (draft: WallSplitDraft | null) => void;
|
|
15
|
+
}>>;
|
|
16
|
+
//# sourceMappingURL=split-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-store.d.ts","sourceRoot":"","sources":["../../src/wall/split-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEtE,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,YAAY;WAChB,cAAc,GAAG,IAAI;cAClB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,KAAK,IAAI;GAI/C,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AnyNode } from '@pascal-app/core';
|
|
2
|
+
/**
|
|
3
|
+
* The wall's `split` affordance (3D): cut markers, segment lengths and the
|
|
4
|
+
* pointer binding. The session (`split-session.ts`) owns the scope and draft;
|
|
5
|
+
* the plan layer renders the same draft.
|
|
6
|
+
*/
|
|
7
|
+
export default function WallSplitTool(_props: {
|
|
8
|
+
node: AnyNode;
|
|
9
|
+
}): import("react").JSX.Element | null;
|
|
10
|
+
//# sourceMappingURL=split-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-tool.d.ts","sourceRoot":"","sources":["../../src/wall/split-tool.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAA6C,MAAM,kBAAkB,CAAA;AAgB1F;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,sCA8J9D"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { getWallThickness, sceneRegistry, useScene } from '@pascal-app/core';
|
|
4
|
+
import { DRAFT_LABEL_Y_OFFSET, DraftMeasurementLabel, EDITOR_LAYER, formatLinearMeasurement, NO_RAYCAST, } from '@pascal-app/editor';
|
|
5
|
+
import { BATCHED_LAYER, getSceneTheme, SCENE_LAYER, useViewer } from '@pascal-app/viewer';
|
|
6
|
+
import { useFrame, useThree } from '@react-three/fiber';
|
|
7
|
+
import { useEffect, useRef } from 'react';
|
|
8
|
+
import { Raycaster, Vector2 } from 'three';
|
|
9
|
+
import { bindWallSplitPointer } from './split-pointer';
|
|
10
|
+
import { wallSplitDistance, wallSplitMarkerColor, wallSplitSegmentLabels } from './split-preview';
|
|
11
|
+
import { useWallSplit } from './split-store';
|
|
12
|
+
/**
|
|
13
|
+
* The wall's `split` affordance (3D): cut markers, segment lengths and the
|
|
14
|
+
* pointer binding. The session (`split-session.ts`) owns the scope and draft;
|
|
15
|
+
* the plan layer renders the same draft.
|
|
16
|
+
*/
|
|
17
|
+
export default function WallSplitTool(_props) {
|
|
18
|
+
const draft = useWallSplit((s) => s.draft);
|
|
19
|
+
const wallId = draft?.wallId;
|
|
20
|
+
const wall = useScene((s) => (draft ? s.nodes[draft.wallId] : undefined));
|
|
21
|
+
const unit = useViewer((s) => s.unit);
|
|
22
|
+
const metricNotation = useViewer((s) => s.metricNotation);
|
|
23
|
+
const isDark = useViewer((s) => getSceneTheme(s.sceneTheme).appearance === 'dark');
|
|
24
|
+
const { gl, camera } = useThree();
|
|
25
|
+
const root = useRef(null);
|
|
26
|
+
const column = useRef(null);
|
|
27
|
+
const top = useRef(null);
|
|
28
|
+
const base = useRef(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!wallId)
|
|
31
|
+
return;
|
|
32
|
+
const surface = gl.domElement;
|
|
33
|
+
const raycaster = new Raycaster();
|
|
34
|
+
raycaster.layers.set(SCENE_LAYER);
|
|
35
|
+
raycaster.layers.enable(BATCHED_LAYER);
|
|
36
|
+
const pointer = new Vector2();
|
|
37
|
+
return bindWallSplitPointer(surface, (event) => {
|
|
38
|
+
if (event.target !== surface)
|
|
39
|
+
return null;
|
|
40
|
+
const current = useScene.getState().nodes[wallId];
|
|
41
|
+
const object = sceneRegistry.nodes.get(wallId);
|
|
42
|
+
if (current?.type !== 'wall' || !object?.parent)
|
|
43
|
+
return null;
|
|
44
|
+
const rect = surface.getBoundingClientRect();
|
|
45
|
+
if (!rect.width || !rect.height)
|
|
46
|
+
return null;
|
|
47
|
+
pointer.set(((event.clientX - rect.left) / rect.width) * 2 - 1, 1 - ((event.clientY - rect.top) / rect.height) * 2);
|
|
48
|
+
raycaster.setFromCamera(pointer, camera);
|
|
49
|
+
object.updateWorldMatrix(true, true);
|
|
50
|
+
const hit = raycaster.intersectObject(object, true)[0];
|
|
51
|
+
if (!hit)
|
|
52
|
+
return null;
|
|
53
|
+
const local = object.parent.worldToLocal(hit.point.clone());
|
|
54
|
+
return wallSplitDistance(current, [local.x, local.z]);
|
|
55
|
+
});
|
|
56
|
+
}, [wallId, gl, camera]);
|
|
57
|
+
useFrame(() => {
|
|
58
|
+
if (!(root.current && column.current && top.current && base.current))
|
|
59
|
+
return;
|
|
60
|
+
if (wall?.type !== 'wall' || !draft)
|
|
61
|
+
return;
|
|
62
|
+
const object = sceneRegistry.nodes.get(wall.id);
|
|
63
|
+
if (!object?.parent || !object.geometry) {
|
|
64
|
+
root.current.visible = false;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
root.current.visible = true;
|
|
68
|
+
object.updateWorldMatrix(true, false);
|
|
69
|
+
root.current.matrix.copy(object.parent.matrixWorld);
|
|
70
|
+
if (!object.geometry.boundingBox)
|
|
71
|
+
object.geometry.computeBoundingBox();
|
|
72
|
+
const bounds = object.geometry.boundingBox;
|
|
73
|
+
const bottom = object.position.y + (bounds?.min.y ?? 0);
|
|
74
|
+
const height = bounds ? bounds.max.y - bounds.min.y : (wall.height ?? 3);
|
|
75
|
+
column.current.position.y = bottom + height / 2;
|
|
76
|
+
column.current.scale.y = Math.max(0.05, height + 0.05);
|
|
77
|
+
top.current.position.y = bottom + height + DRAFT_LABEL_Y_OFFSET;
|
|
78
|
+
base.current.position.y = bottom + 0.01;
|
|
79
|
+
});
|
|
80
|
+
if (!draft || wall?.type !== 'wall')
|
|
81
|
+
return null;
|
|
82
|
+
const { preview, snap } = draft;
|
|
83
|
+
const thickness = getWallThickness(wall);
|
|
84
|
+
const color = wallSplitMarkerColor(preview.valid);
|
|
85
|
+
const cut = preview.frames[0]?.point;
|
|
86
|
+
const guide = snap?.kind === 'alignment' && cut
|
|
87
|
+
? {
|
|
88
|
+
x: (snap.anchor[0] + cut.x) / 2,
|
|
89
|
+
z: (snap.anchor[1] + cut.y) / 2,
|
|
90
|
+
length: Math.hypot(cut.x - snap.anchor[0], cut.y - snap.anchor[1]),
|
|
91
|
+
angle: -Math.atan2(cut.y - snap.anchor[1], cut.x - snap.anchor[0]),
|
|
92
|
+
}
|
|
93
|
+
: null;
|
|
94
|
+
return (_jsxs("group", { ref: root, matrixAutoUpdate: false, children: [_jsx("group", { ref: column, children: preview.frames.map(({ point, tangent }, index) => (_jsxs("group", { position: [point.x, 0, point.y], rotation: [0, -Math.atan2(tangent.y, tangent.x), 0], userData: { testId: 'pascal-split-marker', valid: preview.valid }, children: [_jsxs("mesh", { layers: EDITOR_LAYER, raycast: NO_RAYCAST, renderOrder: 8900, scale: [0.074, 1, thickness + 0.12], children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: preview.valid ? '#f7f3ed' : '#faece9', depthTest: false, depthWrite: false, toneMapped: false })] }), _jsxs("mesh", { layers: EDITOR_LAYER, raycast: NO_RAYCAST, renderOrder: 8910, scale: [0.026, 1.001, thickness + 0.07], children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: color, depthTest: false, depthWrite: false, toneMapped: false })] })] }, index))) }), _jsx("group", { ref: top, children: !preview.valid && cut ? (_jsx(DraftMeasurementLabel, { color: color, label: preview.message, position: [cut.x, 0, cut.y], shadowColor: isDark ? '#111111' : '#ffffff' })) : (wallSplitSegmentLabels(wall, preview).map((segment, index) => (_jsx(DraftMeasurementLabel, { color: isDark ? '#ffffff' : '#111111', label: `${formatLinearMeasurement(segment.length, unit, metricNotation)}${segment.count > 1 ? ` × ${segment.count}` : ''}`, position: [segment.midpoint[0], 0, segment.midpoint[1]], shadowColor: isDark ? '#111111' : '#ffffff' }, index)))) }), _jsx("group", { ref: base, children: guide && (_jsxs("mesh", { layers: EDITOR_LAYER, position: [guide.x, 0, guide.z], raycast: NO_RAYCAST, renderOrder: 8890, rotation: [0, guide.angle, 0], scale: [guide.length, 0.01, 0.02], children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false, depthWrite: false, opacity: 0.8, toneMapped: false, transparent: true })] })) })] }));
|
|
95
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/wall/system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/wall/system.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAyChD;AASD;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,WAAW,mCAShB,CAAA;AAED,eAAe,WAAW,CAAA"}
|
package/dist/wall/system.js
CHANGED
|
@@ -4,6 +4,8 @@ import { useLiveNodeOverrides, useScene } from '@pascal-app/core';
|
|
|
4
4
|
import { timeSpan, WallCutout, WallSystem } from '@pascal-app/viewer';
|
|
5
5
|
import { useFrame } from '@react-three/fiber';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
|
+
import { curtainWallGeometryAdapter } from './curtain-wall-adapter';
|
|
8
|
+
import { getCurtainAwareWallMaterials } from './curtain-wall-materials';
|
|
7
9
|
import { buildWallTreatmentLevelData, clearWallTreatmentMiterCache, sameTreatmentWalls, treatmentProudKeys, useWallTreatmentLevelData, } from './treatment-level-data';
|
|
8
10
|
import { wallTreatmentProudOffsets } from './treatments';
|
|
9
11
|
import { WallBatchSystem } from './wall-batch-system';
|
|
@@ -93,6 +95,6 @@ const WallTreatmentMiterSystem = () => {
|
|
|
93
95
|
* draw calls instead of one per wall face run.
|
|
94
96
|
*/
|
|
95
97
|
const WallSystems = () => {
|
|
96
|
-
return (_jsxs(_Fragment, { children: [_jsx(WallTreatmentMiterSystem, {}), _jsx(WallSystem, {}), _jsx(WallCutout, {}), _jsx(WallBatchSystem, {})] }));
|
|
98
|
+
return (_jsxs(_Fragment, { children: [_jsx(WallTreatmentMiterSystem, {}), _jsx(WallSystem, { geometryAdapter: curtainWallGeometryAdapter }), _jsx(WallCutout, { materialResolver: getCurtainAwareWallMaterials }), _jsx(WallBatchSystem, {})] }));
|
|
97
99
|
};
|
|
98
100
|
export default WallSystems;
|
package/dist/wall/tool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/wall/tool.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/wall/tool.tsx"],"names":[],"mappings":"AAs7BA,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAS5B,CAAA;AAED,eAAe,QAAQ,CAAA"}
|
package/dist/wall/tool.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { calculateLevelMiters, collectAlignmentAnchors, DEFAULT_LEVEL_HEIGHT, emitter, GROUND_SUPPORT_ID, getWallMiterBoundaryPoints, resolveAlignment, resolveBuildingForLevel, sceneRegistry, useScene, wallClosesRoom, } from '@pascal-app/core';
|
|
3
|
-
import { CursorSphere, chainEndJoinsExistingWall, clearPlacementSurface, createWallOnCurrentLevel, EDITOR_LAYER, formatAngleRadians, formatLinearMeasurement, getAngleArcToSegmentReference, getAngleToSegmentReference, getSegmentAngleReferenceAtPoint, isAlignmentGuideActive, isAngleSnapActive, isMagneticSnapActive, markToolCancelConsumed, publishHorizontalConstructionPlane, publishPlacementSurface, resampleTerrainConstructionPlane, resolveEventConstructionPlane, resolvePointerSupportSurface, snapWallDraftPointDetailed, triggerSFX, useAlignmentGuides, useEditor, useFloorplanDraftPreview, useSegmentDraftChain, useWallSnapIndicator, WALL_CONNECT_SNAP_RADIUS, WALL_JOIN_SNAP_RADIUS, } from '@pascal-app/editor';
|
|
3
|
+
import { CursorSphere, chainEndJoinsExistingWall, clearPlacementSurface, createWallOnCurrentLevel, DraftMeasurementLabel, EDITOR_LAYER, formatAngleRadians, formatLinearMeasurement, getAngleArcToSegmentReference, getAngleToSegmentReference, getSegmentAngleReferenceAtPoint, isAlignmentGuideActive, isAngleSnapActive, isMagneticSnapActive, markToolCancelConsumed, publishHorizontalConstructionPlane, publishPlacementSurface, resampleTerrainConstructionPlane, resolveEventConstructionPlane, resolvePointerSupportSurface, snapWallDraftPointDetailed, triggerSFX, useAlignmentGuides, useEditor, useFloorplanDraftPreview, useSegmentDraftChain, useWallSnapIndicator, WALL_CONNECT_SNAP_RADIUS, WALL_JOIN_SNAP_RADIUS, } from '@pascal-app/editor';
|
|
4
4
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer';
|
|
5
5
|
import { useThree } from '@react-three/fiber';
|
|
6
6
|
import { useEffect, useRef, useState } from 'react';
|
|
7
7
|
import { DoubleSide, Vector3 } from 'three';
|
|
8
|
-
import { DraftAngleArc, DraftAxisGuides,
|
|
8
|
+
import { DraftAngleArc, DraftAxisGuides, getNearestAxisAngleLabel, } from '../shared/draft-axis-guides';
|
|
9
|
+
import { useWallDrawingMode, useWallDrawingModeKeys } from './drawing-mode';
|
|
10
|
+
import RectangleWallTool from './rectangle-tool';
|
|
9
11
|
/**
|
|
10
12
|
* Phase 5 Stage D — wall placement tool (kind-owned).
|
|
11
13
|
*
|
|
@@ -289,7 +291,7 @@ function getBelowLevelWalls() {
|
|
|
289
291
|
.sort((a, b) => b.level - a.level)[0];
|
|
290
292
|
return getLevelWalls(belowLevel?.id ?? null, nodes);
|
|
291
293
|
}
|
|
292
|
-
|
|
294
|
+
const LineWallTool = () => {
|
|
293
295
|
const unit = useViewer((state) => state.unit);
|
|
294
296
|
const metricNotation = useViewer((state) => state.metricNotation);
|
|
295
297
|
const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
|
|
@@ -331,9 +333,6 @@ export const WallTool = () => {
|
|
|
331
333
|
const [axisGuide, setAxisGuide] = useState(null);
|
|
332
334
|
const measurementColor = isDark ? '#ffffff' : '#111111';
|
|
333
335
|
const measurementShadowColor = isDark ? '#111111' : '#ffffff';
|
|
334
|
-
// Clear preset-seeded defaults on deactivation so a later manual wall draw
|
|
335
|
-
// isn't built with a stale preset's parameters. Unmount-only.
|
|
336
|
-
useEffect(() => () => useEditor.getState().setToolDefaults('wall', null), []);
|
|
337
336
|
useEffect(() => {
|
|
338
337
|
let gridPosition = [0, 0];
|
|
339
338
|
let previousWallEnd = null;
|
|
@@ -382,6 +381,7 @@ export const WallTool = () => {
|
|
|
382
381
|
const pointedSurfaceFor = (event) => event.nativeEvent?.target instanceof HTMLCanvasElement
|
|
383
382
|
? resolvePointerSupportSurface(cameraRef.current, event.position, {
|
|
384
383
|
includeNodeTopSurfaces: true,
|
|
384
|
+
pointerRay: event.nativeEvent.ray,
|
|
385
385
|
})
|
|
386
386
|
: null;
|
|
387
387
|
const snappedWallConstructionPlane = (targetWallIds, walls) => {
|
|
@@ -679,4 +679,12 @@ export const WallTool = () => {
|
|
|
679
679
|
}, [unit, metricNotation]);
|
|
680
680
|
return (_jsxs("group", { children: [_jsx(DraftAxisGuides, { guide: axisGuide, labelColor: measurementColor, labelShadowColor: measurementShadowColor }), _jsx(CursorSphere, { height: previewHeight, ref: cursorRef }), _jsxs("mesh", { layers: EDITOR_LAYER, ref: wallPreviewRef, renderOrder: 1, visible: false, children: [_jsx("boxGeometry", {}), _jsx("meshBasicMaterial", { color: "#818cf8", depthTest: false, depthWrite: false, opacity: 0.5, side: DoubleSide, transparent: true })] }), draftMeasurement && (_jsxs(_Fragment, { children: [_jsx(DraftMeasurementLabel, { color: measurementColor, label: draftMeasurement.lengthLabel, position: draftMeasurement.lengthPosition, shadowColor: measurementShadowColor }), draftMeasurement.angleLabels.map((angleLabel) => (_jsxs("group", { children: [_jsx(DraftAngleArc, { arc: angleLabel.arc, color: measurementColor }), _jsx(DraftMeasurementLabel, { color: measurementColor, label: angleLabel.label, position: angleLabel.position, shadowColor: measurementShadowColor })] }, angleLabel.id)))] }))] }));
|
|
681
681
|
};
|
|
682
|
+
export const WallTool = () => {
|
|
683
|
+
// Clear preset-seeded defaults on deactivation so a later manual wall draw
|
|
684
|
+
// isn't built with a stale preset's parameters. Unmount-only.
|
|
685
|
+
useEffect(() => () => useEditor.getState().setToolDefaults('wall', null), []);
|
|
686
|
+
useWallDrawingModeKeys();
|
|
687
|
+
const mode = useWallDrawingMode((s) => s.mode);
|
|
688
|
+
return mode === 'rectangle' ? _jsx(RectangleWallTool, {}) : _jsx(LineWallTool, {});
|
|
689
|
+
};
|
|
682
690
|
export default WallTool;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-batch-system.d.ts","sourceRoot":"","sources":["../../src/wall/wall-batch-system.tsx"],"names":[],"mappings":"AAGA,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,KAAK,QAAQ,EAAW,IAAI,EAAiB,MAAM,OAAO,CAAA;AAkGnE,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAKnD;AA6BD,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"wall-batch-system.d.ts","sourceRoot":"","sources":["../../src/wall/wall-batch-system.tsx"],"names":[],"mappings":"AAGA,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,KAAK,QAAQ,EAAW,IAAI,EAAiB,MAAM,OAAO,CAAA;AAkGnE,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAKnD;AA6BD,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAA;AAiCtF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,GAAG,OAAO,CAEnF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAW5E;AAMD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,eAAe,GAAE,WAAW,CAAC,MAAM,CAAa,GAC/C,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAoBnC;AAsFD,eAAO,MAAM,eAAe,YAmE3B,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,IAAI,EACtB,OAAO,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAA;CAAE,QAyI3D"}
|
|
@@ -103,6 +103,9 @@ function toCandidate(nodeId, node, excludedNodeIds = EMPTY_IDS) {
|
|
|
103
103
|
return null;
|
|
104
104
|
if (node.visible === false)
|
|
105
105
|
return null;
|
|
106
|
+
// Curtain walls use a separate caster that excludes their transparent panes.
|
|
107
|
+
if (node.wallType === 'curtain')
|
|
108
|
+
return null;
|
|
106
109
|
const mesh = sceneRegistry.nodes.get(nodeId);
|
|
107
110
|
if (!mesh?.visible)
|
|
108
111
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/window/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAGT,cAAc,EAEd,QAAQ,EAER,UAAU,IAAI,cAAc,EAC7B,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/window/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAGT,cAAc,EAEd,QAAQ,EAER,UAAU,IAAI,cAAc,EAC7B,MAAM,kBAAkB,CAAA;AAwBzB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAQrC,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,GAC3B,SAAS,GAAG,IAAI,CAWlB;AAwMD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAAc,CAAC,OAAO,UAAU,CAsG9D,CAAA"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { getDormerWallHorizontalBoundsAtHeight, getDormerWallOpeningVerticalBounds, } from '@pascal-app/core';
|
|
2
|
+
import { curtainOpeningResizeMax } from '../shared/curtain-opening-limits';
|
|
2
3
|
import { buildWindowFloorplanSchedule, computeWindowFloorplanLevelData, } from '../shared/opening-documentation';
|
|
3
4
|
import { publishOpeningResizeGuides } from '../shared/opening-guides-runtime';
|
|
5
|
+
import { createOpeningPropertyPreview } from '../shared/opening-property-preview';
|
|
6
|
+
import { openingPropertyPreviewHost } from '../shared/opening-property-preview-host';
|
|
4
7
|
import { readRoofFaceHeightMax, readRoofFaceWidthMax } from '../shared/roof-opening-host';
|
|
5
8
|
import { buildRoofWallOpeningCut } from '../shared/roof-wall-opening-cut';
|
|
6
9
|
import { readHostWallCeiling } from '../shared/wall-opening-ceiling';
|
|
@@ -82,7 +85,7 @@ function windowWidthHandle(side) {
|
|
|
82
85
|
const roofMax = readRoofFaceWidthMax(n, scene, sign);
|
|
83
86
|
if (roofMax !== null)
|
|
84
87
|
return Math.max(MIN_WINDOW_WIDTH, roofMax);
|
|
85
|
-
return readWallLength(n, scene);
|
|
88
|
+
return curtainOpeningResizeMax(n, scene.nodes(), 'x', sign) ?? readWallLength(n, scene);
|
|
86
89
|
},
|
|
87
90
|
currentValue: (n) => n.width,
|
|
88
91
|
onDrag: (node) => publishOpeningResizeGuides(node, true),
|
|
@@ -131,6 +134,9 @@ function windowHeightHandle(edge) {
|
|
|
131
134
|
// Maximum: distance from the anchored edge to the wall's allowed Y
|
|
132
135
|
// bounds. Top arrow caps at the wall's resolved ceiling - bottom;
|
|
133
136
|
// bottom arrow caps at top (positive Y room above the floor).
|
|
137
|
+
const curtainMax = curtainOpeningResizeMax(n, scene.nodes(), 'y', sign);
|
|
138
|
+
if (curtainMax !== undefined)
|
|
139
|
+
return curtainMax;
|
|
134
140
|
const wallH = readHostWallCeiling(n.wallId, scene);
|
|
135
141
|
const anchored = edge === 'top' ? n.position[1] - n.height / 2 : n.position[1] + n.height / 2;
|
|
136
142
|
return edge === 'top'
|
|
@@ -157,11 +163,51 @@ function windowHeightHandle(edge) {
|
|
|
157
163
|
portalTarget: resolveWindowHandlePortalTarget,
|
|
158
164
|
};
|
|
159
165
|
}
|
|
166
|
+
function windowRadiusHandle(index) {
|
|
167
|
+
const corners = [
|
|
168
|
+
[-1, 1],
|
|
169
|
+
[1, 1],
|
|
170
|
+
[1, -1],
|
|
171
|
+
[-1, -1],
|
|
172
|
+
];
|
|
173
|
+
return {
|
|
174
|
+
kind: 'corner-radius',
|
|
175
|
+
corner: corners[index],
|
|
176
|
+
width: (node) => node.width,
|
|
177
|
+
height: (node) => node.height,
|
|
178
|
+
currentValue: (node) => node.openingRadiusMode === 'individual'
|
|
179
|
+
? (node.openingCornerRadii[index] ?? 0)
|
|
180
|
+
: node.cornerRadius,
|
|
181
|
+
max: (node) => Math.min(node.width, node.height) / 2,
|
|
182
|
+
apply: (node, radius, _scene, modifiers) => {
|
|
183
|
+
if (!modifiers.shiftKey) {
|
|
184
|
+
return { openingShape: 'rounded', openingRadiusMode: 'all', cornerRadius: radius };
|
|
185
|
+
}
|
|
186
|
+
const radii = node.openingRadiusMode === 'individual'
|
|
187
|
+
? [...node.openingCornerRadii]
|
|
188
|
+
: [node.cornerRadius, node.cornerRadius, node.cornerRadius, node.cornerRadius];
|
|
189
|
+
radii[index] = radius;
|
|
190
|
+
return {
|
|
191
|
+
openingShape: 'rounded',
|
|
192
|
+
openingRadiusMode: 'individual',
|
|
193
|
+
openingCornerRadii: radii,
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
createPreview: (node) => createOpeningPropertyPreview(node.id, openingPropertyPreviewHost),
|
|
197
|
+
visible: (node) => node.openingShape !== 'arch',
|
|
198
|
+
portal: 'grandparent',
|
|
199
|
+
portalTarget: resolveWindowHandlePortalTarget,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
160
202
|
const windowHandles = [
|
|
161
203
|
windowWidthHandle('left'),
|
|
162
204
|
windowWidthHandle('right'),
|
|
163
205
|
windowHeightHandle('top'),
|
|
164
206
|
windowHeightHandle('bottom'),
|
|
207
|
+
windowRadiusHandle(0),
|
|
208
|
+
windowRadiusHandle(1),
|
|
209
|
+
windowRadiusHandle(2),
|
|
210
|
+
windowRadiusHandle(3),
|
|
165
211
|
];
|
|
166
212
|
/**
|
|
167
213
|
* Window — Phase 5 batch kind. Mirrors door's shape: hosted on walls,
|
|
@@ -217,6 +263,7 @@ export const windowDefinition = {
|
|
|
217
263
|
},
|
|
218
264
|
parametrics: windowParametrics,
|
|
219
265
|
handles: windowHandles,
|
|
266
|
+
rendersChildren: false,
|
|
220
267
|
renderer: {
|
|
221
268
|
kind: 'parametric',
|
|
222
269
|
module: () => import('./renderer'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floorplan-affordances.d.ts","sourceRoot":"","sources":["../../src/window/floorplan-affordances.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"floorplan-affordances.d.ts","sourceRoot":"","sources":["../../src/window/floorplan-affordances.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EAMxB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAQzB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,EAAE,mBAAmB,CAAC,UAAU,CAkEjE,CAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { useLiveNodeOverrides, useScene, } from '@pascal-app/core';
|
|
1
|
+
import { getWallCurveLength, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { curtainOpeningLimits } from '../shared/curtain-opening-limits';
|
|
3
|
+
import { projectPlanPointToWallLocalX } from '../shared/wall-attach-target';
|
|
2
4
|
const MIN_WINDOW_WIDTH = 0.3;
|
|
3
5
|
/**
|
|
4
6
|
* 2D drag affordance for the window's width side-arrows. Sister to the 3D
|
|
@@ -27,16 +29,12 @@ export const windowWidthAffordance = {
|
|
|
27
29
|
const initialWindowZ = node.position[2];
|
|
28
30
|
const growDir = side === 'end' ? 1 : -1;
|
|
29
31
|
const anchorX = side === 'end' ? initialWindowX - initialWidth / 2 : initialWindowX + initialWidth / 2;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const wallLength = Math.hypot(dx, dz) || 1;
|
|
35
|
-
const dirX = dx / wallLength;
|
|
36
|
-
const dirZ = dz / wallLength;
|
|
37
|
-
const maxWidth = growDir > 0 ? wallLength - anchorX : anchorX;
|
|
32
|
+
const wallLength = wall ? getWallCurveLength(wall) : 1;
|
|
33
|
+
const limits = curtainOpeningLimits(node, nodes);
|
|
34
|
+
const margin = limits?.margin ?? 0;
|
|
35
|
+
const maxWidth = growDir > 0 ? (limits?.length ?? wallLength) - margin - anchorX : anchorX - margin;
|
|
38
36
|
const projectToWallLocalX = (planPoint) => {
|
|
39
|
-
return (planPoint
|
|
37
|
+
return wall ? projectPlanPointToWallLocalX(wall, planPoint) : planPoint[0];
|
|
40
38
|
};
|
|
41
39
|
const initialPointerLocalX = projectToWallLocalX(initialPlanPoint);
|
|
42
40
|
let lastWidth = initialWidth;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/window/move-tool.tsx"],"names":[],"mappings":"AAAA,OAAO,EAiBL,UAAU,EACX,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/window/move-tool.tsx"],"names":[],"mappings":"AAAA,OAAO,EAiBL,UAAU,EACX,MAAM,kBAAkB,CAAA;AAkEzB;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAiyClD,CAAA;AAED,eAAe,cAAc,CAAA"}
|
package/dist/window/move-tool.js
CHANGED
|
@@ -4,6 +4,7 @@ import { calculateItemRotation, clearPlacementSurface, consumePlacementDragRelea
|
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { BoxGeometry, EdgesGeometry, Vector3 } from 'three';
|
|
6
6
|
import { LineBasicNodeMaterial } from 'three/webgpu';
|
|
7
|
+
import { commitOpeningMove } from '../shared/commit-opening-move';
|
|
7
8
|
import { dormerEventFromHostedWindow, getDormerWindowWorldNormal, getDormerWindowWorldYaw, resolveDormerWindowTarget, } from '../shared/dormer-wall-opening-placement';
|
|
8
9
|
import { clearOpeningGuides3D, publishOpeningGuidesForWallEvent, resolveSillSnap, } from '../shared/opening-guides-runtime';
|
|
9
10
|
import { beginOpeningMoveHistorySession } from '../shared/opening-move-history';
|
|
@@ -493,14 +494,13 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
|
|
|
493
494
|
visible: original.visible,
|
|
494
495
|
});
|
|
495
496
|
history.commitStep(() => {
|
|
496
|
-
|
|
497
|
+
commitOpeningMove(movingWindowNode.id, {
|
|
497
498
|
position: [target.clampedX, target.clampedY, 0],
|
|
498
499
|
rotation: [0, target.itemRotation, 0],
|
|
499
500
|
side: target.side,
|
|
500
501
|
parentId: target.wallId,
|
|
501
502
|
wallId: target.wallId,
|
|
502
503
|
roofSegmentId: undefined,
|
|
503
|
-
metadata: {},
|
|
504
504
|
visible: true,
|
|
505
505
|
});
|
|
506
506
|
});
|
|
@@ -736,7 +736,7 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
|
|
|
736
736
|
visible: original.visible,
|
|
737
737
|
});
|
|
738
738
|
history.commitStep(() => {
|
|
739
|
-
|
|
739
|
+
commitOpeningMove(movingWindowNode.id, {
|
|
740
740
|
position: target.position,
|
|
741
741
|
rotation,
|
|
742
742
|
side,
|
|
@@ -746,7 +746,6 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
|
|
|
746
746
|
wallId: undefined,
|
|
747
747
|
roofSegmentId: undefined,
|
|
748
748
|
roofFace: undefined,
|
|
749
|
-
metadata: {},
|
|
750
749
|
visible: true,
|
|
751
750
|
});
|
|
752
751
|
});
|
|
@@ -937,7 +936,7 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
|
|
|
937
936
|
visible: original.visible,
|
|
938
937
|
});
|
|
939
938
|
history.commitStep(() => {
|
|
940
|
-
|
|
939
|
+
commitOpeningMove(movingWindowNode.id, {
|
|
941
940
|
position: target.position,
|
|
942
941
|
rotation: [0, 0, 0],
|
|
943
942
|
side: 'front',
|
|
@@ -945,7 +944,6 @@ const MoveWindowTool = ({ node: movingWindowNode }) => {
|
|
|
945
944
|
wallId: undefined,
|
|
946
945
|
roofSegmentId: segmentId,
|
|
947
946
|
roofFace: target.face.id,
|
|
948
|
-
metadata: {},
|
|
949
947
|
visible: true,
|
|
950
948
|
});
|
|
951
949
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/window/panel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/window/panel.tsx"],"names":[],"mappings":"AAwFA,MAAM,CAAC,OAAO,UAAU,WAAW,uCA20BlC"}
|