Initial commit: Fusion->Blender->GLB pipeline + contract for ThreeJS bridge

This commit is contained in:
marco
2026-06-09 17:43:13 +02:00
commit cdf4bfb3ab
9 changed files with 2829 additions and 0 deletions

119
THREEJS_USAGE.md Normal file
View File

@@ -0,0 +1,119 @@
# Three.js — come usare plotter.glb
## Caricamento e accesso a joints/motion links
```javascript
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('plotter.glb', (gltf) => {
const root = gltf.scene;
scene.add(root);
// ----------------------------------------------------------
// 1) Dati Fusion (joints, asBuiltJoints, motionLinks)
// Salvati in scene.extras.fusion come stringa JSON
// ----------------------------------------------------------
const sceneIndex = gltf.parser.json.scene ?? 0;
const sceneJson = gltf.parser.json.scenes[sceneIndex];
let fusion = null;
try {
const raw = sceneJson?.extras?.fusion;
fusion = raw ? JSON.parse(raw) : null;
} catch (e) {
console.warn('Fusion extras non parsabile', e);
}
if (fusion) {
console.log('Joints:', fusion.joints.length);
console.log('AsBuilt:', fusion.asBuiltJoints.length);
console.log('MotionLinks:', fusion.motionLinks.length);
console.log('Metadata:', fusion.metadata);
}
// ----------------------------------------------------------
// 2) Indice nome -> Object3D per trovare i nodi del joint
// Ogni Empty ha userData.fusion_fullPathName
// ----------------------------------------------------------
const byFullPath = new Map();
root.traverse((obj) => {
const fp = obj.userData?.fusion_fullPathName;
if (fp) byFullPath.set(fp, obj);
});
// ----------------------------------------------------------
// 3) Esempio: applicare un joint slider
// ----------------------------------------------------------
const j = fusion.joints.find(jt => jt.type === 'slider');
if (j) {
const child = byFullPath.get(j.childFullPath);
if (child) {
const axis = new THREE.Vector3(...j.axis).normalize();
// Sposta il child lungo l'asse di 100 mm = 0.1 m
child.position.addScaledVector(axis, 0.1);
}
}
// ----------------------------------------------------------
// 4) Esempio: applicare un joint revolute
// ----------------------------------------------------------
const r = fusion.joints.find(jt => jt.type === 'revolute');
if (r) {
const child = byFullPath.get(r.childFullPath);
if (child) {
const axis = new THREE.Vector3(...r.axis).normalize();
const angle = Math.PI / 4; // 45 gradi
// Rotazione attorno all'asse, applicata in local space
child.rotateOnAxis(axis, angle);
}
}
});
```
## Struttura di `scene.extras.fusion`
```jsonc
{
"metadata": { "units": "mm", "scaleFactor": 0.01, ... },
"joints": [
{
"name": "joint_carrello_y",
"type": "slider", // rigid | revolute | slider | cylindrical | pin_slot | planar | ball
"parent": "CARRELLO_X",
"child": "CARRELLO_Y",
"parentFullPath": "TELAIO:1+CARRELLO_X:1",
"childFullPath": "TELAIO:1+CARRELLO_X:1+CARRELLO_Y:1",
"origin": [x, y, z], // in metri
"axis": [x, y, z], // direzione unitaria
"secondaryAxis": [...], // solo pin_slot / planar
"slideLimits": { "minimumValue": 0, "maximumValue": 0.22, ... }, // metri
"rotationLimits": { ... }, // radianti
"slideValue": 0.05, // metri (posizione corrente)
"rotationValue": 0.5, // radianti
"isSuppressed": false,
"isLightBulbOn": true,
"isLocked": false
}
],
"asBuiltJoints": [ /* stessa shape di joints */ ],
"motionLinks": [
{
"name": "ML1",
"joint1": "joint_motor",
"joint2": "joint_belt",
"ratio": 2.5,
"reversed": false,
"isSuppressed": false
}
]
}
```
## Note
- **Unità:** GLB e dati joint sono in **metri**.
- **Asse Y-up:** convenzione glTF (cambiata da Blender al momento dell'export).
- **Nodi Fusion:** ogni Empty Blender ha `userData.fusion_fullPathName`, `userData.fusion_componentName`, `userData.fusion_id` (visibili in `Object3D.userData` lato Three.js).
- **Mesh:** sono parentate sotto gli Empty corrispondenti. Per muovere un sotto-assieme, sposta l'Empty (Three.js eredita le trasformazioni come ti aspetti).
- **Motion links:** la logica di "vincolo" tra joints (ratio, reversed) la devi implementare tu nel render loop: leggi `slideValue/rotationValue` di joint1, calcola joint2 = joint1 × ratio (con segno), applica a `byFullPath.get(joint2.childFullPath)`.