feat(PBR): extract Blender PBR properties and apply in 3D viewer
Extract Base Color, Metallic, Roughness, Transmission, IOR from Blender asset library materials via catalog_assets.py. Store in catalog JSON and serve via /api/asset-libraries/pbr-map endpoint. Frontend viewers apply PBR properties to Three.js MeshStandardMaterial using hex color strings (avoiding Three.js ColorManagement sRGB/linear issues). Key fixes: - RLS bypass for material alias lookup in pbr-map endpoint - pbrMap empty guard prevents premature grey fallback in viewers - Cache-Control: no-cache on pbr-map requests to avoid stale data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { PartMaterialEntry, PartMaterialMap } from '../../api/cad'
|
||||
import type { MaterialPBR, MaterialPBRMap } from '../../api/assetLibraries'
|
||||
|
||||
/**
|
||||
* Normalize a GLB mesh name by stripping suffixes added by the export pipeline:
|
||||
@@ -61,6 +62,44 @@ export function resolvePartMaterial(
|
||||
return bestKey ? partMaterials[bestKey] : undefined
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// remapToPartKeys
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Remap a PartMaterialMap keyed by normalized OCC names to partKey slugs.
|
||||
*
|
||||
* When partKeyMap is available (from GLB extras), Excel-imported material keys
|
||||
* like "GE360-HF_000_P_ASM_1" are converted to partKey slugs like
|
||||
* "ge360_hf_000_p_asm_1" so they match what the viewer resolves each mesh to.
|
||||
*
|
||||
* Keys not found in partKeyMap are preserved as-is (backwards compat for old GLBs).
|
||||
*/
|
||||
export function remapToPartKeys(
|
||||
materials: PartMaterialMap,
|
||||
partKeyMap: Record<string, string>,
|
||||
): PartMaterialMap {
|
||||
if (!partKeyMap || Object.keys(partKeyMap).length === 0) return materials
|
||||
const mapKeys = Object.keys(partKeyMap)
|
||||
const result: PartMaterialMap = {}
|
||||
for (const [key, entry] of Object.entries(materials)) {
|
||||
// 1. Exact match
|
||||
if (partKeyMap[key]) { result[partKeyMap[key]] = entry; continue }
|
||||
// 2. Prefix match: cad_part_materials may have extra _1 instance suffixes
|
||||
// that partKeyMap doesn't (e.g. "PART_04_1" vs partKeyMap "PART_04")
|
||||
let matched = false
|
||||
for (const mk of mapKeys) {
|
||||
if (key.startsWith(mk + '_') || key === mk) {
|
||||
result[partKeyMap[mk]] = entry
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!matched) result[key] = entry // preserve unmapped
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// convertCadPartMaterials
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -85,3 +124,51 @@ export function convertCadPartMaterials(
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PBR material helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Apply PBR material properties from the Blender asset library to a
|
||||
* Three.js MeshStandardMaterial.
|
||||
*
|
||||
* The `mat` parameter is typed as `any` to avoid importing THREE in this
|
||||
* utility module — callers pass `THREE.MeshStandardMaterial` instances.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function applyPBRToMaterial(mat: any, pbr: MaterialPBR): void {
|
||||
if (!mat || !('color' in mat)) return
|
||||
// Use hex string via color.set() — reliable across all Three.js versions and
|
||||
// avoids colorSpace/gamma issues with setRGB() + ColorManagement.
|
||||
mat.color.set(pbrColorHex(pbr))
|
||||
mat.metalness = pbr.metallic
|
||||
mat.roughness = pbr.roughness
|
||||
if (pbr.transmission && pbr.transmission > 0.1) {
|
||||
mat.transparent = true
|
||||
mat.opacity = 1 - pbr.transmission * 0.7
|
||||
}
|
||||
mat.needsUpdate = true
|
||||
}
|
||||
|
||||
/** Convert PBR base_color to hex string for UI swatches. */
|
||||
export function pbrColorHex(pbr: MaterialPBR): string {
|
||||
const [r, g, b] = pbr.base_color
|
||||
return '#' + [r, g, b].map(v => Math.round(v * 255).toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a preview hex color for a material entry, using PBR data when available.
|
||||
* Replaces the old hardcoded SCHAEFFLER_COLORS lookup.
|
||||
*/
|
||||
export function previewColorForEntry(
|
||||
entry: PartMaterialEntry,
|
||||
pbrMap?: MaterialPBRMap,
|
||||
): string {
|
||||
if (entry.type === 'hex') return entry.value
|
||||
if (pbrMap) {
|
||||
const pbr = pbrMap[entry.value]
|
||||
if (pbr) return pbrColorHex(pbr)
|
||||
}
|
||||
return '#888888'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user