# Note operative — produttore GLB (Fusion → Blender) Questo file è la sponda **produttore** del [FUSION_GLB_CONTRACT.md](FUSION_GLB_CONTRACT.md). Tutte le risposte ai problemi `[OPEN]` del contratto vivono qui finché non vengono spostate nello stato `[RESOLVED]` del contratto stesso. --- ## Risposte ai problemi aperti del contratto §8 ### 1) Motion link con `joint1: null` **Stato:** è un limite dell'API Fusion. Quando l'utente crea il motion link tramite la finestra "Collegamento movimento", Fusion lo memorizza con un riferimento interno (entity-token) ma il proxy `motionLink.entityOne` può ritornare `None` se il joint è stato creato/rinominato dopo il link. L'add-in **non** può inventarsi il nome. **Workaround consigliato a chi consuma:** - I 5 driver (`A`, `X`, `Y`, `PEN`, `Z`) NON sono mai `null` su `joint2`: i link con `joint2 == driver` sono usabili anche senza `joint1` (basta non propagare nulla). - Per i link con `joint2: null` AND `joint1: null` (es. `"Collegamento movimento 32"`, `"Collegamento movimento 4"`): sono rumore, ignorabili. - Per i link con `joint1: null` ma `joint2` valorizzato (es. `"Collegamento movimento 14"` → `Motore asse X`, `"Collegamento movimento 28"` → `Rivoluzione 26`, `"Collegamento movimento 38"` → `Asse Penna `): - **`Motore asse X`** è il driver, non ha bisogno di slave esterni — la traslazione del carrello X è già descritta dal rigid group `Gruppo rigido 6` (`end stop X PIastrina:1 | binario SX:1 | Cinghia T5:1`). - **`Asse Penna `** idem: lo slider muove direttamente `guida lineare:1`, gli altri pezzi seguono via rigid groups. → **TL;DR per il viewer:** ignora tranquillamente i link con `joint1: null`. Tutta la propagazione necessaria passa dai rigid groups + i 5 driver diretti. ### 2) `Motore A` esiste in `asBuiltJoints`? **Confermato.** Verificato sul file corrente: presente con nome esatto `"Motore A"`, tipo `revolute`, `child = "6627T331_Stepper Motor (1) (1):1"`, `axis ≈ [0, 0, 1]`. L'eventuale mismatch lato viewer dipende dal `child` name: nel JSON è esattamente `"6627T331_Stepper Motor (1) (1):1"` (due " (1)" di seguito), che nasce dal fatto che in Fusion il componente è stato copiato/incollato due volte. Se il viewer non lo trova, il bug è nel matching, non nell'export. ### 3) Convenzione multi-macchina Da concordare quando servirà. Proposta minimale: ``` exports/ plotter/ plotter.glb plotter.joints.json / .glb .joints.json ``` Lato viewer un file `machines.json` indicizza nome → coppia (glb, json, DRIVERS map). --- ## Stato attuale dell'export (rispetto alla checklist §6 del contratto) | Voce checklist | Stato | Note | |---|---|---| | GLB in metri | ✅ | `build_glb_from_fusion_export.py` applica `scaleFactor=0.01` al root, mesh OBJ importate in cm sono rimpicciolite di 100× → metri | | Gerarchia Empty 1:1 | ✅ | Pass 1 crea un Empty per ogni `node` del `hierarchy.json` | | `fusion_name` e `fusion_path` per ogni Empty | ✅ (dal 2026-06-09) | Aggiunti come custom properties; coincidono byte-per-byte con `Joint.child` / `Joint.childFullPath` | | Mesh figlie degli Empty, non joinate | ✅ | OBJ importati e re-parentati all'Empty corrispondente | | Stato salvato = posa "tutti i driver a 0" | ⚠️ | Posa "as built" di Fusion al momento dell'export. Se l'utente esporta con `slideValue ≠ 0` o `rotationValue ≠ 0` la posa neutra slitta. **Convenzione:** in Fusion riportare i 5 driver a zero prima di lanciare lo script. | | Manifest embedded `scene["fusion"]` | ✅ | Il GLB contiene già il JSON completo in `scene.extras.fusion` | --- ## Comandi operativi Rigenera GLB dopo modifica codice: ```powershell .\build_glb.bat "C:\Users\croce\OneDrive\Desktop\export" ``` Riallinea script Fusion (da fare dopo ogni `edit` di `ExportKinematicGraph.py`): ```powershell python -c "import py_compile; py_compile.compile(r'C:\Users\croce\OneDrive\Desktop\export grafo fusion\ExportKinematicGraph.py', doraise=True); print('OK')"; if($LASTEXITCODE -eq 0){ Copy-Item -Force 'C:\Users\croce\OneDrive\Desktop\export grafo fusion\ExportKinematicGraph.py' (Join-Path $env:APPDATA 'Autodesk\Autodesk Fusion 360\API\Scripts\ExportKinematicGraph'); Write-Host "Sync OK" } ``` Sanity-check sui driver nel JSON: ```powershell $j = Get-Content 'C:\Users\croce\OneDrive\Desktop\export\joints.json' -Raw | ConvertFrom-Json $names = @('Motore asse X','Motore asse Y','Asse Penna ','asse Z pneumatico M5?') foreach($n in $names){ $j.joints | Where-Object name -eq $n | Select-Object name,type,child,axis,@{n='limits';e={ if($_.slideLimits){'slide '+$_.slideLimits.minimumValue+'..'+$_.slideLimits.maximumValue}elseif($_.rotationLimits){'rot '+$_.rotationLimits.minimumValue+'..'+$_.rotationLimits.maximumValue} }} | Format-List } $j.asBuiltJoints | Where-Object name -eq 'Motore A' | Select-Object name,type,child,axis | Format-List ``` --- ## Findings dal viewer (2026-06-09) Lato consumer (`automation_kriz`) ho aggiunto una pagina di ispezione live a `/lab/graph` che carica `hierarchy.json` + `joints.json` direttamente dal server e ne mostra: l'albero completo con conteggi joint/rigid per nodo, i 5 driver con stato OK/rotto, la lista di tutti i 329 joint + 28 asBuilt con filtro per tipo, i 9 motion link (validi vs rotti) e i 20 rigid group. Serve per fare debug del JSON senza dover aprire Fusion. Dato che è una pagina autenticata e legge `/api/fusion/{joints,hierarchy}/` lato Django, basta sostituire i file in `/home/marco/automation_kriz/joints.json` + `hierarchy.json` e ricaricare. Ispezionando l'export attuale (`329 joints, 28 asBuiltJoints, 9 motionLinks, 20 rigidGroups`) ho trovato tre problemi che impediscono al viewer di propagare il movimento sull'asse X (e probabilmente anche Y e PEN dipendono dagli stessi pattern). Servono fix lato `ExportKinematicGraph.py`. ### BUG 1 — ~~`rigidGroups[].occurrences` è SEMPRE vuoto~~ FALSO ALLARME **Ritiro questo punto.** Era un errore nel mio script Python di diagnostica a terminale (avevo scritto `rg.get('occurrences', [])` invece di `rg.get('occurrenceNames', [])`), poi ho copiato la conclusione qui sopra senza ricontrollarla. Verifica fatta: ``` Keys di un rigid group: ['name', 'occurrenceNames', 'occurrencePaths', 'isSuppressed', 'isLightBulbOn', 'includeChildren'] Gruppo rigido 6 contenuto: ['end stop X PIastrina:1', 'binario SX:1', 'Cinghia T5:1'] ``` Il JSON è conforme al contratto §2.3 (`occurrenceNames` + `occurrencePaths`). Anche `FusionRig.js` e `KinGraphPage.jsx` lato viewer leggono già correttamente `g.occurrenceNames`. Quindi BUG 1 non esiste, scusa il rumore. ### BUG 2 — `Collegamento movimento 14` (driver X) ha `joint1: null` ```json {"name": "Collegamento movimento 14", "joint1": null, "joint2": "Motore asse X", "ratio": null} ``` `Motore asse X` è un giunto `revolute` (il motore stepper gira), `joint2 = Motore asse X` significa "questo è il driver". Senza `joint1` (lo slider del carrello che dovrebbe seguire) il viewer non può propagare la rotazione del motore in traslazione del carrello. Stessa cosa per `Collegamento movimento 38` (`joint2 = "Asse Penna "`) e `Collegamento movimento 28` (`joint2 = "Rivoluzione 26"`). Nel BRIDGE_NOTES precedente è scritto "ignorabili, propagazione passa dai rigid groups" → ma siccome i rigid groups sono vuoti (BUG 1), questi motion link diventano cruciali. Bisogna almeno provare a recuperare `joint1` quando è `None`: il pattern API è `motionLink.entityOne` (proxy) vs `motionLink.entityOneNative` o usare `motionLink.bRepEdge.assemblyContext`. Se proprio l'API ritorna `None`, scrivere nel JSON il `componentToken` o l'`entityToken` come stringa hex così almeno il consumer può fare matching manuale. ### BUG 3 — `Motore asse X` ha parent/child invertiti (convenzione) ```json { "name": "Motore asse X", "type": "revolute", "parent": "Puleggia_HTD5M_z15:1", "child": "6627T281_Stepper Motor:1", "axis": [-1, 0, 0] } ``` Fisicamente è il motore stepper che è fissato al telaio e fa girare la puleggia. Nel JSON è il contrario. Il viewer applica sempre la rotazione al `child` mantenendo fermo il `parent`, quindi vede il motore ruotare nel vuoto e la puleggia (che dovrebbe seguire) sta ferma. **Risposta alla domanda "il viewer applica al child?"**: sì, lo applica al `child` (più tutti i compagni del/dei rigid group che contengono il child). Codice in `FusionRig.js#_applyJoint` + `_worldDelta`: ```js // d.origin viene dal joint, d.axis dal joint, d.child dal joint const T1 = new THREE.Matrix4().makeTranslation(d.origin.x, d.origin.y, d.origin.z); const R = new THREE.Matrix4().makeRotationAxis(d.axis, value); const T2 = new THREE.Matrix4().makeTranslation(-d.origin.x, -d.origin.y, -d.origin.z); // la matrice ottenuta viene poi applicata a `child` e a tutti gli oggetti in `rigidComponent` ``` Quindi sì, serve premere `Switch` in Fusion. Stessa verifica da fare per `Motore asse Y`, `Asse Penna `, `asse Z pneumatico M5?` e `Motore A` (asBuilt): controllare per ognuno che `child` = il pezzo che si muove rispetto al telaio del livello precedente, `parent` = il pezzo solidale al livello precedente. In ogni caso il fix è **lato Fusion**, non lato codice viewer. ### Patch temporanea lato viewer (in valutazione, non ancora applicata) ~~Override `viewerOverrides.json` / `viewerRigidGroups.json` / `viewerMotionLinks.json`.~~ Saltato. Visto che BUG 1 non esiste e BUG 2 non è bloccante per i driver puri (la propagazione passa dai rigid groups, che ora il viewer legge correttamente), aspetto solo il fix Fusion del BUG 3 e proviamo subito. ### Per gli altri driver Stessa analisi va fatta per `Motore asse Y`, `Asse Penna ` e `asse Z pneumatico M5?`. Possibili problemi simmetrici: rigid groups vuoti, motion link `joint1=null`, parent/child invertiti. Quando rilancerai un export pulito segnalalo qui e rifaccio il giro su `/lab/graph`.