Files
HartOMat/plan.md
T
Hartmut ca62319688 feat: sharp edge pipeline V02, tessellation presets, media cache-bust, GMSH plan
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>
2026-03-11 14:40:36 +01:00

279 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan: GMSH Tessellation — Eliminate Fan Triangles on Cylindrical Surfaces
## Kontext
OCC `BRepMesh_IncrementalMesh` erzeugt strukturell fehlerhafte Dreiecke bei periodischen Flächen (Vollzylinder, Ringe):
1. **Fan-Dreiecke** an u=0=2π Naht-Kanten — BRepMesh tesselliert jede Fläche unabhängig. Am Nahtübergang entstehen hochvalente Dreiecke mit Valenz 30-40, weil die Kantenmittelpunkte nicht zur Flächentessellierung konformieren.
2. **Faceting** auf großen Zylindern — angular_deflection (Winkelbedingung) und linear_deflection (Abstandsbedingung) erzeugen unterschiedlich viele Vertices an Kanten vs. Flächeninneres. OCC überbrückt die Lücke mit Fan-Dreiecken.
Diese Fehler können **nicht** mit Deflection-Parametern behoben werden — auch Fine-Settings (`0.02 rad / 0.01 mm`) zeigen exakt dieselben Artefakte. Delabella-Algorithmus liefert dieselbe Dreiecksanzahl (332.394 gegenüber DEFAULT 332.394).
**Lösung**: GMSH Frontal-Delaunay Mesher via GMSH Python API. GMSH:
- Kennt die periodischen Naht-Kanten der B-rep-Topologie
- Erzeugt konformierende Netze über Flächen-Grenzen hinweg
- Nutzt den OCC-Kernel intern (dieselbe STEP-Repräsentation)
- Ist pip-installierbar: `pip install gmsh``gmsh 4.15.1`
**Scope**: Änderungen nur in `export_step_to_gltf.py` und Dockerfile. Die Sharp-Edge-Pipeline (`_extract_sharp_edge_pairs`), das GLB-Format, Blender-Seite und die XCAF→RWGltf_CafWriter-Kette bleiben unverändert.
---
## Betroffene Dateien
| Datei | Änderung |
|---|---|
| `render-worker/Dockerfile` | `gmsh>=4.15.0` installieren |
| `render-worker/scripts/export_step_to_gltf.py` | GMSH-Tessellierung als Alternative zu BRepMesh |
| `backend/app/api/routers/admin.py` | Setting `tessellation_engine` (`occ`\|`gmsh`) |
| `backend/app/domains/pipeline/tasks/export_glb.py` | Setting lesen, `--tessellation_engine` an CLI-Aufruf übergeben |
---
## Tasks (in Reihenfolge)
### Task 1: Dockerfile — `gmsh` installieren
- **Datei**: `render-worker/Dockerfile`
- **Was**: Nach der `trimesh`-Zeile einfügen:
```dockerfile
# GMSH for Frontal-Delaunay tessellation (alternative to OCC BRepMesh)
RUN pip3 install --no-cache-dir "gmsh>=4.15.0"
```
- **Akzeptanzkriterium**: `docker compose exec render-worker python3 -c "import gmsh; print(gmsh.__version__)"` gibt `4.15.x`.
- **Abhängigkeiten**: keine
### Task 2: `export_step_to_gltf.py` — CLI-Argument `--tessellation_engine`
- **Datei**: `render-worker/scripts/export_step_to_gltf.py`
- **Was**: In `parse_args()` ein neues Argument:
```python
parser.add_argument(
"--tessellation_engine", choices=["occ", "gmsh"], default="occ",
help="Tessellation backend: 'occ' = BRepMesh (default), 'gmsh' = Frontal-Delaunay",
)
```
- **Akzeptanzkriterium**: `--help` listet `--tessellation_engine`.
- **Abhängigkeiten**: keine
### Task 3: `export_step_to_gltf.py` — Funktion `_tessellate_with_gmsh()`
- **Datei**: `render-worker/scripts/export_step_to_gltf.py`
- **Was**: Neue Funktion vor `main()`. Nimmt den XCAF-Compound und Deflection-Parameter. Strategie:
```
Für jede Leaf-Shape in der XCAF-Hierarchie:
1. Schreibe die TopoDS_Shape als temporäre .brep-Datei
2. Lade via gmsh.model.occ.importShapes(brep_path) in GMSH-OCC-Kernel
3. Setze Mesh-Parameter:
- MeshSizeMin/Max aus linear_deflection
- Algorithm = 6 (Frontal-Delaunay)
- RecombineAll = 0 (behalte Dreiecke, keine Quads)
4. gmsh.model.mesh.generate(2) — 2D Oberflächen-Mesh
5. Lese Knoten + Dreiecke via gmsh.model.mesh.getNodes() / getElementsByType(2)
6. Baue Poly_Triangulation aus Knoten + Dreiecken
7. Setze per BRep_Builder auf die TopoDS_Face (oder direkt auf die Shell)
8. Lösche tmp-Datei
```
Wichtige OCP-APIs:
```python
from OCP.BRep import BRep_Builder
from OCP.BRepTools import BRepTools
from OCP.Poly import Poly_Triangulation
from OCP.TColgp import TColgp_Array1OfPnt
from OCP.TShort import TShort_Array1OfShortInteger
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_FACE, TopAbs_SHELL
from OCP.TopoDS import TopoDS
from OCP.gp import gp_Pnt
```
Konkreter Code-Ablauf für `Poly_Triangulation`-Erstellung:
```python
# nodes: list of (x, y, z) in mm
# triangles: list of (n1, n2, n3) 1-indexed
n_nodes = len(nodes)
n_tris = len(triangles)
arr_pts = TColgp_Array1OfPnt(1, n_nodes)
for idx, (x, y, z) in enumerate(nodes, 1):
arr_pts.SetValue(idx, gp_Pnt(x, y, z))
arr_tris = Poly_Array1OfTriangle(1, n_tris)
for idx, (a, b, c) in enumerate(triangles, 1):
arr_tris.SetValue(idx, Poly_Triangle(a, b, c))
tri = Poly_Triangulation(arr_pts, arr_tris)
# BRep_Builder.UpdateFace weist Triangulation einer Face zu
builder = BRep_Builder()
builder.UpdateFace(face, tri, loc, precision)
```
**Wichtig**: GMSH gibt face-lokale Nodeindices zurück. Die XCAF-Assembly-Location (`TopLoc_Location`) muss für die Koordinatentransformation berücksichtigt werden, damit die Triangulation im richtigen Koordinatenrahmen liegt.
**Fallback**: Wenn GMSH für eine bestimmte Face fehlschlägt (z.B. degenerierte Fläche) → BRepMesh für diese Face als Fallback.
- **Akzeptanzkriterium**:
- `python3 export_step_to_gltf.py --step_path /tmp/test.stp --output_path /tmp/out.glb --tessellation_engine gmsh` läuft ohne Fehler
- Log zeigt „GMSH tessellation: N faces processed, M triangles total"
- GLB kann in Blender geöffnet werden, keine degenerierten Dreiecke
- Keine Fan-Vertices mit Valenz > 10 an Zylindernaht-Kanten
- **Abhängigkeiten**: Task 1, Task 2
### Task 4: Admin-Setting `tessellation_engine`
- **Datei**: `backend/app/api/routers/admin.py`
- **Was**: In `SETTINGS_DEFAULTS` eintragen:
```python
"tessellation_engine": "occ", # "occ" | "gmsh"
```
In `SettingsOut` ergänzen:
```python
tessellation_engine: str = "occ"
```
In der Admin-UI-Beschreibung (Docstring oder Kommentar) dokumentieren.
- **Akzeptanzkriterium**: `GET /api/admin/settings` gibt `tessellation_engine: "occ"` zurück.
- **Abhängigkeiten**: keine
### Task 5: `export_glb.py` — Setting durchreichen
- **Datei**: `backend/app/domains/pipeline/tasks/export_glb.py`
- **Was**: In `generate_gltf_geometry_task()` (und `generate_gltf_production_task()` wo der OCC-Befehl aufgebaut wird):
```python
tessellation_engine = sys_settings.get("tessellation_engine", "occ")
# ...
occ_cmd = [
...,
"--tessellation_engine", tessellation_engine,
]
```
- **Akzeptanzkriterium**: Admin stellt `tessellation_engine` auf `gmsh` → nächster GLB-Export nutzt GMSH.
- **Abhängigkeiten**: Task 2, Task 4
### Task 6: Frontend — Dropdown in Admin-Settings
- **Datei**: `frontend/src/pages/Admin.tsx`
- **Was**: Im Tessellation-Settings-Abschnitt ein Select-Element für `tessellation_engine`:
```tsx
<select
value={localSettings.tessellation_engine}
onChange={(e) => handleChange('tessellation_engine', e.target.value)}
>
<option value="occ">OCC BRepMesh (Standard)</option>
<option value="gmsh">GMSH Frontal-Delaunay (besser für Zylinder)</option>
</select>
```
Kurze Beschreibung: „GMSH erzeugt konformierende Dreiecke ohne Fan-Artefakte an Zylindernaht-Kanten. Verarbeitungszeit: +10-30% pro Modell."
- **Akzeptanzkriterium**: Dropdown sichtbar und speichert Setting korrekt.
- **Abhängigkeiten**: Task 4
---
## GMSH-Implementierungsdetails
### GMSH API Ablauf (pseudo-code)
```python
import gmsh
import tempfile
from pathlib import Path
from OCP.BRepTools import BRepTools
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_FACE
def _tessellate_with_gmsh(shape, linear_deflection: float, angular_deflection: float) -> None:
"""Replace BRepMesh with GMSH Frontal-Delaunay tessellation."""
gmsh.initialize()
gmsh.option.setNumber("General.Terminal", 0) # suppress output
gmsh.option.setNumber("Mesh.Algorithm", 6) # Frontal-Delaunay
gmsh.option.setNumber("Mesh.RecombineAll", 0) # triangles only
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", linear_deflection * 0.5)
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", linear_deflection * 5.0)
gmsh.option.setNumber("Mesh.AngleToleranceFacetOverlap", angular_deflection * 57.3)
with tempfile.NamedTemporaryFile(suffix=".brep", delete=False) as tmp:
brep_path = tmp.name
try:
BRepTools.Write_s(shape, brep_path)
gmsh.model.add("shape")
gmsh.model.occ.importShapes(brep_path)
gmsh.model.occ.synchronize()
gmsh.model.mesh.generate(2)
# Build Poly_Triangulation per face and write back via BRep_Builder
_write_gmsh_triangulation_to_occ(shape)
finally:
gmsh.finalize()
Path(brep_path).unlink(missing_ok=True)
```
### Poly_Triangulation Write-Back
GMSH nummeriert Surfaces durch. Per XCAF-Leaf muss die Entsprechung zwischen
GMSH Surface-Tags und OCC TopoDS_Face-Objekten hergestellt werden:
- GMSH gibt bei `occ.importShapes()` die Surface-Tags zurück
- Die Reihenfolge entspricht der TopExp_Explorer-Iteration über `TopAbs_FACE`
- `BRep_Builder.UpdateFace(face, tri_triangulation, location, tolerance)` setzt die Triangulation
### Parameter-Mapping: OCC → GMSH
| OCC Parameter | GMSH Entsprechung |
|---|---|
| `linear_deflection` (mm) | `CharacteristicLengthMax = linear_deflection * 3` |
| `angular_deflection` (rad) | `Mesh.MinimumCirclePoints = ceil(2π/angular_deflection)` |
---
## Migrations-Check
**Keine Migration erforderlich.** Nur Rendering-Pipeline-Änderungen. `tessellation_engine` wird in `system_settings` gespeichert (bestehendes Key-Value-Store, keine Schema-Änderung).
---
## Reihenfolge-Empfehlung
```
Task 1 (Dockerfile) → rebuild render-worker
→ Task 2+3 (export_step_to_gltf.py) → manueller Test
→ Task 4 (admin.py) → Task 5 (export_glb.py)
→ Task 6 (Frontend)
→ End-to-End Test: Admin → GMSH → Produkt-GLB regenerieren → Viewer prüfen
```
Manueller Test nach Task 3:
```bash
# In render-worker container:
python3 /render-scripts/export_step_to_gltf.py \
--step_path /tmp/81113-l_cut.stp \
--output_path /tmp/test_gmsh.glb \
--tessellation_engine gmsh \
--linear_deflection 0.03 \
--angular_deflection 0.05
# Dann Production GLB:
/opt/blender/blender --background \
--python /render-scripts/export_gltf.py -- \
--glb_path /tmp/test_gmsh.glb \
--output_path /tmp/test_gmsh_prod.glb \
--smooth_angle 30
# In Blender öffnen: kein Faceting, keine Fan-Vertices an Naht-Kanten
```
---
## Risiken / Offene Fragen
1. **GMSH Surface-Tag ↔ OCC Face-Mapping**: Die Reihenfolge der Surface-Tags bei `importShapes()` muss mit `TopExp_Explorer(FACE)` übereinstimmen. Falls nicht 1:1 → Koordinaten-basiertes Matching (Schwerpunkt der Face vs. GMSH-Mesh-Centroid) als Fallback.
2. **Performance**: GMSH Frontal-Delaunay ist typisch 2-5× langsamer als BRepMesh (BRepMesh 0.18s → GMSH ~0.4-0.9s für 25 Parts). Für 175-teilige Assemblies: 3-8 Min statt 1.5 Min. Liegt im 3-Min-Budget für Produktions-GLBs.
3. **GMSH subprocess-Isolation**: `gmsh.initialize()` / `gmsh.finalize()` sind nicht thread-safe. Da render-worker concurrency=1, ist das kein Problem.
4. **Sharp-Edge-Extraktion**: `_extract_sharp_edge_pairs()` läuft NACH der Tessellierung und nutzt die analytischen B-rep-Kurven (GCPnts_UniformAbscissa) — unabhängig vom Tessellierungsalgorithmus. Bleibt unverändert.
5. **Assembly-Locations**: Wenn GMSH eine Assembly-Shape als Ganzes importiert, werden Instance-Transformationen flachgeklopft. Dies ist erwünscht (Tessellierung in Weltkoordinaten), muss aber mit der späteren BRepBuilderAPI_Transform mm→m-Skalierung abgestimmt werden.
Plan fertig. Bitte mit `/implement` fortfahren.