feat(pose_3d): modello reale G1 (URDF + STL meshes) al posto della silhouette finta
Sostituisce torso/head/hips/gambe/spalle primitive in tab Pose 3D con il modello URDF G1 (g1_body29_hand14), caricato via urdf-loader 0.12.6. Vendor: - web/vendor/g1/g1.urdf + 64 mesh STL (~52 MB, copiati da G1_API_Server/assets/g1/) - web/vendor/three/addons/loaders/URDFLoader.js (npm urdf-loader@0.12.6), import path adattato a 'three/addons/loaders/STLLoader.js' (importmap del sito) e ColladaLoader stubbato (G1 URDF è solo STL) - web/vendor/three/addons/loaders/URDFClasses.js (companion del loader) pose_3d.js: - Aggiunge loadG1Urdf(scene) async che configura loader.loadMeshCb per servire le STL dal mount /static/vendor/g1/meshes/ - Best-effort: in caso di errore di parsing/fetch il body non viene aggiunto ma la tab continua a funzionare (polsi, UCS, gizmo)
This commit is contained in:
1 parent
18dce0d951
commit
8c09d79505
68 files changed
+2651
-37
No files matched your search
+45
-37
@@ -4,6 +4,8 @@
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
import { TransformControls } from 'three/addons/controls/TransformControls.js';
|
||||
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
|
||||
import URDFLoader from 'three/addons/loaders/URDFLoader.js';
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const POLL_MS = 1000;
|
||||
@@ -81,6 +83,45 @@ const SHOULDER = {
|
||||
left: new THREE.Vector3(0, 0.20, 0.60),
|
||||
};
|
||||
|
||||
// ----- URDF loader -----
|
||||
// Carica il modello G1 reale a partire dal URDF vendored sotto
|
||||
// /static/vendor/g1/. Le mesh STL referenziate nel URDF (path relativi tipo
|
||||
// "meshes/pelvis.STL") vengono risolti contro lo stesso prefisso.
|
||||
async function loadG1Urdf(scene) {
|
||||
const loader = new URDFLoader();
|
||||
loader.loadMeshCb = function (path, manager, onComplete) {
|
||||
// path arrives like "meshes/pelvis.STL"; resolve to our static path
|
||||
const url = `/static/vendor/g1/${path}`;
|
||||
const stl = new STLLoader(manager);
|
||||
stl.load(
|
||||
url,
|
||||
(geometry) => {
|
||||
const mat = new THREE.MeshStandardMaterial({
|
||||
color: 0x9099a3, metalness: 0.4, roughness: 0.6,
|
||||
transparent: true, opacity: 0.85,
|
||||
});
|
||||
const mesh = new THREE.Mesh(geometry, mat);
|
||||
onComplete(mesh);
|
||||
},
|
||||
undefined,
|
||||
(err) => onComplete(null, err),
|
||||
);
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.load(
|
||||
'/static/vendor/g1/g1.urdf',
|
||||
(robot) => {
|
||||
// G1 URDF uses Z-up too (Unitree convention) so no rotation needed
|
||||
scene.add(robot);
|
||||
console.log('[pose_3d] G1 URDF caricato:', robot.name || 'g1');
|
||||
resolve(robot);
|
||||
},
|
||||
undefined,
|
||||
(err) => reject(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ----- scene init -----
|
||||
function initScene() {
|
||||
const canvas = $('p3Canvas');
|
||||
@@ -117,45 +158,12 @@ function initScene() {
|
||||
frontArrow.scale.set(0.18, 0.045, 1); // più largo ma sottile, "etichetta a terra"
|
||||
scene.add(frontArrow);
|
||||
|
||||
// Body silhouette: torso + head + legs + shoulders
|
||||
const bodyMat = new THREE.MeshStandardMaterial({
|
||||
color: 0x4a5560, transparent: true, opacity: 0.6,
|
||||
// Modello G1 reale via URDF (best-effort: se il loader fallisce, niente
|
||||
// body — la tab funziona comunque coi soli polsi/UCS).
|
||||
loadG1Urdf(scene).catch((e) => {
|
||||
console.warn('[pose_3d] URDF G1 non caricato:', e.message);
|
||||
});
|
||||
|
||||
// Torso: 20cm x 40cm x 50cm, centered at TORSO_POS
|
||||
const torso = new THREE.Mesh(new THREE.BoxGeometry(0.20, 0.40, 0.50), bodyMat);
|
||||
torso.position.copy(TORSO_POS);
|
||||
scene.add(torso);
|
||||
|
||||
// Head: sfera 12cm sopra il torso
|
||||
const head = new THREE.Mesh(
|
||||
new THREE.SphereGeometry(0.09, 24, 16), bodyMat,
|
||||
);
|
||||
head.position.set(TORSO_POS.x, TORSO_POS.y, TORSO_POS.z + 0.25 + 0.09);
|
||||
scene.add(head);
|
||||
|
||||
// Hips (rectangle) just below torso
|
||||
const hips = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.36, 0.10), bodyMat);
|
||||
hips.position.set(TORSO_POS.x, TORSO_POS.y, TORSO_POS.z - 0.25 - 0.05);
|
||||
scene.add(hips);
|
||||
|
||||
// Gambe: 2 cilindri verticali, da hip a Z=0
|
||||
const legGeom = new THREE.CylinderGeometry(0.06, 0.06, 0.55, 16);
|
||||
for (const ly of [-0.10, 0.10]) {
|
||||
const leg = new THREE.Mesh(legGeom, bodyMat);
|
||||
leg.rotation.x = Math.PI / 2; // asse cilindro su Z
|
||||
leg.position.set(TORSO_POS.x, TORSO_POS.y + ly, (TORSO_POS.z - 0.30 - 0.275));
|
||||
scene.add(leg);
|
||||
}
|
||||
|
||||
// Spalle: 2 sferette in corrispondenza degli SHOULDER
|
||||
const shoulderGeom = new THREE.SphereGeometry(0.04, 16, 12);
|
||||
for (const side of ['right', 'left']) {
|
||||
const sh = new THREE.Mesh(shoulderGeom, bodyMat);
|
||||
sh.position.copy(SHOULDER[side]);
|
||||
scene.add(sh);
|
||||
}
|
||||
|
||||
// Arms
|
||||
for (const side of ['right', 'left']) {
|
||||
const wrist = makeWristMarker(side === 'right' ? 0x4da3ff : 0xff5e7e);
|
||||
|
||||
Reference in new issue
Block a user