Sharp Edge Pipeline V02:
- export_step_to_gltf.py: replace BRep_Tool.Polygon3D_s (returns None in XCAF) with
GCPnts_UniformAbscissa curve sampling at 0.3mm step — extracts 17,129 segment pairs
- Inject sharp_edge_pairs + sharp_threshold_deg into GLB extras (scenes[0].extras)
via binary GLB JSON-chunk patching (no extra dependency)
- export_gltf.py: read schaeffler_sharp_edge_pairs from Blender scene custom props,
apply via KD-tree to mark edges sharp=True + seam=True (OCC mm Z-up → Blender transform)
- tools/restore_sharp_marks.py: dual-pass (dihedral angle + OCC pairs), updated coordinate
transform (X, -Z, Y) * 0.001
Tessellation:
- Admin UI: Draft / Standard / Fine preset buttons with active-state highlighting
- Default angular deflection: preview 0.5→0.1 rad, production 0.2→0.05 rad
- export_glb.py: read updated defaults from system_settings
Media / Cache:
- media/service.py: get_download_url appends ?v={file_size_bytes} cache-buster
- media/router.py: Cache-Control: no-cache for all download/thumbnail endpoints
Render pipeline:
- still_render.py / turntable_render.py: shared GPU activation + camera improvements
- render_order_line.py: global render position support
- render_thumbnail.py: updated defaults
Frontend:
- InlineCadViewer: file_size_bytes-aware URL update triggers re-fetch on regeneration
- ThreeDViewer: material panel, part selection, PBR mode improvements
- Admin.tsx: tessellation preset cards, GMSH setting dropdown
- MediaBrowser, ProductDetail, OrderDetail, Orders: various UI improvements
- New: MaterialPanel, GlobalRenderPositionsPanel, StepIndicator components
- New: renderPositions.ts API client
Plans / Docs:
- plan.md: GMSH Frontal-Delaunay tessellation plan (6 tasks)
- LEARNINGS.md: OCC Polygon3D_s None issue + GCPnts fix
- .gitignore: add backend/core (core dump from root process)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.0 KiB
Review Report: CAD Viewer Material Assignment Fix + Feature Parity
Datum: 2026-03-10
Ergebnis: ✅ Freigabe
Gefundene Probleme
[InlineCadViewer.tsx + ThreeDViewer.tsx] Misleading comment on isolateMode reset effect
Schwere: Gering (Kommentar)
In both files the comment reads:
// Reset isolateMode and hideAssigned when no part is pinned
useEffect(() => {
if (!pinnedPart) setIsolateMode('none') // ← only resets isolateMode, not hideAssigned!
}, [pinnedPart])
The comment says "and hideAssigned" but the effect only calls setIsolateMode('none'). The behavior is actually correct — hideAssigned should NOT be reset when unpinning (it's a persistent view toggle). Only the comment is wrong.
Empfehlung: Change to // Reset isolateMode when no part is pinned.
Positiv aufgefallen
Bug fix: MaterialPanel invisible in ThreeDViewer — root cause correctly identified
The diagnosis was precise: the outer <div onClick={() => setPinnedPart(null)}> was receiving the
native DOM bubble from every canvas click, calling setPinnedPart(null) in the same React batch as
setPinnedPart(name) from the THREE.js event handler — final state always null.
The two-part fix is clean and idiomatic:
onClick={(e) => e.stopPropagation()}on the viewport div absorbs DOM clicksonPointerMissed={() => setPinnedPart(null)}on the R3F Canvas handles the "click empty space" case via the THREE.js raycaster (fires only when no mesh is hit) — this is exactly the right R3F API for this use case
cadUtils.ts — normalization regex extension
/_AF\d+(_ASM)?$/i is minimal and correct. It handles:
_AF0,_AF1(existing, unchanged)_AF0_ASM,_AF1_ASM(new — assembly-node suffix)- Case-insensitive flag is defensive and correct
- The loop-until-stable pattern handles nested suffixes as before
_ASMalone (without_AF\d+) is NOT stripped — correct, it's part of base names likeGE360-HF_000_P_ASM_ASM
Combined visibility useEffect — correct design
Merging hideAssigned + isolateMode into a single traversal effect avoids
ordering ambiguity between two independent effects competing on the same mesh.visible and
mat.opacity. The priority order (hideAssigned first, then isolateMode) is explicit and logical.
The pinned part (isSelected) is always protected from hiding regardless of mode. ✓
Effect separation is clean
- Color-apply effect: only touches
mat.color→ deps[modelReady, partMaterials] - Unassigned glow effect: only touches
mat.emissive→ deps[modelReady, showUnassigned, partMaterials] - Combined visibility effect: only touches
mesh.visible/mat.opacity/mat.transparent→ deps[modelReady, pinnedPart, isolateMode, hideAssigned, partMaterials]
No effect touches another effect's properties — no race conditions.
GPU hint and DPR cap
gl={{ powerPreference: 'high-performance' }} + dpr={[1, 1.5]} on both Canvas elements.
preserveDrawingBuffer: true correctly kept only in ThreeDViewer (required for screenshot capture).
"Hide assigned" toolbar button correctly conditional
{assignedCount > 0 && (...)} in InlineCadViewer and
{modelReady && Object.keys(partMaterials).length > 0 && (...)} in ThreeDViewer — button only
appears when there is something to hide.
Debug log is dev-only
if (!import.meta.env.DEV || ...) guard ensures the console output and traversal overhead
never reach production. The output logs both matched and unmatched keys, which is exactly what's
needed to diagnose remaining name mismatches after the normalization fix.
Feature parity achieved
ThreeDViewer and InlineCadViewer now have matching material-assignment features:
- ✓
showUnassignedhighlight toggle with count badge - ✓
hideAssignedtoggle (new, both viewers) - ✓
isolateMode(ghost / hide) via MaterialPanel (both viewers) - ✓
onPointerMissedcloses panel on empty-space click in ThreeDViewer
Empfehlung
Freigabe. The one Gering comment issue can be fixed inline.
Review abgeschlossen. Ergebnis: ✅