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:
2026-03-13 10:37:23 +01:00
co-authored by Claude Opus 4.6
parent 577dd1ca7e
commit d843162e5f
12 changed files with 764 additions and 351 deletions
+1 -13
View File
@@ -27,14 +27,6 @@ export default function CadPreviewPage() {
refetchInterval: generating ? 3_000 : false,
})
// Load production GLB if available
const { data: productionAssets } = useQuery({
queryKey: ['media-assets', id, 'gltf_production'],
queryFn: () => getMediaAssets({ cad_file_id: id!, asset_types: ['gltf_production'] }),
enabled: !!id,
staleTime: 30_000,
})
// Load blend assets for download
const { data: blendAssets } = useQuery({
queryKey: ['media-assets', id, 'blend_production'],
@@ -66,7 +58,6 @@ export default function CadPreviewPage() {
}
const latestGltf = gltfAssets?.[0]
const latestProduction = productionAssets?.[0]
const latestBlend = blendAssets?.[0]
// While checking for assets, show a neutral loading screen (don't attempt to render ThreeDViewer)
@@ -80,7 +71,7 @@ export default function CadPreviewPage() {
}
// No GLB at all — show generate prompt
if (!latestGltf && !latestProduction) {
if (!latestGltf) {
return (
<div className="fixed inset-0 z-50 flex flex-col bg-gray-950">
<div className="flex items-center justify-between px-5 py-3 bg-gray-900 border-b border-gray-800">
@@ -129,14 +120,11 @@ export default function CadPreviewPage() {
cadFileId={id}
onClose={() => navigate(-1)}
geometryGltfUrl={latestGltf?.download_url ?? undefined}
productionGltfUrl={latestProduction?.download_url ?? undefined}
hasGeometryGlb={!!latestGltf}
hasProductionGlb={!!latestProduction}
isGeneratingGeometry={generating}
onGenerateGeometry={() => generateMutation.mutate()}
downloadUrls={{
glb: latestGltf?.download_url ?? undefined,
production: latestProduction?.download_url ?? undefined,
blend: latestBlend?.download_url ?? undefined,
}}
/>