feat(paint-slots): authored item materials + unified slot painting

Phase 1 + paint unification of the paint-slots plan.

- core: scene-material data layer (materials map mirroring collections,
  undo/partialize/setScene full-graph support), SceneMaterial schema,
  scene:/library: MaterialRef helpers + parseMaterialRef, slot id helpers
  (deriveSlotId/slotLabelFromId), slots map on ItemNode, hitObject on
  PaintResolveArgs, optional PaintCapability.commit.
- viewer: resolveMaterialRef (library:/scene: -> three material, null on
  dangling).
- nodes(item): renderer keeps authored GLB materials for slot-authored
  assets and applies per-slot overrides per-instance (never mutates the
  shared cached GLB); textures-off still collapses to furnishing role;
  non-authored items unchanged. Item paint capability + registration.
- editor: item joins the unified (nodeId, slotId) paint dispatch; item
  paint target + slot reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-15 10:01:08 -04:00
co-authored by Claude Fable 5
parent cf24b62c44
commit 7afb286e47
16 changed files with 627 additions and 78 deletions
+1
View File
@@ -68,6 +68,7 @@ export {
MONO_PALETTE,
PRESET_PALETTES,
type RenderShading,
resolveMaterialRef,
resolveSurfaceColor,
WHITE_PALETTE,
} from './lib/materials'
+21
View File
@@ -4,7 +4,10 @@ import {
type MaterialPresetPayload,
type MaterialProperties,
type MaterialSchema,
parseMaterialRef,
resolveMaterial,
type SceneMaterial,
type SceneMaterialId,
type SurfaceRole,
} from '@pascal-app/core'
import * as THREE from 'three'
@@ -486,6 +489,24 @@ export function createMaterial(
return threeMaterial
}
/**
* Resolve a MaterialRef ('library:<id>' | 'scene:<id>') to a three.js material.
* Returns null for an unknown / dangling ref so callers fall back to the
* slot's default (authored material, then themed default). Never throws.
*/
export function resolveMaterialRef(
ref: string | undefined,
sceneMaterials: Record<SceneMaterialId, SceneMaterial> | undefined,
shading: RenderShading = 'rendered',
): THREE.Material | null {
const parsed = parseMaterialRef(ref)
if (!parsed) return null
if (parsed.kind === 'library') return createMaterialFromPresetRef(ref, shading)
const sceneMaterial = sceneMaterials?.[parsed.id as SceneMaterialId]
if (!sceneMaterial) return null
return createMaterial(sceneMaterial.material, shading)
}
export function createDefaultMaterial(
color = '#ffffff',
roughness = 0.9,