@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/box-vent/panel.js
CHANGED
|
@@ -127,17 +127,17 @@ export default function BoxVentPanel() {
|
|
|
127
127
|
{ label: 'Box', value: 'box' },
|
|
128
128
|
{ label: 'Cap', value: 'cap' },
|
|
129
129
|
{ label: 'Dome', value: 'dome' },
|
|
130
|
-
], value: node.style ?? 'cap' }) }), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Width", max: 1000, min: 0.15, onChange: (v) => previewProp({ width: v }), onCommit: (v) => handleUpdate({ width: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value:
|
|
130
|
+
], value: node.style ?? 'cap' }) }), _jsxs(PanelSection, { title: "Dimensions", children: [_jsx(SliderControl, { label: "Width", max: 1000, min: 0.15, onChange: (v) => previewProp({ width: v }), onCommit: (v) => handleUpdate({ width: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.width }), _jsx(SliderControl, { label: "Depth", max: 1000, min: 0.15, onChange: (v) => previewProp({ depth: v }), onCommit: (v) => handleUpdate({ depth: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.depth }), _jsx(SliderControl, { label: "Height", max: 1000, min: 0.05, onChange: (v) => previewProp({ height: v }), onCommit: (v) => handleUpdate({ height: v }), precision: 2, restoreOnCommit: false, step: 0.01, unit: "m", value: node.height }), node.style === 'cap' && (_jsx(SliderControl, { label: "Hood Overhang", max: Math.max(0.02, node.width), min: 0, onChange: (v) => previewProp({ hoodOverhang: v }), onCommit: (v) => handleUpdate({ hoodOverhang: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.hoodOverhang ?? 0 })), node.style === 'box' && (_jsxs(_Fragment, { children: [_jsx(SliderControl, { label: "Base Inset", max: Math.max(0.005, Math.min(node.width, node.depth) / 2 - 0.005), min: 0, onChange: (v) => previewProp({ baseInset: v }), onCommit: (v) => handleUpdate({ baseInset: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.baseInset ?? 0.06 }), _jsx(SliderControl, { label: "Base Height", max: Math.max(0.01, node.height - 0.005), min: 0.005, onChange: (v) => previewProp({ baseHeight: v }), onCommit: (v) => handleUpdate({ baseHeight: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.baseHeight ?? 0.04 }), _jsx(SliderControl, { label: "Corner Bevel", max: Math.max(0, Math.min(node.width, node.depth) / 2 - (node.baseInset ?? 0.06) - 0.001), min: 0, onChange: (v) => previewProp({ cornerBevel: v }), onCommit: (v) => handleUpdate({ cornerBevel: v }), precision: 3, restoreOnCommit: false, step: 0.002, unit: "m", value: node.cornerBevel ?? 0.012 })] })), node.style === 'cap' && (_jsxs(_Fragment, { children: [_jsx(SliderControl, { label: "Cap Height", max: Math.max(0.02, node.height - 0.01), min: 0.01, onChange: (v) => previewProp({ capHeight: v }), onCommit: (v) => handleUpdate({ capHeight: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.capHeight ?? 0.07 }), _jsx(SliderControl, { label: "Gap Height", max: Math.max(0, node.height - Math.max(0.01, node.capHeight ?? 0.07) - 0.005), min: 0, onChange: (v) => previewProp({ capGap: v }), onCommit: (v) => handleUpdate({ capGap: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.capGap ?? 0 }), _jsx(SliderControl, { label: "Top Taper", max: 1, min: 0, onChange: (v) => previewProp({ topTaper: v }), onCommit: (v) => handleUpdate({ topTaper: v }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "", value: Math.round((node.topTaper ?? 0.4) * 100) / 100 })] })), node.style === 'dome' && (_jsxs(_Fragment, { children: [_jsx(SliderControl, { label: "Dome Curvature", max: 1.5, min: 0.3, onChange: (v) => previewProp({ domeCurvature: v }), onCommit: (v) => handleUpdate({ domeCurvature: v }), precision: 2, restoreOnCommit: false, step: 0.05, unit: "", value: Math.round((node.domeCurvature ?? 1.0) * 100) / 100 }), _jsx(SliderControl, { label: "Base Flange", max: 0.2, min: 0, onChange: (v) => previewProp({ hoodOverhang: v }), onCommit: (v) => handleUpdate({ hoodOverhang: v }), precision: 3, restoreOnCommit: false, step: 0.005, unit: "m", value: node.hoodOverhang ?? 0.04 })] }))] }), _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({
|
|
131
131
|
position: [v, node.position[1] ?? 0, node.position[2] ?? 0],
|
|
132
132
|
}), onCommit: (v) => handleUpdate({
|
|
133
133
|
position: [v, node.position[1] ?? 0, node.position[2] ?? 0],
|
|
134
|
-
}), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value:
|
|
134
|
+
}), 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({
|
|
135
135
|
position: [node.position[0] ?? 0, v, node.position[2] ?? 0],
|
|
136
136
|
}), onCommit: (v) => handleUpdate({
|
|
137
137
|
position: [node.position[0] ?? 0, v, node.position[2] ?? 0],
|
|
138
|
-
}), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value:
|
|
138
|
+
}), 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({
|
|
139
139
|
position: [node.position[0] ?? 0, node.position[1] ?? 0, v],
|
|
140
140
|
}), onCommit: (v) => handleUpdate({
|
|
141
141
|
position: [node.position[0] ?? 0, node.position[1] ?? 0, v],
|
|
142
|
-
}), precision: 2, restoreOnCommit: false, step: 0.05, unit: "m", value:
|
|
142
|
+
}), 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 })] }) })] }));
|
|
143
143
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CabinetModuleNode } from '@pascal-app/core';
|
|
2
|
+
import type { SinkLayout } from './stack';
|
|
3
|
+
export declare const BASIN_WALL = 0.012;
|
|
4
|
+
export declare const FAUCET_SETBACK = 0.03;
|
|
5
|
+
export type SinkBowlSpec = {
|
|
6
|
+
centerX: number;
|
|
7
|
+
width: number;
|
|
8
|
+
depth: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Bowl rects in module-local X/Z given the usable countertop footprint.
|
|
12
|
+
* Shared by the 3D cut, the run-countertop cut, and the 2D floorplan symbol.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sinkBowls(layout: SinkLayout, usableWidth: number, usableDepth: number): SinkBowlSpec[];
|
|
15
|
+
export declare function sinkOpening(bowl: SinkBowlSpec): SinkBowlSpec;
|
|
16
|
+
export declare function cooktopFootprint(node: Pick<CabinetModuleNode, 'width' | 'depth'>): {
|
|
17
|
+
width: number;
|
|
18
|
+
depth: number;
|
|
19
|
+
};
|
|
20
|
+
export declare const FAUCET_BASE_RADIUS = 0.032;
|
|
21
|
+
export declare function sinkFaucetFootprint(bowls: readonly SinkBowlSpec[]): {
|
|
22
|
+
x: number;
|
|
23
|
+
z: number;
|
|
24
|
+
radius: number;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=appliance-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appliance-layout.d.ts","sourceRoot":"","sources":["../../src/cabinet/appliance-layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,eAAO,MAAM,UAAU,QAAQ,CAAA;AAI/B,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC,MAAM,MAAM,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5E;;;GAGG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,YAAY,EAAE,CAuBhB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY,CAE5D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,OAAO,CAAC;;;EAKhF;AAED,eAAO,MAAM,kBAAkB,QAAQ,CAAA;AAEvC,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE;;;;EAQjE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const BASIN_WALL = 0.012;
|
|
2
|
+
const BASIN_CORNER_MARGIN = 0.06;
|
|
3
|
+
// Centers the faucet base in the strip between the bowl's back edge and the
|
|
4
|
+
// countertop's back edge (BASIN_CORNER_MARGIN wide).
|
|
5
|
+
export const FAUCET_SETBACK = 0.03;
|
|
6
|
+
/**
|
|
7
|
+
* Bowl rects in module-local X/Z given the usable countertop footprint.
|
|
8
|
+
* Shared by the 3D cut, the run-countertop cut, and the 2D floorplan symbol.
|
|
9
|
+
*/
|
|
10
|
+
export function sinkBowls(layout, usableWidth, usableDepth) {
|
|
11
|
+
const depth = Math.max(0.1, usableDepth - BASIN_CORNER_MARGIN * 2);
|
|
12
|
+
const full = Math.max(0.15, usableWidth - BASIN_CORNER_MARGIN * 2);
|
|
13
|
+
if (layout === 'single') {
|
|
14
|
+
const width = Math.min(0.7, full);
|
|
15
|
+
return [{ centerX: 0, width, depth }];
|
|
16
|
+
}
|
|
17
|
+
const divider = 0.03;
|
|
18
|
+
if (layout === 'double') {
|
|
19
|
+
const width = Math.min(0.42, (full - divider) / 2);
|
|
20
|
+
return [
|
|
21
|
+
{ centerX: -(width + divider) / 2, width, depth },
|
|
22
|
+
{ centerX: (width + divider) / 2, width, depth },
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
// double-offset: 60/40 split
|
|
26
|
+
const total = Math.min(0.86, full);
|
|
27
|
+
const main = (total - divider) * 0.6;
|
|
28
|
+
const side = (total - divider) * 0.4;
|
|
29
|
+
return [
|
|
30
|
+
{ centerX: -(total / 2) + main / 2, width: main, depth },
|
|
31
|
+
{ centerX: total / 2 - side / 2, width: side, depth },
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
export function sinkOpening(bowl) {
|
|
35
|
+
return { ...bowl, width: bowl.width - BASIN_WALL, depth: bowl.depth - BASIN_WALL };
|
|
36
|
+
}
|
|
37
|
+
export function cooktopFootprint(node) {
|
|
38
|
+
return {
|
|
39
|
+
width: Math.max(0.32, Math.min(node.width - 0.01, 0.76)),
|
|
40
|
+
depth: Math.max(0.28, Math.min(node.depth - 0.04, 0.53)),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export const FAUCET_BASE_RADIUS = 0.032;
|
|
44
|
+
export function sinkFaucetFootprint(bowls) {
|
|
45
|
+
const minX = Math.min(...bowls.map((bowl) => bowl.centerX - bowl.width / 2));
|
|
46
|
+
const maxX = Math.max(...bowls.map((bowl) => bowl.centerX + bowl.width / 2));
|
|
47
|
+
return {
|
|
48
|
+
x: (minX + maxX) / 2,
|
|
49
|
+
z: -bowls[0].depth / 2 - FAUCET_SETBACK,
|
|
50
|
+
radius: FAUCET_BASE_RADIUS,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { CabinetNode, GeometryContext } from '@pascal-app/core';
|
|
2
|
+
import { type SinkBowlSpec } from './appliance-layout';
|
|
3
|
+
export type CabinetSlab = {
|
|
4
|
+
size: [number, number, number];
|
|
5
|
+
position: [number, number, number];
|
|
6
|
+
};
|
|
7
|
+
export type RunSinkCut = {
|
|
8
|
+
bowls: SinkBowlSpec[];
|
|
9
|
+
x: number;
|
|
10
|
+
z: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function getCabinetCountertopLayout(node: CabinetNode, ctx?: GeometryContext): {
|
|
13
|
+
span: import("./run-layout").RunSpan;
|
|
14
|
+
modules: {
|
|
15
|
+
object: "node";
|
|
16
|
+
parentId: string | null;
|
|
17
|
+
visible: boolean;
|
|
18
|
+
metadata: Record<string, unknown>;
|
|
19
|
+
position: [number, number, number];
|
|
20
|
+
rotation: number;
|
|
21
|
+
width: number;
|
|
22
|
+
depth: number;
|
|
23
|
+
carcassHeight: number;
|
|
24
|
+
operationState: number;
|
|
25
|
+
plinthHeight: number;
|
|
26
|
+
toeKickDepth: number;
|
|
27
|
+
boardThickness: number;
|
|
28
|
+
countertopThickness: number;
|
|
29
|
+
countertopOverhang: number;
|
|
30
|
+
countertopBackOverhang: number;
|
|
31
|
+
withFinishedBack: boolean;
|
|
32
|
+
frontThickness: number;
|
|
33
|
+
frontGap: number;
|
|
34
|
+
frontStyle: "slab" | "shaker" | "raised-arch";
|
|
35
|
+
panelReady: boolean;
|
|
36
|
+
handleStyle: "none" | "bar" | "cutout" | "hole" | "knob";
|
|
37
|
+
handlePosition: "center" | "top" | "auto";
|
|
38
|
+
frontOverlay: "full" | "inset";
|
|
39
|
+
withBottomPanel: boolean;
|
|
40
|
+
showPlinth: boolean;
|
|
41
|
+
withCountertop: boolean;
|
|
42
|
+
id: `cabinet-module_${string}`;
|
|
43
|
+
type: "cabinet-module";
|
|
44
|
+
children: string[];
|
|
45
|
+
cabinetType: "base" | "tall";
|
|
46
|
+
moduleKind: "standard" | "corner-filler";
|
|
47
|
+
topFinish: "trim" | "none" | "top-cabinet";
|
|
48
|
+
topFinishHeight: number;
|
|
49
|
+
topFinishDepth: number;
|
|
50
|
+
name?: string | undefined;
|
|
51
|
+
camera?: {
|
|
52
|
+
position: [number, number, number];
|
|
53
|
+
target: [number, number, number];
|
|
54
|
+
mode: "perspective" | "orthographic";
|
|
55
|
+
fov?: number | undefined;
|
|
56
|
+
zoom?: number | undefined;
|
|
57
|
+
} | undefined;
|
|
58
|
+
supportSlabId?: string | undefined;
|
|
59
|
+
material?: {
|
|
60
|
+
id?: string | undefined;
|
|
61
|
+
preset?: "custom" | "white" | "brick" | "concrete" | "wood" | "glass" | "metal" | "plaster" | "tile" | "marble" | undefined;
|
|
62
|
+
properties?: {
|
|
63
|
+
color: string;
|
|
64
|
+
roughness: number;
|
|
65
|
+
metalness: number;
|
|
66
|
+
opacity: number;
|
|
67
|
+
transparent: boolean;
|
|
68
|
+
side: "front" | "back" | "double";
|
|
69
|
+
} | undefined;
|
|
70
|
+
texture?: {
|
|
71
|
+
url: string;
|
|
72
|
+
repeat?: [number, number] | undefined;
|
|
73
|
+
scale?: number | undefined;
|
|
74
|
+
} | undefined;
|
|
75
|
+
} | undefined;
|
|
76
|
+
materialPreset?: string | undefined;
|
|
77
|
+
slots?: Record<string, string> | undefined;
|
|
78
|
+
stack?: ({
|
|
79
|
+
type: "shelf";
|
|
80
|
+
id: string;
|
|
81
|
+
shelfCount?: number | undefined;
|
|
82
|
+
height?: number | undefined;
|
|
83
|
+
} | {
|
|
84
|
+
type: "drawer";
|
|
85
|
+
id: string;
|
|
86
|
+
drawerCount?: number | undefined;
|
|
87
|
+
height?: number | undefined;
|
|
88
|
+
} | {
|
|
89
|
+
type: "door";
|
|
90
|
+
id: string;
|
|
91
|
+
doorType?: "glass" | "double" | "single-left" | "single-right" | undefined;
|
|
92
|
+
shelfCount?: number | undefined;
|
|
93
|
+
height?: number | undefined;
|
|
94
|
+
} | {
|
|
95
|
+
type: "sink";
|
|
96
|
+
id: string;
|
|
97
|
+
sinkLayout?: "single" | "double" | "double-offset" | undefined;
|
|
98
|
+
height?: number | undefined;
|
|
99
|
+
} | {
|
|
100
|
+
type: "oven";
|
|
101
|
+
id: string;
|
|
102
|
+
height?: number | undefined;
|
|
103
|
+
} | {
|
|
104
|
+
type: "microwave";
|
|
105
|
+
id: string;
|
|
106
|
+
height?: number | undefined;
|
|
107
|
+
} | {
|
|
108
|
+
type: "dishwasher";
|
|
109
|
+
id: string;
|
|
110
|
+
height?: number | undefined;
|
|
111
|
+
} | {
|
|
112
|
+
type: "cooktop-gas";
|
|
113
|
+
id: string;
|
|
114
|
+
cooktopLayout?: "gas-2burner" | "gas-4burner" | "gas-5burner-wok" | "gas-6burner" | undefined;
|
|
115
|
+
cooktopBurnersOn?: boolean | undefined;
|
|
116
|
+
cooktopActiveBurners?: number[] | undefined;
|
|
117
|
+
cooktopKnobProgress?: number[] | undefined;
|
|
118
|
+
cooktopShowGrate?: boolean | undefined;
|
|
119
|
+
height?: number | undefined;
|
|
120
|
+
} | {
|
|
121
|
+
type: "cooktop-induction";
|
|
122
|
+
id: string;
|
|
123
|
+
cooktopLayout?: "induction-2zone" | "induction-4zone" | undefined;
|
|
124
|
+
cooktopBurnersOn?: boolean | undefined;
|
|
125
|
+
cooktopActiveBurners?: number[] | undefined;
|
|
126
|
+
cooktopKnobProgress?: number[] | undefined;
|
|
127
|
+
cooktopShowGrate?: boolean | undefined;
|
|
128
|
+
height?: number | undefined;
|
|
129
|
+
} | {
|
|
130
|
+
type: "pull-out-pantry";
|
|
131
|
+
id: string;
|
|
132
|
+
shelfCount?: number | undefined;
|
|
133
|
+
pantryRackStyle?: "glass" | "wire" | "tray" | undefined;
|
|
134
|
+
height?: number | undefined;
|
|
135
|
+
} | {
|
|
136
|
+
type: "fridge-single";
|
|
137
|
+
id: string;
|
|
138
|
+
height?: number | undefined;
|
|
139
|
+
} | {
|
|
140
|
+
type: "fridge-double";
|
|
141
|
+
id: string;
|
|
142
|
+
height?: number | undefined;
|
|
143
|
+
} | {
|
|
144
|
+
type: "fridge-top-freezer";
|
|
145
|
+
id: string;
|
|
146
|
+
height?: number | undefined;
|
|
147
|
+
} | {
|
|
148
|
+
type: "fridge-bottom-freezer";
|
|
149
|
+
id: string;
|
|
150
|
+
height?: number | undefined;
|
|
151
|
+
} | {
|
|
152
|
+
type: "hood-pyramid";
|
|
153
|
+
id: string;
|
|
154
|
+
height?: number | undefined;
|
|
155
|
+
} | {
|
|
156
|
+
type: "hood-curved-glass";
|
|
157
|
+
id: string;
|
|
158
|
+
height?: number | undefined;
|
|
159
|
+
})[] | undefined;
|
|
160
|
+
openSide?: "left" | "right" | undefined;
|
|
161
|
+
cornerShelf?: boolean | undefined;
|
|
162
|
+
}[];
|
|
163
|
+
ends: import("./run-layout").RunSpanEnds;
|
|
164
|
+
backOverhang: number;
|
|
165
|
+
countertop: CabinetSlab | null;
|
|
166
|
+
bar: {
|
|
167
|
+
slab: CabinetSlab;
|
|
168
|
+
support: CabinetSlab;
|
|
169
|
+
edge: NonNullable<CabinetNode["barLedge"]>["edge"];
|
|
170
|
+
} | null;
|
|
171
|
+
sinkCuts: RunSinkCut[];
|
|
172
|
+
}[];
|
|
173
|
+
//# sourceMappingURL=countertop-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"countertop-layout.d.ts","sourceRoot":"","sources":["../../src/cabinet/countertop-layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,WAAW,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvF,OAAO,EAAE,KAAK,YAAY,EAAa,MAAM,oBAAoB,CAAA;AAIjE,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAExE,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8BvE,WAAW;iBACR,WAAW;cACd,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;;;IA4DvD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { sinkBowls } from './appliance-layout';
|
|
2
|
+
import { getRunSpanEnds, getRunSpanGroups } from './run-layout';
|
|
3
|
+
import { compartmentSinkLayout } from './stack';
|
|
4
|
+
export function getCabinetCountertopLayout(node, ctx) {
|
|
5
|
+
const modules = (ctx?.children ?? []).filter((child) => child.type === 'cabinet-module');
|
|
6
|
+
const groups = getRunSpanGroups(modules, { runTier: node.runTier });
|
|
7
|
+
const ends = getRunSpanEnds(node, ctx, groups.map(({ span }) => span));
|
|
8
|
+
// A back bar replaces the seating overhang; side bars leave it alone.
|
|
9
|
+
const backOverhang = node.withCountertop && node.barLedge?.edge !== 'back' ? node.countertopBackOverhang : 0;
|
|
10
|
+
return groups.map(({ span, modules: spanModules }, index) => {
|
|
11
|
+
const spanEnds = ends[index];
|
|
12
|
+
const { leftOverhang, rightOverhang } = spanEnds;
|
|
13
|
+
const width = span.width + leftOverhang + rightOverhang;
|
|
14
|
+
const centerX = span.centerX + (rightOverhang - leftOverhang) / 2;
|
|
15
|
+
const depth = span.depth + node.countertopOverhang + backOverhang;
|
|
16
|
+
const centerZ = span.centerZ + (node.countertopOverhang - backOverhang) / 2;
|
|
17
|
+
const countertop = node.withCountertop && span.hasCountertop && node.countertopThickness > 0
|
|
18
|
+
? {
|
|
19
|
+
size: [width, node.countertopThickness, depth],
|
|
20
|
+
position: [centerX, span.topY + node.countertopThickness / 2, centerZ],
|
|
21
|
+
}
|
|
22
|
+
: null;
|
|
23
|
+
const ledge = node.barLedge;
|
|
24
|
+
let bar = null;
|
|
25
|
+
if (ledge &&
|
|
26
|
+
span.hasCountertop &&
|
|
27
|
+
(ledge.edge === 'back' ||
|
|
28
|
+
(ledge.edge === 'left' && index === 0) ||
|
|
29
|
+
(ledge.edge === 'right' && index === groups.length - 1))) {
|
|
30
|
+
const thickness = Math.max(node.countertopThickness, 0.02);
|
|
31
|
+
const height = Math.max(0.1, ledge.height - thickness);
|
|
32
|
+
if (ledge.edge === 'back') {
|
|
33
|
+
const backZ = span.minZ - (node.withFinishedBack ? node.boardThickness : 0);
|
|
34
|
+
bar = {
|
|
35
|
+
edge: ledge.edge,
|
|
36
|
+
support: {
|
|
37
|
+
size: [width, height, node.boardThickness],
|
|
38
|
+
position: [centerX, height / 2, backZ - node.boardThickness / 2],
|
|
39
|
+
},
|
|
40
|
+
slab: {
|
|
41
|
+
size: [width, thickness, ledge.depth],
|
|
42
|
+
position: [centerX, height + thickness / 2, backZ - ledge.depth / 2],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const sign = ledge.edge === 'left' ? -1 : 1;
|
|
48
|
+
const edgeX = ledge.edge === 'left' ? span.minX : span.maxX;
|
|
49
|
+
bar = {
|
|
50
|
+
edge: ledge.edge,
|
|
51
|
+
support: {
|
|
52
|
+
size: [node.boardThickness, height, depth],
|
|
53
|
+
position: [edgeX + (sign * node.boardThickness) / 2, height / 2, centerZ],
|
|
54
|
+
},
|
|
55
|
+
slab: {
|
|
56
|
+
size: [ledge.depth, thickness, depth],
|
|
57
|
+
position: [edgeX + (sign * ledge.depth) / 2, height + thickness / 2, centerZ],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const sinkCuts = [];
|
|
63
|
+
if (countertop) {
|
|
64
|
+
for (const module of modules) {
|
|
65
|
+
if (module.position[0] < span.minX - 1e-4 || module.position[0] > span.maxX + 1e-4)
|
|
66
|
+
continue;
|
|
67
|
+
// The default stack contains only storage; avoid generating compartment IDs in a query.
|
|
68
|
+
const sink = module.stack?.find((compartment) => compartment.type === 'sink');
|
|
69
|
+
if (!sink)
|
|
70
|
+
continue;
|
|
71
|
+
sinkCuts.push({
|
|
72
|
+
bowls: sinkBowls(compartmentSinkLayout(sink), Math.max(0.01, module.width - 2 * module.boardThickness), module.depth),
|
|
73
|
+
x: module.position[0],
|
|
74
|
+
z: module.position[2],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { span, modules: spanModules, ends: spanEnds, backOverhang, countertop, bar, sinkCuts };
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/cabinet/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,WAAW,IAAI,eAAe,EAG9B,oBAAoB,EAMpB,cAAc,EACd,QAAQ,EACT,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/cabinet/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,WAAW,IAAI,eAAe,EAG9B,oBAAoB,EAMpB,cAAc,EACd,QAAQ,EACT,MAAM,kBAAkB,CAAA;AAyDzB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAwKzD,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,eAAe,EACrB,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC3C,oBAAoB,EAAE,CAcxB;AAiKD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAA;IAC1B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAC1C,mBAAmB,CAWrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,SAAS,mBAAmB,EAAE,EAC1C,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,QAgD9B;AAED,oEAAoE;AACpE,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAexE;AAgvDD,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,OAAO,WAAW,CA8MhE,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,OAAO,iBAAiB,CAyK5E,CAAA"}
|
|
@@ -5,6 +5,7 @@ import { buildCabinetFloorplan, buildCabinetModuleFloorplan } from './floorplan'
|
|
|
5
5
|
import { cabinetModuleFloorplanMoveTarget } from './floorplan-move';
|
|
6
6
|
import { cabinetFloorplanSiblingOverrides } from './floorplan-overrides';
|
|
7
7
|
import { buildCabinetGeometry } from './geometry';
|
|
8
|
+
import { withCabinetHostedChildren } from './hosted-resize';
|
|
8
9
|
import { toggleCabinetOperationState } from './interaction';
|
|
9
10
|
import { cabinetModuleParentFrame } from './move-frame';
|
|
10
11
|
import { cabinetPaint } from './paint';
|
|
@@ -21,6 +22,7 @@ import { cabinetSceneAction } from './scene-action';
|
|
|
21
22
|
import { CabinetModuleNode, CabinetNode } from './schema';
|
|
22
23
|
import { cabinetSlots } from './slots';
|
|
23
24
|
import { backAnchoredModuleZ, isHoodCompartmentType, minCabinetCarcassHeightForStack, stackForCabinet, } from './stack';
|
|
25
|
+
import { cabinetSurfaceProvider } from './surfaces';
|
|
24
26
|
import { cabinetFloorplanAffectedIds, cabinetTreeChildIds, cabinetTreeHidden, cabinetTreeLabel, } from './tree-structure';
|
|
25
27
|
import { findClosestCabinetWallInPlan, resolveCabinetModuleWallSnapLocal, resolveCabinetRunWallSnap, resolveCabinetWallFaceOffset, } from './wall-snap';
|
|
26
28
|
function isCabinetDuplicableNode(node) {
|
|
@@ -334,7 +336,7 @@ export function cabinetRunNeighborSignature(run) {
|
|
|
334
336
|
run.carcassHeight,
|
|
335
337
|
run.runTier,
|
|
336
338
|
run.countertopOverhang,
|
|
337
|
-
run.children ?? [],
|
|
339
|
+
(run.children ?? []).filter((id) => id.startsWith('cabinet_') || id.startsWith('cabinet-module_')),
|
|
338
340
|
cabinetLayoutRevision(run.metadata),
|
|
339
341
|
]);
|
|
340
342
|
}
|
|
@@ -1698,7 +1700,8 @@ function cabinetRotateHandle() {
|
|
|
1698
1700
|
};
|
|
1699
1701
|
}
|
|
1700
1702
|
function cabinetHandles(node, sceneApi) {
|
|
1701
|
-
|
|
1703
|
+
const structuralChildren = (node.children ?? []).filter((id) => id.startsWith('cabinet_') || id.startsWith('cabinet-module_'));
|
|
1704
|
+
if (structuralChildren.length > 0) {
|
|
1702
1705
|
const connectedRuns = sceneApi && node.runTier === 'base' ? connectedBaseRuns(node, sceneApi) : [];
|
|
1703
1706
|
const depthHandles = sceneApi
|
|
1704
1707
|
? connectedRuns.map((run) => cabinetConnectedRunDepthHandle(node, run, sceneApi))
|
|
@@ -1715,7 +1718,7 @@ function cabinetHandles(node, sceneApi) {
|
|
|
1715
1718
|
cabinetHeightHandle(),
|
|
1716
1719
|
cabinetRotateHandle(),
|
|
1717
1720
|
];
|
|
1718
|
-
if (
|
|
1721
|
+
if (structuralChildren.length === 0) {
|
|
1719
1722
|
handles.unshift(cabinetWidthHandle('left'), cabinetWidthHandle('right'));
|
|
1720
1723
|
}
|
|
1721
1724
|
return handles;
|
|
@@ -1821,6 +1824,7 @@ export const cabinetDefinition = {
|
|
|
1821
1824
|
duplicable: { subtree: true, prepareSubtreeClone: prepareCabinetSubtreeClone },
|
|
1822
1825
|
deletable: true,
|
|
1823
1826
|
surfaces: {
|
|
1827
|
+
hosting: cabinetSurfaceProvider,
|
|
1824
1828
|
top: {
|
|
1825
1829
|
height: (node, context) => cabinetLocalBounds(node, context.nodes).maxY,
|
|
1826
1830
|
},
|
|
@@ -1845,10 +1849,10 @@ export const cabinetDefinition = {
|
|
|
1845
1849
|
// Dirty-cascade: a dirtied run re-marks its hosted modules so their
|
|
1846
1850
|
// composite geometry re-flows with the run (see `cascadeDirty`).
|
|
1847
1851
|
relations: {
|
|
1848
|
-
hosts: ['cabinet', 'cabinet-module'],
|
|
1852
|
+
hosts: ['cabinet', 'cabinet-module', 'item', 'procedural-item'],
|
|
1849
1853
|
},
|
|
1850
1854
|
parametrics: cabinetParametrics,
|
|
1851
|
-
handles: cabinetHandles,
|
|
1855
|
+
handles: (node, sceneApi) => cabinetHandles(node, sceneApi).map(withCabinetHostedChildren),
|
|
1852
1856
|
geometry: buildCabinetGeometry,
|
|
1853
1857
|
exportAnimation: ({ node, object }) => bakeCabinetAnimationClip(node, object),
|
|
1854
1858
|
system: {
|
|
@@ -1857,6 +1861,7 @@ export const cabinetDefinition = {
|
|
|
1857
1861
|
},
|
|
1858
1862
|
// `operationState` is deliberately absent — door/drawer poses are applied
|
|
1859
1863
|
// per-frame by the cabinet animation system, not by geometry rebuilds.
|
|
1864
|
+
geometryChildTypes: ['cabinet', 'cabinet-module'],
|
|
1860
1865
|
geometryKey: (n) => JSON.stringify([
|
|
1861
1866
|
n.width,
|
|
1862
1867
|
n.depth,
|
|
@@ -1890,7 +1895,7 @@ export const cabinetDefinition = {
|
|
|
1890
1895
|
// overhang re-trims against the neighbor's new spans (the neighbor's
|
|
1891
1896
|
// own fields never appear in this key).
|
|
1892
1897
|
JSON.stringify(cabinetAdjacencyRevision(n.metadata)),
|
|
1893
|
-
JSON.stringify(n.children ?? []),
|
|
1898
|
+
JSON.stringify((n.children ?? []).filter((id) => id.startsWith('cabinet_') || id.startsWith('cabinet-module_'))),
|
|
1894
1899
|
JSON.stringify(n.stack ?? null),
|
|
1895
1900
|
]),
|
|
1896
1901
|
floorplan: buildCabinetFloorplan,
|
|
@@ -2022,11 +2027,13 @@ export const cabinetModuleDefinition = {
|
|
|
2022
2027
|
sceneAction: cabinetSceneAction,
|
|
2023
2028
|
slots: () => cabinetSlots(),
|
|
2024
2029
|
},
|
|
2030
|
+
relations: { hosts: ['cabinet', 'cabinet-module', 'item', 'procedural-item'] },
|
|
2025
2031
|
parametrics: cabinetModuleParametrics,
|
|
2026
|
-
handles: cabinetModuleHandles,
|
|
2032
|
+
handles: () => cabinetModuleHandles().map(withCabinetHostedChildren),
|
|
2027
2033
|
geometry: buildCabinetGeometry,
|
|
2028
2034
|
exportAnimation: ({ node, object }) => bakeCabinetAnimationClip(node, object),
|
|
2029
2035
|
// `operationState` is deliberately absent — see cabinetDefinition.geometryKey.
|
|
2036
|
+
geometryChildTypes: ['cabinet', 'cabinet-module'],
|
|
2030
2037
|
geometryKey: (n) => JSON.stringify([
|
|
2031
2038
|
n.cabinetType,
|
|
2032
2039
|
n.moduleKind,
|
|
@@ -2056,7 +2063,7 @@ export const cabinetModuleDefinition = {
|
|
|
2056
2063
|
JSON.stringify(n.material ?? null),
|
|
2057
2064
|
JSON.stringify(n.materialPreset ?? null),
|
|
2058
2065
|
JSON.stringify(n.slots ?? null),
|
|
2059
|
-
JSON.stringify(n.children ?? []),
|
|
2066
|
+
JSON.stringify((n.children ?? []).filter((id) => id.startsWith('cabinet_') || id.startsWith('cabinet-module_'))),
|
|
2060
2067
|
JSON.stringify(n.stack ?? null),
|
|
2061
2068
|
]),
|
|
2062
2069
|
floorplan: buildCabinetModuleFloorplan,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cooktop.d.ts","sourceRoot":"","sources":["../../../src/cabinet/geometry/cooktop.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,EAMN,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"cooktop.d.ts","sourceRoot":"","sources":["../../../src/cabinet/geometry/cooktop.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,EAMN,MAAM,OAAO,CAAA;AAQd,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,6BAA6B,EAClC,KAAK,aAAa,EAMnB,MAAM,UAAU,CAAA;AACjB,OAAO,EAGL,KAAK,mBAAmB,EAWzB,MAAM,UAAU,CAAA;AAEjB,eAAO,MAAM,qBAAqB,QAAQ,CAAA;AAC1C,MAAM,MAAM,iBAAiB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AA+BtE,MAAM,MAAM,iBAAiB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAgBhG,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,iBAAiB,EAAE,CASxE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,iBAAiB,EAAE,CAMzE;AA+SD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,mBAAmB,EACzB,WAAW,EAAE,kBAAkB,EAC/B,IAAI,EAAE,6BAA6B,EACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,QAmJd"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AdditiveBlending, BoxGeometry, CylinderGeometry, DoubleSide, Group, Mesh, MeshBasicMaterial, RingGeometry, SphereGeometry, TorusGeometry, } from 'three';
|
|
2
|
+
import { cooktopFootprint } from '../appliance-layout';
|
|
2
3
|
import { COOKTOP_FLAME_COUNT, cooktopFlameSeed, createCooktopFlameGeometry, updateCooktopFlameTube, } from '../cooktop-flame';
|
|
3
4
|
import { compartmentCooktopActiveBurners, compartmentCooktopBurnersOn, compartmentCooktopKnobProgress, compartmentCooktopLayout, compartmentCooktopShowGrate, } from '../stack';
|
|
4
5
|
import { addBox, applianceDisplayMaterial, cooktopBurnerMaterial, cooktopGlassMaterial, cooktopGrateMaterial, cooktopInductionActiveZoneMaterial, cooktopInductionZoneMaterial, cooktopKnobHitMaterial, cooktopKnobOnMaterial, cooktopTrimMaterial, createWorldScaleBoxGeometry, stampSlot, } from './shared';
|
|
@@ -192,8 +193,7 @@ export function addCooktopCompartment(group, node, compartment, type, topY, inde
|
|
|
192
193
|
const knobProgress = compartmentCooktopKnobProgress(compartment, type);
|
|
193
194
|
const burnersOn = activeBurners.size > 0 || compartmentCooktopBurnersOn(compartment);
|
|
194
195
|
const name = type === 'cooktop-gas' ? `cabinet-cooktop-gas-${index}` : `cabinet-cooktop-induction-${index}`;
|
|
195
|
-
const
|
|
196
|
-
const frameDepth = Math.max(0.28, Math.min(node.depth - 0.04, 0.53));
|
|
196
|
+
const { width: frameWidth, depth: frameDepth } = cooktopFootprint(node);
|
|
197
197
|
const surfaceWidth = Math.max(0.28, frameWidth - 0.026);
|
|
198
198
|
const surfaceDepth = Math.max(0.24, frameDepth - 0.026);
|
|
199
199
|
const surfaceThickness = 0.012;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cabinet/geometry/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvF,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACpE,OAAO,EAAE,KAAK,EAAQ,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cabinet/geometry/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvF,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACpE,OAAO,EAAE,KAAK,EAAQ,MAAM,OAAO,CAAA;AAMnC,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,iBAAiB,EAAE,CAIxE;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,eAAe,GAAG,SAAS,EAChC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,OAAO,EACjB,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,KAAK,GAAG,IAAI,CAoId"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Group, Mesh } from 'three';
|
|
2
|
-
import {
|
|
3
|
-
import { compartmentSinkLayout, stackForCabinet } from '../stack';
|
|
2
|
+
import { getCabinetCountertopLayout } from '../countertop-layout';
|
|
4
3
|
import { buildFrontGeometry } from './fronts';
|
|
5
4
|
import { addBox, getCabinetSlotMaterials } from './shared';
|
|
6
|
-
import { cutSinkIntoCountertop
|
|
5
|
+
import { cutSinkIntoCountertop } from './sink';
|
|
7
6
|
export function getRunModules(ctx) {
|
|
8
7
|
return (ctx?.children ?? []).filter((child) => child.type === 'cabinet-module');
|
|
9
8
|
}
|
|
@@ -14,15 +13,9 @@ export function buildCabinetRunGeometry(node, ctx, shading, textures, colorPrese
|
|
|
14
13
|
const group = new Group();
|
|
15
14
|
const materials = getCabinetSlotMaterials(node, ctx, shading, textures, colorPreset, sceneTheme);
|
|
16
15
|
const plinth = node.showPlinth ? node.plinthHeight : 0;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const spans = getRunSpans(modules, { runTier: node.runTier });
|
|
21
|
-
const spanEnds = getRunSpanEnds(node, ctx, spans);
|
|
22
|
-
for (const span of spans) {
|
|
23
|
-
const spanIndex = spans.indexOf(span);
|
|
24
|
-
const barEdge = node.barLedge?.edge;
|
|
25
|
-
const { leftOverhang, rightOverhang, exposedLeft, exposedRight } = spanEnds[spanIndex];
|
|
16
|
+
for (const layout of getCabinetCountertopLayout(node, ctx)) {
|
|
17
|
+
const { span, backOverhang, countertop: slab, bar, sinkCuts } = layout;
|
|
18
|
+
const { leftOverhang, rightOverhang, exposedLeft, exposedRight } = layout.ends;
|
|
26
19
|
const toeKickDepth = node.showPlinth
|
|
27
20
|
? Math.min(node.toeKickDepth, span.depth - node.boardThickness * 2)
|
|
28
21
|
: 0;
|
|
@@ -52,39 +45,9 @@ export function buildCabinetRunGeometry(node, ctx, shading, textures, colorPrese
|
|
|
52
45
|
group.add(endPanel);
|
|
53
46
|
}
|
|
54
47
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const spanHasBar = node.barLedge &&
|
|
59
|
-
span.hasCountertop &&
|
|
60
|
-
(barEdge === 'back' ||
|
|
61
|
-
(barEdge === 'left' && spanIndex === 0) ||
|
|
62
|
-
(barEdge === 'right' && spanIndex === spans.length - 1));
|
|
63
|
-
if (node.barLedge && spanHasBar) {
|
|
64
|
-
const slabThickness = Math.max(node.countertopThickness, 0.02);
|
|
65
|
-
const supportHeight = Math.max(0.1, node.barLedge.height - slabThickness);
|
|
66
|
-
// Knee wall spans the slab's full footprint on the shared axis so the
|
|
67
|
-
// two faces stay flush — a carcass-sized wall reads as a seam under
|
|
68
|
-
// the wider slab.
|
|
69
|
-
if (barEdge === 'back') {
|
|
70
|
-
const backZ = span.minZ - (node.withFinishedBack ? node.boardThickness : 0);
|
|
71
|
-
const barWidth = span.width + leftOverhang + rightOverhang;
|
|
72
|
-
const barCenterX = span.centerX + (rightOverhang - leftOverhang) / 2;
|
|
73
|
-
addBox(group, [barWidth, supportHeight, node.boardThickness], [barCenterX, supportHeight / 2, backZ - node.boardThickness / 2], materials.front, 'cabinet-run-bar-support', 'front');
|
|
74
|
-
addBox(group, [barWidth, slabThickness, node.barLedge.depth], [barCenterX, supportHeight + slabThickness / 2, backZ - node.barLedge.depth / 2], materials.countertop, 'cabinet-run-bar-slab', 'countertop');
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
const sign = barEdge === 'left' ? -1 : 1;
|
|
78
|
-
const edgeX = barEdge === 'left' ? span.minX : span.maxX;
|
|
79
|
-
const slabDepth = span.depth + node.countertopOverhang + backOverhang;
|
|
80
|
-
const slabCenterZ = span.centerZ + (node.countertopOverhang - backOverhang) / 2;
|
|
81
|
-
addBox(group, [node.boardThickness, supportHeight, slabDepth], [edgeX + sign * (node.boardThickness / 2), supportHeight / 2, slabCenterZ], materials.front, 'cabinet-run-bar-support', 'front');
|
|
82
|
-
addBox(group, [node.barLedge.depth, slabThickness, slabDepth], [
|
|
83
|
-
edgeX + sign * (node.barLedge.depth / 2),
|
|
84
|
-
supportHeight + slabThickness / 2,
|
|
85
|
-
slabCenterZ,
|
|
86
|
-
], materials.countertop, 'cabinet-run-bar-slab', 'countertop');
|
|
87
|
-
}
|
|
48
|
+
if (bar) {
|
|
49
|
+
addBox(group, bar.support.size, bar.support.position, materials.front, 'cabinet-run-bar-support', 'front');
|
|
50
|
+
addBox(group, bar.slab.size, bar.slab.position, materials.countertop, 'cabinet-run-bar-slab', 'countertop');
|
|
88
51
|
}
|
|
89
52
|
// Waterfall ends: the slab material drops to the floor on exposed run
|
|
90
53
|
// ends (skipped where a neighbor abuts or a side bar occupies the end).
|
|
@@ -100,32 +63,8 @@ export function buildCabinetRunGeometry(node, ctx, shading, textures, colorPrese
|
|
|
100
63
|
addBox(group, [node.countertopThickness, span.topY, slabDepth], [outerX - sign * (node.countertopThickness / 2), span.topY / 2, slabCenterZ], materials.countertop, `cabinet-run-waterfall-${side}`, 'countertop');
|
|
101
64
|
}
|
|
102
65
|
}
|
|
103
|
-
if (
|
|
104
|
-
const countertop = addBox(group,
|
|
105
|
-
span.width + leftOverhang + rightOverhang,
|
|
106
|
-
node.countertopThickness,
|
|
107
|
-
span.depth + node.countertopOverhang + backOverhang,
|
|
108
|
-
], [
|
|
109
|
-
span.centerX + (rightOverhang - leftOverhang) / 2,
|
|
110
|
-
span.topY + node.countertopThickness / 2,
|
|
111
|
-
span.centerZ + (node.countertopOverhang - backOverhang) / 2,
|
|
112
|
-
], materials.countertop, 'cabinet-run-countertop', 'countertop');
|
|
113
|
-
// Undermount sink modules cut their bowl openings out of the run's
|
|
114
|
-
// slab (modules inside a run never own a countertop themselves).
|
|
115
|
-
const sinkCuts = [];
|
|
116
|
-
for (const module of modules) {
|
|
117
|
-
if (module.position[0] < span.minX - 1e-4 || module.position[0] > span.maxX + 1e-4) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
const sink = stackForCabinet(module).find((compartment) => compartment.type === 'sink');
|
|
121
|
-
if (!sink)
|
|
122
|
-
continue;
|
|
123
|
-
sinkCuts.push({
|
|
124
|
-
bowls: sinkBowls(compartmentSinkLayout(sink), Math.max(0.01, module.width - 2 * module.boardThickness), module.depth),
|
|
125
|
-
x: module.position[0],
|
|
126
|
-
z: module.position[2],
|
|
127
|
-
});
|
|
128
|
-
}
|
|
66
|
+
if (slab) {
|
|
67
|
+
const countertop = addBox(group, slab.size, slab.position, materials.countertop, 'cabinet-run-countertop', 'countertop');
|
|
129
68
|
if (sinkCuts.length > 0) {
|
|
130
69
|
group.remove(countertop);
|
|
131
70
|
let cut = countertop;
|