Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export type {
ScalableConfig,
SceneApi,
SelectableConfig,
SlotDeclaration,
SnapPointKind,
SnappableConfig,
SnapServicesLike,
Expand Down
33 changes: 33 additions & 0 deletions packages/core/src/registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ComponentType } from 'react'
import type { BufferGeometry, Object3D } from 'three'
import type { ZodObject, z } from 'zod'
import type { MaterialSchema } from '../schema/material'
import type { SceneMaterial, SceneMaterialId } from '../schema/scene-material'
import type { AnyNode, AnyNodeId } from '../schema/types'
import type { HandleList } from './handles'
import type { CloneNodesIntoOptions, Subtree } from './subtree'
Expand Down Expand Up @@ -43,6 +44,15 @@ export type GeometryContext = {
* has cheap access to siblings through `ctx.siblings`).
*/
levelData?: unknown
/**
* The scene's shared material library (`useScene.materials`), passed so a
* pure geometry builder can resolve `scene:<id>` slot refs without importing
* `useScene`. Populated by `<GeometrySystem>` for every `def.geometry` call;
* undefined for `def.floorplan`. `library:<id>` refs resolve against the
* static catalog and need no store, so builders only consult this for
* `scene:` refs.
*/
materials?: Record<SceneMaterialId, SceneMaterial>
/**
* Optional view state — only populated for `def.floorplan` builders. The
* 2D floor-plan layer surfaces selection / hover here so kinds can vary
Expand Down Expand Up @@ -1023,6 +1033,16 @@ export type Capabilities = {
*/
ceilingCut?: CeilingCutCapability
paint?: PaintCapability
/**
* Declares the kind's paintable slots — the `{ slotId, label, default }`
* contract shared by items (scanned from the GLB) and procedural kinds
* (declared here). Procedural generators tag their emitted geometry with
* `userData.slotId` and resolve each slot's material from
* `node.slots[slotId]` → this declaration's `default` → role colour. The
* declaration is a function of the node because a kind's slot set can depend
* on its parameters (a shelf has a `back` slot only when it has a back).
*/
slots?: (node: AnyNode) => SlotDeclaration[]
/**
* Kind is placed by clicking on a wall (door, window). When set, the
* floor-plan layer lets wall background clicks pass through during
Expand Down Expand Up @@ -1114,6 +1134,19 @@ export type Capabilities = {
* the `selectedMaterialTarget` round-trip, the paint-mode toolbar.
* Kinds with no paint behaviour omit `paint`.
*/
/**
* One paintable slot a kind exposes. `slotId` is the stable key written into
* `node.slots`; `label` is the human name (sentence case). `default` is the
* slot's fallback appearance when no override is set — either a `MaterialRef`
* (`library:<id>` / `scene:<id>`) or a `#rrggbb` colour. Mirrors the shape
* items derive from their GLB material names.
*/
export type SlotDeclaration = {
slotId: string
label: string
default?: string
}

export type PaintCapability = {
/**
* Resolve which logical surface the user clicked. Returns `null`
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/schema/nodes/shelf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ export const ShelfNode = BaseNode.extend({
// unchanged once `'shelf'` is added to `MaterialTarget`.
material: MaterialSchema.optional(),
materialPreset: z.string().optional(),

// Per-slot material overrides, mirroring `ItemNode.slots`. Key = slot id
// (`shelves` / `frame` / `back`, see `shelfSlots`), value = a `MaterialRef`
// string (`library:<id>` or `scene:<id>`). Absent slot = fall back to the
// legacy whole-shelf `material` / `materialPreset`, then the registry slot
// default. A dangling ref renders the default (never blocks).
slots: z.record(z.string(), z.string()).optional(),
})

export type ShelfNode = z.infer<typeof ShelfNode>
9 changes: 7 additions & 2 deletions packages/nodes/src/shelf/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { buildShelfFloorplan } from './floorplan'
import { shelfResizeAffordance, shelfRotateAffordance } from './floorplan-affordances'
import { shelfFloorplanMoveTarget } from './floorplan-move'
import { buildShelfGeometry, shelfRowSurfaceYs } from './geometry'
import { shelfPaint } from './paint'
import { shelfParametrics } from './parametrics'
import { ShelfNode } from './schema'
import { shelfSlots } from './slots'

const SIDE_HANDLE_OFFSET = 0.18
const HEIGHT_HANDLE_OFFSET = 0.22
Expand Down Expand Up @@ -155,8 +157,8 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
withBottom: true,
bracketStyle: 'minimal',
// material / materialPreset left undefined — geometry falls back to
// `DEFAULT_SHELF_MATERIAL` (off-white), and paint mode writes the
// chosen catalog material into these fields.
// the per-slot off-white default, and slot paint mode writes chosen
// catalog materials into `slots`.
}),

capabilities: {
Expand All @@ -183,6 +185,8 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
selectable: { hitVolume: 'bbox' },
duplicable: true,
deletable: true,
paint: shelfPaint,
slots: (n) => shelfSlots(n as ShelfNode),
// Slab elevation lift via the generic `<FloorElevationSystem>` — a
// shelf sitting over a raised slab visually rests on top of it.
floorPlaced: {
Expand Down Expand Up @@ -233,6 +237,7 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
s.bracketStyle,
s.material,
s.materialPreset,
JSON.stringify(s.slots ?? null),
])
},
floorplan: buildShelfFloorplan,
Expand Down
2 changes: 1 addition & 1 deletion packages/nodes/src/shelf/floorplan-move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const shelfFloorplanMoveTarget: FloorplanMoveTarget<ShelfNode> = ({ node,
},
canCommit() {
const live = useScene.getState().nodes[shelfId] as ShelfNode | undefined
if (!live || live.type !== 'shelf') return false
if (live?.type !== 'shelf') return false
return !(lastPosition[0] === originalPosition[0] && lastPosition[2] === originalPosition[2])
},
}
Expand Down
Loading
Loading