Files
HartOMat/plan.md
T
Hartmut 382a18fd02 feat(O): UI-Vollständigkeit + v3-Workflows + OCC-Kantenanalyse
Backend:
- Phase I: notification_configs router (GET/PUT/{event}/{channel}/POST reset)
  war bereits in notifications.py — add-alias endpoint in uploads.py ergänzt
- OutputType schema: workflow_definition_id + workflow_name fields;
  PATCH unterstützt Workflow-Zuweisung; _enrich_workflow_names() batch query
- Dispatch-Integration: orders.py dispatch_renders() → dispatch_render_with_workflow()
  mit Legacy-Fallback; neues Logging
- uploads.py: POST /validations/{id}/add-alias für Material-Lücken

Pipeline:
- step_processor.py: extract_mesh_edge_data() via OCC — berechnet Dihedralwinkel
  aller Kanten, liefert suggested_smooth_angle + sharp_edge_midpoints
  Integriert in extract_cad_metadata() und process_cad_file()
- domains/rendering/tasks.py: apply_asset_library_materials_task (K3),
  export_gltf_for_order_line_task → Blender export_gltf.py (K4),
  export_blend_for_order_line_task → export_blend.py fix (K5)
- render-worker/scripts/still_render.py: _mark_sharp_and_seams() mit
  OCC midpoint KD-tree matching + UV-Seam-Markierung
- render-worker/scripts/blender_render.py: identische Funktion + mesh_attributes parsing

Frontend:
- Layout.tsx: Upload-Link in Sidebar (alle User); Asset Libraries Link (admin/PM)
- App.tsx: /asset-libraries Route
- AssetLibrary.tsx: neue Seite (Upload, Catalog-Anzeige, Refresh, Toggle, Delete)
- OutputTypeTable.tsx: Workflow-Dropdown + Legacy/Workflow Badge
- ProductDetail.tsx: Geometry-Karte (Volumen, Surface, BBox, Sharp-Winkel)
- api/outputTypes.ts + api/products.ts: neue Felder
- api/imports.ts: ImportValidation API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:20:55 +01:00

