120 lines
4.4 KiB
Markdown
120 lines
4.4 KiB
Markdown
# 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)`.
|