shelf: v2 — cubby default, withBottom, item hosting, paintable surface
Schema v2 adds style/rows/columns/withBack/withSides/withBottom/bracketStyle and a `children: ItemNode[]` field for item hosting. Schema-level defaults preserve the v1 wall-shelf visual so existing scenes load unchanged; the placement tool spreads `shelfDefinition.defaults()` for fresh shelves (cubby 3x2 at 1m × 0.5m × 1.8m, thickness 0.05m, back/sides/bottom on). Four style geometries (wall-shelf / bookshelf / open-rack / cubby) share the dimensional schema. `shelfRowSurfaceYs` exposes one host surface per row, plus the bottom-board top when `withBottom` is on for cubby / bookshelf. Material is a single paintable surface (same shape walls / slabs / stairs use); `DEFAULT_SHELF_MATERIAL` aligned with `DEFAULT_WALL_MATERIAL` so unpainted shelves read as the canonical off-white. Preview clones each cached material before mutating `transparent / opacity` on the ghost — without the clone the mutation leaked into the cached `getShelfMaterial` instance every committed shelf was using, rendering them all see-through after the first placement preview rendered. Store hardening: `migrateNodes` patches missing `children: []` on v1 shelves, and `updateNodesAction` reparenting tolerates a missing children array on the new parent. `MaterialTarget` enum adds `'shelf'` so paint mode picks up the kind. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
56e022a093
commit
924567293a
@@ -1,12 +1,13 @@
|
||||
import type { NodeDefinition } from '@pascal-app/core'
|
||||
import { buildShelfFloorplan } from './floorplan'
|
||||
import { buildShelfGeometry } from './geometry'
|
||||
import { shelfFloorplanMoveTarget } from './floorplan-move'
|
||||
import { buildShelfGeometry, shelfRowSurfaceYs } from './geometry'
|
||||
import { shelfParametrics } from './parametrics'
|
||||
import { ShelfNode } from './schema'
|
||||
|
||||
export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
|
||||
kind: 'shelf',
|
||||
schemaVersion: 1,
|
||||
schemaVersion: 2,
|
||||
schema: ShelfNode,
|
||||
category: 'furnish',
|
||||
|
||||
@@ -15,14 +16,23 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
|
||||
parentId: null,
|
||||
visible: true,
|
||||
metadata: {},
|
||||
children: [],
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0],
|
||||
width: 1.2,
|
||||
depth: 0.3,
|
||||
thickness: 0.04,
|
||||
height: 0.9,
|
||||
width: 1,
|
||||
depth: 0.5,
|
||||
thickness: 0.05,
|
||||
height: 1.8,
|
||||
style: 'cubby',
|
||||
rows: 3,
|
||||
columns: 2,
|
||||
withBack: true,
|
||||
withSides: true,
|
||||
withBottom: true,
|
||||
bracketStyle: 'minimal',
|
||||
color: '#a07050',
|
||||
// material / materialPreset left undefined — geometry falls back to
|
||||
// `DEFAULT_SHELF_MATERIAL` (off-white), and paint mode writes the
|
||||
// chosen catalog material into these fields.
|
||||
}),
|
||||
|
||||
capabilities: {
|
||||
@@ -31,17 +41,34 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
|
||||
axes: ['y'],
|
||||
snapAngles: [0, Math.PI / 4, Math.PI / 2, (3 * Math.PI) / 4, Math.PI],
|
||||
},
|
||||
// The whole point of shelf: things can stack on it. Surface height
|
||||
// resolves from the node so multiple shelves at different heights stack
|
||||
// correctly (vs a fixed-height table).
|
||||
// Multi-row hosting: each row's top board exposes a surface so items
|
||||
// can stack on whichever row the cursor targets. `surfaces.top`
|
||||
// points at the topmost board (legacy compatibility — code that
|
||||
// assumes a single surface still works). `surfaces.custom` emits
|
||||
// one `SurfacePoint` per row centered on (0, rowY, 0) — the
|
||||
// placement coordinator's shelf strategy picks the closest by
|
||||
// cursor local-Y and snaps there.
|
||||
surfaces: {
|
||||
top: { height: (n) => (n as ShelfNode).height + (n as ShelfNode).thickness },
|
||||
top: { height: (n) => shelfRowSurfaceYs(n as ShelfNode).at(-1) ?? 0 },
|
||||
custom: (n) =>
|
||||
shelfRowSurfaceYs(n as ShelfNode).map((y) => ({
|
||||
position: [0, y, 0] as const,
|
||||
normal: [0, 1, 0] as const,
|
||||
})),
|
||||
},
|
||||
selectable: { hitVolume: 'bbox' },
|
||||
duplicable: true,
|
||||
deletable: true,
|
||||
},
|
||||
|
||||
// Items host on shelves the same way they host on slabs / other items —
|
||||
// declared here so the placement coordinator's shelf strategy can
|
||||
// confirm parent-kind compatibility before reparenting.
|
||||
relations: {
|
||||
hosts: ['item'],
|
||||
cascadeDelete: 'descendants',
|
||||
},
|
||||
|
||||
parametrics: shelfParametrics,
|
||||
|
||||
// Three-checkbox composition: shelf needs only pure builder functions.
|
||||
@@ -49,10 +76,17 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
|
||||
// mount and rebuild on dirty; the <FloorplanRegistryLayer> calls
|
||||
// buildShelfFloorplan for the 2D top-down view. No renderer.tsx, no
|
||||
// system.tsx, no inline floor-plan SVG — see
|
||||
// `wiki/architecture/node-definitions.md`. Shelf is the reference port
|
||||
// proving Phase 4's boilerplate collapse for both 3D and 2D.
|
||||
// `wiki/architecture/node-definitions.md`.
|
||||
geometry: buildShelfGeometry,
|
||||
floorplan: buildShelfFloorplan,
|
||||
// 2D move handler — Path 1 in `FloorplanRegistryMoveOverlay`. Without
|
||||
// this the overlay falls through to Path 2 which stomps the SVG
|
||||
// entry's `transform` attribute (set by the floor-plan layer to
|
||||
// position the shelf at `node.position`), producing the "ultra slow,
|
||||
// wrong place" symptom the user observed. Path 1 writes live
|
||||
// transforms during drag for real-time 3D sync and commits via a
|
||||
// single tracked `updateNode`.
|
||||
floorplanMoveTarget: shelfFloorplanMoveTarget,
|
||||
|
||||
preview: () => import('./preview'),
|
||||
tool: () => import('./tool'),
|
||||
@@ -63,14 +97,14 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
|
||||
|
||||
presentation: {
|
||||
label: 'Shelf',
|
||||
description: 'A horizontal surface for stacking other items.',
|
||||
icon: { kind: 'url', src: '/icons/column.png' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 50,
|
||||
description: 'A configurable shelving unit. Items host on each row.',
|
||||
icon: { kind: 'url', src: '/icons/shelf.png' },
|
||||
paletteSection: 'furnish',
|
||||
paletteOrder: 30,
|
||||
},
|
||||
|
||||
mcp: {
|
||||
description:
|
||||
'A parametric shelf with adjustable dimensions and bracket style. Stackable on its top surface.',
|
||||
'A parametric shelving unit. Four styles (wall-shelf / bookshelf / open-rack / cubby) with configurable rows, columns, sides, and back. Items host on each row.',
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user