@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/skylight/panel.js
CHANGED
|
@@ -218,7 +218,7 @@ export default function SkylightPanel() {
|
|
|
218
218
|
return (_jsx("button", { className: cn('flex min-h-12 items-center gap-2.5 rounded-lg border px-3 py-2.5 text-left text-xs transition-colors', isSelected
|
|
219
219
|
? 'border-orange-400/60 bg-orange-400/10 text-foreground'
|
|
220
220
|
: 'border-border/50 bg-[#2C2C2E] text-muted-foreground hover:bg-[#3e3e3e] hover:text-foreground'), onClick: () => handleTypeChange(skylightType), type: "button", children: _jsx("span", { className: "min-w-0 truncate font-medium", children: SKYLIGHT_TYPE_PRESETS[skylightType].label }) }, skylightType));
|
|
221
|
-
}) }), _jsx(SliderControl, { label: "Glass Thickness", max: 0.05, min: 0.002, onChange: (v) => previewProp({ glassThickness: v }), onCommit: (v) => commitProp({ glassThickness: v }), precision: 3, restoreOnCommit: false, step: 0.001, unit: "m", value:
|
|
221
|
+
}) }), _jsx(SliderControl, { label: "Glass Thickness", max: 0.05, min: 0.002, onChange: (v) => previewProp({ glassThickness: v }), onCommit: (v) => commitProp({ glassThickness: v }), precision: 3, restoreOnCommit: false, step: 0.001, unit: "m", value: node.glassThickness ?? 0.01 }), activeSkylightType === 'lantern' && (_jsxs(_Fragment, { children: [_jsx(SliderControl, { label: "Lantern Height", max: 1.0, min: 0.05, onChange: (v) => previewProp({ lanternHeight: v }), onCommit: (v) => commitProp({ lanternHeight: v }), precision: 3, restoreOnCommit: false, step: 0.01, unit: "m", value: node.lanternHeight ?? 0.25 }), _jsx(SliderControl, { label: "Top Scale", max: 0.95, min: 0, onChange: (v) => previewProp({ lanternTopScale: v }), onCommit: (v) => commitProp({ lanternTopScale: v }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "", value: Math.round((node.lanternTopScale ?? 0.25) * 100) / 100 })] })), activeSkylightType === 'opening' && (_jsxs(_Fragment, { children: [_jsx(SliderControl, { label: "Open", max: 1, min: 0, onChange: (v) => previewProp({ operationState: v }), onCommit: (v) => commitProp({ operationState: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "", value: Math.round((node.operationState ?? 0) * 100) / 100 }), _jsx(SliderControl, { label: "Opening Angle", max: 80, min: 0, onChange: (deg) => previewProp({ openingAngle: (deg * Math.PI) / 180 }), onCommit: (deg) => commitProp({ openingAngle: (deg * Math.PI) / 180 }), precision: 0, restoreOnCommit: false, step: 1, unit: "\u00B0", value: Math.round(((node.openingAngle ?? Math.PI / 10) * 180) / Math.PI) }), _jsx(SegmentedControl, { onChange: (v) => commitProp({ openingSide: v }), options: [
|
|
222
222
|
{ label: 'Top', value: 'top' },
|
|
223
223
|
{ label: 'Bottom', value: 'bottom' },
|
|
224
224
|
{ label: 'Left', value: 'left' },
|
|
@@ -226,23 +226,23 @@ export default function SkylightPanel() {
|
|
|
226
226
|
], value: (node.openingSide ?? 'top') }), _jsx(SegmentedControl, { onChange: (v) => commitProp({ motorHousing: v === 'yes' }), options: [
|
|
227
227
|
{ label: 'Motor', value: 'yes' },
|
|
228
228
|
{ label: 'No Motor', value: 'no' },
|
|
229
|
-
], value: (node.motorHousing ?? false) ? 'yes' : 'no' }), (node.motorHousing ?? false) && (_jsx(SliderControl, { label: "Motor Housing", max: 0.2, min: 0.03, onChange: (v) => previewProp({ motorHousingSize: v }), onCommit: (v) => commitProp({ motorHousingSize: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value:
|
|
229
|
+
], value: (node.motorHousing ?? false) ? 'yes' : 'no' }), (node.motorHousing ?? false) && (_jsx(SliderControl, { label: "Motor Housing", max: 0.2, min: 0.03, onChange: (v) => previewProp({ motorHousingSize: v }), onCommit: (v) => commitProp({ motorHousingSize: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.motorHousingSize ?? 0.08 }))] })), activeSkylightType === 'sliding' && (_jsxs(_Fragment, { children: [_jsx(SliderControl, { label: "Open", max: 1, min: 0, onChange: (v) => previewProp({ operationState: v }), onCommit: (v) => commitProp({ operationState: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "", value: Math.round((node.operationState ?? 0) * 100) / 100 }), _jsx(SegmentedControl, { onChange: (v) => commitProp({ slideDirection: v }), options: [
|
|
230
230
|
{ label: 'Along Z', value: 'z' },
|
|
231
231
|
{ label: 'Along X', value: 'x' },
|
|
232
|
-
], value: (node.slideDirection ?? 'z') }), _jsx(SliderControl, { label: "Track Width", max: 0.12, min: 0.02, onChange: (v) => previewProp({ trackWidth: v }), onCommit: (v) => commitProp({ trackWidth: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value:
|
|
232
|
+
], value: (node.slideDirection ?? 'z') }), _jsx(SliderControl, { label: "Track Width", max: 0.12, min: 0.02, onChange: (v) => previewProp({ trackWidth: v }), onCommit: (v) => commitProp({ trackWidth: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.trackWidth ?? 0.045 })] }))] }), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Width", max: 1000, min: 0.3, onChange: (v) => previewProp({ width: v }), onCommit: (v) => commitProp({ width: v }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: node.width }), _jsx(SliderControl, { label: "Height", max: 1000, min: 0.3, onChange: (v) => previewProp({ height: v }), onCommit: (v) => commitProp({ height: v }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: node.height })] }), _jsxs(PanelSection, { title: "Frame", children: [_jsx(SliderControl, { label: "Thickness", max: 0.2, min: 0.02, onChange: (v) => previewProp({ frameThickness: v }), onCommit: (v) => commitProp({ frameThickness: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.frameThickness ?? 0.05 }), _jsx(SliderControl, { label: "Depth", max: 0.3, min: 0.02, onChange: (v) => previewProp({ frameDepth: v }), onCommit: (v) => commitProp({ frameDepth: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.frameDepth ?? 0.08 }), _jsx(SliderControl, { label: "Cutout Offset", max: 0.2, min: 0, onChange: (v) => previewProp({ cutoutOffset: v }), onCommit: (v) => commitProp({ cutoutOffset: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.cutoutOffset ?? 0.01 })] }), _jsxs(PanelSection, { title: "Curb", children: [_jsx(SegmentedControl, { onChange: (v) => handleUpdate({ curb: v === 'yes' }), options: [
|
|
233
233
|
{ label: 'Yes', value: 'yes' },
|
|
234
234
|
{ label: 'No', value: 'no' },
|
|
235
|
-
], value: (node.curb ?? false) ? 'yes' : 'no' }), (node.curb ?? false) && (_jsx(SliderControl, { label: "Height", max: 0.3, min: 0.02, onChange: (v) => previewProp({ curbHeight: v }), onCommit: (v) => commitProp({ curbHeight: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value:
|
|
235
|
+
], value: (node.curb ?? false) ? 'yes' : 'no' }), (node.curb ?? false) && (_jsx(SliderControl, { label: "Height", max: 0.3, min: 0.02, onChange: (v) => previewProp({ curbHeight: v }), onCommit: (v) => commitProp({ curbHeight: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.curbHeight ?? 0.1 }))] }), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: "X", max: Math.round(worldMaxX * 10) / 10, min: Math.round(worldMinX * 10) / 10, onChange: (newWorldX) => {
|
|
236
236
|
if (!segment)
|
|
237
237
|
return;
|
|
238
238
|
const local = worldToSegLocal(newWorldX, worldZ_now, segment);
|
|
239
239
|
previewProp({ position: [local.localX, 0, local.localZ] });
|
|
240
|
-
}, onCommit: (newWorldX) => commitWorldPosition(newWorldX, worldZ_now), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value:
|
|
240
|
+
}, onCommit: (newWorldX) => commitWorldPosition(newWorldX, worldZ_now), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: worldX_now }), _jsx(SliderControl, { label: "Z", max: Math.round(worldMaxZ * 10) / 10, min: Math.round(worldMinZ * 10) / 10, onChange: (newWorldZ) => {
|
|
241
241
|
if (!segment)
|
|
242
242
|
return;
|
|
243
243
|
const local = worldToSegLocal(worldX_now, newWorldZ, segment);
|
|
244
244
|
previewProp({ position: [local.localX, 0, local.localZ] });
|
|
245
|
-
}, onCommit: (newWorldZ) => commitWorldPosition(worldX_now, newWorldZ), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value:
|
|
245
|
+
}, onCommit: (newWorldZ) => commitWorldPosition(worldX_now, newWorldZ), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: worldZ_now }), _jsx(SliderControl, { label: "Rotation", max: 180, min: -180, onChange: (degrees) => {
|
|
246
246
|
const newWorldRot = (degrees * Math.PI) / 180;
|
|
247
247
|
if (!skylightObj)
|
|
248
248
|
return;
|
package/dist/slab/panel.js
CHANGED
|
@@ -203,7 +203,7 @@ export function SlabPanel() {
|
|
|
203
203
|
{ label: 'Standard (5cm)', elevation: 0.05 },
|
|
204
204
|
{ label: 'Thick (15cm)', elevation: 0.15 },
|
|
205
205
|
];
|
|
206
|
-
return (_jsxs(PanelWrapper, { icon: "/icons/floor.webp", onClose: handleClose, title: node.name || 'Slab', width: 320, children: [_jsxs(PanelSection, { title: "Elevation", children: [_jsx(SliderControl, { label: node.recessed ? 'Floor' : 'Surface', max: 20, min: -3, onChange: handleElevationChange, precision: 3, step: 0.01, unit: "m", value:
|
|
206
|
+
return (_jsxs(PanelWrapper, { icon: "/icons/floor.webp", onClose: handleClose, title: node.name || 'Slab', width: 320, children: [_jsxs(PanelSection, { title: "Elevation", children: [_jsx(SliderControl, { label: node.recessed ? 'Floor' : 'Surface', max: 20, min: -3, onChange: handleElevationChange, precision: 3, step: 0.01, unit: "m", value: node.elevation }), _jsx(SliderControl, { label: node.recessed ? 'Rim' : 'Base', max: 20, min: -3, onChange: handleAnchorChange, precision: 3, step: 0.01, unit: "m", value: getSlabAnchorElevation(node) }), node.recessed ? (_jsx(SliderControl, { label: "Depth", max: 1000, min: MIN_SLAB_THICKNESS, onChange: handleRecessDepthChange, precision: 2, step: 0.01, unit: "m", value: getSlabRecessDepth(node) })) : (_jsx(SliderControl, { label: "Thickness", max: 1000, min: MIN_SLAB_THICKNESS, onChange: handleThicknessChange, precision: 2, step: 0.01, unit: "m", value: node.thickness ?? 0.05 })), !node.recessed && (_jsxs(_Fragment, { children: [_jsx("div", { className: "px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Foundation" }), _jsx(SegmentedControl, { onChange: handleTerrainModeChange, options: [
|
|
207
207
|
{ label: 'Fixed', value: 'fixed' },
|
|
208
208
|
{ label: 'Follows terrain', value: 'terrain' },
|
|
209
209
|
], value: node.fillToTerrain ? 'terrain' : 'fixed' }), node.fillToTerrain && (_jsx("div", { className: "px-1 text-[11px] text-muted-foreground", children: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." }))] })), _jsx("div", { className: "mt-2 grid grid-cols-2 gap-1.5 px-1 pb-1", children: elevationPresets.map((preset) => (_jsx(ActionButton, { label: preset.label, onClick: () => handleElevationPreset(preset.elevation) }, preset.label))) })] }), _jsx(PanelSection, { title: "Info", children: _jsxs("div", { className: "flex items-center justify-between px-2 py-1 text-muted-foreground text-sm", children: [_jsx("span", { children: "Area" }), _jsxs("span", { className: "font-mono text-white", children: [area.toFixed(2), " m\u00B2"] })] }) }), _jsxs(PanelSection, { title: "Holes", children: [node.holes && node.holes.length > 0 ? (_jsx("div", { className: "flex flex-col gap-1 pb-2", children: node.holes.map((hole, index) => {
|
|
@@ -139,8 +139,8 @@ export default function SolarPanelPanel() {
|
|
|
139
139
|
return (_jsxs("button", { className: cn('flex min-h-14 flex-col items-start gap-0.5 rounded-lg border px-2.5 py-2 text-left text-xs transition-colors', isSelected
|
|
140
140
|
? 'border-orange-400/60 bg-orange-400/10 text-foreground'
|
|
141
141
|
: 'border-border/50 bg-[#2C2C2E] text-muted-foreground hover:bg-[#3e3e3e] hover:text-foreground'), onClick: () => handlePresetChange(card.key), type: "button", children: [_jsxs("span", { className: "flex items-center gap-1.5 font-medium", children: [_jsx(LayoutGrid, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "truncate", children: card.label })] }), _jsx("span", { className: "pl-[20px] text-[10px] tabular-nums opacity-70", children: formatDims(dims.panelWidth, dims.panelHeight) })] }, card.key));
|
|
142
|
-
}) }), !activePreset && (_jsx("p", { className: "px-1 pt-1 text-[11px] text-muted-foreground", children: "Custom \u2014 dimensions don't match any preset" }))] }), _jsxs(PanelSection, { title: "Array", children: [_jsx(SliderControl, { label: "Rows", max: 20, min: 1, onChange: (v) => previewProp({ rows: Math.round(v) }), onCommit: (v) => commitProp({ rows: Math.round(v) }), precision: 0, restoreOnCommit: false, step: 1, value: num(node.rows, 4) }), _jsx(SliderControl, { label: "Columns", max: 20, min: 1, onChange: (v) => previewProp({ columns: Math.round(v) }), onCommit: (v) => commitProp({ columns: Math.round(v) }), precision: 0, restoreOnCommit: false, step: 1, value: num(node.columns, 5) }), _jsx(SliderControl, { label: "Gap X", max: 0.2, min: 0, onChange: (v) => previewProp({ gapX: v }), onCommit: (v) => commitProp({ gapX: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value:
|
|
142
|
+
}) }), !activePreset && (_jsx("p", { className: "px-1 pt-1 text-[11px] text-muted-foreground", children: "Custom \u2014 dimensions don't match any preset" }))] }), _jsxs(PanelSection, { title: "Array", children: [_jsx(SliderControl, { label: "Rows", max: 20, min: 1, onChange: (v) => previewProp({ rows: Math.round(v) }), onCommit: (v) => commitProp({ rows: Math.round(v) }), precision: 0, restoreOnCommit: false, step: 1, value: num(node.rows, 4) }), _jsx(SliderControl, { label: "Columns", max: 20, min: 1, onChange: (v) => previewProp({ columns: Math.round(v) }), onCommit: (v) => commitProp({ columns: Math.round(v) }), precision: 0, restoreOnCommit: false, step: 1, value: num(node.columns, 5) }), _jsx(SliderControl, { label: "Gap X", max: 0.2, min: 0, onChange: (v) => previewProp({ gapX: v }), onCommit: (v) => commitProp({ gapX: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: num(node.gapX, 0.02) }), _jsx(SliderControl, { label: "Gap Y", max: 0.2, min: 0, onChange: (v) => previewProp({ gapY: v }), onCommit: (v) => commitProp({ gapY: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: num(node.gapY, 0.02) }), _jsx(ActionGroup, { children: _jsx(ActionButton, { disabled: !segment, label: "Auto-fit to roof", onClick: handleAutoFit }) }), autoFitMessage ? _jsx("p", { className: "px-1 text-amber-400 text-xs", children: autoFitMessage }) : null] }), _jsxs(PanelSection, { title: "Panel", children: [_jsx(SliderControl, { label: "Width", max: 1000, min: 0.3, onChange: (v) => previewProp({ panelWidth: v }), onCommit: (v) => commitProp({ panelWidth: v }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: num(node.panelWidth, 1) }), _jsx(SliderControl, { label: "Height", max: 1000, min: 0.3, onChange: (v) => previewProp({ panelHeight: v }), onCommit: (v) => commitProp({ panelHeight: v }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: num(node.panelHeight, 1.65) }), _jsx(ActionGroup, { children: _jsx(ActionButton, { label: "Flip orientation", onClick: handleFlip }) }), _jsx(SliderControl, { label: "Frame thickness", max: 0.1, min: 0.005, onChange: (v) => previewProp({ frameThickness: v }), onCommit: (v) => commitProp({ frameThickness: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: num(node.frameThickness, 0.04) }), _jsx(SliderControl, { label: "Frame depth", max: 0.1, min: 0.005, onChange: (v) => previewProp({ frameDepth: v }), onCommit: (v) => commitProp({ frameDepth: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: num(node.frameDepth, 0.04) })] }), _jsxs(PanelSection, { title: "Mounting", children: [_jsx(SegmentedControl, { onChange: (v) => handleUpdate({ mountingType: v }), options: [
|
|
143
143
|
{ label: 'Flush', value: 'flush' },
|
|
144
144
|
{ label: 'Tilted', value: 'tilted' },
|
|
145
|
-
], value: node.mountingType ?? 'flush' }), node.mountingType === 'tilted' && (_jsx(SliderControl, { label: "Tilt angle", max: 45, min: 0, onChange: (v) => previewProp({ tiltAngle: v }), onCommit: (v) => commitProp({ tiltAngle: v }), precision: 0, restoreOnCommit: false, step: 1, unit: "\u00B0", value: Math.round(num(node.tiltAngle, 15)) })), _jsx(SliderControl, { label: "Standoff", max: 0.3, min: 0, onChange: (v) => previewProp({ standoffHeight: v }), onCommit: (v) => commitProp({ standoffHeight: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value:
|
|
145
|
+
], value: node.mountingType ?? 'flush' }), node.mountingType === 'tilted' && (_jsx(SliderControl, { label: "Tilt angle", max: 45, min: 0, onChange: (v) => previewProp({ tiltAngle: v }), onCommit: (v) => commitProp({ tiltAngle: v }), precision: 0, restoreOnCommit: false, step: 1, unit: "\u00B0", value: Math.round(num(node.tiltAngle, 15)) })), _jsx(SliderControl, { label: "Standoff", max: 0.3, min: 0, onChange: (v) => previewProp({ standoffHeight: v }), onCommit: (v) => commitProp({ standoffHeight: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: num(node.standoffHeight, 0.05) })] }), _jsx(PanelSection, { title: "Actions", children: _jsx(ActionGroup, { children: _jsx(ActionButton, { className: "hover:bg-red-500/20", icon: _jsx(Trash2, { className: "h-3.5 w-3.5 text-red-400" }), label: "Delete", onClick: handleDelete }) }) })] }));
|
|
146
146
|
}
|
package/dist/spawn/panel.js
CHANGED
|
@@ -67,5 +67,5 @@ export default function SpawnPanel() {
|
|
|
67
67
|
return null;
|
|
68
68
|
const rotationDegrees = Math.round(((draftRotation ?? node.rotation) * 180) / Math.PI);
|
|
69
69
|
const storedRotationDegrees = Math.round((node.rotation * 180) / Math.PI);
|
|
70
|
-
return (_jsxs(PanelWrapper, { icon: "/icons/spawn-point.webp", onClose: handleClose, title: "Spawn Point", width: 300, children: [_jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: "X", max: node.position[0] + 2, min: node.position[0] - 2, onChange: (value) => handleUpdate({ position: [value, node.position[1], node.position[2]] }), precision: 2, step: 0.01, unit: "m", value:
|
|
70
|
+
return (_jsxs(PanelWrapper, { icon: "/icons/spawn-point.webp", onClose: handleClose, title: "Spawn Point", width: 300, children: [_jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: "X", max: node.position[0] + 2, min: node.position[0] - 2, onChange: (value) => handleUpdate({ position: [value, node.position[1], node.position[2]] }), precision: 2, step: 0.01, unit: "m", value: node.position[0] }), _jsx(SliderControl, { label: "Y", max: node.position[1] + 2, min: node.position[1] - 2, onChange: (value) => handleUpdate({ position: [node.position[0], value, node.position[2]] }), precision: 2, step: 0.01, unit: "m", value: node.position[1] }), _jsx(SliderControl, { label: "Z", max: node.position[2] + 2, min: node.position[2] - 2, onChange: (value) => handleUpdate({ position: [node.position[0], node.position[1], value] }), precision: 2, step: 0.01, unit: "m", value: node.position[2] })] }), _jsx(PanelSection, { title: "Facing", children: _jsx(SliderControl, { label: "Yaw", max: storedRotationDegrees + 90, min: storedRotationDegrees - 90, onChange: handleRotationChange, onCommit: commitRotation, precision: 0, step: 1, unit: "\u00B0", value: rotationDegrees }) }), _jsx(PanelSection, { title: "Actions", children: _jsxs(ActionGroup, { children: [_jsx(ActionButton, { icon: _jsx(Move, { className: "h-4 w-4" }), label: "Move", onClick: handleMove }), _jsx(ActionButton, { className: "border-red-500/40 text-red-200 hover:bg-red-500/15", icon: _jsx(Trash2, { className: "h-4 w-4" }), label: "Delete", onClick: handleDelete })] }) })] }));
|
|
71
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/stair/panel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/stair/panel.tsx"],"names":[],"mappings":"AAuEA,MAAM,CAAC,OAAO,UAAU,UAAU,uCAwjBjC"}
|
package/dist/stair/panel.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { getLevelDisplayName, resolveStairTotalRise, runAsSingleSceneHistoryStep, StairSegmentNode as StairSegmentNodeSchema, useScene, } from '@pascal-app/core';
|
|
4
|
-
import { ActionButton, ActionGroup, duplicateStairSubtree, getStairLevelOptions, MetricControl, PanelSection, PanelWrapper, resolveStairDestinationLevel, resolveStairFromLevelId, resolveStairToLevelId, SegmentedControl, SliderControl, ToggleControl, triggerSFX, useEditor, } from '@pascal-app/editor';
|
|
4
|
+
import { ActionButton, ActionGroup, duplicateStairSubtree, formatLinearMeasurement, getStairLevelOptions, MetricControl, PanelSection, PanelWrapper, resolveStairDestinationLevel, resolveStairFromLevelId, resolveStairToLevelId, SegmentedControl, SliderControl, ToggleControl, triggerSFX, useEditor, } from '@pascal-app/editor';
|
|
5
5
|
import { useViewer } from '@pascal-app/viewer';
|
|
6
6
|
import { Copy, Move, Plus, Trash2 } from 'lucide-react';
|
|
7
7
|
import { useCallback, useMemo } from 'react';
|
|
@@ -33,6 +33,8 @@ const DECK_DESTINATION_MIN_ELEVATION = 0.5;
|
|
|
33
33
|
export default function StairPanel() {
|
|
34
34
|
const selectedId = useViewer((s) => s.selection.selectedIds[0]);
|
|
35
35
|
const selectedCount = useViewer((s) => s.selection.selectedIds.length);
|
|
36
|
+
const unit = useViewer((s) => s.unit);
|
|
37
|
+
const metricNotation = useViewer((s) => s.metricNotation);
|
|
36
38
|
const setSelection = useViewer((s) => s.setSelection);
|
|
37
39
|
const updateNode = useScene((s) => s.updateNode);
|
|
38
40
|
const createNode = useScene((s) => s.createNode);
|
|
@@ -206,23 +208,23 @@ export default function StairPanel() {
|
|
|
206
208
|
const resolvedToLevelId = resolveStairToLevelId(nodes, node, resolvedFromLevelId, levels);
|
|
207
209
|
const deckNode = node.deckSlabId ? nodes[node.deckSlabId] : undefined;
|
|
208
210
|
const attachedDeck = deckNode?.type === 'slab' ? deckNode : undefined;
|
|
209
|
-
const resolvedRise =
|
|
211
|
+
const resolvedRise = resolveStairTotalRise(node, nodes);
|
|
210
212
|
return (_jsxs(PanelWrapper, { icon: "/icons/stairs.webp", onClose: handleClose, title: node.name || 'Staircase', width: 300, children: [_jsx(PanelSection, { title: "Type", children: _jsx(SegmentedControl, { onChange: handleStairTypeChange, options: STAIR_TYPE_OPTIONS, value: node.stairType ?? 'straight' }) }), _jsx(PanelSection, { title: "Opening", children: _jsxs("div", { className: "space-y-3", children: [attachedDeck ? null : (_jsx(ToggleControl, { checked: (node.slabOpeningMode ?? 'none') === 'destination', label: "Auto Cutout", onChange: handleAutoCutoutChange })), _jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "px-1 text-[11px] text-muted-foreground uppercase tracking-[0.14em]", children: "From Level" }), _jsx("select", { className: "h-9 w-full rounded-lg border border-border/50 bg-[#2C2C2E] px-3 text-foreground text-sm", onChange: (event) => handleFromLevelChange(event.target.value), value: resolvedFromLevelId ?? '', children: levels.map((level) => (_jsx("option", { value: level.id, children: getLevelDisplayName(level) }, level.id))) })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "px-1 text-[11px] text-muted-foreground uppercase tracking-[0.14em]", children: "To" }), _jsxs("select", { className: "h-9 w-full rounded-lg border border-border/50 bg-[#2C2C2E] px-3 text-foreground text-sm", onChange: (event) => handleDestinationChange(event.target.value), value: attachedDeck ? attachedDeck.id : (resolvedToLevelId ?? ''), children: [levels.map((level) => (_jsx("option", { value: level.id, children: getLevelDisplayName(level) }, level.id))), candidateDecks.map((deck) => (_jsx("option", { value: deck.id, children: deck.name || 'Deck' }, deck.id)))] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "px-1 text-[11px] text-muted-foreground uppercase tracking-[0.14em]", children: "Rise" }), _jsx(SegmentedControl, { onChange: (value) => handleUpdate(value === 'custom' ? { totalRise: resolvedRise } : { totalRise: undefined }), options: [
|
|
211
213
|
{ label: attachedDeck ? 'Follows deck' : 'Follows level', value: 'follows' },
|
|
212
214
|
{ label: 'Custom rise', value: 'custom' },
|
|
213
|
-
], value: node.totalRise == null ? 'follows' : 'custom' }), node.totalRise == null ? (_jsxs("div", { className: "px-1 text-[11px] text-muted-foreground", children: ["Currently ", resolvedRise,
|
|
215
|
+
], value: node.totalRise == null ? 'follows' : 'custom' }), node.totalRise == null ? (_jsxs("div", { className: "px-1 text-[11px] text-muted-foreground", children: ["Currently ", formatLinearMeasurement(resolvedRise, unit, metricNotation)] })) : (_jsx(MetricControl, { label: "Rise", max: 1000, min: 0.2, onChange: (value) => handleUpdate({ totalRise: value }), precision: 2, step: 0.05, unit: "m", value: resolvedRise }))] }), attachedDeck ? null : (_jsxs(_Fragment, { children: [_jsx(SegmentedControl, { onChange: (value) => handleAutoCutoutChange(value === 'destination'), options: STAIR_SLAB_OPENING_OPTIONS, value: node.slabOpeningMode ?? 'none' }), (node.slabOpeningMode ?? 'none') === 'destination' ? (_jsx(MetricControl, { label: "Opening Offset", max: 0.5, min: 0, onChange: (value) => handleUpdate({ openingOffset: value }), precision: 2, step: 0.01, unit: "m", value: node.openingOffset ?? 0 })) : null] })), node.stairType === 'spiral' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "px-1 text-[11px] text-muted-foreground uppercase tracking-[0.14em]", children: "Landing" }), _jsx(SegmentedControl, { onChange: (value) => handleUpdate({ topLandingMode: value }), options: TOP_LANDING_MODE_OPTIONS, value: node.topLandingMode ?? 'none' })] }), (node.topLandingMode ?? 'none') === 'integrated' && (_jsx(MetricControl, { label: "Top Landing", max: 5, min: 0.3, onChange: (value) => handleUpdate({ topLandingDepth: value }), precision: 2, step: 0.05, unit: "m", value: node.topLandingDepth ?? 0.9 }))] }))] }) }), node.stairType === 'straight' && (_jsxs(PanelSection, { title: "Segments", children: [_jsx("div", { className: "flex flex-col gap-1", children: segments.map((seg, i) => (_jsxs("button", { className: "flex items-center justify-between rounded-lg border border-border/50 bg-[#2C2C2E] px-3 py-2 text-foreground text-sm transition-colors hover:bg-[#3e3e3e]", onClick: () => handleSelectSegment(seg.id), type: "button", children: [_jsx("span", { className: "truncate", children: seg.name || `Segment ${i + 1}` }), _jsx("span", { className: "text-muted-foreground text-xs capitalize", children: seg.segmentType })] }, seg.id))) }), _jsxs("div", { className: "flex gap-1.5", children: [_jsx(ActionButton, { icon: _jsx(Plus, { className: "h-3.5 w-3.5" }), label: "Add flight", onClick: handleAddFlight }), _jsx(ActionButton, { icon: _jsx(Plus, { className: "h-3.5 w-3.5" }), label: "Add landing", onClick: handleAddLanding })] })] })), (node.stairType === 'curved' || node.stairType === 'spiral') && (_jsxs(PanelSection, { title: "Geometry", children: [_jsx(MetricControl, { label: "Width", max: 10, min: 0.4, onChange: (value) => handleUpdate({ width: value }), precision: 2, step: 0.05, unit: "m", value: node.width ?? 1 }), _jsx(MetricControl, { label: "Steps", max: 32, min: 2, onChange: (value) => handleUpdate({ stepCount: Math.max(2, Math.round(value)) }), precision: 0, step: 1, unit: "", value: Math.max(2, Math.round(node.stepCount ?? 10)) }), node.stairType !== 'spiral' && (_jsx(ToggleControl, { checked: node.fillToFloor ?? true, label: "Fit To Floor", onChange: (checked) => handleUpdate({ fillToFloor: checked }) })), (node.stairType === 'spiral' || !(node.fillToFloor ?? true)) && (_jsx(MetricControl, { label: "Thickness", max: 1, min: 0.02, onChange: (value) => handleUpdate({ thickness: value }), precision: 2, step: 0.01, unit: "m", value: node.thickness ?? 0.25 })), _jsx(MetricControl, { label: "Inner Radius", max: 10, min: node.stairType === 'spiral' ? 0.05 : 0.2, onChange: (value) => handleUpdate({ innerRadius: value }), precision: 2, step: 0.05, unit: "m", value: node.innerRadius ?? 0.9 }), _jsx(SliderControl, { label: "Sweep", max: node.stairType === 'spiral' ? 720 : 270, min: node.stairType === 'spiral' ? -720 : -270, onChange: (degrees) => handleUpdate({ sweepAngle: (degrees * Math.PI) / 180 }), precision: 0, step: 1, unit: "\u00B0", value: Math.round(((node.sweepAngle ?? Math.PI / 2) * 180) / Math.PI) }), node.stairType === 'spiral' && (_jsxs(_Fragment, { children: [_jsx(ToggleControl, { checked: node.showCenterColumn ?? true, label: "Center Column", onChange: (checked) => handleUpdate({ showCenterColumn: checked }) }), _jsx(ToggleControl, { checked: node.showStepSupports ?? true, label: "Step Supports", onChange: (checked) => handleUpdate({ showStepSupports: checked }) })] }))] })), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: "X", onChange: (v) => {
|
|
214
216
|
const pos = [...node.position];
|
|
215
217
|
pos[0] = v;
|
|
216
218
|
handleUpdate({ position: pos });
|
|
217
|
-
}, precision: 2, step: 0.05, unit: "m", value:
|
|
219
|
+
}, precision: 2, step: 0.05, unit: "m", value: node.position[0] }), _jsx(SliderControl, { label: "Y", onChange: (v) => {
|
|
218
220
|
const pos = [...node.position];
|
|
219
221
|
pos[1] = v;
|
|
220
222
|
handleUpdate({ position: pos });
|
|
221
|
-
}, precision: 2, step: 0.05, unit: "m", value:
|
|
223
|
+
}, precision: 2, step: 0.05, unit: "m", value: node.position[1] }), _jsx(SliderControl, { label: "Z", onChange: (v) => {
|
|
222
224
|
const pos = [...node.position];
|
|
223
225
|
pos[2] = v;
|
|
224
226
|
handleUpdate({ position: pos });
|
|
225
|
-
}, precision: 2, step: 0.05, unit: "m", value:
|
|
227
|
+
}, precision: 2, step: 0.05, unit: "m", value: node.position[2] }), _jsx(SliderControl, { label: "Rotation", max: 180, min: -180, onChange: (degrees) => {
|
|
226
228
|
handleUpdate({ rotation: (degrees * Math.PI) / 180 });
|
|
227
229
|
}, precision: 0, step: 1, unit: "\u00B0", value: Math.round((node.rotation * 180) / Math.PI) }), _jsxs("div", { className: "flex gap-1.5 px-1 pt-2 pb-1", children: [_jsx(ActionButton, { label: "-45\u00B0", onClick: () => {
|
|
228
230
|
triggerSFX('sfx:item-rotate');
|
|
@@ -230,5 +232,5 @@ export default function StairPanel() {
|
|
|
230
232
|
} }), _jsx(ActionButton, { label: "+45\u00B0", onClick: () => {
|
|
231
233
|
triggerSFX('sfx:item-rotate');
|
|
232
234
|
handleUpdate({ rotation: node.rotation + Math.PI / 4 });
|
|
233
|
-
} })] })] }), _jsxs(PanelSection, { title: "Railing", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ railingMode: value }), options: RAILING_MODE_OPTIONS, value: node.railingMode ?? 'none' }), (node.railingMode ?? 'none') !== 'none' && (_jsx(SliderControl, { label: "Height", max: 1.4, min: 0.7, onChange: (value) => handleUpdate({ railingHeight: value }), precision: 2, step: 0.02, unit: "m", value:
|
|
235
|
+
} })] })] }), _jsxs(PanelSection, { title: "Railing", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ railingMode: value }), options: RAILING_MODE_OPTIONS, value: node.railingMode ?? 'none' }), (node.railingMode ?? 'none') !== 'none' && (_jsx(SliderControl, { label: "Height", max: 1.4, min: 0.7, onChange: (value) => handleUpdate({ railingHeight: value }), precision: 2, step: 0.02, unit: "m", value: node.railingHeight ?? 0.92 }))] }), _jsx(PanelSection, { title: "Actions", children: _jsxs(ActionGroup, { children: [_jsx(ActionButton, { icon: _jsx(Move, { className: "h-3.5 w-3.5" }), label: "Move", onClick: handleMove }), _jsx(ActionButton, { icon: _jsx(Copy, { className: "h-3.5 w-3.5" }), label: "Duplicate", onClick: handleDuplicate }), _jsx(ActionButton, { className: "hover:bg-red-500/20", icon: _jsx(Trash2, { className: "h-3.5 w-3.5 text-red-400" }), label: "Delete", onClick: handleDelete })] }) })] }));
|
|
234
236
|
}
|
|
@@ -141,19 +141,19 @@ export default function StairSegmentPanel() {
|
|
|
141
141
|
updates.length = 3.0;
|
|
142
142
|
}
|
|
143
143
|
handleUpdate(updates);
|
|
144
|
-
}, options: SEGMENT_TYPE_OPTIONS, value: node.segmentType }) }), !isFirstSegment && (_jsx(PanelSection, { title: "Attachment", children: _jsx(SegmentedControl, { onChange: (v) => handleUpdate({ attachmentSide: v }), options: ATTACHMENT_SIDE_OPTIONS, value: node.attachmentSide }) })), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Width", max: 1000, min: 0.5, onChange: (v) => handleUpdate({ width: v }), precision: 2, step: 0.1, unit: "m", value:
|
|
144
|
+
}, options: SEGMENT_TYPE_OPTIONS, value: node.segmentType }) }), !isFirstSegment && (_jsx(PanelSection, { title: "Attachment", children: _jsx(SegmentedControl, { onChange: (v) => handleUpdate({ attachmentSide: v }), options: ATTACHMENT_SIDE_OPTIONS, value: node.attachmentSide }) })), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Width", max: 1000, min: 0.5, onChange: (v) => handleUpdate({ width: v }), precision: 2, step: 0.1, unit: "m", value: node.width }), _jsx(SliderControl, { label: "Length", max: 1000, min: 0.5, onChange: (v) => handleUpdate({ length: v }), precision: 2, step: 0.1, unit: "m", value: node.length }), node.segmentType === 'stair' && (_jsxs(_Fragment, { children: [_jsx(SliderControl, { label: "Height", max: 1000, min: 0.5, onChange: handleFlightHeightChange, precision: 2, step: 0.1, unit: "m", value: node.height }), parentFollowsLevel && (_jsx("div", { className: "px-1 text-[11px] text-muted-foreground", children: "Editing switches the stair to Custom rise" })), _jsx(SliderControl, { label: "Steps", max: 30, min: 2, onChange: (v) => handleUpdate({ stepCount: Math.round(v) }), precision: 0, step: 1, unit: "", value: node.stepCount })] }))] }), _jsx(PanelSection, { title: "Structure", children: _jsxs("div", { className: "space-y-3", children: [_jsx(ToggleControl, { checked: node.fillToFloor, label: "Fill to floor", onChange: (checked) => handleUpdate({ fillToFloor: checked }) }), !node.fillToFloor && (_jsx(SliderControl, { label: "Thickness", max: 1000, min: 0.05, onChange: (v) => handleUpdate({ thickness: v }), precision: 2, step: 0.05, unit: "m", value: node.thickness ?? 0.25 }))] }) }), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: "X", onChange: (v) => {
|
|
145
145
|
const pos = [...node.position];
|
|
146
146
|
pos[0] = v;
|
|
147
147
|
handleUpdate({ position: pos });
|
|
148
|
-
}, precision: 2, step: 0.05, unit: "m", value:
|
|
148
|
+
}, precision: 2, step: 0.05, unit: "m", value: node.position[0] }), _jsx(SliderControl, { label: "Y", onChange: (v) => {
|
|
149
149
|
const pos = [...node.position];
|
|
150
150
|
pos[1] = v;
|
|
151
151
|
handleUpdate({ position: pos });
|
|
152
|
-
}, precision: 2, step: 0.05, unit: "m", value:
|
|
152
|
+
}, precision: 2, step: 0.05, unit: "m", value: node.position[1] }), _jsx(SliderControl, { label: "Z", onChange: (v) => {
|
|
153
153
|
const pos = [...node.position];
|
|
154
154
|
pos[2] = v;
|
|
155
155
|
handleUpdate({ position: pos });
|
|
156
|
-
}, precision: 2, step: 0.05, unit: "m", value:
|
|
156
|
+
}, precision: 2, step: 0.05, unit: "m", value: node.position[2] }), _jsx(SliderControl, { label: "Rotation", max: 180, min: -180, onChange: (degrees) => {
|
|
157
157
|
handleUpdate({ rotation: (degrees * Math.PI) / 180 });
|
|
158
158
|
}, precision: 0, step: 1, unit: "\u00B0", value: Math.round((node.rotation * 180) / Math.PI) }), _jsxs("div", { className: "flex gap-1.5 px-1 pt-2 pb-1", children: [_jsx(ActionButton, { label: "-45\u00B0", onClick: () => {
|
|
159
159
|
triggerSFX('sfx:item-rotate');
|
|
@@ -123,5 +123,5 @@ export default function TurbineVentPanel() {
|
|
|
123
123
|
return (_jsxs(PanelWrapper, { icon: "/icons/roof.webp", onBack: node.roofSegmentId ? handleBack : undefined, onClose: handleClose, title: node.name || 'Turbine Vent', width: 300, children: [_jsx(PanelSection, { title: "Style", children: _jsx(SegmentedControl, { onChange: (v) => handleUpdate({ style: v }), options: [
|
|
124
124
|
{ label: 'Globe', value: 'globe' },
|
|
125
125
|
{ label: 'Cylinder', value: 'cylinder' },
|
|
126
|
-
], value: node.style ?? 'globe' }) }), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Diameter", max: 1000, min: 0.15, onChange: (v) => previewProp({ diameter: v }), onCommit: (v) => handleUpdate({ diameter: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value:
|
|
126
|
+
], value: node.style ?? 'globe' }) }), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Diameter", max: 1000, min: 0.15, onChange: (v) => previewProp({ diameter: v }), onCommit: (v) => handleUpdate({ diameter: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.diameter }), _jsx(SliderControl, { label: "Height", max: 1000, min: 0.2, onChange: (v) => previewProp({ height: v }), onCommit: (v) => handleUpdate({ height: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.height }), _jsx(SliderControl, { label: "Neck Height", max: Math.max(0.04, node.height * 0.5), min: 0.02, onChange: (v) => previewProp({ neckHeight: v }), onCommit: (v) => handleUpdate({ neckHeight: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.neckHeight ?? 0.09 }), _jsx(SliderControl, { label: "Vanes", max: 36, min: 6, onChange: (v) => previewProp({ vaneCount: Math.round(v) }), onCommit: (v) => handleUpdate({ vaneCount: Math.round(v) }), precision: 0, restoreOnCommit: false, step: 1, unit: "", value: Math.round(node.vaneCount ?? 20) })] }), _jsxs(PanelSection, { title: "Motion", children: [_jsx(ActionGroup, { children: _jsx(ActionButton, { icon: isSpinning ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }), label: isSpinning ? 'Pause' : 'Play', onClick: handleToggleSpin }) }), _jsx(SliderControl, { label: "Spin Speed", max: 4, min: 0, onChange: (v) => previewProp({ spinSpeed: v }), onCommit: (v) => handleUpdate({ spinSpeed: v }), precision: 1, restoreOnCommit: false, step: 0.1, unit: "rad/s", value: Math.round((node.spinSpeed ?? 0) * 10) / 10 })] }), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: "X", max: Math.round(((segment?.width ?? 10) / 2) * 100) / 100, min: -Math.round(((segment?.width ?? 10) / 2) * 100) / 100, onChange: (v) => previewProp({ position: [v, node.position[1] ?? 0, node.position[2] ?? 0] }), onCommit: (v) => handleUpdate({ position: [v, node.position[1] ?? 0, node.position[2] ?? 0] }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: node.position[0] ?? 0 }), _jsx(SliderControl, { label: "Y", max: Math.max((segment?.wallHeight ?? 3) + (segment ? getActiveRoofHeight(segment) : 3) + 2, (node.position[1] ?? 0) + 0.1), min: Math.min(0, (node.position[1] ?? 0) - 0.5), onChange: (v) => previewProp({ position: [node.position[0] ?? 0, v, node.position[2] ?? 0] }), onCommit: (v) => handleUpdate({ position: [node.position[0] ?? 0, v, node.position[2] ?? 0] }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: node.position[1] ?? 0 }), _jsx(SliderControl, { label: "Z", max: Math.round(((segment?.depth ?? 10) / 2) * 100) / 100, min: -Math.round(((segment?.depth ?? 10) / 2) * 100) / 100, onChange: (v) => previewProp({ position: [node.position[0] ?? 0, node.position[1] ?? 0, v] }), onCommit: (v) => handleUpdate({ position: [node.position[0] ?? 0, node.position[1] ?? 0, v] }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: node.position[2] ?? 0 }), _jsx(SliderControl, { label: "Rotation", max: 180, min: -180, onChange: (deg) => previewProp({ rotation: (deg * Math.PI) / 180 }), onCommit: (deg) => handleUpdate({ rotation: (deg * Math.PI) / 180 }), precision: 0, restoreOnCommit: false, step: 1, unit: "\u00B0", value: Math.round(((node.rotation ?? 0) * 180) / Math.PI) })] }), _jsx(PanelSection, { title: "Actions", children: _jsxs(ActionGroup, { children: [_jsx(ActionButton, { icon: _jsx(Move, { className: "h-3.5 w-3.5" }), label: "Move", onClick: handleMove }), _jsx(ActionButton, { icon: _jsx(Copy, { className: "h-3.5 w-3.5" }), label: "Duplicate", onClick: handleDuplicate }), _jsx(ActionButton, { className: "hover:bg-red-500/20", icon: _jsx(Trash2, { className: "h-3.5 w-3.5 text-red-400" }), label: "Delete", onClick: handleDelete })] }) })] }));
|
|
127
127
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type NodeDefinition } from '@pascal-app/core';
|
|
2
|
+
import { UnitNode } from './schema';
|
|
3
|
+
/**
|
|
4
|
+
* Unit — a referencing overlay, not a container. It lists member zone ids
|
|
5
|
+
* and owns no geometry: member zones carry its tint, and the site panel
|
|
6
|
+
* drives selection (declaring `selectable` would subscribe the kind to 3D
|
|
7
|
+
* clicks that have nothing to hit). Not placeable: no tool, no palette tile.
|
|
8
|
+
*/
|
|
9
|
+
export declare const unitDefinition: NodeDefinition<typeof UnitNode>;
|
|
10
|
+
//# sourceMappingURL=definition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/unit/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA8B,MAAM,kBAAkB,CAAA;AAElF,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CAkC1D,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { UnitNode as UnitNodeSchema } from '@pascal-app/core';
|
|
2
|
+
import { unitParametrics } from './parametrics';
|
|
3
|
+
import { UnitNode } from './schema';
|
|
4
|
+
/**
|
|
5
|
+
* Unit — a referencing overlay, not a container. It lists member zone ids
|
|
6
|
+
* and owns no geometry: member zones carry its tint, and the site panel
|
|
7
|
+
* drives selection (declaring `selectable` would subscribe the kind to 3D
|
|
8
|
+
* clicks that have nothing to hit). Not placeable: no tool, no palette tile.
|
|
9
|
+
*/
|
|
10
|
+
export const unitDefinition = {
|
|
11
|
+
kind: 'unit',
|
|
12
|
+
schemaVersion: 1,
|
|
13
|
+
schema: UnitNode,
|
|
14
|
+
category: 'site',
|
|
15
|
+
defaults: () => {
|
|
16
|
+
const stub = UnitNodeSchema.parse({ id: 'unit_default', type: 'unit' });
|
|
17
|
+
const { id: _id, type: _type, ...rest } = stub;
|
|
18
|
+
return rest;
|
|
19
|
+
},
|
|
20
|
+
capabilities: {
|
|
21
|
+
duplicable: false,
|
|
22
|
+
deletable: true,
|
|
23
|
+
presettable: false,
|
|
24
|
+
},
|
|
25
|
+
parametrics: unitParametrics,
|
|
26
|
+
// No dirty consumer rebuilds this kind — see NodeDefinition.dirtyTracking.
|
|
27
|
+
dirtyTracking: false,
|
|
28
|
+
presentation: {
|
|
29
|
+
label: 'Unit',
|
|
30
|
+
description: 'A named group of zones forming one apartment, hotel room or commercial lot.',
|
|
31
|
+
icon: { kind: 'url', src: '/icons/zone.webp' },
|
|
32
|
+
paletteSection: 'site',
|
|
33
|
+
hidden: true,
|
|
34
|
+
},
|
|
35
|
+
mcp: {
|
|
36
|
+
description: 'A named occupancy unit (apartment, hotel room, commercial lot, common area) under a building, referencing member zones that may span levels.',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/unit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { unitDefinition } from './definition';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parametrics.d.ts","sourceRoot":"","sources":["../../src/unit/parametrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEtE,eAAO,MAAM,eAAe,EAAE,oBAAoB,CAAC,QAAQ,CAG1D,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/unit/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UnitNode } from '@pascal-app/core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-panel.d.ts","sourceRoot":"","sources":["../../src/unit/unit-panel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,QAAQ,EAGd,MAAM,kBAAkB,CAAA;AAgCzB,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,+BA2J7D"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { buildUnitReport, getLevelDisplayName, UNIT_KINDS, unitWarnings, useScene, } from '@pascal-app/core';
|
|
4
|
+
import { ActionButton, ActionGroup, formatAreaLabel, PanelSection, PanelWrapper, triggerSFX, } from '@pascal-app/editor';
|
|
5
|
+
import { useViewer } from '@pascal-app/viewer';
|
|
6
|
+
import { Trash2, X } from 'lucide-react';
|
|
7
|
+
import { useCallback, useMemo } from 'react';
|
|
8
|
+
import { PanelSelect, PanelTextField } from '../shared/panel-fields';
|
|
9
|
+
const UNIT_KIND_LABELS = {
|
|
10
|
+
apartment: 'Apartment',
|
|
11
|
+
'hotel-room': 'Hotel room',
|
|
12
|
+
commercial: 'Commercial',
|
|
13
|
+
common: 'Common',
|
|
14
|
+
};
|
|
15
|
+
const UNIT_KIND_OPTIONS = UNIT_KINDS.map((value) => ({ label: UNIT_KIND_LABELS[value], value }));
|
|
16
|
+
function ReportRow({ label, value }) {
|
|
17
|
+
return (_jsxs("div", { className: "flex items-baseline gap-2 rounded-md border border-border/50 bg-background/35 px-2.5 py-2 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("span", { className: "ml-auto font-mono font-medium text-foreground tabular-nums", children: value })] }));
|
|
18
|
+
}
|
|
19
|
+
export default function UnitPanel({ node }) {
|
|
20
|
+
const nodes = useScene((state) => state.nodes);
|
|
21
|
+
const updateNode = useScene((state) => state.updateNode);
|
|
22
|
+
const setSelection = useViewer((state) => state.setSelection);
|
|
23
|
+
const areaUnit = useViewer((state) => state.unit);
|
|
24
|
+
const focusedUnitId = useViewer((state) => state.focusedUnitId);
|
|
25
|
+
const report = useMemo(() => buildUnitReport(node, nodes), [node, nodes]);
|
|
26
|
+
const warnings = useMemo(() => unitWarnings(node, nodes), [node, nodes]);
|
|
27
|
+
const memberLevels = useMemo(() => {
|
|
28
|
+
const ordinals = new Map();
|
|
29
|
+
for (const member of report.members) {
|
|
30
|
+
if (member.levelId && member.levelOrdinal !== null) {
|
|
31
|
+
ordinals.set(member.levelId, member.levelOrdinal);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return [...ordinals.entries()]
|
|
35
|
+
.sort((a, b) => a[1] - b[1])
|
|
36
|
+
.map(([levelId]) => {
|
|
37
|
+
const level = nodes[levelId];
|
|
38
|
+
return { levelId, name: level?.type === 'level' ? getLevelDisplayName(level) : '' };
|
|
39
|
+
});
|
|
40
|
+
}, [nodes, report]);
|
|
41
|
+
const levelNameById = useMemo(() => new Map(memberLevels.map((level) => [level.levelId, level.name])), [memberLevels]);
|
|
42
|
+
const levelSpanLabel = useMemo(() => {
|
|
43
|
+
const names = memberLevels.map((level) => level.name).filter(Boolean);
|
|
44
|
+
return names.length > 0 ? names.join(', ') : '—';
|
|
45
|
+
}, [memberLevels]);
|
|
46
|
+
const update = useCallback((patch) => updateNode(node.id, patch), [node.id, updateNode]);
|
|
47
|
+
const removeMember = useCallback((zoneId) => update({ members: node.members.filter((id) => id !== zoneId) }), [node.members, update]);
|
|
48
|
+
const handleClose = useCallback(() => setSelection({ selectedIds: [] }), [setSelection]);
|
|
49
|
+
const handleDelete = useCallback(() => {
|
|
50
|
+
triggerSFX('sfx:structure-delete');
|
|
51
|
+
useScene.getState().deleteNode(node.id);
|
|
52
|
+
setSelection({ selectedIds: [] });
|
|
53
|
+
}, [node.id, setSelection]);
|
|
54
|
+
const warningMessages = warnings.map((warning) => {
|
|
55
|
+
if (warning.code === 'non-adjacent-levels') {
|
|
56
|
+
return 'Members sit on levels that are not adjacent.';
|
|
57
|
+
}
|
|
58
|
+
return 'No zones assigned yet.';
|
|
59
|
+
});
|
|
60
|
+
return (_jsxs(PanelWrapper, { icon: "/icons/zone.webp", onClose: handleClose, title: node.name || 'Unit', width: 320, children: [_jsxs(PanelSection, { title: "Unit", children: [_jsx(PanelTextField, { label: "Name", onCommit: (name) => update({ name }), value: node.name }), _jsx(PanelSelect, { label: "Kind", onChange: (kind) => update({ kind: kind }), options: UNIT_KIND_OPTIONS, value: node.kind }), _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: "Color" }), _jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-mono text-muted-foreground text-xs", children: node.color }), _jsx("input", { className: "h-6 w-8 cursor-pointer rounded border border-border/50 bg-transparent", onChange: (event) => update({ color: event.target.value }), type: "color", value: node.color })] })] })] }), _jsxs(PanelSection, { title: "Members", children: [report.members.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs leading-snug", children: "No zones yet. Draw zones while this unit is focused, or pick it from a zone's inspector." })) : (report.members.map((member) => (_jsxs("div", { className: "flex h-9 items-center gap-2 rounded-lg border border-border/50 bg-[#2C2C2E] px-3 text-xs", children: [_jsx("span", { className: "min-w-0 flex-1 truncate text-foreground", children: member.name || 'Zone' }), _jsx("span", { className: "shrink-0 truncate text-muted-foreground", children: member.levelId ? levelNameById.get(member.levelId) : '' }), _jsx("span", { className: "shrink-0 font-mono text-muted-foreground tabular-nums", children: formatAreaLabel(member.areaM2, areaUnit, 1) }), _jsx("button", { "aria-label": `Remove ${member.name || 'zone'} from unit`, className: "shrink-0 rounded-full p-1 text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground", onClick: () => removeMember(member.zoneId), type: "button", children: _jsx(X, { className: "h-3.5 w-3.5" }) })] }, member.zoneId)))), warningMessages.length > 0 ? (_jsx("ul", { className: "flex flex-col gap-1 text-amber-300 text-xs leading-snug", children: warningMessages.map((message, index) => (_jsx("li", { children: message }, warnings[index]?.code))) })) : null] }), _jsxs(PanelSection, { title: "Report", children: [_jsx(ReportRow, { label: "Members", value: String(report.memberCount) }), _jsx(ReportRow, { label: "Levels", value: levelSpanLabel }), _jsx(ReportRow, { label: "Gross area", value: formatAreaLabel(report.grossAreaM2, areaUnit, 2) })] }), _jsx(PanelSection, { title: "Actions", children: _jsxs(ActionGroup, { children: [focusedUnitId !== node.id && (_jsx(ActionButton, { className: "rounded-full", label: "Focus", onClick: () => useViewer.getState().setFocusedUnit(node.id) })), _jsx(ActionButton, { className: "text-destructive hover:text-destructive", icon: _jsx(Trash2, { className: "h-3.5 w-3.5" }), label: "Delete", onClick: handleDelete })] }) })] }));
|
|
61
|
+
}
|
package/dist/window/panel.js
CHANGED
|
@@ -343,7 +343,7 @@ export default function WindowPanel() {
|
|
|
343
343
|
], value: node.casementStyle ?? 'single' }), (node.casementStyle ?? 'single') === 'single' && (_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ hingesSide: value }), options: [
|
|
344
344
|
{ value: 'left', label: 'Left' },
|
|
345
345
|
{ value: 'right', label: 'Right' },
|
|
346
|
-
], value: node.hingesSide ?? 'left' }))] })), showOperationSection && (_jsx("div", { className: "mt-2", children: _jsx(SliderControl, { label: operationLabel, max: 1, min: 0, onChange: setOperationState, precision: 2, restoreOnCommit: false, step: 0.05, value: Math.round((node.operationState ?? 0) * 100) / 100 }) }))] })), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: _jsxs(_Fragment, { children: ["X", _jsx("sub", { className: "ml-[1px] text-[11px] opacity-70", children: "pos" })] }), onChange: (v) => handleUpdate({ position: [v, node.position[1], node.position[2]] }), precision: 2, step: 0.1, unit: "m", value:
|
|
346
|
+
], value: node.hingesSide ?? 'left' }))] })), showOperationSection && (_jsx("div", { className: "mt-2", children: _jsx(SliderControl, { label: operationLabel, max: 1, min: 0, onChange: setOperationState, precision: 2, restoreOnCommit: false, step: 0.05, value: Math.round((node.operationState ?? 0) * 100) / 100 }) }))] })), _jsxs(PanelSection, { title: "Position", children: [_jsx(SliderControl, { label: _jsxs(_Fragment, { children: ["X", _jsx("sub", { className: "ml-[1px] text-[11px] opacity-70", children: "pos" })] }), onChange: (v) => handleUpdate({ position: [v, node.position[1], node.position[2]] }), precision: 2, step: 0.1, unit: "m", value: node.position[0] }), _jsx(SliderControl, { label: _jsxs(_Fragment, { children: ["Y", _jsx("sub", { className: "ml-[1px] text-[11px] opacity-70", children: "pos" })] }), onChange: (v) => handleUpdate({ position: [node.position[0], v, node.position[2]] }), precision: 2, step: 0.1, unit: "m", value: node.position[1] }), showFlipSide && (_jsx("div", { className: "px-1 pt-2 pb-1", children: _jsx(ActionButton, { className: "w-full", icon: _jsx(FlipHorizontal2, { className: "h-4 w-4" }), label: "Flip Side", onClick: handleFlip }) }))] }), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Width", min: 0, onChange: (v) => handleUpdate(getDimensionUpdates({ width: v })), precision: 2, restoreOnCommit: false, step: 0.1, unit: "m", value: node.width }), _jsx(SliderControl, { label: "Height", min: 0, onChange: (v) => handleUpdate(getDimensionUpdates({ height: v })), precision: 2, restoreOnCommit: false, step: 0.1, unit: "m", value: node.height })] }), showWindowShapeSection && (_jsxs(PanelSection, { title: "Top Shape", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({
|
|
347
347
|
openingShape: value,
|
|
348
348
|
...(value === 'rounded'
|
|
349
349
|
? {
|
|
@@ -362,28 +362,28 @@ export default function WindowPanel() {
|
|
|
362
362
|
], value: windowShape }), windowShape === 'rounded' && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ openingRadiusMode: value }), options: [
|
|
363
363
|
{ value: 'all', label: 'All' },
|
|
364
364
|
{ value: 'individual', label: 'Individual' },
|
|
365
|
-
], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), precision: 2, step: 0.05, unit: "m", value:
|
|
365
|
+
], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), precision: 2, step: 0.05, unit: "m", value: cornerRadius })) : (_jsx(_Fragment, { children: [
|
|
366
366
|
['Top Left', 0],
|
|
367
367
|
['Top Right', 1],
|
|
368
368
|
['Bottom Right', 2],
|
|
369
369
|
['Bottom Left', 3],
|
|
370
|
-
].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), precision: 2, step: 0.05, unit: "m", value:
|
|
370
|
+
].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), precision: 2, step: 0.05, unit: "m", value: openingCornerRadii[index] ?? 0 }, label))) })), _jsx(SliderControl, { label: "Reveal Radius", max: 0.08, min: 0, onChange: (value) => previewWindowUpdate('openingRevealRadius', value), onCommit: (value) => commitWindowPreview('openingRevealRadius', value), precision: 3, step: 0.005, unit: "m", value: openingRevealRadius })] })), windowShape === 'arch' && (_jsx("div", { className: "mt-2 flex flex-col gap-1", children: _jsx(SliderControl, { label: "Arch Height", max: Math.max(0.05, node.height), min: 0.05, onChange: (value) => handleUpdate({ archHeight: value }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: archHeight }) }))] })), showOpeningShapeSection && (_jsxs(PanelSection, { title: "Opening Shape", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ openingShape: value }), options: [
|
|
371
371
|
{ value: 'rectangle', label: 'Rect' },
|
|
372
372
|
{ value: 'rounded', label: 'Rounded' },
|
|
373
373
|
{ value: 'arch', label: 'Arch' },
|
|
374
374
|
], value: openingShape }), openingShape === 'rounded' && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx(SegmentedControl, { onChange: (value) => handleUpdate({ openingRadiusMode: value }), options: [
|
|
375
375
|
{ value: 'all', label: 'All' },
|
|
376
376
|
{ value: 'individual', label: 'Individual' },
|
|
377
|
-
], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), precision: 2, step: 0.05, unit: "m", value:
|
|
377
|
+
], value: openingRadiusMode }), openingRadiusMode === 'all' ? (_jsx(SliderControl, { label: "Corner Radius", max: maxRoundedRadius, min: 0, onChange: (value) => previewWindowUpdate('cornerRadius', value), onCommit: (value) => commitWindowPreview('cornerRadius', value), precision: 2, step: 0.05, unit: "m", value: cornerRadius })) : (_jsx(_Fragment, { children: [
|
|
378
378
|
['Top Left', 0],
|
|
379
379
|
['Top Right', 1],
|
|
380
380
|
['Bottom Right', 2],
|
|
381
381
|
['Bottom Left', 3],
|
|
382
|
-
].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), precision: 2, step: 0.05, unit: "m", value:
|
|
382
|
+
].map(([label, index]) => (_jsx(SliderControl, { label: label, max: maxRoundedRadius, min: 0, onChange: (value) => setOpeningCornerRadius(index, value), onCommit: (value) => setOpeningCornerRadius(index, value, true), precision: 2, step: 0.05, unit: "m", value: openingCornerRadii[index] ?? 0 }, label))) })), _jsx(SliderControl, { label: "Reveal Radius", max: 0.08, min: 0, onChange: (value) => previewWindowUpdate('openingRevealRadius', value), onCommit: (value) => commitWindowPreview('openingRevealRadius', value), precision: 3, step: 0.005, unit: "m", value: openingRevealRadius })] })), openingShape === 'arch' && (_jsx("div", { className: "mt-2 flex flex-col gap-1", children: _jsx(SliderControl, { label: "Arch Height", max: Math.max(0.05, node.height), min: 0.05, onChange: (value) => handleUpdate({ archHeight: value }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value: archHeight }) }))] })), !isOpening && (_jsxs(_Fragment, { children: [showFrameSection && (_jsxs(PanelSection, { title: "Frame", children: [_jsx(SliderControl, { label: "Thickness", min: 0, onChange: (v) => handleUpdate({ frameThickness: v }), precision: 3, step: 0.01, unit: "m", value: node.frameThickness }), _jsx(SliderControl, { label: "Depth", min: 0, onChange: (v) => handleUpdate({ frameDepth: v }), precision: 3, step: 0.01, unit: "m", value: node.frameDepth })] })), showGridSection && (_jsxs(PanelSection, { title: "Grid", children: [_jsx(SliderControl, { label: "Columns", max: 8, min: 1, onChange: (v) => {
|
|
383
383
|
const n = Math.max(1, Math.min(8, Math.round(v)));
|
|
384
384
|
handleUpdate({ columnRatios: Array(n).fill(1 / n) });
|
|
385
385
|
}, precision: 0, step: 1, value: numCols }), _jsx(SliderControl, { label: "Rows", max: 8, min: 1, onChange: (v) => {
|
|
386
386
|
const n = Math.max(1, Math.min(8, Math.round(v)));
|
|
387
387
|
handleUpdate({ rowRatios: Array(n).fill(1 / n) });
|
|
388
|
-
}, precision: 0, step: 1, value: numRows }), numCols > 1 && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("div", { className: "mb-1 px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Col Widths" }), normCols.map((ratio, i) => (_jsx(SliderControl, { label: `C${i + 1}`, max: 95, min: 5, onChange: (v) => setColumnRatio(i, v / 100), precision: 1, step: 1, unit: "%", value: Math.round(ratio * 100 * 10) / 10 }, `c-${i}`))), _jsx("div", { className: "mt-1 border-border/50 border-t pt-1", children: _jsx(SliderControl, { label: "Divider", max: 0.1, min: 0.005, onChange: (v) => handleUpdate({ columnDividerThickness: v }), precision: 3, step: 0.01, unit: "m", value:
|
|
388
|
+
}, precision: 0, step: 1, value: numRows }), numCols > 1 && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("div", { className: "mb-1 px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Col Widths" }), normCols.map((ratio, i) => (_jsx(SliderControl, { label: `C${i + 1}`, max: 95, min: 5, onChange: (v) => setColumnRatio(i, v / 100), precision: 1, step: 1, unit: "%", value: Math.round(ratio * 100 * 10) / 10 }, `c-${i}`))), _jsx("div", { className: "mt-1 border-border/50 border-t pt-1", children: _jsx(SliderControl, { label: "Divider", max: 0.1, min: 0.005, onChange: (v) => handleUpdate({ columnDividerThickness: v }), precision: 3, step: 0.01, unit: "m", value: node.columnDividerThickness ?? 0.03 }) })] })), numRows > 1 && (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("div", { className: "mb-1 px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider", children: "Row Heights" }), normRows.map((ratio, i) => (_jsx(SliderControl, { label: `R${i + 1}`, max: 95, min: 5, onChange: (v) => setRowRatio(i, v / 100), precision: 1, step: 1, unit: "%", value: Math.round(ratio * 100 * 10) / 10 }, `r-${i}`))), _jsx("div", { className: "mt-1 border-border/50 border-t pt-1", children: _jsx(SliderControl, { label: "Divider", max: 0.1, min: 0.005, onChange: (v) => handleUpdate({ rowDividerThickness: v }), precision: 3, step: 0.01, unit: "m", value: node.rowDividerThickness ?? 0.03 }) })] }))] })), showSillSection && (_jsxs(PanelSection, { title: "Sill", children: [_jsx(ToggleControl, { checked: node.sill, label: "Enable Sill", onChange: (checked) => handleUpdate({ sill: checked }) }), node.sill && (_jsxs("div", { className: "mt-1 flex flex-col gap-1", children: [_jsx(SliderControl, { label: "Depth", min: 0, onChange: (v) => handleUpdate({ sillDepth: v }), precision: 3, step: 0.01, unit: "m", value: node.sillDepth }), _jsx(SliderControl, { label: "Thickness", min: 0, onChange: (v) => handleUpdate({ sillThickness: v }), precision: 3, step: 0.01, unit: "m", value: node.sillThickness })] }))] }))] })), _jsx(PanelSection, { title: "Actions", children: _jsxs(ActionGroup, { children: [_jsx(ActionButton, { icon: _jsx(Move, { className: "h-3.5 w-3.5" }), label: "Move", onClick: handleMove }), _jsx(ActionButton, { icon: _jsx(Copy, { className: "h-3.5 w-3.5" }), label: "Duplicate", onClick: handleDuplicate }), _jsx(ActionButton, { className: "hover:bg-red-500/20", icon: _jsx(Trash2, { className: "h-3.5 w-3.5 text-red-400" }), label: "Delete", onClick: handleDelete })] }) })] }));
|
|
389
389
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/zone/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,kBAAkB,CAAA;AAezB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/zone/definition.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,kBAAkB,CAAA;AAezB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGnC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,OAAO,QAAQ,CAmF1D,CAAA"}
|
package/dist/zone/definition.js
CHANGED
|
@@ -8,6 +8,7 @@ import { zoneParametrics } from './parametrics';
|
|
|
8
8
|
import { zoneQuickMeasurement } from './quick-measurement';
|
|
9
9
|
import { buildRoomFloorplanSchedule } from './room-documentation';
|
|
10
10
|
import { ZoneNode } from './schema';
|
|
11
|
+
import { buildingUnitsForZone } from './unit-membership';
|
|
11
12
|
/**
|
|
12
13
|
* Zone — Stage A. Custom-behavior escape hatch: zone uses TSL shader
|
|
13
14
|
* materials + `<Html>` portals + per-frame uniform poking, so it
|
|
@@ -60,7 +61,12 @@ export const zoneDefinition = {
|
|
|
60
61
|
priority: 4,
|
|
61
62
|
},
|
|
62
63
|
floorplan: buildZoneFloorplan,
|
|
63
|
-
|
|
64
|
+
// Every unit under the building, not just the current owner: a zone that
|
|
65
|
+
// joins its first unit has no owner to depend on until the unit changes.
|
|
66
|
+
floorplanDependencies: (node, nodes) => [
|
|
67
|
+
...(node.autoFromWalls ? node.boundaryWallIds : []),
|
|
68
|
+
...buildingUnitsForZone(node, (id) => nodes[id]).map((unit) => unit.id),
|
|
69
|
+
],
|
|
64
70
|
// 2D body move — centroid-pivot polygon mover (same as slab / ceiling).
|
|
65
71
|
// Without this, zone fell through to the overlay's generic free-translate
|
|
66
72
|
// path, which committed a `position` field zone has no schema for, so the
|
package/dist/zone/floorplan.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type FloorplanGeometry, type GeometryContext, type ZoneNode } from '@pascal-app/core';
|
|
2
2
|
/**
|
|
3
|
-
* Stage C floor-plan builder for zone. Zones are colored polygons
|
|
4
|
-
* fill + outline both come from `zone.color`. Selection adds an
|
|
5
|
-
* accent-colored outline.
|
|
3
|
+
* Stage C floor-plan builder for zone. Zones are colored polygons.
|
|
6
4
|
*
|
|
7
5
|
* The zone's `name` renders as a centered text label at the polygon's
|
|
8
6
|
* geometric centroid. The registry layer sorts zones before every
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/zone/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAEpB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"floorplan.d.ts","sourceRoot":"","sources":["../../src/zone/floorplan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAEpB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AASzB;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,iBAAiB,GAAG,IAAI,CAmIjG"}
|