@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.
Files changed (154) hide show
  1. package/dist/box-vent/panel.js +4 -4
  2. package/dist/cabinet/appliance-layout.d.ts +26 -0
  3. package/dist/cabinet/appliance-layout.d.ts.map +1 -0
  4. package/dist/cabinet/appliance-layout.js +52 -0
  5. package/dist/cabinet/countertop-layout.d.ts +173 -0
  6. package/dist/cabinet/countertop-layout.d.ts.map +1 -0
  7. package/dist/cabinet/countertop-layout.js +80 -0
  8. package/dist/cabinet/definition.d.ts.map +1 -1
  9. package/dist/cabinet/definition.js +15 -8
  10. package/dist/cabinet/geometry/cooktop.d.ts.map +1 -1
  11. package/dist/cabinet/geometry/cooktop.js +2 -2
  12. package/dist/cabinet/geometry/run.d.ts.map +1 -1
  13. package/dist/cabinet/geometry/run.js +10 -71
  14. package/dist/cabinet/geometry/sink.d.ts +2 -12
  15. package/dist/cabinet/geometry/sink.d.ts.map +1 -1
  16. package/dist/cabinet/geometry/sink.js +8 -39
  17. package/dist/cabinet/hosted-resize.d.ts +8 -0
  18. package/dist/cabinet/hosted-resize.d.ts.map +1 -0
  19. package/dist/cabinet/hosted-resize.js +158 -0
  20. package/dist/cabinet/run-layout.d.ts +8 -1
  21. package/dist/cabinet/run-layout.d.ts.map +1 -1
  22. package/dist/cabinet/run-layout.js +22 -14
  23. package/dist/cabinet/run-ops.d.ts +3 -3
  24. package/dist/cabinet/run-ops.js +3 -3
  25. package/dist/cabinet/run-panel.d.ts +1 -1
  26. package/dist/cabinet/run-panel.d.ts.map +1 -1
  27. package/dist/cabinet/run-panel.js +16 -7
  28. package/dist/cabinet/surfaces.d.ts +4 -0
  29. package/dist/cabinet/surfaces.d.ts.map +1 -0
  30. package/dist/cabinet/surfaces.js +101 -0
  31. package/dist/cabinet/tree-structure.d.ts.map +1 -1
  32. package/dist/cabinet/tree-structure.js +10 -9
  33. package/dist/ceiling/definition.js +1 -1
  34. package/dist/ceiling/floorplan-affordances.d.ts +4 -4
  35. package/dist/ceiling/panel.js +1 -1
  36. package/dist/chimney/panel.js +9 -9
  37. package/dist/cupola/panel.js +1 -1
  38. package/dist/door/panel.js +12 -12
  39. package/dist/dormer/panel-position-section.js +2 -2
  40. package/dist/dormer/panel.js +1 -1
  41. package/dist/duct-segment/parametrics.d.ts.map +1 -1
  42. package/dist/duct-segment/parametrics.js +5 -1
  43. package/dist/duct-segment/tool.d.ts +1 -0
  44. package/dist/duct-segment/tool.d.ts.map +1 -1
  45. package/dist/duct-segment/tool.js +10 -8
  46. package/dist/eyebrow-vent/panel.js +1 -1
  47. package/dist/fence/inspector-editors.js +1 -1
  48. package/dist/imported-mesh/definition.d.ts +5 -0
  49. package/dist/imported-mesh/definition.d.ts.map +1 -0
  50. package/dist/imported-mesh/definition.js +38 -0
  51. package/dist/imported-mesh/floorplan.d.ts +4 -0
  52. package/dist/imported-mesh/floorplan.d.ts.map +1 -0
  53. package/dist/imported-mesh/floorplan.js +34 -0
  54. package/dist/imported-mesh/geometry.d.ts +5 -0
  55. package/dist/imported-mesh/geometry.d.ts.map +1 -0
  56. package/dist/imported-mesh/geometry.js +34 -0
  57. package/dist/imported-mesh/index.d.ts +4 -0
  58. package/dist/imported-mesh/index.d.ts.map +1 -0
  59. package/dist/imported-mesh/index.js +3 -0
  60. package/dist/imported-mesh/schema.d.ts +2 -0
  61. package/dist/imported-mesh/schema.d.ts.map +1 -0
  62. package/dist/imported-mesh/schema.js +1 -0
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +8 -0
  66. package/dist/item/floorplan-move.d.ts +9 -1
  67. package/dist/item/floorplan-move.d.ts.map +1 -1
  68. package/dist/item/floorplan-move.js +55 -2
  69. package/dist/item/floorplan.d.ts.map +1 -1
  70. package/dist/item/floorplan.js +11 -0
  71. package/dist/item/move-tool.js +1 -1
  72. package/dist/item/panel.js +1 -1
  73. package/dist/pipe-segment/parametrics.d.ts.map +1 -1
  74. package/dist/pipe-segment/parametrics.js +5 -1
  75. package/dist/procedural-item/definition.d.ts +4 -0
  76. package/dist/procedural-item/definition.d.ts.map +1 -0
  77. package/dist/procedural-item/definition.js +254 -0
  78. package/dist/procedural-item/geometry.d.ts +31 -0
  79. package/dist/procedural-item/geometry.d.ts.map +1 -0
  80. package/dist/procedural-item/geometry.js +88 -0
  81. package/dist/procedural-item/index.d.ts +3 -0
  82. package/dist/procedural-item/index.d.ts.map +1 -0
  83. package/dist/procedural-item/index.js +2 -0
  84. package/dist/procedural-item/move-session.d.ts +15 -0
  85. package/dist/procedural-item/move-session.d.ts.map +1 -0
  86. package/dist/procedural-item/move-session.js +197 -0
  87. package/dist/procedural-item/move-tool.d.ts +5 -0
  88. package/dist/procedural-item/move-tool.d.ts.map +1 -0
  89. package/dist/procedural-item/move-tool.js +193 -0
  90. package/dist/procedural-item/renderer.d.ts +5 -0
  91. package/dist/procedural-item/renderer.d.ts.map +1 -0
  92. package/dist/procedural-item/renderer.js +66 -0
  93. package/dist/ridge-vent/panel.js +4 -4
  94. package/dist/roof/panel.d.ts.map +1 -1
  95. package/dist/roof/panel.js +7 -5
  96. package/dist/roof-segment/panel.d.ts.map +1 -1
  97. package/dist/roof-segment/panel.js +5 -5
  98. package/dist/shared/automatic-run-end-cap.d.ts +1 -0
  99. package/dist/shared/automatic-run-end-cap.d.ts.map +1 -1
  100. package/dist/shared/automatic-run-end-cap.js +10 -0
  101. package/dist/shared/distribution-run-tool.d.ts +10 -0
  102. package/dist/shared/distribution-run-tool.d.ts.map +1 -1
  103. package/dist/shared/distribution-run-tool.js +56 -11
  104. package/dist/shared/panel-fields.d.ts +15 -0
  105. package/dist/shared/panel-fields.d.ts.map +1 -0
  106. package/dist/shared/panel-fields.js +31 -0
  107. package/dist/shared/resting-surface-plan.d.ts +3 -0
  108. package/dist/shared/resting-surface-plan.d.ts.map +1 -0
  109. package/dist/shared/resting-surface-plan.js +28 -0
  110. package/dist/shelf/definition.d.ts.map +1 -1
  111. package/dist/shelf/definition.js +2 -1
  112. package/dist/shelf/floorplan-affordances.d.ts.map +1 -1
  113. package/dist/shelf/floorplan-affordances.js +5 -3
  114. package/dist/shelf/floorplan-move.d.ts.map +1 -1
  115. package/dist/shelf/floorplan-move.js +3 -2
  116. package/dist/shelf/geometry.d.ts.map +1 -1
  117. package/dist/shelf/geometry.js +2 -10
  118. package/dist/skylight/panel.js +6 -6
  119. package/dist/slab/panel.js +1 -1
  120. package/dist/solar-panel/panel.js +2 -2
  121. package/dist/spawn/panel.js +1 -1
  122. package/dist/stair/panel.d.ts.map +1 -1
  123. package/dist/stair/panel.js +9 -7
  124. package/dist/stair-segment/panel.js +4 -4
  125. package/dist/turbine-vent/panel.js +1 -1
  126. package/dist/unit/definition.d.ts +10 -0
  127. package/dist/unit/definition.d.ts.map +1 -0
  128. package/dist/unit/definition.js +38 -0
  129. package/dist/unit/index.d.ts +2 -0
  130. package/dist/unit/index.d.ts.map +1 -0
  131. package/dist/unit/index.js +1 -0
  132. package/dist/unit/parametrics.d.ts +3 -0
  133. package/dist/unit/parametrics.d.ts.map +1 -0
  134. package/dist/unit/parametrics.js +4 -0
  135. package/dist/unit/schema.d.ts +2 -0
  136. package/dist/unit/schema.d.ts.map +1 -0
  137. package/dist/unit/schema.js +1 -0
  138. package/dist/unit/unit-panel.d.ts +5 -0
  139. package/dist/unit/unit-panel.d.ts.map +1 -0
  140. package/dist/unit/unit-panel.js +61 -0
  141. package/dist/window/panel.js +6 -6
  142. package/dist/zone/definition.d.ts.map +1 -1
  143. package/dist/zone/definition.js +7 -1
  144. package/dist/zone/floorplan.d.ts +1 -3
  145. package/dist/zone/floorplan.d.ts.map +1 -1
  146. package/dist/zone/floorplan.js +43 -28
  147. package/dist/zone/quantities-panel.d.ts.map +1 -1
  148. package/dist/zone/quantities-panel.js +33 -29
  149. package/dist/zone/renderer.d.ts.map +1 -1
  150. package/dist/zone/renderer.js +20 -6
  151. package/dist/zone/unit-membership.d.ts +12 -0
  152. package/dist/zone/unit-membership.d.ts.map +1 -0
  153. package/dist/zone/unit-membership.js +22 -0
  154. package/package.json +14 -8
