# threejs > Three.js scene-graph parsing and export workflows: mesh baking, InstancedMesh expansion, part partitioning, per-link OBJ export, and URDF articulation. - Author: Lichen Zhu - Repository: LichenZhu/skillsbench - Version: 20260128002316 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/LichenZhu/skillsbench - Web: https://mule.run/skillshub/@@LichenZhu/skillsbench~threejs:20260128002316 --- --- name: threejs description: Three.js scene-graph parsing and export workflows: mesh baking, InstancedMesh expansion, part partitioning, per-link OBJ export, and URDF articulation. --- # Three.js Scene Graph + Export ## Quick start 1) Load the module, call `createScene()`, then `updateMatrixWorld(true)`. 2) Treat **named `THREE.Group` nodes as parts/links**. 3) Assign each mesh to its **nearest named ancestor**. 4) Bake world transforms into geometry before export. 5) Export per-part OBJ (and optionally URDF) using deterministic ordering. ## Scene graph essentials ``` Object3D (root) ├── Group (part) │ ├── Mesh │ └── Group (child part) │ └── Mesh └── Group (another part) └── Mesh ``` - `THREE.Scene` / `THREE.Object3D`: containers - `THREE.Group`: logical part (no geometry) - `THREE.Mesh`: geometry + material ## Part partitioning rules - Parts are **named groups** at any depth. - Each mesh belongs to the **nearest named ancestor**. - Nested named groups are separate parts; **do not merge** their meshes into parents. - Skip empty parts; sort part names for determinism. ```javascript function buildPartMap(root) { const parts = new Map(); root.traverse((obj) => { if (obj.isGroup && obj.name) parts.set(obj.name, { group: obj, meshes: [] }); }); root.traverse((obj) => { if (!obj.isMesh) return; let parent = obj.parent; while (parent && !(parent.isGroup && parent.name)) parent = parent.parent; if (parent && parts.has(parent.name)) parts.get(parent.name).meshes.push(obj); }); return Array.from(parts.values()).filter((p) => p.meshes.length > 0); } ``` ## Mesh baking (world transforms) Always bake transforms before export: ```javascript root.updateMatrixWorld(true); let geom = mesh.geometry.clone(); geom.applyMatrix4(mesh.matrixWorld); if (geom.index) geom = geom.toNonIndexed(); if (!geom.attributes.normal) geom.computeVertexNormals(); ``` ### InstancedMesh expansion ```javascript const tempMatrix = new THREE.Matrix4(); const instanceMatrix = new THREE.Matrix4(); obj.getMatrixAt(i, instanceMatrix); tempMatrix.copy(obj.matrixWorld).multiply(instanceMatrix); ``` ### Axis conversion (Y-up to Z-up) ```javascript const axisMatrix = new THREE.Matrix4().makeRotationX(-Math.PI / 2); geom.applyMatrix4(axisMatrix); ``` ## Per-part OBJ export - Collect meshes owned by each part. - **Do not traverse into child named groups** when exporting a part. - Merge baked geometries per part and write `.obj`. Reference: `references/link-export-rules.md`. ## Relation between Three.js and URDF articulation - Use named groups as links. - Parent is the nearest named ancestor link. - Joint type defaults to `fixed` unless evidence suggests `revolute`/`prismatic`. - Sort link and joint names for determinism. References: - `references/joint-type-heuristics.md` - `references/urdf-minimal.md` ## Scripts - InstancedMesh OBJ exporter: `node scripts/export_instanced_obj.mjs` - Per-link OBJ exporter: `node scripts/export_link_objs.mjs --input --out-dir ` - URDF builder: `node scripts/build_urdf_from_scene.mjs --input --output --mesh-dir ` Adjust inputs/outputs inside scripts as needed.