382a18fd02
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>
240 lines
13 KiB
Markdown
240 lines
13 KiB
Markdown
# 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 30–60°)
|
||
- `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.
|