240 lines
13 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: UI-Vollständigkeit + Workflows — Phase O
**Ziel**: Alle implementierten Backend-Features im UI zugänglich machen + v3-Workflows vollständig verdrahten.
---
## Betroffene Dateien
| Datei | Änderung |
|-------|----------|
| `frontend/src/components/layout/Layout.tsx` | Upload-Link hinzufügen |
| `frontend/src/pages/Admin.tsx` | OutputType-Tabelle: Workflow-Dropdown |
| `frontend/src/pages/AssetLibrary.tsx` | NEU: Asset Library Management UI |
| `frontend/src/api/asset_libraries.ts` | NEU: API-Client |
| `frontend/src/pages/ProductDetail.tsx` | Mesh-Attribute-Anzeige |
| `frontend/src/pages/Upload.tsx` | Sanity-Check-Dialog nach Import |
| `frontend/src/api/imports.ts` | NEU: import_validation API |
| `frontend/src/App.tsx` | Route /asset-libraries |
| `backend/app/api/routers/notification_configs.py` | NEU: notification_configs CRUD |
| `backend/app/main.py` | notification_configs router registrieren |
| `backend/app/api/routers/orders.py` | dispatch_renders → dispatch_render_with_workflow |
| `backend/app/api/routers/output_types.py` | workflow_definition_id im PATCH |
| `backend/app/schemas/output_type.py` | workflow_definition_id im Schema |
| `backend/app/domains/rendering/tasks.py` | K3: apply_asset_library_materials_task |
| `backend/app/tasks/step_tasks.py` | OCC sharp edge extraction in render_step_thumbnail |
| `render-worker/scripts/still_render.py` | mark_sharp / UV seams support |
| `render-worker/scripts/blender_render.py` | mark_sharp / UV seams support |
| `backend/app/services/step_processor.py` | extract_mesh_edge_data() für sharp edges |
---
## Tasks
### Task 1: Upload-Link in Sidebar [QUICK WIN]
- **Datei**: `frontend/src/components/layout/Layout.tsx`
- **Was**: `Upload`-Icon + NavLink zu `/upload` in der Sidebar für alle eingeloggten User
- **Akzeptanzkriterium**: Upload-Link sichtbar in Sidebar
### Task 2: notification_configs Backend-Router [Phase I]
- **Datei**: `backend/app/api/routers/notification_configs.py` (NEU), `backend/app/main.py`
- **Was**: REST-Endpoints für `notification_configs` Tabelle (044 bereits migriert):
- `GET /api/notification-configs` — gibt configs für aktuellen User zurück (mit Defaults falls keine Zeilen)
- `PUT /api/notification-configs/{event_type}/{channel}` — setzt enabled=true/false
- `POST /api/notification-configs/reset` — löscht alle configs des Users → Defaults gelten wieder
- Response: `[{event_type, channel, enabled}]`
- Auth: `get_current_user` (jeder kann seine eigenen Configs verwalten)
- **Akzeptanzkriterium**: NotificationSettings.tsx zeigt Toggle-Matrix und speichert korrekt
### Task 3: OutputType → WorkflowDefinition — Schema + API
- **Datei**: `backend/app/schemas/output_type.py`, `backend/app/api/routers/output_types.py`
- **Was**:
- `OutputTypeOut` + `OutputTypePatch`: `workflow_definition_id: uuid.UUID | None` hinzufügen
- PATCH-Handler: `workflow_definition_id` setzen wenn in body
- `OutputTypeOut` soll `workflow_name: str | None` als convenience field enthalten
- **Akzeptanzkriterium**: `PATCH /api/output-types/{id}` mit `{"workflow_definition_id": "..."}` funktioniert
### Task 4: Workflow-Dispatch Integration
- **Datei**: `backend/app/api/routers/orders.py`
- **Was**: In `dispatch_renders()` (Zeile 910):
- Statt `dispatch_order_line_render.delay(str(line.id))` aufrufen:
- `from app.domains.rendering.dispatch_service import dispatch_render_with_workflow`
- `dispatch_render_with_workflow(str(line.id))` aufrufen
- Das dispatch_service lädt OutputType.workflow_definition_id und nutzt Celery Canvas falls verknüpft; fällt auf Legacy zurück wenn nicht.
- **Akzeptanzkriterium**: Dispatch nutzt neuen Pfad; Legacy-Fallback bleibt erhalten
### Task 5: Asset Library API-Client (Frontend)
- **Datei**: `frontend/src/api/asset_libraries.ts` (NEU)
- **Was**:
```typescript
export interface AssetLibrary { id, name, description, original_filename, catalog: {materials: string[], node_groups: string[]}, is_active, created_at }
export async function listAssetLibraries(): Promise<AssetLibrary[]>
export async function uploadAssetLibrary(name: string, file: File, description?: string): Promise<AssetLibrary>
export async function refreshLibraryCatalog(id: string): Promise<AssetLibrary>
export async function deleteAssetLibrary(id: string): Promise<void>
export async function updateAssetLibrary(id: string, data: Partial<AssetLibrary>): Promise<AssetLibrary>
```
- **Akzeptanzkriterium**: TypeScript kompiliert fehlerfrei
### Task 6: Asset Library Management Page (K2)
- **Datei**: `frontend/src/pages/AssetLibrary.tsx` (NEU)
- **Was**: Seite `/asset-libraries` (admin/PM):
- Liste der Asset Libraries als Karten: Name, Filename, Badge-Grid mit Materialien/Node-Groups aus `catalog`
- Upload-Button: Datei-Input für `.blend` + Name-Feld → `uploadAssetLibrary()`
- "Refresh Catalog" Button je Library → `refreshLibraryCatalog(id)` → Toast
- Toggle `is_active` → `updateAssetLibrary()`
- Delete-Button → `deleteAssetLibrary()`
- Leer-Zustand: "No asset libraries yet — upload a .blend file"
- **Akzeptanzkriterium**: Libraries hochladen, Katalog anzeigen, löschen
### Task 7: Asset Library Route + Sidebar-Link
- **Datei**: `frontend/src/App.tsx`, `frontend/src/components/layout/Layout.tsx`
- **Was**:
- App.tsx: Route `/asset-libraries` → `<AssetLibraryPage />` (AdminRoute)
- Layout.tsx: Sidebar-Link "Asset Libraries" mit `Library`-Icon (admin/PM)
- **Abhängigkeiten**: Task 6
### Task 8: OutputType Workflow-Dropdown (Frontend)
- **Datei**: `frontend/src/pages/Admin.tsx` (OutputTypeTable-Bereich)
- **Was**: In der OutputType-Tabelle eine neue Spalte "Workflow":
- Dropdown mit allen WorkflowDefinitions (aus `GET /api/workflows`) + "— None —"
- Bei Änderung: `PATCH /api/output-types/{id}` mit `{workflow_definition_id: ...}`
- Wenn kein Workflow: zeige "Legacy" Badge; wenn Workflow: zeige Workflow-Name als grünes Badge
- **Akzeptanzkriterium**: Workflow kann pro OutputType zugewiesen werden
### Task 9: Excel Sanity-Check Backend (Phase H)
- **Datei**: `backend/app/domains/imports/sanity_check.py` (NEU), `backend/app/domains/imports/router.py`
- **Was**:
- Sync-Funktion `run_sanity_check(import_validation_id: str)`:
- Lädt ImportValidation-Record
- Iteriert über `rows` (ParsedRows aus Excel)
- Für jede Zeile: prüft ob `name_cad_modell` eine CadFile zugeordnet hat (`cad_files.original_name ILIKE`)
- Prüft ob `cad_part_materials` alle Materialien in `materials`-Tabelle (via Alias-Lookup) auflösbar sind
- Erstellt `summary: {total_rows, rows_with_cad, rows_without_cad, material_gaps: [{product, missing_material}]}`
- Status → 'completed'
- Celery-Task `validate_excel_import_task(import_validation_id)` Queue `step_processing`
- Endpoint `GET /api/imports/{id}/validation` — gibt ImportValidation zurück
- Endpoint `POST /api/imports/{id}/add-alias` — schnell einen Alias hinzufügen (part_name → material)
- ImportValidation DB-Zugriif: sync SQLAlchemy (Celery-kompatibel)
- **Akzeptanzkriterium**: Nach Excel-Upload wird Import-Validierung automatisch gequeuet; `summary` liefert Material-Lücken
### Task 10: Upload.tsx — Sanity-Check-Dialog (Phase H)
- **Datei**: `frontend/src/pages/Upload.tsx`
- **Was**: Nach erfolgreichem Excel-Upload:
- `GET /api/imports/{id}/validation` pollen (alle 3s, max 30s)
- Wenn status='completed': Ampel-Dialog anzeigen:
- Grün-Badge: "X Produkte mit STEP-Datei"
- Gelb-Badge: "Y Produkte ohne STEP-Datei"
- Rote Liste: Material-Lücken (Part-Name → fehlendes Material, mit "Add Alias" Button)
- "Proceed" Button schließt Dialog
- Import API erweitern: `api/imports.ts` mit `getImportValidation(id)`, `addMaterialAlias()`
- **Akzeptanzkriterium**: Nach Upload erscheint Dialog mit Produktions-Readiness
### Task 11: Mesh-Attribute Anzeige in ProductDetail (Phase D)
- **Datei**: `frontend/src/pages/ProductDetail.tsx`
- **Was**: Im CAD-File-Bereich, nach dem Status-Badge:
- Wenn `product.cad_file.mesh_attributes` vorhanden: kleine Info-Karte
- Felder: `volume_cm3` (aus `mesh_attributes.volume_mm3 / 1000` → "12.5 cm³"),
`surface_area_cm2`, `bounding_box` ("W×H×D mm"), `sharp_angle_deg` (aus `suggested_smooth_angle`)
- Label "Geometry" mit `Ruler`-Icon
- **API-Änderung**: Product-API gibt `cad_file.mesh_attributes` zurück (prüfen ob vorhanden)
- **Akzeptanzkriterium**: Volumen, Oberfläche, BBox in ProductDetail sichtbar (wenn vorhanden)
### Task 12: OCC Edge-Analyse → mesh_attributes (Sharp/Seam)
- **Datei**: `backend/app/services/step_processor.py`
- **Was**: Neue Funktion `extract_mesh_edge_data(step_path: str) -> dict`:
- Öffnet STEP via OCC
- Iteriert über alle Faces und deren Edges
- Berechnet Winkel zwischen adjazenten Faces per Edge (Dihedralwinkel)
- Sammelt:
- `suggested_smooth_angle`: Median-Winkel aller Kanten wo Winkel > 5° (typisch 3060°)
- `has_mechanical_edges`: bool (True wenn mehrere Kanten mit Winkel > 60° → Lagerkante)
- `sharp_edge_midpoints`: Liste von `[x,y,z]` mm-Koordinaten der scharfen Kanten-Mittelpunkte (max 500 Stück, für Winkel > 45°)
- Integriert in `extract_cad_metadata()`: nach `_extract_step_objects()` aufrufen, Ergebnis in `mesh_attributes` mergen
- Fallback: bei OCC-Fehler gracefully `{}` zurückgeben
- **Akzeptanzkriterium**: `cad_files.mesh_attributes` enthält `suggested_smooth_angle` nach Verarbeitung
### Task 13: Blender-Scripts — mark_sharp + UV-Seams
- **Dateien**: `render-worker/scripts/still_render.py`, `render-worker/scripts/blender_render.py`
- **Was**: Nach STL-Import, vor dem Render:
1. Wenn `mesh_attributes.suggested_smooth_angle` vorhanden: diesen Winkel statt globalem `smooth_angle` nutzen
2. Neue Funktion `_mark_sharp_edges(obj, smooth_angle_deg, sharp_edge_midpoints=None)`:
- Setzt `obj.data.auto_smooth_angle = math.radians(smooth_angle_deg)`
- Wählt Kanten aus: `bpy.ops.mesh.edges_select_sharp(sharpness=math.radians(smooth_angle_deg))`
- Ruft `bpy.ops.mesh.mark_sharp()` auf
- Wenn `sharp_edge_midpoints` vorhanden: KD-Tree matching → zusätzliche Kanten markieren
3. Neue Funktion `_create_uv_seams_from_sharps(obj)`:
- Startet Edit-Mode
- Selektiert alle Sharp-Kanten: `[e for e in mesh.edges if e.use_edge_sharp]`
- Markiert diese als Seams: `edge.use_seam = True`
- Ruft `bpy.ops.uv.smart_project(angle_limit=math.radians(smooth_angle_deg))` auf
4. Beide Funktionen nach `_import_stl()` aufrufen (Mode A + Mode B)
- **Akzeptanzkriterium**: Gerenderte Bilder zeigen korrekte Kanten für Lager (30° Winkel scharf sichtbar)
### Task 14: K3 — apply_asset_library_materials_task
- **Datei**: `backend/app/domains/rendering/tasks.py`
- **Was**: Neuer Celery-Task:
```python
@celery_app.task(name="...apply_asset_library_materials_task", queue="thumbnail_rendering")
def apply_asset_library_materials_task(order_line_id: str, asset_library_id: str) -> dict:
# Lädt OrderLine, CadFile, AssetLibrary
# Prüft ob asset_library.blend_file_path existiert
# Ruft Blender subprocess auf mit asset_library.py:
# blender --background --python asset_library.py -- --stl_path X --asset_library_blend Y --material_map '{...}'
# Returns {'status': 'applied', 'materials_count': N}
```
Skript `render-worker/scripts/asset_library.py` existiert bereits.
- **Akzeptanzkriterium**: Task läuft ohne Fehler wenn Blender verfügbar
### Task 15: K4/K5 — export_gltf + export_blend via Blender
- **Datei**: `backend/app/domains/rendering/tasks.py`
- **Was**: `export_gltf_for_order_line_task` und `export_blend_for_order_line_task` überarbeiten:
- Statt trimesh: Blender subprocess mit `export_gltf.py` / `export_blend.py`
- Asset Library path aus LinkedAssetLibrary (via OutputType) übergeben falls vorhanden
- GLB → MinIO `production-exports/{cad_file_id}/{order_line_id}.glb`
- .blend → MinIO `production-exports/{cad_file_id}/{order_line_id}.blend`
- MediaAsset erstellen mit `gltf_production` / `blend_production` type
- **Akzeptanzkriterium**: Export-Tasks produzieren GLB/BLEND-Dateien in MinIO
---
## Abhängigkeiten
```
Sofort (parallel):
Task 1 (Upload Link)
Task 2 (Notification Config Backend)
Task 3 (OutputType Schema)
Task 5 (Asset Library API)
Task 9 (Sanity Check Backend)
Task 12 (OCC Edge Analyse)
Nach Task 3:
Task 4 (Dispatch Integration)
Task 8 (OutputType Workflow Dropdown)
Nach Task 5+6:
Task 6 (Asset Library Page) — braucht Task 5
Task 7 (Route + Sidebar) — braucht Task 6
Nach Task 9:
Task 10 (Upload Sanity Dialog)
Nach Task 11:
Task 11 (Mesh Display) — unabhängig
Nach Task 12:
Task 13 (Blender Scripts)
Nach Task 14:
Task 15 (K4/K5 Exports)
```
## Migrations-Check
Alle benötigten Migrationen existieren bereits:
- 043: import_validations ✅
- 044: notification_configs ✅
- 045: asset_libraries ✅
Keine neue Migration nötig.