@pascal-app/nodes 1.0.0 → 1.0.1
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/box-vent/panel.js +4 -4
- package/dist/cabinet/appliance-layout.d.ts +26 -0
- package/dist/cabinet/appliance-layout.d.ts.map +1 -0
- package/dist/cabinet/appliance-layout.js +52 -0
- package/dist/cabinet/countertop-layout.d.ts +173 -0
- package/dist/cabinet/countertop-layout.d.ts.map +1 -0
- package/dist/cabinet/countertop-layout.js +80 -0
- package/dist/cabinet/definition.d.ts.map +1 -1
- package/dist/cabinet/definition.js +15 -8
- package/dist/cabinet/geometry/cooktop.d.ts.map +1 -1
- package/dist/cabinet/geometry/cooktop.js +2 -2
- package/dist/cabinet/geometry/run.d.ts.map +1 -1
- package/dist/cabinet/geometry/run.js +10 -71
- package/dist/cabinet/geometry/sink.d.ts +2 -12
- package/dist/cabinet/geometry/sink.d.ts.map +1 -1
- package/dist/cabinet/geometry/sink.js +8 -39
- package/dist/cabinet/hosted-resize.d.ts +8 -0
- package/dist/cabinet/hosted-resize.d.ts.map +1 -0
- package/dist/cabinet/hosted-resize.js +158 -0
- package/dist/cabinet/run-layout.d.ts +8 -1
- package/dist/cabinet/run-layout.d.ts.map +1 -1
- package/dist/cabinet/run-layout.js +22 -14
- package/dist/cabinet/run-ops.d.ts +3 -3
- package/dist/cabinet/run-ops.js +3 -3
- package/dist/cabinet/run-panel.d.ts +1 -1
- package/dist/cabinet/run-panel.d.ts.map +1 -1
- package/dist/cabinet/run-panel.js +16 -7
- package/dist/cabinet/surfaces.d.ts +4 -0
- package/dist/cabinet/surfaces.d.ts.map +1 -0
- package/dist/cabinet/surfaces.js +101 -0
- package/dist/cabinet/tree-structure.d.ts.map +1 -1
- package/dist/cabinet/tree-structure.js +10 -9
- package/dist/ceiling/definition.js +1 -1
- package/dist/ceiling/floorplan-affordances.d.ts +4 -4
- package/dist/ceiling/panel.js +1 -1
- package/dist/chimney/panel.js +9 -9
- package/dist/cupola/panel.js +1 -1
- package/dist/door/panel.js +12 -12
- package/dist/dormer/panel-position-section.js +2 -2
- package/dist/dormer/panel.js +1 -1
- package/dist/duct-segment/parametrics.d.ts.map +1 -1
- package/dist/duct-segment/parametrics.js +5 -1
- package/dist/duct-segment/tool.d.ts +1 -0
- package/dist/duct-segment/tool.d.ts.map +1 -1
- package/dist/duct-segment/tool.js +10 -8
- package/dist/eyebrow-vent/panel.js +1 -1
- package/dist/fence/inspector-editors.js +1 -1
- package/dist/imported-mesh/definition.d.ts +5 -0
- package/dist/imported-mesh/definition.d.ts.map +1 -0
- package/dist/imported-mesh/definition.js +38 -0
- package/dist/imported-mesh/floorplan.d.ts +4 -0
- package/dist/imported-mesh/floorplan.d.ts.map +1 -0
- package/dist/imported-mesh/floorplan.js +34 -0
- package/dist/imported-mesh/geometry.d.ts +5 -0
- package/dist/imported-mesh/geometry.d.ts.map +1 -0
- package/dist/imported-mesh/geometry.js +34 -0
- package/dist/imported-mesh/index.d.ts +4 -0
- package/dist/imported-mesh/index.d.ts.map +1 -0
- package/dist/imported-mesh/index.js +3 -0
- package/dist/imported-mesh/schema.d.ts +2 -0
- package/dist/imported-mesh/schema.d.ts.map +1 -0
- package/dist/imported-mesh/schema.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/item/floorplan-move.d.ts +9 -1
- package/dist/item/floorplan-move.d.ts.map +1 -1
- package/dist/item/floorplan-move.js +55 -2
- package/dist/item/floorplan.d.ts.map +1 -1
- package/dist/item/floorplan.js +11 -0
- package/dist/item/move-tool.js +1 -1
- package/dist/item/panel.js +1 -1
- package/dist/pipe-segment/parametrics.d.ts.map +1 -1
- package/dist/pipe-segment/parametrics.js +5 -1
- package/dist/procedural-item/definition.d.ts +4 -0
- package/dist/procedural-item/definition.d.ts.map +1 -0
- package/dist/procedural-item/definition.js +254 -0
- package/dist/procedural-item/geometry.d.ts +31 -0
- package/dist/procedural-item/geometry.d.ts.map +1 -0
- package/dist/procedural-item/geometry.js +88 -0
- package/dist/procedural-item/index.d.ts +3 -0
- package/dist/procedural-item/index.d.ts.map +1 -0
- package/dist/procedural-item/index.js +2 -0
- package/dist/procedural-item/move-session.d.ts +15 -0
- package/dist/procedural-item/move-session.d.ts.map +1 -0
- package/dist/procedural-item/move-session.js +197 -0
- package/dist/procedural-item/move-tool.d.ts +5 -0
- package/dist/procedural-item/move-tool.d.ts.map +1 -0
- package/dist/procedural-item/move-tool.js +193 -0
- package/dist/procedural-item/renderer.d.ts +5 -0
- package/dist/procedural-item/renderer.d.ts.map +1 -0
- package/dist/procedural-item/renderer.js +66 -0
- package/dist/ridge-vent/panel.js +4 -4
- package/dist/roof/panel.d.ts.map +1 -1
- package/dist/roof/panel.js +7 -5
- package/dist/roof-segment/panel.d.ts.map +1 -1
- package/dist/roof-segment/panel.js +5 -5
- package/dist/shared/automatic-run-end-cap.d.ts +1 -0
- package/dist/shared/automatic-run-end-cap.d.ts.map +1 -1
- package/dist/shared/automatic-run-end-cap.js +10 -0
- package/dist/shared/distribution-run-tool.d.ts +10 -0
- package/dist/shared/distribution-run-tool.d.ts.map +1 -1
- package/dist/shared/distribution-run-tool.js +56 -11
- package/dist/shared/panel-fields.d.ts +15 -0
- package/dist/shared/panel-fields.d.ts.map +1 -0
- package/dist/shared/panel-fields.js +31 -0
- package/dist/shared/resting-surface-plan.d.ts +3 -0
- package/dist/shared/resting-surface-plan.d.ts.map +1 -0
- package/dist/shared/resting-surface-plan.js +28 -0
- package/dist/shelf/definition.d.ts.map +1 -1
- package/dist/shelf/definition.js +2 -1
- package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
- package/dist/shelf/floorplan-affordances.js +5 -3
- package/dist/shelf/floorplan-move.d.ts.map +1 -1
- package/dist/shelf/floorplan-move.js +3 -2
- package/dist/shelf/geometry.d.ts.map +1 -1
- package/dist/shelf/geometry.js +2 -10
- package/dist/skylight/panel.js +6 -6
- package/dist/slab/panel.js +1 -1
- package/dist/solar-panel/panel.js +2 -2
- package/dist/spawn/panel.js +1 -1
- package/dist/stair/panel.d.ts.map +1 -1
- package/dist/stair/panel.js +9 -7
- package/dist/stair-segment/panel.js +4 -4
- package/dist/turbine-vent/panel.js +1 -1
- package/dist/unit/definition.d.ts +10 -0
- package/dist/unit/definition.d.ts.map +1 -0
- package/dist/unit/definition.js +38 -0
- package/dist/unit/index.d.ts +2 -0
- package/dist/unit/index.d.ts.map +1 -0
- package/dist/unit/index.js +1 -0
- package/dist/unit/parametrics.d.ts +3 -0
- package/dist/unit/parametrics.d.ts.map +1 -0
- package/dist/unit/parametrics.js +4 -0
- package/dist/unit/schema.d.ts +2 -0
- package/dist/unit/schema.d.ts.map +1 -0
- package/dist/unit/schema.js +1 -0
- package/dist/unit/unit-panel.d.ts +5 -0
- package/dist/unit/unit-panel.d.ts.map +1 -0
- package/dist/unit/unit-panel.js +61 -0
- package/dist/window/panel.js +6 -6
- package/dist/zone/definition.d.ts.map +1 -1
- package/dist/zone/definition.js +7 -1
- package/dist/zone/floorplan.d.ts +1 -3
- package/dist/zone/floorplan.d.ts.map +1 -1
- package/dist/zone/floorplan.js +43 -28
- package/dist/zone/quantities-panel.d.ts.map +1 -1
- package/dist/zone/quantities-panel.js +33 -29
- package/dist/zone/renderer.d.ts.map +1 -1
- package/dist/zone/renderer.js +20 -6
- package/dist/zone/unit-membership.d.ts +12 -0
- package/dist/zone/unit-membership.d.ts.map +1 -0
- package/dist/zone/unit-membership.js +22 -0
- package/package.json +14 -8
package/dist/zone/floorplan.js
CHANGED
|
@@ -2,10 +2,9 @@ import { resolveAutoZonePolygon, } from '@pascal-app/core';
|
|
|
2
2
|
import { floorplanGeometryMetadata, readFloorplanContext } from '@pascal-app/editor';
|
|
3
3
|
import { formatConstructionLength, } from '../shared/construction-length';
|
|
4
4
|
import { buildRoomClearDimensions } from './room-clear-dimensions';
|
|
5
|
+
import { owningUnitForZone } from './unit-membership';
|
|
5
6
|
/**
|
|
6
|
-
* Stage C floor-plan builder for zone. Zones are colored polygons
|
|
7
|
-
* fill + outline both come from `zone.color`. Selection adds an
|
|
8
|
-
* accent-colored outline.
|
|
7
|
+
* Stage C floor-plan builder for zone. Zones are colored polygons.
|
|
9
8
|
*
|
|
10
9
|
* The zone's `name` renders as a centered text label at the polygon's
|
|
11
10
|
* geometric centroid. The registry layer sorts zones before every
|
|
@@ -18,23 +17,25 @@ export function buildZoneFloorplan(node, ctx) {
|
|
|
18
17
|
return null;
|
|
19
18
|
const view = ctx.viewState;
|
|
20
19
|
const floorplanContext = readFloorplanContext(ctx);
|
|
21
|
-
const palette = view?.palette;
|
|
22
20
|
const isSelected = view?.selected ?? false;
|
|
23
21
|
const isHighlighted = view?.highlighted ?? false;
|
|
24
22
|
const showSelectedChrome = isSelected || isHighlighted;
|
|
25
23
|
const points = ring.map(([x, z]) => [x, z]);
|
|
26
|
-
const
|
|
24
|
+
const unit = owningUnitForZone(node, ctx.resolve);
|
|
25
|
+
const tintColor = unit?.color ?? node.color;
|
|
26
|
+
const stroke = node.color;
|
|
27
|
+
const focusOpacity = view?.focusedUnitId && !view.focusedUnitMemberIds?.includes(node.id) ? 0.35 : 1;
|
|
27
28
|
const isRoom = node.spaceRole === 'room';
|
|
28
29
|
const fillOpacity = isRoom ? (isSelected ? 0.12 : 0.04) : isSelected ? 0.28 : 0.16;
|
|
29
30
|
const children = [
|
|
30
31
|
{
|
|
31
32
|
kind: 'polygon',
|
|
32
33
|
points,
|
|
33
|
-
fill:
|
|
34
|
-
fillOpacity,
|
|
34
|
+
fill: tintColor,
|
|
35
|
+
fillOpacity: fillOpacity * focusOpacity,
|
|
35
36
|
stroke,
|
|
36
37
|
strokeWidth: showSelectedChrome ? 0.08 : 0.05,
|
|
37
|
-
strokeOpacity: showSelectedChrome ? 0.96 : 0.72,
|
|
38
|
+
strokeOpacity: (showSelectedChrome ? 0.96 : 0.72) * focusOpacity,
|
|
38
39
|
strokeLinejoin: 'round',
|
|
39
40
|
vectorEffect: 'non-scaling-stroke',
|
|
40
41
|
},
|
|
@@ -88,44 +89,58 @@ export function buildZoneFloorplan(node, ctx) {
|
|
|
88
89
|
const [cx, cy] = polygonCentroid(ring);
|
|
89
90
|
const name = node.name?.trim();
|
|
90
91
|
if (isRoom) {
|
|
91
|
-
children.push(...buildRoomLabels(node, cx, cy, view?.unit ?? 'metric', floorplanContext.purpose === 'document' ? 'document' : 'editor', floorplanContext.metricNotation, stroke));
|
|
92
|
+
children.push(...buildRoomLabels(node, cx, cy, view?.unit ?? 'metric', floorplanContext.purpose === 'document' ? 'document' : 'editor', floorplanContext.metricNotation, stroke, unit?.name));
|
|
92
93
|
if (floorplanContext.automaticDimensions) {
|
|
93
94
|
children.push(...buildRoomClearDimensions(node, ctx));
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
else if (name) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
const unitName = unit?.name.trim();
|
|
99
|
+
const lines = unitName
|
|
100
|
+
? [
|
|
101
|
+
{ text: name, fontSize: ZONE_LABEL_FONT_SIZE, fontWeight: 500 },
|
|
102
|
+
{ text: unitName, fontSize: ZONE_UNIT_LABEL_FONT_SIZE, fontWeight: 600 },
|
|
103
|
+
]
|
|
104
|
+
: [{ text: name, fontSize: ZONE_LABEL_FONT_SIZE, fontWeight: 500 }];
|
|
105
|
+
const startY = cy - ((lines.length - 1) * ROOM_LABEL_LINE_SPACING) / 2;
|
|
106
|
+
lines.forEach((line, index) => {
|
|
107
|
+
children.push({
|
|
108
|
+
kind: 'text',
|
|
109
|
+
x: cx,
|
|
110
|
+
y: startY + index * ROOM_LABEL_LINE_SPACING,
|
|
111
|
+
text: line.text,
|
|
112
|
+
// Same constants the legacy `FLOORPLAN_ZONE_LABEL_FONT_SIZE` uses
|
|
113
|
+
// (0.2 plan metres ≈ readable at typical building zooms).
|
|
114
|
+
fontSize: line.fontSize,
|
|
115
|
+
fill: '#ffffff',
|
|
116
|
+
stroke: node.color,
|
|
117
|
+
strokeWidth: line.fontSize * 0.35,
|
|
118
|
+
paintOrder: 'stroke',
|
|
119
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
120
|
+
fontWeight: line.fontWeight,
|
|
121
|
+
textAnchor: 'middle',
|
|
122
|
+
dominantBaseline: 'central',
|
|
123
|
+
opacity: showSelectedChrome ? 1 : 0.92,
|
|
124
|
+
upright: true,
|
|
125
|
+
});
|
|
115
126
|
});
|
|
116
127
|
}
|
|
117
128
|
return { kind: 'group', children };
|
|
118
129
|
}
|
|
119
130
|
const ZONE_LABEL_FONT_SIZE = 0.2;
|
|
131
|
+
const ZONE_UNIT_LABEL_FONT_SIZE = 0.13;
|
|
120
132
|
const ROOM_NAME_FONT_SIZE = 0.2;
|
|
121
133
|
const ROOM_NUMBER_FONT_SIZE = 0.16;
|
|
122
134
|
const ROOM_DETAIL_FONT_SIZE = 0.11;
|
|
123
135
|
const ROOM_LABEL_LINE_SPACING = 0.18;
|
|
124
|
-
function buildRoomLabels(node, x, y, unit, profile, metricNotation, color) {
|
|
136
|
+
function buildRoomLabels(node, x, y, unit, profile, metricNotation, color, unitName) {
|
|
125
137
|
const lines = [];
|
|
126
138
|
const name = node.name.trim();
|
|
127
139
|
if (name)
|
|
128
140
|
lines.push({ text: name, fontSize: ROOM_NAME_FONT_SIZE, fontWeight: 700 });
|
|
141
|
+
if (unitName?.trim()) {
|
|
142
|
+
lines.push({ text: unitName.trim(), fontSize: ROOM_NUMBER_FONT_SIZE, fontWeight: 600 });
|
|
143
|
+
}
|
|
129
144
|
if (node.roomNumber) {
|
|
130
145
|
lines.push({ text: node.roomNumber, fontSize: ROOM_NUMBER_FONT_SIZE, fontWeight: 600 });
|
|
131
146
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quantities-panel.d.ts","sourceRoot":"","sources":["../../src/zone/quantities-panel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"quantities-panel.d.ts","sourceRoot":"","sources":["../../src/zone/quantities-panel.tsx"],"names":[],"mappings":"AA8RA,MAAM,CAAC,OAAO,UAAU,mBAAmB,uCA8F1C"}
|
|
@@ -3,8 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { deriveZoneQuantityReport, resolveAutoZonePolygon, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { formatAreaLabel, formatLinearMeasurement, formatVolumeLabel, MetricControl, PanelSection, ToggleControl, } from '@pascal-app/editor';
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer';
|
|
6
|
-
import {
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
7
|
import { useShallow } from 'zustand/react/shallow';
|
|
8
|
+
import { PanelSelect, PanelTextField } from '../shared/panel-fields';
|
|
9
|
+
import { buildingUnitsForZone } from './unit-membership';
|
|
8
10
|
function ZonePlanSketch({ edgeLengths, metricNotation, polygon, unit, }) {
|
|
9
11
|
if (polygon.length < 3) {
|
|
10
12
|
return (_jsx("div", { className: "flex h-28 items-center justify-center rounded-md border border-border/50 text-muted-foreground text-xs", children: "Zone boundary unavailable" }));
|
|
@@ -44,43 +46,45 @@ function ZonePlanSketch({ edgeLengths, metricNotation, polygon, unit, }) {
|
|
|
44
46
|
function QuantityRow({ abbreviation, format, label, quantity, }) {
|
|
45
47
|
return (_jsxs("div", { className: "rounded-md border border-border/50 bg-background/35 px-2.5 py-2", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "font-mono font-semibold text-cyan-600 text-[10px]", children: abbreviation }), _jsx("span", { className: "text-muted-foreground text-xs", children: label }), _jsx("span", { className: "ml-auto font-mono font-medium text-foreground text-xs tabular-nums", children: quantity.status === 'available' ? format(quantity.value) : 'Not proven' })] }), _jsx("div", { className: "mt-1 text-[10px] text-muted-foreground leading-snug", children: quantity.status === 'available' ? quantity.note : quantity.reason })] }));
|
|
46
48
|
}
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
function ZoneUnitPanel({ zone }) {
|
|
50
|
+
const units = useScene(useShallow((state) => buildingUnitsForZone(zone, (id) => state.nodes[id])));
|
|
51
|
+
if (units.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
const current = units.find((unit) => unit.members.includes(zone.id));
|
|
54
|
+
const handleChange = (nextId) => {
|
|
55
|
+
const { nodes, updateNodes } = useScene.getState();
|
|
56
|
+
const updates = [];
|
|
57
|
+
for (const unit of Object.values(nodes)) {
|
|
58
|
+
if (unit.type !== 'unit')
|
|
59
|
+
continue;
|
|
60
|
+
const isMember = unit.members.includes(zone.id);
|
|
61
|
+
if (unit.id === nextId && !isMember) {
|
|
62
|
+
updates.push({ id: unit.id, data: { members: [...unit.members, zone.id] } });
|
|
63
|
+
}
|
|
64
|
+
else if (unit.id !== nextId && isMember) {
|
|
65
|
+
updates.push({
|
|
66
|
+
id: unit.id,
|
|
67
|
+
data: { members: unit.members.filter((id) => id !== zone.id) },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
56
70
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
onCommit(next);
|
|
60
|
-
else
|
|
61
|
-
setDraft(value);
|
|
71
|
+
if (updates.length > 0)
|
|
72
|
+
updateNodes(updates);
|
|
62
73
|
};
|
|
63
|
-
return (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
cancelRef.current = true;
|
|
68
|
-
event.currentTarget.blur();
|
|
69
|
-
}
|
|
70
|
-
}, type: "text", value: draft })] }));
|
|
71
|
-
}
|
|
72
|
-
function RoomSelect({ label, onChange, options, value, }) {
|
|
73
|
-
return (_jsxs("label", { className: "flex h-10 items-center justify-between gap-3 rounded-lg border border-border/50 bg-[#2C2C2E] px-3 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("select", { className: "min-w-0 rounded-md border border-border/50 bg-[#232325] px-2 py-1 text-foreground text-xs outline-none", onChange: (event) => onChange(event.target.value), value: value, children: options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value))) })] }));
|
|
74
|
+
return (_jsx(PanelSection, { title: "Unit", children: _jsx(PanelSelect, { label: "Unit", onChange: handleChange, options: [
|
|
75
|
+
{ label: 'None', value: '' },
|
|
76
|
+
...units.map((unit) => ({ label: unit.name || 'Unit', value: unit.id })),
|
|
77
|
+
], value: current?.id ?? '' }) }));
|
|
74
78
|
}
|
|
75
79
|
function RoomDocumentationPanel({ zone }) {
|
|
76
80
|
const updateNode = useScene((state) => state.updateNode);
|
|
77
81
|
const update = (patch) => updateNode(zone.id, patch);
|
|
78
82
|
const isRoom = zone.spaceRole === 'room';
|
|
79
|
-
return (_jsxs(PanelSection, { title: "Room documentation", children: [_jsx(ToggleControl, { checked: isRoom, label: "Architectural room", onChange: (checked) => update({ spaceRole: checked ? 'room' : 'generic' }) }), isRoom ? (_jsxs(_Fragment, { children: [_jsx(
|
|
83
|
+
return (_jsxs(PanelSection, { title: "Room documentation", children: [_jsx(ToggleControl, { checked: isRoom, label: "Architectural room", onChange: (checked) => update({ spaceRole: checked ? 'room' : 'generic' }) }), isRoom ? (_jsxs(_Fragment, { children: [_jsx(PanelTextField, { label: "Room name", onCommit: (name) => update({ name }), value: zone.name }), _jsx(PanelTextField, { label: "Room number", onCommit: (roomNumber) => update({ roomNumber }), value: zone.roomNumber }), _jsx(PanelSelect, { label: "Enclosure", onChange: (enclosureStatus) => update({ enclosureStatus: enclosureStatus }), options: [
|
|
80
84
|
{ label: 'Auto-detect', value: 'auto' },
|
|
81
85
|
{ label: 'Enclosed', value: 'enclosed' },
|
|
82
86
|
{ label: 'Open', value: 'open' },
|
|
83
|
-
], value: zone.enclosureStatus }), _jsx(
|
|
87
|
+
], value: zone.enclosureStatus }), _jsx(PanelTextField, { label: "Occupancy / use", onCommit: (occupancy) => update({ occupancy }), value: zone.occupancy }), _jsx(PanelTextField, { label: "Floor finish", onCommit: (floorFinish) => update({ floorFinish }), value: zone.floorFinish }), _jsx(PanelTextField, { label: "Wall finish", onCommit: (wallFinish) => update({ wallFinish }), value: zone.wallFinish }), _jsx(PanelTextField, { label: "Ceiling finish", onCommit: (ceilingFinish) => update({ ceilingFinish }), value: zone.ceilingFinish }), _jsx(MetricControl, { label: "Ceiling height", max: 20, min: 0.1, onChange: (ceilingHeight) => update({ ceilingHeight }), precision: 2, step: 0.05, unit: "m", value: zone.ceilingHeight }), _jsx(PanelSelect, { label: "Clear dimensions", onChange: (clearDimensionPolicy) => update({
|
|
84
88
|
clearDimensionPolicy: clearDimensionPolicy,
|
|
85
89
|
}), options: [
|
|
86
90
|
{ label: 'None', value: 'none' },
|
|
@@ -124,5 +128,5 @@ export default function ZoneQuantitiesPanel() {
|
|
|
124
128
|
const report = useMemo(() => (effectiveZone ? deriveZoneQuantityReport(effectiveZone, effectiveNodes) : null), [effectiveNodes, effectiveZone]);
|
|
125
129
|
if (!effectiveZone || !report)
|
|
126
130
|
return null;
|
|
127
|
-
return (_jsxs(_Fragment, { children: [_jsx(RoomDocumentationPanel, { zone: effectiveZone }), _jsxs(PanelSection, { title: effectiveZone.spaceRole === 'room' ? 'Room quantities' : 'Zone quantities', children: [_jsxs("div", { className: "overflow-hidden rounded-md border border-cyan-950/20 bg-[#f8faf7] text-slate-950", children: [_jsxs("div", { className: "flex items-center border-cyan-950/15 border-b px-2.5 py-2", children: [_jsx("span", { className: "font-semibold text-[11px]", children: effectiveZone.name }), _jsx("span", { className: "ml-auto rounded-full border border-cyan-800/25 bg-cyan-50 px-2 py-0.5 text-cyan-900 text-[9px]", children: report.classification === 'enclosed-room' ? 'Enclosed room' : 'Footprint only' })] }), _jsxs("div", { className: "flex items-baseline gap-2 px-2.5 py-2 font-mono text-[10px]", children: [_jsx("span", { className: "text-cyan-800", children: "A" }), _jsx("span", { children: formatAreaLabel(report.footprintArea, unit, 2) }), _jsx("span", { className: "ml-auto text-slate-600", children: "P" }), _jsx("span", { children: formatLinearMeasurement(report.perimeter, unit, metricNotation) })] })] }), _jsx(ZonePlanSketch, { edgeLengths: report.edgeLengths, metricNotation: metricNotation, polygon: effectiveZone.polygon, unit: unit }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(QuantityRow, { abbreviation: "Aw", format: (value) => formatAreaLabel(value, unit, 2), label: "Wall surface", quantity: report.wallSurface }), _jsx(QuantityRow, { abbreviation: "Af", format: (value) => formatAreaLabel(value, unit, 2), label: "Floor surface", quantity: report.floorSurface }), _jsx(QuantityRow, { abbreviation: "V", format: (value) => formatVolumeLabel(value, unit, 2), label: "Volume", quantity: report.volume })] })] })] }));
|
|
131
|
+
return (_jsxs(_Fragment, { children: [_jsx(ZoneUnitPanel, { zone: effectiveZone }), _jsx(RoomDocumentationPanel, { zone: effectiveZone }), _jsxs(PanelSection, { title: effectiveZone.spaceRole === 'room' ? 'Room quantities' : 'Zone quantities', children: [_jsxs("div", { className: "overflow-hidden rounded-md border border-cyan-950/20 bg-[#f8faf7] text-slate-950", children: [_jsxs("div", { className: "flex items-center border-cyan-950/15 border-b px-2.5 py-2", children: [_jsx("span", { className: "font-semibold text-[11px]", children: effectiveZone.name }), _jsx("span", { className: "ml-auto rounded-full border border-cyan-800/25 bg-cyan-50 px-2 py-0.5 text-cyan-900 text-[9px]", children: report.classification === 'enclosed-room' ? 'Enclosed room' : 'Footprint only' })] }), _jsxs("div", { className: "flex items-baseline gap-2 px-2.5 py-2 font-mono text-[10px]", children: [_jsx("span", { className: "text-cyan-800", children: "A" }), _jsx("span", { children: formatAreaLabel(report.footprintArea, unit, 2) }), _jsx("span", { className: "ml-auto text-slate-600", children: "P" }), _jsx("span", { children: formatLinearMeasurement(report.perimeter, unit, metricNotation) })] })] }), _jsx(ZonePlanSketch, { edgeLengths: report.edgeLengths, metricNotation: metricNotation, polygon: effectiveZone.polygon, unit: unit }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(QuantityRow, { abbreviation: "Aw", format: (value) => formatAreaLabel(value, unit, 2), label: "Wall surface", quantity: report.wallSurface }), _jsx(QuantityRow, { abbreviation: "Af", format: (value) => formatAreaLabel(value, unit, 2), label: "Floor surface", quantity: report.floorSurface }), _jsx(QuantityRow, { abbreviation: "V", format: (value) => formatVolumeLabel(value, unit, 2), label: "Volume", quantity: report.volume })] })] })] }));
|
|
128
132
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/zone/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/zone/renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AA0GzB,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,uCA+MxD,CAAA;AAED,eAAe,YAAY,CAAA"}
|
package/dist/zone/renderer.js
CHANGED
|
@@ -8,6 +8,7 @@ import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, Shape } from
|
|
|
8
8
|
import { color, float, uniform, uv } from 'three/tsl';
|
|
9
9
|
import { MeshBasicNodeMaterial } from 'three/webgpu';
|
|
10
10
|
import { useShallow } from 'zustand/react/shallow';
|
|
11
|
+
import { owningUnitForZone } from './unit-membership';
|
|
11
12
|
const Y_OFFSET = 0.01;
|
|
12
13
|
const WALL_HEIGHT = 2.3;
|
|
13
14
|
/**
|
|
@@ -113,6 +114,10 @@ export const ZoneRenderer = ({ node }) => {
|
|
|
113
114
|
return override ? { ...dependency, ...override } : dependency;
|
|
114
115
|
}), [dependencyIds, dependencyNodes, dependencyOverrides, node]);
|
|
115
116
|
const polygon = livePolygon ?? proceduralPolygon;
|
|
117
|
+
// The selector returns the unit node itself, so only edits to that unit
|
|
118
|
+
// or its membership re-render this zone.
|
|
119
|
+
const unit = useScene((s) => owningUnitForZone(node, (id) => s.nodes[id]));
|
|
120
|
+
const tintColor = unit?.color ?? node.color;
|
|
116
121
|
// Create floor shape from polygon
|
|
117
122
|
const floorShape = useMemo(() => {
|
|
118
123
|
if (!polygon || polygon.length < 3)
|
|
@@ -158,15 +163,15 @@ export const ZoneRenderer = ({ node }) => {
|
|
|
158
163
|
}, [polygon]);
|
|
159
164
|
// Create materials
|
|
160
165
|
const floorMaterial = useMemo(() => {
|
|
161
|
-
if (!
|
|
166
|
+
if (!tintColor)
|
|
162
167
|
return null;
|
|
163
|
-
return createFloorMaterial(
|
|
164
|
-
}, [
|
|
168
|
+
return createFloorMaterial(tintColor);
|
|
169
|
+
}, [tintColor]);
|
|
165
170
|
const wallMaterial = useMemo(() => {
|
|
166
|
-
if (!node
|
|
171
|
+
if (!node.color)
|
|
167
172
|
return null;
|
|
168
173
|
return createWallGradientMaterial(node.color);
|
|
169
|
-
}, [node
|
|
174
|
+
}, [node.color]);
|
|
170
175
|
const handlers = useNodeEvents(node, 'zone');
|
|
171
176
|
if (!(node && floorShape && wallGeometry && floorMaterial && wallMaterial)) {
|
|
172
177
|
return null;
|
|
@@ -183,7 +188,16 @@ export const ZoneRenderer = ({ node }) => {
|
|
|
183
188
|
color: 'white',
|
|
184
189
|
textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
|
|
185
190
|
textAlign: 'center',
|
|
186
|
-
}, children: _jsx("span", { children: node.name }) }),
|
|
191
|
+
}, children: _jsx("span", { children: node.name }) }), unit && (_jsx("div", { style: {
|
|
192
|
+
marginTop: '2px',
|
|
193
|
+
padding: '1px 6px',
|
|
194
|
+
borderRadius: '999px',
|
|
195
|
+
backgroundColor: tintColor,
|
|
196
|
+
color: 'white',
|
|
197
|
+
fontSize: '10px',
|
|
198
|
+
lineHeight: '14px',
|
|
199
|
+
whiteSpace: 'nowrap',
|
|
200
|
+
}, children: unit.name })), _jsxs("div", { className: "label-pin", style: {
|
|
187
201
|
display: 'flex',
|
|
188
202
|
flexDirection: 'column',
|
|
189
203
|
alignItems: 'center',
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AnyNode, AnyNodeId, UnitNode, ZoneNode } from '@pascal-app/core';
|
|
2
|
+
type ResolveNode = (id: AnyNodeId) => AnyNode | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Units under the building that owns this zone's level. Units are building
|
|
5
|
+
* children, so this walks that short list instead of scanning every node
|
|
6
|
+
* the way `unitsForZone` does — cheap enough for per-zone store selectors.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildingUnitsForZone(zone: Pick<ZoneNode, 'parentId'>, resolve: ResolveNode): UnitNode[];
|
|
9
|
+
/** The unit whose colour and name a member zone wears: the first one listing it. */
|
|
10
|
+
export declare function owningUnitForZone(zone: Pick<ZoneNode, 'id' | 'parentId'>, resolve: ResolveNode): UnitNode | null;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=unit-membership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-membership.d.ts","sourceRoot":"","sources":["../../src/zone/unit-membership.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE9E,KAAK,WAAW,GAAG,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,GAAG,SAAS,CAAA;AAEzD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAChC,OAAO,EAAE,WAAW,GACnB,QAAQ,EAAE,CAUZ;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,UAAU,CAAC,EACvC,OAAO,EAAE,WAAW,GACnB,QAAQ,GAAG,IAAI,CAEjB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Units under the building that owns this zone's level. Units are building
|
|
3
|
+
* children, so this walks that short list instead of scanning every node
|
|
4
|
+
* the way `unitsForZone` does — cheap enough for per-zone store selectors.
|
|
5
|
+
*/
|
|
6
|
+
export function buildingUnitsForZone(zone, resolve) {
|
|
7
|
+
const level = zone.parentId ? resolve(zone.parentId) : undefined;
|
|
8
|
+
const building = level?.parentId ? resolve(level.parentId) : undefined;
|
|
9
|
+
if (building?.type !== 'building')
|
|
10
|
+
return [];
|
|
11
|
+
const units = [];
|
|
12
|
+
for (const childId of building.children) {
|
|
13
|
+
const child = resolve(childId);
|
|
14
|
+
if (child?.type === 'unit')
|
|
15
|
+
units.push(child);
|
|
16
|
+
}
|
|
17
|
+
return units;
|
|
18
|
+
}
|
|
19
|
+
/** The unit whose colour and name a member zone wears: the first one listing it. */
|
|
20
|
+
export function owningUnitForZone(zone, resolve) {
|
|
21
|
+
return buildingUnitsForZone(zone, resolve).find((unit) => unit.members.includes(zone.id)) ?? null;
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/nodes",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Built-in node bundles for the Pascal 3D editor
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Built-in node bundles for the Pascal 3D editor — one folder per kind, exported as `builtinPlugin`",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./procedural-item": {
|
|
15
|
+
"types": "./dist/procedural-item/index.d.ts",
|
|
16
|
+
"import": "./dist/procedural-item/index.js",
|
|
17
|
+
"default": "./dist/procedural-item/index.js"
|
|
13
18
|
}
|
|
14
19
|
},
|
|
15
20
|
"files": [
|
|
@@ -23,9 +28,9 @@
|
|
|
23
28
|
"prepublishOnly": "bun run build && bun test"
|
|
24
29
|
},
|
|
25
30
|
"peerDependencies": {
|
|
26
|
-
"@pascal-app/core": "^1.0.
|
|
27
|
-
"@pascal-app/editor": "^1.0.
|
|
28
|
-
"@pascal-app/viewer": "^1.0.
|
|
31
|
+
"@pascal-app/core": "^1.0.1",
|
|
32
|
+
"@pascal-app/editor": "^1.0.1",
|
|
33
|
+
"@pascal-app/viewer": "^1.0.1",
|
|
29
34
|
"@react-three/drei": "^10",
|
|
30
35
|
"@react-three/fiber": "^9",
|
|
31
36
|
"lucide-react": "^1",
|
|
@@ -35,10 +40,11 @@
|
|
|
35
40
|
"zustand": "^5"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
38
|
-
"@pascal-app/core": "^1.0.
|
|
39
|
-
"@pascal-app/editor": "^1.0.
|
|
40
|
-
"@pascal-app/viewer": "^1.0.
|
|
43
|
+
"@pascal-app/core": "^1.0.1",
|
|
44
|
+
"@pascal-app/editor": "^1.0.1",
|
|
45
|
+
"@pascal-app/viewer": "^1.0.1",
|
|
41
46
|
"@pascal/typescript-config": "*",
|
|
47
|
+
"@react-three/test-renderer": "^9.1.0",
|
|
42
48
|
"@types/bun": "^1.3.0",
|
|
43
49
|
"@types/node": "^22.19.12",
|
|
44
50
|
"@types/react": "^19.2.2",
|