feat(paint-slots): paintable slots on the procedural shelf (phase 5)
Proves the unified (nodeId, slotId) slot contract on a procedural generator, beyond items and walls. A shelf now exposes three paintable slots — shelves / frame / back — painted through the same PaintCapability dispatch and the same node.slots: Record<slotId, MaterialRef> shape items use. Foundation (shared, reusable by future procedural kinds): - core: SlotDeclaration type + capabilities.slots(node) registry declaration; GeometryContext gains `materials` so a pure builder can resolve scene:<id> slot refs without importing useScene. - viewer GeometrySystem: threads the scene material library into every builder ctx, and re-dirties (bypassing the geometryKey skip) any geometry node that references a scene material when that material changes — so editing a custom colour propagates to every shelf using it, matching items. Shelf: - schema: slots: Record<string, MaterialRef> (mirrors ItemNode). - geometry: per-slot material resolution (slot override -> legacy whole-shelf -> declared default colour); every mesh stamped with userData.slotId; DEFAULT_SHELF_MATERIAL retired (declared default gives identical off-white). - paint.ts: PaintCapability (resolveRole from userData.slotId, scene-material commit for one-off colours, preview restricted to __fromGeometry meshes so hosted items aren't ghosted, getEffectiveMaterial incl. legacy fallback). - definition: paint + slots capabilities; slots folded into geometryKey. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
89a7232a67
commit
101341d98d
@@ -103,6 +103,7 @@ export type {
|
||||
ScalableConfig,
|
||||
SceneApi,
|
||||
SelectableConfig,
|
||||
SlotDeclaration,
|
||||
SnapPointKind,
|
||||
SnappableConfig,
|
||||
SnapServicesLike,
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user