35 payloads move to tooling/scripts/blender/ and stay outside package scope. They run under Blender's bundled Python, which cannot see the repo venv, so they physically cannot import tooling.core — holding them to the D-263 contract would either fail the gate forever or force the contract to be weakened for everyone, and the second is how a gate stops meaning anything. Count verified by import rather than filename: 33 import bpy/bmesh directly, and the two that do not are still payloads per their own usage lines. garment-fit/make_logo.py is the one genuine non-payload and stays for T-1290. The bash wrapper is retired rather than kept. Keeping it would have put the install-resolution logic in two places, which is the duplication T-1286 had just finished collapsing three copies of. domains/blender/service.py owns the decisions — resolve_blender (native beats flatpak, ordering preserved), resolve_payload, absolutise — and only run_payload performs. test_blender.py pins all of them without launching Blender, which matters here more than usual: the thing being launched is a 200 MB GUI application that writes GLBs. `reach blender run` takes a registered payload name OR a path to any script, because the wrapper served both — the spikes and the glb-gen skill hand it one-off scripts of their own. An unknown name enumerates all 35 and exits 2. The exclusion now defends itself. check_carve_out_stays_carved fails if `scripts` is added to PACKAGE_ROOTS, if the payload directory empties (an empty exclusion proves nothing), or if an __init__.py appears there (which would make the payloads importable — the coupling the carve-out exists to prevent). All three arms mutation-proved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
43 lines
1.5 KiB
Python
43 lines
1.5 KiB
Python
"""Blender recon: dump skeleton/mesh facts for a Sidekick FBX and our armature.
|
|
|
|
Usage: reach blender run recon_dump.py -- <fbx> <our_armature_glb>
|
|
"""
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
argv = sys.argv[sys.argv.index("--") + 1:]
|
|
fbx_path, glb_path = argv[0], argv[1]
|
|
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
bpy.ops.import_scene.fbx(filepath=fbx_path)
|
|
print("=== SIDEKICK FBX:", fbx_path.split("/")[-1])
|
|
sk_bones = []
|
|
for obj in bpy.data.objects:
|
|
if obj.type == "ARMATURE":
|
|
sk_bones = sorted(b.name for b in obj.data.bones)
|
|
print("ARMATURE:", obj.name, "bones:", len(sk_bones))
|
|
elif obj.type == "MESH":
|
|
keys = obj.data.shape_keys
|
|
print(
|
|
"MESH:", obj.name,
|
|
"verts:", len(obj.data.vertices),
|
|
"dim:", [round(d, 3) for d in obj.dimensions],
|
|
"shapekeys:", [k.name for k in keys.key_blocks] if keys else [],
|
|
"vgroups:", len(obj.vertex_groups),
|
|
)
|
|
print("SK_BONES:", ",".join(sk_bones))
|
|
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
bpy.ops.import_scene.gltf(filepath=glb_path)
|
|
print("=== OURS:", glb_path.split("/")[-1])
|
|
our_bones = []
|
|
for obj in bpy.data.objects:
|
|
if obj.type == "ARMATURE":
|
|
our_bones = sorted(b.name for b in obj.data.bones)
|
|
print("ARMATURE:", obj.name, "bones:", len(our_bones))
|
|
print("OUR_BONES:", ",".join(our_bones))
|
|
|
|
overlap = set(sk_bones) & set(our_bones)
|
|
print("OVERLAP:", len(overlap), "of", len(sk_bones), "sidekick /", len(our_bones), "ours")
|