@@ -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: Math.round(node.width * 100) / 100 }), _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: Math.round(node.depth * 100) / 100 }), _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: Math.round(node.height * 100) / 100 }), 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: Math.round((node.hoodOverhang ?? 0) * 1000) / 1000 })), 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: Math.round((node.baseInset ?? 0.06) * 1000) / 1000 }), _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: Math.round((node.baseHeight ?? 0.04) * 1000) / 1000 }), _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: Math.round((node.cornerBevel ?? 0.012) * 1000) / 1000 })] })), 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: Math.round((node.capHeight ?? 0.07) * 1000) / 1000 }), _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: Math.round((node.capGap ?? 0) * 1000) / 1000 }), _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: Math.round((node.hoodOverhang ?? 0.04) * 1000) / 1000 })] }))] }), _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({
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: Math.round((node.position[0] ?? 0) * 100) / 100 }), _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({
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: Math.round((node.position[1] ?? 0) * 100) / 100 }), _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({
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: Math.round((node.position[2] ?? 0) * 100) / 100 }), _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 })] }) })] }));
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;AAwDzB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAuKzD,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,CAaxE;AA6uDD,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,OAAO,WAAW,CAwMhE,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,OAAO,iBAAiB,CAmK5E,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
- if ((node.children ?? []).length > 0) {
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 ((node.children ?? []).length === 0) {
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;AAOd,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,QAoJd"}
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 frameWidth = Math.max(0.32, Math.min(node.width - 0.01, 0.76));
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;AAOnC,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,CAmNd"}
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 { getRunSpanEnds, getRunSpans } from '../run-layout';
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, sinkBowls } from './sink';
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
- // A back-edge bar ledge occupies the back edge, superseding the seating
18
- // overhang; side-edge bars leave it alone.
19
- const backOverhang = node.withCountertop && node.barLedge?.edge !== 'back' ? node.countertopBackOverhang : 0;
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
- // Raised bar counter: knee wall against one run face topped by a slab at
56
- // bar height, cantilevered outward as knee space for stools. Side bars
57
- // apply only to the run's end span on that side.
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 (node.withCountertop && span.hasCountertop && node.countertopThickness > 0) {
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;