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:
co-authored by
Claude Fable 5
parent
cf24b62c44
commit
7afb286e47
@@ -0,0 +1,27 @@
|
||||
export const SLOT_MATERIAL_PREFIX = 'slot_'
|
||||
|
||||
/** A glTF material name marks a paintable slot when it starts with `slot_` (case-insensitive). */
|
||||
export function isSlotMaterialName(name: string): boolean {
|
||||
return name.toLowerCase().startsWith(SLOT_MATERIAL_PREFIX)
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the stable slot id from a glTF material name:
|
||||
* strip the `slot_` prefix (case-insensitive), drop Blender numeric dedupe
|
||||
* suffixes like `.001`, lowercase the remainder. Returns null when the name
|
||||
* is not a slot material. Used by BOTH the upload scan (later) and the
|
||||
* renderer so DB metadata and runtime meshes can never drift.
|
||||
*/
|
||||
export function deriveSlotId(materialName: string): string | null {
|
||||
if (!isSlotMaterialName(materialName)) return null
|
||||
let rest = materialName.slice(SLOT_MATERIAL_PREFIX.length)
|
||||
rest = rest.replace(/\.\d+$/, '')
|
||||
return rest.toLowerCase()
|
||||
}
|
||||
|
||||
/** slot id -> display label: underscores to spaces, sentence case. e.g. 'bed_frame' -> 'Bed frame'. */
|
||||
export function slotLabelFromId(slotId: string): string {
|
||||
const spaced = slotId.replace(/_/g, ' ').trim()
|
||||
if (!spaced) return spaced
|
||||
return spaced.charAt(0).toUpperCase() + spaced.slice(1)
|
||||
}
|
||||
Reference in New Issue
Block a user