feat(paint-slots): migrate stair onto the unified slot model

Stair exposes three paintable slots — treads / body / railing (railing only
when railingMode != none) — on node.slots. The 2-material-index body mesh
maps materialIndex 0->treads, 1->body via userData.slotIds; railing meshes
tag userData.slotId='railing'. Per-slot resolution layers over the viewer's
base body/railing materials only in textures-on mode: node.slots ref ->
legacy per-part field (preserved) -> declared default (treads wood-woodplank48,
body wood-woodfine2, railing metal-steel). A custom preview swaps the targeted
body material-array index and whole-mesh railing materials. Monochrome
unchanged. Stops using DEFAULT_STAIR_MATERIAL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-17 15:33:42 -04:00
co-authored by Claude Opus 4.8
parent a8f32dd47a
commit 16c117142f
4 changed files with 233 additions and 28 deletions
+4
View File
@@ -407,7 +407,9 @@ import {
} from './floorplan-affordances'
import { stairFloorplanMoveTarget } from './floorplan-move'
import { stairParametrics } from './parametrics'
import { stairPaint } from './paint'
import { StairNode } from './schema'
import { stairSlots } from './slots'
/**
* Stair — Stage A. Composite node like roof: owns overall framing,
@@ -444,6 +446,8 @@ export const stairDefinition: NodeDefinition<typeof StairNode> = {
footprints: (node, ctx) =>
ctx ? getStairFloorPlacedFootprints(node as StairNodeType, ctx.nodes) : [],
},
slots: (node) => stairSlots(node as StairNodeType),
paint: stairPaint,
},
// Bespoke move shared with roof / roof-segment / stair-segment via