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,110 +1,295 @@
|
||||
# Plan: P12 — Codebase Hygiene Sprint (CLAUDE.md + Type Safety + Stale References)
|
||||
# Plan: Extract PBR Material Properties from Blender Asset Library for 3D Viewer
|
||||
|
||||
> **Date:** 2026-03-13 | **Branch:** refactor/v2
|
||||
|
||||
## Context
|
||||
|
||||
All 10 roadmap priorities are complete. A codebase scan reveals three categories of debt:
|
||||
The 3D viewer currently shows all materials as flat colors from a hardcoded `SCHAEFFLER_COLORS` map in `MaterialPanel.tsx` (17 entries). These hex colors don't match the actual Blender materials — a "Steel-Bare" material that looks metallic and reflective in Blender renders appears as flat gray `#8a9ca8` in the viewer. The user wants visual parity: if a material is blue plastic in Blender, it should look like blue plastic in the 3D viewer too.
|
||||
|
||||
1. **CLAUDE.md is dangerously stale**: References 11 services (4 deleted), `worker-thumbnail` (now `render-worker`), `blender-renderer`/`threejs-renderer`/`flamenco` (all removed), wrong roles (`admin` instead of `global_admin`/`tenant_admin`), deleted STL endpoints, and wrong task locations. Since CLAUDE.md is the AI instruction file, every future conversation gets wrong context.
|
||||
**Source of truth**: The Blender `.blend` asset library already contains all PBR properties (Base Color, Metallic, Roughness, Transmission, IOR) in Principled BSDF nodes for all 35 Schaeffler materials. These values are defined in `MaterialNamingSchema/generate_blend.py`.
|
||||
|
||||
2. **Frontend type safety**: 4 unnecessary `(rp as any).cancelled` casts in OrderDetail.tsx (the type already has `cancelled`), plus 4 `(item as any).cad_parsed_objects`/`cad_part_materials` casts (need 2 fields added to `OrderItem` interface).
|
||||
**Current flow**: `catalog_assets.py` extracts only material **names** → stored in `AssetLibrary.catalog` JSONB as `{"materials": ["name1", ...]}` → viewer uses hardcoded `SCHAEFFLER_COLORS` hex map.
|
||||
|
||||
3. **Stale service references**: `worker-thumbnail` in the `/scale` endpoint's `ALLOWED_SERVICES`, hardcoded `http://localhost:8080` Flamenco link in OrderDetail.tsx, and obsolete `PLAN.md` + `PLAN_REFACTOR.md` files in the repo root.
|
||||
|
||||
**Parallelization:** All 4 tracks are independent and can run in parallel.
|
||||
**Target flow**: `catalog_assets.py` extracts PBR properties per material → stored in catalog JSONB → new API endpoint serves PBR map to frontend → viewers apply `MeshStandardMaterial` with correct color + roughness + metalness.
|
||||
|
||||
## Affected Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `CLAUDE.md` | Full rewrite — update services, queues, roles, endpoints, structure |
|
||||
| `frontend/src/pages/OrderDetail.tsx` | Remove `(rp as any)` casts (4 sites), remove `(item as any)` casts (4 sites), remove Flamenco hardcoded link |
|
||||
| `frontend/src/api/orders.ts` | Add `cad_parsed_objects` and `cad_part_materials` to `OrderItem` interface |
|
||||
| `backend/app/api/routers/worker.py` | Remove `worker-thumbnail` from `ALLOWED_SERVICES` |
|
||||
| `PLAN.md` | Delete (superseded by ROADMAP.md) |
|
||||
| `PLAN_REFACTOR.md` | Delete (superseded by ROADMAP.md) |
|
||||
| `render-worker/scripts/catalog_assets.py` | Extract PBR properties from Principled BSDF nodes |
|
||||
| `backend/app/api/routers/asset_libraries.py` | Add public `GET /api/asset-libraries/pbr-map` endpoint |
|
||||
| `frontend/src/api/assetLibraries.ts` | Add `fetchMaterialPBR()` + `MaterialPBRMap` type |
|
||||
| `frontend/src/components/cad/cadUtils.ts` | Add `applyPBRToMaterial()` + `pbrColorHex()` helpers |
|
||||
| `frontend/src/components/cad/ThreeDViewer.tsx` | Fetch PBR map, apply PBR props when assigning materials |
|
||||
| `frontend/src/components/cad/InlineCadViewer.tsx` | Same PBR application |
|
||||
| `frontend/src/components/cad/MaterialPanel.tsx` | Replace hardcoded `SCHAEFFLER_COLORS` with dynamic PBR lookup |
|
||||
|
||||
## Tasks (in order)
|
||||
|
||||
### Track A — CLAUDE.md Rewrite
|
||||
### [x] Task 1: Extend catalog_assets.py to extract PBR properties
|
||||
|
||||
### [x] Task 1: Update CLAUDE.md to match current architecture — DONE
|
||||
- **File**: `CLAUDE.md`
|
||||
- **What**: Full rewrite of the project instructions file:
|
||||
- **Ziel**: Remove "Flamenco" reference
|
||||
- **Tech Stack**: Remove Flamenco, Three.js (Playwright), cadquery (STEP→STL). Add: MinIO (S3-compatible storage), OCC (cadquery/OCP for STEP parsing), GMSH (tessellation), usd-core (USD export)
|
||||
- **Services table**: 8 services (postgres, redis, minio, backend, worker, render-worker, beat, frontend). Remove blender-renderer, threejs-renderer, worker-thumbnail, flamenco-manager, flamenco-worker
|
||||
- **Logs section**: `docker compose logs -f render-worker` (not worker-thumbnail or blender-renderer). Rebuild: `docker compose up -d --build backend worker render-worker beat`
|
||||
- **Credentials**: Remove Flamenco Manager line
|
||||
- **Project structure**: Remove `blender-renderer/`, `threejs-renderer/`, `flamenco/`. Add `render-worker/scripts/`. Update `tasks/` description to mention it's a compatibility shim, active tasks in `domains/pipeline/tasks/`. Add `domains/` directory
|
||||
- **Celery queues**: `asset_pipeline` queue on `render-worker` (not `worker-thumbnail`). Remove "blender-renderer only 1 request" note — now it's "render-worker concurrency=1 because Blender is single-threaded". Add `thumbnail_rendering` if it's different from `asset_pipeline` — CHECK: docker-compose says `asset_pipeline`
|
||||
- **Roles**: Add `global_admin`, `tenant_admin`. Update table to 4 roles
|
||||
- **API endpoints**: Remove `generate-stl/{quality}`, `generate-missing-stls`. Add `generate-usd-master`, `generate-gltf-geometry`, `scene-manifest`
|
||||
- **Bekannte Eigenheiten**: Remove Flamenco GPU note
|
||||
- **Pipeline section**: Update to mention OCC/GMSH tessellation, USD export
|
||||
- **Acceptance gate**: `grep -c "blender-renderer\|threejs-renderer\|flamenco\|worker-thumbnail\|11 Services" CLAUDE.md` returns 0
|
||||
- **File**: `render-worker/scripts/catalog_assets.py`
|
||||
- **What**: After opening the .blend file, for each material with `asset_data`, find the `ShaderNodeBsdfPrincipled` node and extract:
|
||||
- `base_color`: `[R, G, B]` from `inputs["Base Color"].default_value` — convert linear→sRGB via `v^(1/2.2)`
|
||||
- `metallic`: float from `inputs["Metallic"].default_value`
|
||||
- `roughness`: float from `inputs["Roughness"].default_value`
|
||||
- `transmission`: float from `inputs["Transmission Weight"].default_value` (0.0 if absent)
|
||||
- `ior`: float from `inputs["IOR"].default_value` (1.45 default)
|
||||
|
||||
Change output format from:
|
||||
```json
|
||||
{"materials": ["Mat1", "Mat2"], "node_groups": [...]}
|
||||
```
|
||||
to:
|
||||
```json
|
||||
{
|
||||
"materials": [
|
||||
{"name": "Mat1", "base_color": [0.76, 0.77, 0.78], "metallic": 1.0, "roughness": 0.35, "transmission": 0.0, "ior": 1.45},
|
||||
...
|
||||
],
|
||||
"node_groups": [...]
|
||||
}
|
||||
```
|
||||
|
||||
Fallback for materials without Principled BSDF: `base_color` from `mat.diffuse_color[:3]` (already sRGB), metallic=0.0, roughness=0.5.
|
||||
|
||||
**Color space note**: Blender's Principled BSDF stores Base Color in **linear** space. Three.js `MeshStandardMaterial.color.setRGB()` expects **sRGB** values (it converts internally to linear for rendering). Convert in the script: `srgb = pow(linear, 1/2.2)`, rounded to 4 decimal places.
|
||||
|
||||
- **Acceptance gate**: Rebuilt render-worker, run catalog refresh → JSON output has PBR properties
|
||||
- **Dependencies**: none
|
||||
- **Risk**: None. Documentation only.
|
||||
- **Risk**: Complex node graphs (textures etc.) — handled by diffuse_color fallback
|
||||
|
||||
### Track B — Frontend Type Safety
|
||||
### [x] Task 2: Rebuild render-worker + refresh catalog
|
||||
|
||||
### [x] Task 2: Fix `as any` casts in OrderDetail.tsx and OrderItem type — DONE
|
||||
- **Files**: `frontend/src/api/orders.ts`, `frontend/src/pages/OrderDetail.tsx`
|
||||
- **File**: No code change — operational step
|
||||
- **What**:
|
||||
1. Add to `OrderItem` interface in `orders.ts`:
|
||||
```bash
|
||||
docker compose up -d --build render-worker
|
||||
# Then POST /api/asset-libraries/{id}/refresh-catalog via Admin UI or curl
|
||||
```
|
||||
The `AssetLibrary.catalog` JSONB column is schema-free — no migration needed.
|
||||
|
||||
- **Acceptance gate**: Active library's catalog has materials with `base_color`, `metallic`, `roughness`
|
||||
- **Dependencies**: Task 1
|
||||
- **Risk**: None
|
||||
|
||||
### [x] Task 3: Add public API endpoint for material PBR map
|
||||
|
||||
- **File**: `backend/app/api/routers/asset_libraries.py`
|
||||
- **What**: Add endpoint **before** the `/{lib_id}` route (to avoid path collision):
|
||||
|
||||
```python
|
||||
@router.get("/pbr-map")
|
||||
async def get_material_pbr_map(db: AsyncSession = Depends(get_db)):
|
||||
"""PBR properties for all materials in the active asset library.
|
||||
Public (no auth) — needed by all 3D viewers.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(AssetLibrary).where(AssetLibrary.is_active == True).limit(1)
|
||||
)
|
||||
lib = result.scalar_one_or_none()
|
||||
if not lib or not lib.catalog:
|
||||
return {}
|
||||
materials = lib.catalog.get("materials", [])
|
||||
pbr_map = {}
|
||||
for m in materials:
|
||||
if isinstance(m, str):
|
||||
continue # old format — skip
|
||||
pbr_map[m["name"]] = {
|
||||
"base_color": m.get("base_color", [0.5, 0.5, 0.5]),
|
||||
"metallic": m.get("metallic", 0.0),
|
||||
"roughness": m.get("roughness", 0.5),
|
||||
"transmission": m.get("transmission", 0.0),
|
||||
"ior": m.get("ior", 1.45),
|
||||
}
|
||||
return JSONResponse(content=pbr_map, headers={"Cache-Control": "public, max-age=3600"})
|
||||
```
|
||||
|
||||
- **Acceptance gate**: `curl localhost:8888/api/asset-libraries/pbr-map` returns keyed PBR map
|
||||
- **Dependencies**: Task 2
|
||||
- **Risk**: Must be placed before `/{lib_id}` route or FastAPI will try to parse "pbr-map" as a UUID
|
||||
|
||||
### [x] Task 4: Add frontend API function + types
|
||||
|
||||
- **File**: `frontend/src/api/assetLibraries.ts`
|
||||
- **What**:
|
||||
1. Add types:
|
||||
```typescript
|
||||
cad_parsed_objects: string[] | null
|
||||
cad_part_materials: Array<{ part_name: string; material_name: string; [key: string]: unknown }>
|
||||
export interface MaterialPBR {
|
||||
base_color: [number, number, number]
|
||||
metallic: number
|
||||
roughness: number
|
||||
transmission?: number
|
||||
ior?: number
|
||||
}
|
||||
export type MaterialPBRMap = Record<string, MaterialPBR>
|
||||
```
|
||||
2. Remove `(rp as any).cancelled` → just `rp.cancelled` (4 sites in OrderDetail.tsx — the type already has `cancelled: number`)
|
||||
3. Remove `(item as any).cad_parsed_objects` → `item.cad_parsed_objects` (2 sites)
|
||||
4. Remove `(item as any).cad_part_materials` → `item.cad_part_materials` (1 site)
|
||||
5. For `(item as any)[c.key]` dynamic access: replace with `(item as Record<string, unknown>)[c.key]` (narrower cast)
|
||||
- **Acceptance gate**: `grep -c "as any" frontend/src/pages/OrderDetail.tsx` decreases by at least 8. Run `docker compose exec frontend npx tsc --noEmit` — no new errors
|
||||
- **Dependencies**: none
|
||||
- **Risk**: Low. Type-only changes, no behavioral change. Must run tsc check.
|
||||
2. Add fetch function:
|
||||
```typescript
|
||||
export async function fetchMaterialPBR(): Promise<MaterialPBRMap> {
|
||||
const { data } = await api.get<MaterialPBRMap>('/asset-libraries/pbr-map')
|
||||
return data
|
||||
}
|
||||
```
|
||||
3. Update `AssetLibraryCatalog.materials` type from `string[]` to `Array<string | {name: string, base_color?: number[], metallic?: number, roughness?: number}>` for backwards compat with old catalogs
|
||||
|
||||
### Track C — Stale Backend Reference
|
||||
- **Acceptance gate**: `npx tsc --noEmit` passes
|
||||
- **Dependencies**: Task 3
|
||||
- **Risk**: None
|
||||
|
||||
### [x] Task 3: Remove `worker-thumbnail` from scale endpoint — DONE
|
||||
- **File**: `backend/app/api/routers/worker.py`
|
||||
### [x] Task 5: Add PBR helpers in cadUtils.ts
|
||||
|
||||
- **File**: `frontend/src/components/cad/cadUtils.ts`
|
||||
- **What**: Add two helpers:
|
||||
|
||||
```typescript
|
||||
import type { MaterialPBR } from '../../api/assetLibraries'
|
||||
|
||||
/** Apply PBR material properties to a Three.js MeshStandardMaterial. */
|
||||
export function applyPBRToMaterial(
|
||||
mat: THREE.MeshStandardMaterial,
|
||||
pbr: MaterialPBR,
|
||||
): void {
|
||||
mat.color.setRGB(pbr.base_color[0], pbr.base_color[1], pbr.base_color[2])
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/** 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('')
|
||||
}
|
||||
```
|
||||
|
||||
Note: `THREE` is a type-only import here — the actual THREE namespace is available at runtime in the viewer components. The helper takes the material as a parameter, so no direct THREE import needed in cadUtils.
|
||||
|
||||
- **Acceptance gate**: `npx tsc --noEmit` passes
|
||||
- **Dependencies**: Task 4
|
||||
- **Risk**: None
|
||||
|
||||
### [x] Task 6: Update ThreeDViewer to apply PBR materials
|
||||
|
||||
- **File**: `frontend/src/components/cad/ThreeDViewer.tsx`
|
||||
- **What**:
|
||||
1. Remove `"worker-thumbnail"` from `ALLOWED_SERVICES` set (line 424)
|
||||
2. Update the `ScaleRequest` docstring/comment (line 367) to list only `"render-worker" | "worker"`
|
||||
3. Update the endpoint docstring (line 414) to remove `worker-thumbnail`
|
||||
- **Acceptance gate**: `grep "worker-thumbnail" backend/app/api/routers/worker.py` returns 0 matches
|
||||
- **Dependencies**: none
|
||||
- **Risk**: None. `worker-thumbnail` service doesn't exist in docker-compose.
|
||||
1. Import `fetchMaterialPBR` and `applyPBRToMaterial` from the new modules
|
||||
2. Add query:
|
||||
```typescript
|
||||
const { data: pbrMap = {} } = useQuery({
|
||||
queryKey: ['material-pbr'],
|
||||
queryFn: fetchMaterialPBR,
|
||||
staleTime: 300_000,
|
||||
})
|
||||
```
|
||||
3. Update the material-application `useEffect` (line ~567). Current code:
|
||||
```typescript
|
||||
if (mat && 'color' in mat) mat.color.set(previewColorForEntry(entry))
|
||||
```
|
||||
Replace with:
|
||||
```typescript
|
||||
if (mat && 'color' in mat) {
|
||||
if (entry.type === 'library' && pbrMap[entry.value]) {
|
||||
applyPBRToMaterial(mat as THREE.MeshStandardMaterial, pbrMap[entry.value])
|
||||
} else {
|
||||
mat.color.set(previewColorForEntry(entry, pbrMap))
|
||||
}
|
||||
}
|
||||
```
|
||||
4. **Important**: Clone materials before modifying. GLB loader shares material instances across meshes. Before the traverse, or inside it, ensure each mesh has its own material:
|
||||
```typescript
|
||||
if (mesh.material) {
|
||||
mesh.material = Array.isArray(mesh.material)
|
||||
? mesh.material.map(m => m.clone())
|
||||
: mesh.material.clone()
|
||||
}
|
||||
```
|
||||
Only clone once — check a flag like `mesh.userData._pbrApplied` to avoid re-cloning on re-renders.
|
||||
5. Add `pbrMap` to the useEffect dependency array
|
||||
|
||||
### Track D — Delete Obsolete Files + Flamenco Link
|
||||
- **Acceptance gate**: Steel parts look metallic/reflective. Plastic parts look matte. Colors match Blender.
|
||||
- **Dependencies**: Task 5
|
||||
- **Risk**: Material cloning increases memory. Acceptable for viewer scenes.
|
||||
|
||||
### [x] Task 4: Delete PLAN.md, PLAN_REFACTOR.md, and remove Flamenco hardcoded link — DONE
|
||||
- **Files**: `PLAN.md`, `PLAN_REFACTOR.md`, `frontend/src/pages/OrderDetail.tsx`
|
||||
### [x] Task 7: Update InlineCadViewer with same PBR logic
|
||||
|
||||
- **File**: `frontend/src/components/cad/InlineCadViewer.tsx`
|
||||
- **What**: Mirror Task 6:
|
||||
1. Add PBR query
|
||||
2. Update material-application useEffect (~line 261)
|
||||
3. Clone materials before modifying
|
||||
4. Add `pbrMap` to dependency array
|
||||
|
||||
- **Acceptance gate**: Inline viewer (product cards) shows PBR materials
|
||||
- **Dependencies**: Task 5
|
||||
- **Risk**: Same as Task 6
|
||||
|
||||
### [x] Task 8: Replace SCHAEFFLER_COLORS with dynamic PBR lookup in MaterialPanel
|
||||
|
||||
- **File**: `frontend/src/components/cad/MaterialPanel.tsx`
|
||||
- **What**:
|
||||
1. Delete `PLAN.md` (superseded by ROADMAP.md — noted in the Archive section)
|
||||
2. Delete `PLAN_REFACTOR.md` (superseded by ROADMAP.md)
|
||||
3. In OrderDetail.tsx (~line 942–950): Remove the `localhost:8080` Flamenco link block. Replace with just the job ID text (since `render_backend_used === 'flamenco'` only applies to historical data, show the ID as plain text instead of a broken link)
|
||||
- **Acceptance gate**: `ls PLAN.md PLAN_REFACTOR.md 2>&1 | grep "No such file"` succeeds. `grep "localhost:8080" frontend/src/pages/OrderDetail.tsx` returns 0 matches
|
||||
- **Dependencies**: none
|
||||
- **Risk**: Low. PLAN files are archived references. Flamenco link is non-functional (service removed).
|
||||
1. Delete the hardcoded `SCHAEFFLER_COLORS` map (lines 12-30)
|
||||
2. Update `previewColorForEntry()` signature to accept optional `pbrMap`:
|
||||
```typescript
|
||||
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'
|
||||
}
|
||||
```
|
||||
3. Add `pbrMap` as an optional prop to `MaterialPanelProps`
|
||||
4. In the material preview swatch area, show metallic/roughness values when PBR data is available:
|
||||
```tsx
|
||||
{pbrEntry && (
|
||||
<span className="text-[10px] text-gray-500">
|
||||
M:{pbrEntry.metallic.toFixed(1)} R:{pbrEntry.roughness.toFixed(1)}
|
||||
</span>
|
||||
)}
|
||||
```
|
||||
5. Update all callers of `previewColorForEntry()` in ThreeDViewer and InlineCadViewer to pass `pbrMap`
|
||||
6. In the material dropdown, show a color swatch next to each material name using PBR data
|
||||
|
||||
- **Acceptance gate**: Material panel shows correct preview colors from Blender. No hardcoded `SCHAEFFLER_COLORS`.
|
||||
- **Dependencies**: Tasks 6, 7
|
||||
- **Risk**: Low — UI-only change
|
||||
|
||||
### [x] Task 9: TypeScript compilation + visual verification
|
||||
|
||||
- **What**:
|
||||
1. `docker compose exec frontend npx tsc --noEmit` — 0 errors
|
||||
2. Open http://localhost:5173/products/{id} — verify steel parts look metallic, plastics look matte
|
||||
- **Acceptance gate**: Zero type errors. Visual match with Blender appearance.
|
||||
- **Dependencies**: Tasks 1-8
|
||||
|
||||
## Migration Check
|
||||
|
||||
No. No database changes.
|
||||
**No migration required.** `AssetLibrary.catalog` is JSONB (schema-free). The new format (materials as objects instead of strings) is a data-level change only.
|
||||
|
||||
## Order Recommendation
|
||||
|
||||
**Fully parallel — all 4 tracks independent:**
|
||||
- **Agent 1**: Task 1 (CLAUDE.md rewrite) — largest, highest impact
|
||||
- **Agent 2**: Task 2 (frontend type safety)
|
||||
- **Agent 3**: Task 3 (worker.py cleanup)
|
||||
- **Agent 4**: Task 4 (file deletion + Flamenco link)
|
||||
1. Render worker script (`catalog_assets.py`) + rebuild — Tasks 1-2
|
||||
2. Backend API endpoint — Task 3
|
||||
3. Frontend types + helpers — Tasks 4-5
|
||||
4. Viewers + MaterialPanel — Tasks 6, 7, 8 (can be parallel)
|
||||
5. Final check — Task 9
|
||||
|
||||
## Risks / Open Questions
|
||||
|
||||
1. **CLAUDE.md as AI instructions**: This file is loaded into every AI conversation as project context. Getting it wrong means every future session starts with bad information. The rewrite must be verified against the actual docker-compose.yml and codebase.
|
||||
1. **Color space**: Blender stores linear colors. Three.js `color.setRGB()` expects sRGB. Converting in `catalog_assets.py` with `pow(v, 1/2.2)` ensures correctness in both the hex UI preview and the Three.js renderer.
|
||||
|
||||
2. **OrderItem `cad_part_materials` type**: Backend returns `list[dict]` — need to check what keys the dicts actually contain. The frontend uses `part_name` and `material_name` based on grep of CadPartMaterials component.
|
||||
2. **Shared materials in GLB**: Three.js GLB loader shares material instances. Must clone before modifying metalness/roughness. Check `userData._pbrApplied` flag to avoid redundant cloning.
|
||||
|
||||
3. **`require_admin_or_pm` rename**: 71 occurrences across 13 files could be renamed to `require_pm_or_above` for consistency. Deferred — it's high churn, low impact (the alias works correctly), and can be a separate micro-task later.
|
||||
3. **Backwards compatibility**: Old catalog format (`materials: string[]`) is handled — the API endpoint skips string entries. Frontend `AssetLibraryCatalog` type uses union.
|
||||
|
||||
4. **Complex node graphs**: Materials with textures instead of simple default values get `diffuse_color` fallback. Texture support is out of scope.
|
||||
|
||||
5. **`previewColorForEntry` callers**: This function is exported and used in both viewers. Adding the optional `pbrMap` parameter is backwards-compatible — existing callers without it still get gray fallback.
|
||||
|
||||
Reference in New Issue
Block a user