@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,220 @@
|
|
|
1
|
+
import { subtractPolygonsFromPolygon } from '@pascal-app/core';
|
|
2
|
+
import { ensureRenderableGeometryAttributes } from '@pascal-app/viewer';
|
|
3
|
+
import { BufferGeometry, Float32BufferAttribute, ShapeUtils, Vector2 } from 'three';
|
|
4
|
+
export function buildStraightCurtainPiecesWithCutouts(pieces, base, cutouts) {
|
|
5
|
+
const positions = [];
|
|
6
|
+
const normals = [];
|
|
7
|
+
const preparedCutouts = cutouts.map((cutout) => {
|
|
8
|
+
const points = cutout.map((point) => [point.x, point.y]);
|
|
9
|
+
const xs = points.map(([x]) => x);
|
|
10
|
+
const ys = points.map(([, y]) => y);
|
|
11
|
+
return {
|
|
12
|
+
points,
|
|
13
|
+
minX: Math.min(...xs),
|
|
14
|
+
maxX: Math.max(...xs),
|
|
15
|
+
minY: Math.min(...ys),
|
|
16
|
+
maxY: Math.max(...ys),
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
const vertex = (point, normal) => {
|
|
20
|
+
positions.push(point[0], point[1], point[2]);
|
|
21
|
+
normals.push(normal[0], normal[1], normal[2]);
|
|
22
|
+
};
|
|
23
|
+
const contains = (ring, point) => {
|
|
24
|
+
let inside = false;
|
|
25
|
+
for (let index = 0, previous = ring.length - 1; index < ring.length; previous = index++) {
|
|
26
|
+
const a = ring[index];
|
|
27
|
+
const b = ring[previous];
|
|
28
|
+
if (a[1] > point[1] !== b[1] > point[1] &&
|
|
29
|
+
point[0] < ((b[0] - a[0]) * (point[1] - a[1])) / (b[1] - a[1]) + a[0]) {
|
|
30
|
+
inside = !inside;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return inside;
|
|
34
|
+
};
|
|
35
|
+
const area = (ring) => Math.abs(ring.reduce((sum, point, index) => {
|
|
36
|
+
const next = ring[(index + 1) % ring.length];
|
|
37
|
+
return sum + point[0] * next[1] - next[0] * point[1];
|
|
38
|
+
}, 0) / 2);
|
|
39
|
+
for (const piece of pieces) {
|
|
40
|
+
const bottom = piece.bottom + base;
|
|
41
|
+
const top = piece.top + base;
|
|
42
|
+
const overlapping = preparedCutouts.filter((cutout) => cutout.maxX > piece.left &&
|
|
43
|
+
cutout.minX < piece.right &&
|
|
44
|
+
cutout.maxY > bottom &&
|
|
45
|
+
cutout.minY < top);
|
|
46
|
+
const subject = [
|
|
47
|
+
[piece.left, bottom],
|
|
48
|
+
[piece.right, bottom],
|
|
49
|
+
[piece.right, top],
|
|
50
|
+
[piece.left, top],
|
|
51
|
+
];
|
|
52
|
+
const rings = subtractPolygonsFromPolygon(subject, overlapping.map(({ points }) => points)).sort((a, b) => area(b) - area(a));
|
|
53
|
+
const groups = [];
|
|
54
|
+
for (const ring of rings) {
|
|
55
|
+
const parent = groups.find(({ contour }) => contains(contour, ring[0]));
|
|
56
|
+
if (parent)
|
|
57
|
+
parent.holes.push([...ring].reverse());
|
|
58
|
+
else
|
|
59
|
+
groups.push({ contour: ring, holes: [] });
|
|
60
|
+
}
|
|
61
|
+
for (const { contour, holes } of groups) {
|
|
62
|
+
const flattened = [contour, ...holes].flat();
|
|
63
|
+
const triangles = ShapeUtils.triangulateShape(contour.map(([x, y]) => new Vector2(x, y)), holes.map((hole) => hole.map(([x, y]) => new Vector2(x, y))));
|
|
64
|
+
for (const triangle of triangles) {
|
|
65
|
+
const aIndex = triangle[0];
|
|
66
|
+
const bIndex = triangle[1];
|
|
67
|
+
const cIndex = triangle[2];
|
|
68
|
+
const a = flattened[aIndex];
|
|
69
|
+
const b = flattened[bIndex];
|
|
70
|
+
const c = flattened[cIndex];
|
|
71
|
+
vertex([a[0], a[1], piece.front], [0, 0, 1]);
|
|
72
|
+
vertex([b[0], b[1], piece.front], [0, 0, 1]);
|
|
73
|
+
vertex([c[0], c[1], piece.front], [0, 0, 1]);
|
|
74
|
+
vertex([c[0], c[1], piece.back], [0, 0, -1]);
|
|
75
|
+
vertex([b[0], b[1], piece.back], [0, 0, -1]);
|
|
76
|
+
vertex([a[0], a[1], piece.back], [0, 0, -1]);
|
|
77
|
+
}
|
|
78
|
+
for (const ring of [contour, ...holes]) {
|
|
79
|
+
for (let index = 0; index < ring.length; index++) {
|
|
80
|
+
const a = ring[index];
|
|
81
|
+
const b = ring[(index + 1) % ring.length];
|
|
82
|
+
const dx = b[0] - a[0];
|
|
83
|
+
const dy = b[1] - a[1];
|
|
84
|
+
const length = Math.hypot(dx, dy);
|
|
85
|
+
if (length <= 1e-6)
|
|
86
|
+
continue;
|
|
87
|
+
const normal = [dy / length, -dx / length, 0];
|
|
88
|
+
vertex([a[0], a[1], piece.back], normal);
|
|
89
|
+
vertex([b[0], b[1], piece.back], normal);
|
|
90
|
+
vertex([b[0], b[1], piece.front], normal);
|
|
91
|
+
vertex([a[0], a[1], piece.back], normal);
|
|
92
|
+
vertex([b[0], b[1], piece.front], normal);
|
|
93
|
+
vertex([a[0], a[1], piece.front], normal);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const geometry = new BufferGeometry();
|
|
99
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
100
|
+
geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3));
|
|
101
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
102
|
+
return geometry;
|
|
103
|
+
}
|
|
104
|
+
// Adjacent fragments of one pane share a boundary, not a second glass surface.
|
|
105
|
+
export function buildStraightCurtainPieces(pieces, base, cullSharedFaces = true) {
|
|
106
|
+
if (!cullSharedFaces) {
|
|
107
|
+
const valuesPerPiece = 6 * 6 * 3;
|
|
108
|
+
const positions = new Float32Array(pieces.length * valuesPerPiece);
|
|
109
|
+
const normals = new Float32Array(pieces.length * valuesPerPiece);
|
|
110
|
+
let offset = 0;
|
|
111
|
+
const vertex = (point, normal) => {
|
|
112
|
+
positions[offset] = point[0];
|
|
113
|
+
normals[offset++] = normal[0];
|
|
114
|
+
positions[offset] = point[1];
|
|
115
|
+
normals[offset++] = normal[1];
|
|
116
|
+
positions[offset] = point[2];
|
|
117
|
+
normals[offset++] = normal[2];
|
|
118
|
+
};
|
|
119
|
+
const quad = (a, b, c, d, normal) => {
|
|
120
|
+
vertex(a, normal);
|
|
121
|
+
vertex(b, normal);
|
|
122
|
+
vertex(c, normal);
|
|
123
|
+
vertex(a, normal);
|
|
124
|
+
vertex(c, normal);
|
|
125
|
+
vertex(d, normal);
|
|
126
|
+
};
|
|
127
|
+
for (const piece of pieces) {
|
|
128
|
+
const { left: l, right: r, bottom, top, front: f, back: k } = piece;
|
|
129
|
+
const b = bottom + base;
|
|
130
|
+
const t = top + base;
|
|
131
|
+
quad([l, b, f], [r, b, f], [r, t, f], [l, t, f], [0, 0, 1]);
|
|
132
|
+
quad([r, b, k], [l, b, k], [l, t, k], [r, t, k], [0, 0, -1]);
|
|
133
|
+
quad([l, b, k], [l, b, f], [l, t, f], [l, t, k], [-1, 0, 0]);
|
|
134
|
+
quad([r, b, f], [r, b, k], [r, t, k], [r, t, f], [1, 0, 0]);
|
|
135
|
+
quad([l, b, k], [r, b, k], [r, b, f], [l, b, f], [0, -1, 0]);
|
|
136
|
+
quad([l, t, f], [r, t, f], [r, t, k], [l, t, k], [0, 1, 0]);
|
|
137
|
+
}
|
|
138
|
+
const geometry = new BufferGeometry();
|
|
139
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
140
|
+
geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3));
|
|
141
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
142
|
+
return geometry;
|
|
143
|
+
}
|
|
144
|
+
const positions = new Float32Array(pieces.length * 6 * 6 * 3);
|
|
145
|
+
const normals = new Float32Array(positions.length);
|
|
146
|
+
let offset = 0;
|
|
147
|
+
const vertex = (point, normal) => {
|
|
148
|
+
positions[offset] = point[0];
|
|
149
|
+
normals[offset++] = normal[0];
|
|
150
|
+
positions[offset] = point[1];
|
|
151
|
+
normals[offset++] = normal[1];
|
|
152
|
+
positions[offset] = point[2];
|
|
153
|
+
normals[offset++] = normal[2];
|
|
154
|
+
};
|
|
155
|
+
const quad = (a, b, c, d, normal) => {
|
|
156
|
+
vertex(a, normal);
|
|
157
|
+
vertex(b, normal);
|
|
158
|
+
vertex(c, normal);
|
|
159
|
+
vertex(a, normal);
|
|
160
|
+
vertex(c, normal);
|
|
161
|
+
vertex(d, normal);
|
|
162
|
+
};
|
|
163
|
+
const sides = ['left', 'right', 'bottom', 'top'];
|
|
164
|
+
const key = (piece, side, coordinate = piece[side]) => `${side}:${coordinate.toFixed(7)}:${piece.front.toFixed(7)}:${piece.back.toFixed(7)}`;
|
|
165
|
+
const edges = new Map();
|
|
166
|
+
if (cullSharedFaces) {
|
|
167
|
+
for (const piece of pieces) {
|
|
168
|
+
for (const side of sides) {
|
|
169
|
+
const id = key(piece, side);
|
|
170
|
+
const neighbors = edges.get(id) ?? [];
|
|
171
|
+
neighbors.push(piece);
|
|
172
|
+
edges.set(id, neighbors);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const piece of pieces) {
|
|
177
|
+
const { left: l, right: r, bottom, top, front: f, back: k } = piece;
|
|
178
|
+
const b = bottom + base;
|
|
179
|
+
const t = top + base;
|
|
180
|
+
quad([l, b, f], [r, b, f], [r, t, f], [l, t, f], [0, 0, 1]);
|
|
181
|
+
quad([r, b, k], [l, b, k], [l, t, k], [r, t, k], [0, 0, -1]);
|
|
182
|
+
for (const side of sides) {
|
|
183
|
+
const vertical = side === 'left' || side === 'right';
|
|
184
|
+
let runs = vertical ? [[bottom, top]] : [[l, r]];
|
|
185
|
+
const opposite = side === 'left' ? 'right' : side === 'right' ? 'left' : side === 'bottom' ? 'top' : 'bottom';
|
|
186
|
+
if (cullSharedFaces) {
|
|
187
|
+
for (const other of edges.get(key(piece, opposite, piece[side])) ?? []) {
|
|
188
|
+
if (other === piece)
|
|
189
|
+
continue;
|
|
190
|
+
const low = vertical ? other.bottom : other.left;
|
|
191
|
+
const high = vertical ? other.top : other.right;
|
|
192
|
+
runs = runs.flatMap(([start, end]) => high <= start || low >= end
|
|
193
|
+
? [[start, end]]
|
|
194
|
+
: [
|
|
195
|
+
...(low > start ? [[start, low]] : []),
|
|
196
|
+
...(high < end ? [[high, end]] : []),
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (const [start, end] of runs) {
|
|
201
|
+
if (vertical) {
|
|
202
|
+
const y0 = start + base, y1 = end + base;
|
|
203
|
+
if (side === 'left')
|
|
204
|
+
quad([l, y0, k], [l, y0, f], [l, y1, f], [l, y1, k], [-1, 0, 0]);
|
|
205
|
+
else
|
|
206
|
+
quad([r, y0, f], [r, y0, k], [r, y1, k], [r, y1, f], [1, 0, 0]);
|
|
207
|
+
}
|
|
208
|
+
else if (side === 'bottom')
|
|
209
|
+
quad([start, b, k], [end, b, k], [end, b, f], [start, b, f], [0, -1, 0]);
|
|
210
|
+
else
|
|
211
|
+
quad([start, t, f], [end, t, f], [end, t, k], [start, t, k], [0, 1, 0]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const geometry = new BufferGeometry();
|
|
216
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions.subarray(0, offset), 3));
|
|
217
|
+
geometry.setAttribute('normal', new Float32BufferAttribute(normals.subarray(0, offset), 3));
|
|
218
|
+
ensureRenderableGeometryAttributes(geometry);
|
|
219
|
+
return geometry;
|
|
220
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BufferGeometry } from 'three';
|
|
2
|
+
export declare const CURTAIN_WALL_SHADOW_NAME = "curtain-wall-shadow";
|
|
3
|
+
export declare function buildCurtainWallShadowGeometry(source: BufferGeometry, opaqueGlass: boolean): BufferGeometry;
|
|
4
|
+
//# sourceMappingURL=curtain-wall-shadow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"curtain-wall-shadow.d.ts","sourceRoot":"","sources":["../../src/wall/curtain-wall-shadow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C,eAAO,MAAM,wBAAwB,wBAAwB,CAAA;AAE7D,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,OAAO,GACnB,cAAc,CAYhB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const CURTAIN_WALL_SHADOW_NAME = 'curtain-wall-shadow';
|
|
2
|
+
export function buildCurtainWallShadowGeometry(source, opaqueGlass) {
|
|
3
|
+
const geometry = source.clone();
|
|
4
|
+
const indices = [];
|
|
5
|
+
for (const group of source.groups) {
|
|
6
|
+
if (group.materialIndex === 1 && !opaqueGlass)
|
|
7
|
+
continue;
|
|
8
|
+
for (let i = group.start; i < group.start + group.count; i++)
|
|
9
|
+
indices.push(source.index ? source.index.getX(i) : i);
|
|
10
|
+
}
|
|
11
|
+
geometry.clearGroups();
|
|
12
|
+
geometry.setIndex(indices);
|
|
13
|
+
geometry.setDrawRange(0, indices.length);
|
|
14
|
+
return geometry;
|
|
15
|
+
}
|
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
import { type NodeDefinition } from '@pascal-app/core';
|
|
2
2
|
import { WallNode } from './schema';
|
|
3
|
-
/**
|
|
4
|
-
* Wall — the Phase 3 stress test of the registry-driven node model.
|
|
5
|
-
*
|
|
6
|
-
* Stage A: registered (capabilities, relations, parametrics, presentation).
|
|
7
|
-
* Stage B: deferred — wall geometry depends on level-batch miter data that
|
|
8
|
-
* doesn't fit the generic `(node, ctx) => Group` shape without `ctx.
|
|
9
|
-
* levelData?.miters`. See plan's "GeometryContext" extension note.
|
|
10
|
-
* `renderer` + `system` keep wrap-exporting legacy WallRenderer +
|
|
11
|
-
* WallSystem + WallCutout.
|
|
12
|
-
* Stage C: `def.floorplan` builder produces the mitered plan footprint
|
|
13
|
-
* polygon from shared floor-plan level data, with `ctx.siblings` as the
|
|
14
|
-
* direct-caller fallback.
|
|
15
|
-
* floorplan-panel.tsx's `wallPolygons` short-circuits to [] when
|
|
16
|
-
* wall is registered.
|
|
17
|
-
*/
|
|
18
3
|
export declare const wallDefinition: NodeDefinition<typeof WallNode>;
|
|
19
4
|
//# sourceMappingURL=definition.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/wall/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/wall/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAA;AA4BzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAyBnC,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CAsN1D,CAAA"}
|
package/dist/wall/definition.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { getWallBaseElevationForNodes, getWallEffectiveHeightForNodes, } from '@pascal-app/core';
|
|
2
|
-
import { DRAFTING_SURFACE_EXTENSION_KEY, } from '@pascal-app/editor';
|
|
2
|
+
import { DRAFTING_SURFACE_EXTENSION_KEY, PANEL_MODEL_EXTENSION, } from '@pascal-app/editor';
|
|
3
3
|
import { buildWallContextualDimensions } from './contextual-dimensions';
|
|
4
4
|
import { hasWallCurveBlockingChildren } from './curve-eligibility';
|
|
5
|
+
import { useWallDrawingMode } from './drawing-mode';
|
|
5
6
|
import { buildWallFloorplan, computeWallFloorplanLevelData } from './floorplan';
|
|
6
7
|
import { wallCurveAffordance, wallMoveEndpointAffordance, wallThicknessAffordance, } from './floorplan-affordances';
|
|
7
8
|
import { wallFloorplanMoveTarget } from './floorplan-move';
|
|
8
9
|
import { wallFloorplanSiblingOverrides } from './floorplan-overrides';
|
|
9
10
|
import { matchWallMeasurementFeature, resolveWallMeasurementFeature, wallMeasurementFeatures, } from './measurement';
|
|
10
11
|
import { wallPaint } from './paint';
|
|
12
|
+
import { wallSettings } from './panel-model';
|
|
11
13
|
import { wallParametrics } from './parametrics';
|
|
12
14
|
import { wallQuickMeasurement } from './quick-measurement';
|
|
13
15
|
import { WallNode } from './schema';
|
|
14
16
|
import { wallSlots } from './slots';
|
|
17
|
+
import { WALL_SPLIT_MAX_CUTS } from './split-preview';
|
|
18
|
+
import { setWallSplitCuts } from './split-session';
|
|
19
|
+
import { useWallSplit } from './split-store';
|
|
15
20
|
/**
|
|
16
21
|
* Wall — the Phase 3 stress test of the registry-driven node model.
|
|
17
22
|
*
|
|
@@ -27,14 +32,18 @@ import { wallSlots } from './slots';
|
|
|
27
32
|
* floorplan-panel.tsx's `wallPolygons` short-circuits to [] when
|
|
28
33
|
* wall is registered.
|
|
29
34
|
*/
|
|
35
|
+
const SPLIT_CUT_COUNTS = Array.from({ length: WALL_SPLIT_MAX_CUTS }, (_, index) => String(index + 1));
|
|
30
36
|
export const wallDefinition = {
|
|
31
37
|
kind: 'wall',
|
|
32
38
|
snapProfile: 'structural',
|
|
33
|
-
schemaVersion:
|
|
39
|
+
schemaVersion: 9,
|
|
34
40
|
schema: WallNode,
|
|
35
41
|
category: 'structure',
|
|
36
42
|
surfaceRole: 'wall',
|
|
37
43
|
extensions: {
|
|
44
|
+
[PANEL_MODEL_EXTENSION]: {
|
|
45
|
+
rows: ({ node, nodes, update }) => wallSettings(node, nodes, update),
|
|
46
|
+
},
|
|
38
47
|
[DRAFTING_SURFACE_EXTENSION_KEY]: {
|
|
39
48
|
kind: 'wall',
|
|
40
49
|
classifyFace: (node, localNormal) => {
|
|
@@ -55,8 +64,11 @@ export const wallDefinition = {
|
|
|
55
64
|
},
|
|
56
65
|
},
|
|
57
66
|
'pascal:editor/floorplan': {
|
|
67
|
+
tool: () => import('./floorplan-tool'),
|
|
68
|
+
reshapeLayers: { split: () => import('./split-floorplan-layer') },
|
|
58
69
|
contextualDimensions: buildWallContextualDimensions,
|
|
59
70
|
actionMenu: {
|
|
71
|
+
actions: () => import('./actions'),
|
|
60
72
|
canCurve: ({ node, nodes }) => !hasWallCurveBlockingChildren(node.children.flatMap((childId) => {
|
|
61
73
|
const child = nodes[childId];
|
|
62
74
|
return child ? [child] : [];
|
|
@@ -82,6 +94,7 @@ export const wallDefinition = {
|
|
|
82
94
|
selectable: { hitVolume: 'bbox' },
|
|
83
95
|
// Front + back faces host items (paintings, shelves, switches).
|
|
84
96
|
surfaces: {
|
|
97
|
+
hosting: false,
|
|
85
98
|
top: {
|
|
86
99
|
height: (node, { nodes }) => {
|
|
87
100
|
const wall = node;
|
|
@@ -101,7 +114,7 @@ export const wallDefinition = {
|
|
|
101
114
|
// `{ slotId, label, default }` contract every other paintable kind exposes.
|
|
102
115
|
// Paint still writes the legacy inline fields for base faces via
|
|
103
116
|
// `wallPaint`; migrating those fully into `node.slots` is a later step.
|
|
104
|
-
slots: () => wallSlots(),
|
|
117
|
+
slots: (node) => wallSlots(node),
|
|
105
118
|
},
|
|
106
119
|
relations: {
|
|
107
120
|
hosts: ['door', 'window', 'item', 'lean-to-extension'],
|
|
@@ -125,6 +138,7 @@ export const wallDefinition = {
|
|
|
125
138
|
curve: () => import('./curve-tool'),
|
|
126
139
|
'move-endpoint': () => import('./move-endpoint-tool'),
|
|
127
140
|
move: () => import('./move-tool'),
|
|
141
|
+
split: () => import('./split-tool'),
|
|
128
142
|
},
|
|
129
143
|
renderer: {
|
|
130
144
|
kind: 'parametric',
|
|
@@ -159,8 +173,55 @@ export const wallDefinition = {
|
|
|
159
173
|
floorplanSiblingOverrides: wallFloorplanSiblingOverrides,
|
|
160
174
|
toolHints: [
|
|
161
175
|
{ key: 'Left click', label: 'Set wall start / end' },
|
|
176
|
+
{
|
|
177
|
+
key: 'R',
|
|
178
|
+
label: 'Shape',
|
|
179
|
+
chip: {
|
|
180
|
+
subscribe: (onChange) => useWallDrawingMode.subscribe(onChange),
|
|
181
|
+
value: () => useWallDrawingMode.getState().mode,
|
|
182
|
+
cycle: () => useWallDrawingMode.getState().toggle(),
|
|
183
|
+
labels: { line: 'Shape: Line', rectangle: 'Shape: Rectangle' },
|
|
184
|
+
icons: { line: 'lucide:minus', rectangle: 'lucide:square' },
|
|
185
|
+
tooltip: 'Wall shape — click or press R to toggle',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
162
188
|
{ key: 'Esc', label: 'Cancel' },
|
|
163
189
|
],
|
|
190
|
+
// The split session (`split-session.ts`) is the wall's own reshape; the HUD
|
|
191
|
+
// shows these while it runs. The snapping chip comes from the scope.
|
|
192
|
+
affordanceHints: {
|
|
193
|
+
split: [
|
|
194
|
+
{ key: 'Left click', label: 'Split at the marks' },
|
|
195
|
+
{
|
|
196
|
+
key: 'Scroll',
|
|
197
|
+
label: 'Cuts',
|
|
198
|
+
chip: {
|
|
199
|
+
subscribe: (onChange) => useWallSplit.subscribe(onChange),
|
|
200
|
+
value: () => String(useWallSplit.getState().draft?.cuts ?? 1),
|
|
201
|
+
cycle: () => {
|
|
202
|
+
const cuts = useWallSplit.getState().draft?.cuts ?? 1;
|
|
203
|
+
setWallSplitCuts(cuts >= WALL_SPLIT_MAX_CUTS ? 1 : cuts + 1);
|
|
204
|
+
},
|
|
205
|
+
labels: Object.fromEntries(SPLIT_CUT_COUNTS.map((count) => [
|
|
206
|
+
count,
|
|
207
|
+
count === '1' ? 'Cuts: 1' : `Cuts: ${count}, even`,
|
|
208
|
+
])),
|
|
209
|
+
icons: Object.fromEntries(SPLIT_CUT_COUNTS.map((count) => [count, 'lucide:scissors'])),
|
|
210
|
+
tooltip: 'Number of cuts — scroll or click to change',
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
key: 'Alt',
|
|
215
|
+
label: 'Free placement',
|
|
216
|
+
// Several cuts are evenly spaced, so there is nothing to place freely.
|
|
217
|
+
visible: {
|
|
218
|
+
subscribe: (onChange) => useWallSplit.subscribe(onChange),
|
|
219
|
+
value: () => (useWallSplit.getState().draft?.cuts ?? 1) === 1,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{ key: 'Esc', label: 'Cancel' },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
164
225
|
presentation: {
|
|
165
226
|
label: 'Wall',
|
|
166
227
|
description: 'A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items.',
|
|
@@ -169,6 +230,6 @@ export const wallDefinition = {
|
|
|
169
230
|
paletteOrder: 10,
|
|
170
231
|
},
|
|
171
232
|
mcp: {
|
|
172
|
-
description: 'A wall
|
|
233
|
+
description: 'A wall defined by endpoints and optional curve sagitta. wallType selects standard or curtain. curtainWall configures construction, framing, grids, glazing, spandrels, and zero-based panel overrides. Thickness is frame depth for curtain walls.',
|
|
173
234
|
},
|
|
174
235
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const useWallDrawingMode: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
2
|
+
mode: "line" | "rectangle";
|
|
3
|
+
toggle: () => void;
|
|
4
|
+
}>>;
|
|
5
|
+
/**
|
|
6
|
+
* R toggles line / rectangle drawing (the HUD's Shape chip cycles the same
|
|
7
|
+
* store). Both wall tools mount this: the 3D tool only exists once the canvas
|
|
8
|
+
* does, and split view mounts both, so one listener serves whichever is up.
|
|
9
|
+
* The mode outlives the tool: re-arming walls resumes the last shape used.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useWallDrawingModeKeys(): void;
|
|
12
|
+
//# sourceMappingURL=drawing-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawing-mode.d.ts","sourceRoot":"","sources":["../../src/wall/drawing-mode.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB;UACvB,MAAM,GAAG,WAAW;YAClB,MAAM,IAAI;GAOjB,CAAA;AAWH;;;;;GAKG;AACH,wBAAgB,sBAAsB,SAOrC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { emitter } from '@pascal-app/core';
|
|
2
|
+
import { isEditableKeyboardTarget } from '@pascal-app/editor';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { create } from 'zustand';
|
|
5
|
+
export const useWallDrawingMode = create((set, get) => ({
|
|
6
|
+
mode: 'line',
|
|
7
|
+
toggle: () => {
|
|
8
|
+
emitter.emit('tool:cancel');
|
|
9
|
+
set({ mode: get().mode === 'rectangle' ? 'line' : 'rectangle' });
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
const onKeyDown = (e) => {
|
|
13
|
+
if (e.repeat || e.metaKey || e.ctrlKey || e.altKey)
|
|
14
|
+
return;
|
|
15
|
+
if (e.key !== 'r' && e.key !== 'R')
|
|
16
|
+
return;
|
|
17
|
+
if (isEditableKeyboardTarget(e.target))
|
|
18
|
+
return;
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
useWallDrawingMode.getState().toggle();
|
|
21
|
+
};
|
|
22
|
+
let owners = 0;
|
|
23
|
+
/**
|
|
24
|
+
* R toggles line / rectangle drawing (the HUD's Shape chip cycles the same
|
|
25
|
+
* store). Both wall tools mount this: the 3D tool only exists once the canvas
|
|
26
|
+
* does, and split view mounts both, so one listener serves whichever is up.
|
|
27
|
+
* The mode outlives the tool: re-arming walls resumes the last shape used.
|
|
28
|
+
*/
|
|
29
|
+
export function useWallDrawingModeKeys() {
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (owners++ === 0)
|
|
32
|
+
window.addEventListener('keydown', onKeyDown);
|
|
33
|
+
return () => {
|
|
34
|
+
if (--owners === 0)
|
|
35
|
+
window.removeEventListener('keydown', onKeyDown);
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type FloorplanToolContext } from '@pascal-app/editor';
|
|
2
|
+
/** The wall's plan tool: line drafting stays with the panel; rectangle mode mounts its own tool. */
|
|
3
|
+
export default function WallFloorplanTool(props: FloorplanToolContext): import("react").JSX.Element | null;
|
|
4
|
+
//# sourceMappingURL=floorplan-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floorplan-tool.d.ts","sourceRoot":"","sources":["../../src/wall/floorplan-tool.tsx"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,oBAAoB,EAO1B,MAAM,oBAAoB,CAAA;AAK3B,oGAAoG;AACpG,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,sCAIpE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { emitter, resolveTerrainWallConstructionOptions, useScene, } from '@pascal-app/core';
|
|
4
|
+
import { markToolCancelConsumed, triggerSFX, useEditor, useFloorplanDraftPreview, useFloorplanRender, useInteractionScope, } from '@pascal-app/editor';
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { useWallDrawingMode, useWallDrawingModeKeys } from './drawing-mode';
|
|
7
|
+
import { createWallRectangle } from './rectangle-command';
|
|
8
|
+
/** The wall's plan tool: line drafting stays with the panel; rectangle mode mounts its own tool. */
|
|
9
|
+
export default function WallFloorplanTool(props) {
|
|
10
|
+
useWallDrawingModeKeys();
|
|
11
|
+
const mode = useWallDrawingMode((s) => s.mode);
|
|
12
|
+
return mode === 'rectangle' ? _jsx(RectangleFloorplanTool, { ...props }) : null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Rectangle mode in the floor plan. Pointer moves stay with the panel, so the
|
|
16
|
+
* cursor, snapping, alignment guides and snap beacon are the line wall's own;
|
|
17
|
+
* this tool only claims clicks and publishes the first corner, and the panel's
|
|
18
|
+
* linear draft layer draws the four draft walls from it.
|
|
19
|
+
*/
|
|
20
|
+
function RectangleFloorplanTool({ activeLevelId }) {
|
|
21
|
+
const group = useRef(null);
|
|
22
|
+
const renderContext = useFloorplanRender();
|
|
23
|
+
const [error, setError] = useState(null);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const svg = group.current?.ownerSVGElement;
|
|
26
|
+
if (!svg || !activeLevelId)
|
|
27
|
+
return;
|
|
28
|
+
const draft = useFloorplanDraftPreview.getState;
|
|
29
|
+
let down = null;
|
|
30
|
+
let construction;
|
|
31
|
+
useInteractionScope.getState().begin({ kind: 'drafting', tool: 'wall' });
|
|
32
|
+
const claim = (e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
e.stopImmediatePropagation();
|
|
35
|
+
};
|
|
36
|
+
const onDown = (e) => {
|
|
37
|
+
if (e.button !== 0 || e.metaKey || e.ctrlKey)
|
|
38
|
+
return;
|
|
39
|
+
claim(e);
|
|
40
|
+
down = [e.clientX, e.clientY];
|
|
41
|
+
};
|
|
42
|
+
const cancel = () => {
|
|
43
|
+
if (draft().wallRectangleDraftStart)
|
|
44
|
+
markToolCancelConsumed();
|
|
45
|
+
draft().setWallRectangleDraftStart(null);
|
|
46
|
+
down = null;
|
|
47
|
+
setError(null);
|
|
48
|
+
};
|
|
49
|
+
const onClick = (e) => {
|
|
50
|
+
if (e.button !== 0 || e.metaKey || e.ctrlKey)
|
|
51
|
+
return;
|
|
52
|
+
claim(e);
|
|
53
|
+
if (!down || Math.hypot(e.clientX - down[0], e.clientY - down[1]) > 5) {
|
|
54
|
+
down = null;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
down = null;
|
|
58
|
+
// The panel's snapped cursor, exactly where a line wall would land.
|
|
59
|
+
const point = draft().cursorPoint;
|
|
60
|
+
if (!point)
|
|
61
|
+
return;
|
|
62
|
+
const start = draft().wallRectangleDraftStart;
|
|
63
|
+
if (!start) {
|
|
64
|
+
construction = resolveTerrainWallConstructionOptions(useScene.getState().nodes, activeLevelId, point, useEditor.getState().toolDefaults.wall);
|
|
65
|
+
draft().setWallRectangleDraftStart(point);
|
|
66
|
+
setError(null);
|
|
67
|
+
triggerSFX('sfx:structure-build-start');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
createWallRectangle(activeLevelId, start, point, useEditor.getState().toolDefaults.wall ?? {}, construction);
|
|
72
|
+
cancel();
|
|
73
|
+
triggerSFX('sfx:structure-build');
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
setError({ message: e.message, at: point });
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const stopDouble = (e) => {
|
|
80
|
+
if (e.button === 0)
|
|
81
|
+
claim(e);
|
|
82
|
+
};
|
|
83
|
+
// The panel publishes the snapped cursor only when it moves to a new
|
|
84
|
+
// point, so each change while a corner is down is the line draft's tick.
|
|
85
|
+
const stopTick = useFloorplanDraftPreview.subscribe((state, previous) => {
|
|
86
|
+
if (state.wallRectangleDraftStart &&
|
|
87
|
+
state.cursorPoint &&
|
|
88
|
+
state.cursorPoint !== previous.cursorPoint)
|
|
89
|
+
triggerSFX('sfx:grid-snap');
|
|
90
|
+
});
|
|
91
|
+
svg.addEventListener('pointerdown', onDown, true);
|
|
92
|
+
svg.addEventListener('click', onClick, true);
|
|
93
|
+
svg.addEventListener('dblclick', stopDouble, true);
|
|
94
|
+
emitter.on('tool:cancel', cancel);
|
|
95
|
+
return () => {
|
|
96
|
+
stopTick();
|
|
97
|
+
svg.removeEventListener('pointerdown', onDown, true);
|
|
98
|
+
svg.removeEventListener('click', onClick, true);
|
|
99
|
+
svg.removeEventListener('dblclick', stopDouble, true);
|
|
100
|
+
emitter.off('tool:cancel', cancel);
|
|
101
|
+
draft().setWallRectangleDraftStart(null);
|
|
102
|
+
useInteractionScope.getState().endIf((s) => s.kind === 'drafting' && s.tool === 'wall');
|
|
103
|
+
};
|
|
104
|
+
}, [activeLevelId]);
|
|
105
|
+
const unitsPerPixel = renderContext?.unitsPerPixel ?? 0.01;
|
|
106
|
+
return (_jsx("g", { ref: group, pointerEvents: "none", children: error && (_jsx("text", { x: error.at[0], y: error.at[1] - 0.2, fill: "#ef4444", fontSize: 12 * unitsPerPixel, children: error.message })) }));
|
|
107
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/wall/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,iBAAiB,EAEtB,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/wall/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAQpB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAKL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAA;AAalC,QAAA,MAAM,yBAAyB,yDAA0D,CAAA;AAEzF,KAAK,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAexE,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,aAAa,CAAA;IACrB,cAAc,EAAE,aAAa,CAAA;IAC7B,iCAAiC,EAAE,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,CAAC,CAAA;CACjG,CAAA;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B,GAAG,sBAAsB,CAqCzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,iBAAiB,GAAG,IAAI,CA8NjG"}
|
package/dist/wall/floorplan.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { calculateLevelMiters, getWallCurveFrameAt, getWallCurveLength, getWallMidpointHandlePoint, getWallPlanFootprint, getWallThickness, isCurvedWall, } from '@pascal-app/core';
|
|
1
|
+
import { calculateLevelMiters, getWallCurveFrameAt, getWallCurveLength, getWallEffectiveHeightForNodes, getWallMidpointHandlePoint, getWallPlanFootprint, getWallThickness, isCurvedWall, } from '@pascal-app/core';
|
|
2
2
|
import { floorplanGeometryMetadata, readFloorplanContext } from '@pascal-app/editor';
|
|
3
3
|
import { constructionDimensionStandard } from '../shared/construction-dimension-standards';
|
|
4
4
|
import { buildCurvedWallConstructionDimensions, buildLevelWallConstructionDimensionPlan, buildWallConstructionDimensions, renderPlannedConstructionDimensions, } from './construction-dimensions';
|
|
5
|
+
import { buildCurtainWallFloorplan } from './curtain-wall-floorplan';
|
|
5
6
|
import { hasWallCurveBlockingChildren } from './curve-eligibility';
|
|
6
7
|
// Same constants the legacy `getFloorplanWall` uses (editor/lib/floorplan/walls.ts).
|
|
7
8
|
// Slightly exaggerates thin walls so the 2D plan stays legible without
|
|
@@ -103,7 +104,7 @@ export function buildWallFloorplan(node, ctx) {
|
|
|
103
104
|
: isHovered && palette
|
|
104
105
|
? palette.wallHoverStroke
|
|
105
106
|
: '#1f2937';
|
|
106
|
-
const fill = showSelectedChrome ? '#ffffff' : '#374151';
|
|
107
|
+
const fill = node.wallType === 'curtain' ? 'transparent' : showSelectedChrome ? '#ffffff' : '#374151';
|
|
107
108
|
const children = [
|
|
108
109
|
{
|
|
109
110
|
kind: 'polygon',
|
|
@@ -121,6 +122,9 @@ export function buildWallFloorplan(node, ctx) {
|
|
|
121
122
|
cursor: isSelected ? 'default' : undefined,
|
|
122
123
|
},
|
|
123
124
|
];
|
|
125
|
+
if (node.wallType === 'curtain') {
|
|
126
|
+
children.push(...buildCurtainWallFloorplan(node, getWallEffectiveHeightForNodes(node, ctx.sceneNodes ?? {}), stroke, ctx.children));
|
|
127
|
+
}
|
|
124
128
|
if (automaticDimensions) {
|
|
125
129
|
const dimensionStroke = isSelected && palette ? palette.selectedStroke : (palette?.measurementStroke ?? '#334155');
|
|
126
130
|
const dimensionStandard = constructionDimensionStandard({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"length-patch.d.ts","sourceRoot":"","sources":["../../src/wall/length-patch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE/F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAW1F"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getClampedWallCurveOffset, getWallCurveLength } from '@pascal-app/core';
|
|
2
|
+
export function buildWallLengthPatch(node, nextLength) {
|
|
3
|
+
const length = getWallCurveLength(node);
|
|
4
|
+
if (!length || !Number.isFinite(nextLength) || nextLength <= 0)
|
|
5
|
+
return {};
|
|
6
|
+
const ratio = nextLength / length;
|
|
7
|
+
return {
|
|
8
|
+
end: [
|
|
9
|
+
node.start[0] + (node.end[0] - node.start[0]) * ratio,
|
|
10
|
+
node.start[1] + (node.end[1] - node.start[1]) * ratio,
|
|
11
|
+
],
|
|
12
|
+
...(node.curveOffset ? { curveOffset: getClampedWallCurveOffset(node) * ratio } : {}),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/wall/move-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,EAwBL,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAqIzB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"move-tool.d.ts","sourceRoot":"","sources":["../../src/wall/move-tool.tsx"],"names":[],"mappings":"AAEA,OAAO,EAwBL,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAqIzB,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CA+jBrD,CAAA;AAED,eAAe,YAAY,CAAA"}
|