[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XESFSSRHCJ82BW96W5466G":3,"skill-same:01M6XESFSSRHCJ82BW96W5466G":71},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":8,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":14,"categories":15,"keywords":16,"publishedAt":17,"linkOnly":18,"markdownContent":19,"markdownBody":20,"frontMatter":21,"refName":22,"commitSha":23,"skillDirectory":24,"forksCount":12,"repositoryHtmlUrl":25,"repositoryHomepageUrl":6,"ownerHtmlUrl":26,"ownerBlogUrl":6,"ownerDescription":27,"updatedAt":28,"install":29,"resources":42},"01M6XESFSSRHCJ82BW96W5466G","threejs",null,"Waaaalnut\u002Fskillsbench","Waaaalnut","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F103995656?v=4","01M6XCS9K1V51EY3JMB7DGMX24","01M6XCS9Q4WPW88W62C55G1H44",0,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002FSKILL.md",[],[5],"2026-11-10 10:12:30",false,"---\nname: threejs\ndescription: Three.js scene-graph parsing and export workflows: mesh baking, InstancedMesh expansion, part partitioning, per-link OBJ export, and URDF articulation.\n---\n\n# Three.js Scene Graph + Export\n\n## Quick start\n\n1) Load the module, call `createScene()`, then `updateMatrixWorld(true)`.\n2) Treat **named `THREE.Group` nodes as parts\u002Flinks**.\n3) Assign each mesh to its **nearest named ancestor**.\n4) Bake world transforms into geometry before export.\n5) Export per-part OBJ (and optionally URDF) using deterministic ordering.\n\n## Scene graph essentials\n\n```\nObject3D (root)\n├── Group (part)\n│   ├── Mesh\n│   └── Group (child part)\n│       └── Mesh\n└── Group (another part)\n    └── Mesh\n```\n\n- `THREE.Scene` \u002F `THREE.Object3D`: containers\n- `THREE.Group`: logical part (no geometry)\n- `THREE.Mesh`: geometry + material\n\n## Part partitioning rules\n\n- Parts are **named groups** at any depth.\n- Each mesh belongs to the **nearest named ancestor**.\n- Nested named groups are separate parts; **do not merge** their meshes into parents.\n- Skip empty parts; sort part names for determinism.\n\n```javascript\nfunction buildPartMap(root) {\n  const parts = new Map();\n  root.traverse((obj) => {\n    if (obj.isGroup && obj.name) parts.set(obj.name, { group: obj, meshes: [] });\n  });\n  root.traverse((obj) => {\n    if (!obj.isMesh) return;\n    let parent = obj.parent;\n    while (parent && !(parent.isGroup && parent.name)) parent = parent.parent;\n    if (parent && parts.has(parent.name)) parts.get(parent.name).meshes.push(obj);\n  });\n  return Array.from(parts.values()).filter((p) => p.meshes.length > 0);\n}\n```\n\n## Mesh baking (world transforms)\n\nAlways bake transforms before export:\n\n```javascript\nroot.updateMatrixWorld(true);\nlet geom = mesh.geometry.clone();\ngeom.applyMatrix4(mesh.matrixWorld);\nif (geom.index) geom = geom.toNonIndexed();\nif (!geom.attributes.normal) geom.computeVertexNormals();\n```\n\n### InstancedMesh expansion\n\n```javascript\nconst tempMatrix = new THREE.Matrix4();\nconst instanceMatrix = new THREE.Matrix4();\nobj.getMatrixAt(i, instanceMatrix);\ntempMatrix.copy(obj.matrixWorld).multiply(instanceMatrix);\n```\n\n### Axis conversion (Y-up to Z-up)\n\n```javascript\nconst axisMatrix = new THREE.Matrix4().makeRotationX(-Math.PI \u002F 2);\ngeom.applyMatrix4(axisMatrix);\n```\n\n## Per-part OBJ export\n\n- Collect meshes owned by each part.\n- **Do not traverse into child named groups** when exporting a part.\n- Merge baked geometries per part and write `\u003Cpart>.obj`.\n\nReference: `references\u002Flink-export-rules.md`.\n\n## Relation between Three.js and URDF articulation\n\n- Use named groups as links.\n- Parent is the nearest named ancestor link.\n- Joint type defaults to `fixed` unless evidence suggests `revolute`\u002F`prismatic`.\n- Sort link and joint names for determinism.\n\nReferences:\n- `references\u002Fjoint-type-heuristics.md`\n- `references\u002Furdf-minimal.md`\n\n## Scripts\n\n- InstancedMesh OBJ exporter: `node scripts\u002Fexport_instanced_obj.mjs`\n- Per-link OBJ exporter: `node scripts\u002Fexport_link_objs.mjs --input \u003Cscene_js> --out-dir \u003Cdir>`\n- URDF builder: `node scripts\u002Fbuild_urdf_from_scene.mjs --input \u003Cscene_js> --output \u003Cfile.urdf> --mesh-dir \u003Cmesh_dir>`\n\nAdjust inputs\u002Foutputs inside scripts as needed.\n","# Three.js Scene Graph + Export\n\n## Quick start\n\n1) Load the module, call `createScene()`, then `updateMatrixWorld(true)`.\n2) Treat **named `THREE.Group` nodes as parts\u002Flinks**.\n3) Assign each mesh to its **nearest named ancestor**.\n4) Bake world transforms into geometry before export.\n5) Export per-part OBJ (and optionally URDF) using deterministic ordering.\n\n## Scene graph essentials\n\n```\nObject3D (root)\n├── Group (part)\n│   ├── Mesh\n│   └── Group (child part)\n│       └── Mesh\n└── Group (another part)\n    └── Mesh\n```\n\n- `THREE.Scene` \u002F `THREE.Object3D`: containers\n- `THREE.Group`: logical part (no geometry)\n- `THREE.Mesh`: geometry + material\n\n## Part partitioning rules\n\n- Parts are **named groups** at any depth.\n- Each mesh belongs to the **nearest named ancestor**.\n- Nested named groups are separate parts; **do not merge** their meshes into parents.\n- Skip empty parts; sort part names for determinism.\n\n```javascript\nfunction buildPartMap(root) {\n  const parts = new Map();\n  root.traverse((obj) => {\n    if (obj.isGroup && obj.name) parts.set(obj.name, { group: obj, meshes: [] });\n  });\n  root.traverse((obj) => {\n    if (!obj.isMesh) return;\n    let parent = obj.parent;\n    while (parent && !(parent.isGroup && parent.name)) parent = parent.parent;\n    if (parent && parts.has(parent.name)) parts.get(parent.name).meshes.push(obj);\n  });\n  return Array.from(parts.values()).filter((p) => p.meshes.length > 0);\n}\n```\n\n## Mesh baking (world transforms)\n\nAlways bake transforms before export:\n\n```javascript\nroot.updateMatrixWorld(true);\nlet geom = mesh.geometry.clone();\ngeom.applyMatrix4(mesh.matrixWorld);\nif (geom.index) geom = geom.toNonIndexed();\nif (!geom.attributes.normal) geom.computeVertexNormals();\n```\n\n### InstancedMesh expansion\n\n```javascript\nconst tempMatrix = new THREE.Matrix4();\nconst instanceMatrix = new THREE.Matrix4();\nobj.getMatrixAt(i, instanceMatrix);\ntempMatrix.copy(obj.matrixWorld).multiply(instanceMatrix);\n```\n\n### Axis conversion (Y-up to Z-up)\n\n```javascript\nconst axisMatrix = new THREE.Matrix4().makeRotationX(-Math.PI \u002F 2);\ngeom.applyMatrix4(axisMatrix);\n```\n\n## Per-part OBJ export\n\n- Collect meshes owned by each part.\n- **Do not traverse into child named groups** when exporting a part.\n- Merge baked geometries per part and write `\u003Cpart>.obj`.\n\nReference: `references\u002Flink-export-rules.md`.\n\n## Relation between Three.js and URDF articulation\n\n- Use named groups as links.\n- Parent is the nearest named ancestor link.\n- Joint type defaults to `fixed` unless evidence suggests `revolute`\u002F`prismatic`.\n- Sort link and joint names for determinism.\n\nReferences:\n- `references\u002Fjoint-type-heuristics.md`\n- `references\u002Furdf-minimal.md`\n\n## Scripts\n\n- InstancedMesh OBJ exporter: `node scripts\u002Fexport_instanced_obj.mjs`\n- Per-link OBJ exporter: `node scripts\u002Fexport_link_objs.mjs --input \u003Cscene_js> --out-dir \u003Cdir>`\n- URDF builder: `node scripts\u002Fbuild_urdf_from_scene.mjs --input \u003Cscene_js> --output \u003Cfile.urdf> --mesh-dir \u003Cmesh_dir>`\n\nAdjust inputs\u002Foutputs inside scripts as needed.\n",{},"main","7eeb258ada72b7f58119b0e7a2c00ea89d9beedd","tasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench","https:\u002F\u002Fgithub.com\u002FWaaaalnut","student of Zhejiang University","2026-04-21 23:16:23",{"source":7,"skillName":5,"commands":30,"prompt":40,"zipAvailable":41},[31,34,37],{"tool":32,"command":33},"npx","npx skills add Waaaalnut\u002Fskillsbench --skill threejs",{"tool":35,"command":36},"bunx","bunx skills add Waaaalnut\u002Fskillsbench --skill threejs",{"tool":38,"command":39},"pnpm","pnpm dlx skills add Waaaalnut\u002Fskillsbench --skill threejs","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002FSKILL.md",true,[43,49,53,57,63,67],{"path":44,"resourceType":45,"resourceTypeDesc":46,"sizeBytes":47,"sourceHtmlUrl":48},"references\u002Fjoint-type-heuristics.md","REFERENCE","参考文档",1083,"https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002Freferences\u002Fjoint-type-heuristics.md",{"path":50,"resourceType":45,"resourceTypeDesc":46,"sizeBytes":51,"sourceHtmlUrl":52},"references\u002Flink-export-rules.md",680,"https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002Freferences\u002Flink-export-rules.md",{"path":54,"resourceType":45,"resourceTypeDesc":46,"sizeBytes":55,"sourceHtmlUrl":56},"references\u002Furdf-minimal.md",677,"https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002Freferences\u002Furdf-minimal.md",{"path":58,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":61,"sourceHtmlUrl":62},"scripts\u002Fbuild_urdf_from_scene.mjs","SCRIPT","脚本",3915,"https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002Fscripts\u002Fbuild_urdf_from_scene.mjs",{"path":64,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":65,"sourceHtmlUrl":66},"scripts\u002Fexport_instanced_obj.mjs",1758,"https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002Fscripts\u002Fexport_instanced_obj.mjs",{"path":68,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":69,"sourceHtmlUrl":70},"scripts\u002Fexport_link_objs.mjs",2861,"https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fthreejs-structure-parser\u002Fenvironment\u002Fskills\u002Fthreejs\u002Fscripts\u002Fexport_link_objs.mjs",[72,86,101,129,151,170],{"skillNo":73,"name":74,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":8,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":82,"publishedAt":17},"01M6XEWMJ6VY3ZC53MPDCPG3GH","data-reconciliation","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fxlsx-recover-data\u002Fenvironment\u002Fskills\u002Fdata-reconciliation\u002FSKILL.md",[77],{"categoryNo":78,"categoryName":79,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[83,84,85],"data reconciliation","reconciliation","data",{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":8,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":17},"01M6XEW8MNPMF1KRXV6ATGFRQF","speech-to-text","Transcribe video to timestamped text using Whisper tiny model (pre-installed).","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-tutorial-indexer\u002Fenvironment\u002Fskills\u002Fspeech-to-text\u002FSKILL.md",[],[93,94,95,96,97,98,99,100],"speech","text","speech to text","transcribe video","whisper","model","Video","transcribe",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":8,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":112,"publishedAt":17},"01M6XEW3FQZP0T1RKQQJFXVY9T","video-processor","Process videos by removing segments and concatenating remaining parts. Use when you need to remove detected pauses\u002Fopenings from videos, create highlight reels, or batch process segment removals using ffmpeg filter_complex.","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Fvideo-processor\u002FSKILL.md",[107],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",[99,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128],"processor","video processor","batch","create","reels","when","parts","segments","process","videos","you","segment","filter","ffmpeg","use","remove",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":8,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":136,"publishedAt":17},"01M6XEVXJ5KZ0NTY3XZKRY4AS1","silence-detector","Detect initial silence segments in audio\u002Fvideo using energy-based analysis. Use when you need to find low-energy periods at the start of recordings (title slides, setup time, pre-roll silence).","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Fsilence-detector\u002FSKILL.md",[135],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},[137,138,139,140,118,123,141,142,143,144,145,120,146,147,99,148,149,150,127],"detector","silence detector","title","slides","time","energy","at","recordings","detect","find","Audio","analysis","start","setup",{"skillNo":152,"name":153,"nameZh":6,"description":154,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":8,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":155,"categories":156,"keywords":157,"publishedAt":17},"01M6XEVT3NGRN1FT7JPMAPXS6M","segment-combiner","Combine multiple segment detection results into a unified list. Use when you need to merge segments from different detectors, prepare removal lists for video processing, or consolidate detection outputs.","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Fsegment-combiner\u002FSKILL.md",[],[124,158,99,159,160,161,162,163,118,164,165,120,123,166,167,127,168,169],"segment combiner","detection","prepare","merge","video processing","for","outputs","combine","processing","consolidate","results","list",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":8,"repositoryNo":11,"starsCount":12,"licenseSpdxId":13,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":179,"publishedAt":17},"01M6XEVMPVASGS2KX86QK4FMT7","report-generator","Generate compression reports for video processing. Use when you need to create structured JSON reports with duration statistics, compression ratios, and segment details after video processing.","https:\u002F\u002Fgithub.com\u002FWaaaalnut\u002Fskillsbench\u002Fblob\u002Fmain\u002Ftasks\u002Fvideo-silence-remover\u002Fenvironment\u002Fskills\u002Freport-generator\u002FSKILL.md",[176],{"categoryNo":177,"categoryName":178,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000034","行政与办公人员",[180,181,182,124,118,163,123,183,166,184,185,186,127,162,187,99,188,189,116],"generator","report generator","report","with","statistics","structured","reports","json","compression","generate"]