A script scanned every tracked doc, rule, skill, agent, hook and source file for tooling/ paths that no longer exist, skipping historical records (sprints, discussions, workshops, governance, generated wiki pages). It found 62. The ones that tell a reader what to RUN now name the reach verb: - The atlas skill still sent agents to tooling/atlas, atlas-verify, atlas-update-field and atlas-commit-and-sync — about forty lines, all retired in T-1285. They now name the `reach atlas` verbs, and the skill records that commit-and-sync STAGES by default (--commit to commit) and takes --corridor as an option. - The clerk agent named tooling/clerk-review (now `reach dev clerk`). The Si and clerk briefings sent those agents to the retired tooling/db/decision and sqlite-query CLIs and to decisions/*.md paths that moved to governance/ in the pql migration. They now name pql. - The ticket-cli rule documented `pql decisions read`, which does not exist; `show` already includes the body. - The culture authoring guide and the RON sources name `reach validate ron`, with the same arguments as before. - The 41 Blender payloads' usage lines ran the retired tooling/blender wrapper, and the docstrings still cited pre-carve-out paths. They now read `reach blender run <payload>`. - Doc comments in server/, client/, wiki TOMLs and the domain modules. What is left is deliberate: "Formerly …" provenance, dated plans and findings docs, the retired-pipeline doc, and a build-artefact path. project.yaml 0.4.14 (mirrored to the client). Comment-only, but four touched files are in the canvas-version registry (trait_catalog_reader.rs, since T-1289, canvas_sources.py itself, and two client files). The gate is path-based and has no override. The previous push was rejected on exactly this. Three of the edits are stamped ledger sources, so systems.db is regenerated and the stamp is fresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
2.8 KiB
Python
94 lines
2.8 KiB
Python
"""
|
|
blender_extract_armature.py
|
|
Usage: reach blender run blender_extract_armature <input.gltf> <output.glb>
|
|
|
|
Loads a Quaternius GLTF file, strips all mesh objects and animation data,
|
|
and exports only the armature as a GLB skeleton file.
|
|
|
|
Requirements:
|
|
- 65-bone hierarchy preserved
|
|
- No mesh geometry
|
|
- No animation data
|
|
- Y-up, -Z forward (glTF default)
|
|
- 1 unit = 1 meter (Quaternius convention)
|
|
"""
|
|
|
|
import sys
|
|
import bpy
|
|
|
|
|
|
def extract_armature(input_path: str, output_path: str) -> None:
|
|
# Clear the default scene
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
|
|
# Import the GLTF/GLB
|
|
print(f"Loading: {input_path}")
|
|
bpy.ops.import_scene.gltf(filepath=input_path)
|
|
|
|
# Report what was imported
|
|
all_objects = list(bpy.context.scene.objects)
|
|
print(f"Imported {len(all_objects)} objects:")
|
|
for obj in all_objects:
|
|
print(f" {obj.name} ({obj.type})")
|
|
|
|
# Find armature objects
|
|
armatures = [obj for obj in all_objects if obj.type == 'ARMATURE']
|
|
if not armatures:
|
|
print("ERROR: No armature found in the imported file.")
|
|
sys.exit(1)
|
|
|
|
armature = armatures[0]
|
|
print(f"Armature: {armature.name} — {len(armature.data.bones)} bones")
|
|
|
|
if len(armature.data.bones) < 60:
|
|
print(f"WARNING: Expected ~65 bones, found {len(armature.data.bones)}. Check compatibility.")
|
|
|
|
# Remove all non-armature objects (meshes, lights, cameras, empties)
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
for obj in all_objects:
|
|
if obj.type != 'ARMATURE':
|
|
obj.select_set(True)
|
|
bpy.ops.object.delete()
|
|
|
|
# Remove all animation data (skeleton only, no poses)
|
|
if armature.animation_data:
|
|
armature.animation_data_clear()
|
|
for action in list(bpy.data.actions):
|
|
bpy.data.actions.remove(action)
|
|
|
|
# Select only the armature for export
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
armature.select_set(True)
|
|
bpy.context.view_layer.objects.active = armature
|
|
|
|
# Export as GLB — skeleton only, no animations, no meshes
|
|
print(f"Exporting to: {output_path}")
|
|
bpy.ops.export_scene.gltf(
|
|
filepath=output_path,
|
|
use_selection=True,
|
|
export_format='GLB',
|
|
export_animations=False,
|
|
export_skins=True,
|
|
export_yup=True,
|
|
)
|
|
|
|
# Verify: report bone count and names
|
|
bones = sorted(armature.data.bones, key=lambda b: b.name)
|
|
print(f"Export complete. {len(bones)} bones:")
|
|
for bone in bones:
|
|
print(f" {bone.name}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
argv = sys.argv
|
|
if "--" not in argv:
|
|
print("Usage: reach blender run blender_extract_armature <input.gltf> <output.glb>")
|
|
sys.exit(1)
|
|
|
|
args = argv[argv.index("--") + 1:]
|
|
if len(args) < 2:
|
|
print("ERROR: Provide input GLTF/GLB path and output GLB path.")
|
|
sys.exit(1)
|
|
|
|
extract_armature(args[0], args[1])
|