Files
settled-reach/spikes/quaternius-aesthetic/scripts/blender/create_placeholder_bodies.py
T
jpmschweitzerandClaude Opus 5 201dabd19b refactor(tooling): T-1273 — the Blender carve-out, and a guard that keeps it carved
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>
2026-09-02 20:55:52 +02:00

116 lines
3.5 KiB
Python

"""
OBSOLETE: Placeholder body type GLBs (workaround for failed bone-scaling approach).
This script was a stopgap to produce placeholder body type files with blue
material tinting after create_body_types.py produced broken meshes. No longer
needed -- the body type pipeline will use hand-authored meshes instead.
Preserved as documentation only.
Run via:
reach blender run \\
spikes/quaternius-aesthetic/scripts/blender/create_placeholder_bodies.py \\
-- <base_characters_dir/> <output_dir/>
"""
import bpy
import sys
import os
argv = sys.argv
argv = argv[argv.index("--") + 1:] if "--" in argv else []
if len(argv) < 2:
print("Usage: -- <base_characters_dir/> <output_dir/>")
sys.exit(1)
INPUT_DIR = argv[0]
OUTPUT_DIR = argv[1]
os.makedirs(OUTPUT_DIR, exist_ok=True)
MALE_GLTF = os.path.join(INPUT_DIR, "Superhero_Male_FullBody.gltf")
FEMALE_GLTF = os.path.join(INPUT_DIR, "Superhero_Female_FullBody.gltf")
PLACEHOLDER_COLOR = (0.2, 0.35, 0.65, 1.0) # muted blue
# muscular keeps original textures (it IS the Superhero base)
# all others get blue placeholder material
VARIANTS = [
("male_thin", MALE_GLTF, True),
("male_average", MALE_GLTF, True),
("male_muscular", MALE_GLTF, False), # keep original
("male_heavy", MALE_GLTF, True),
("female_thin", FEMALE_GLTF, True),
("female_average", FEMALE_GLTF, True),
("female_muscular", FEMALE_GLTF, False), # keep original
("female_heavy", FEMALE_GLTF, True),
("child", FEMALE_GLTF, True),
]
print("=== Create placeholder body types ===")
def clear_scene():
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
for c in list(bpy.data.collections):
bpy.data.collections.remove(c)
def apply_placeholder_material(obj):
"""Replace all materials with solid blue placeholder."""
if obj.type != 'MESH':
return
# Create placeholder material
mat = bpy.data.materials.new(name="placeholder_blue")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = PLACEHOLDER_COLOR
bsdf.inputs["Roughness"].default_value = 1.0
bsdf.inputs["Metallic"].default_value = 0.0
# Clear existing materials and assign placeholder
obj.data.materials.clear()
obj.data.materials.append(mat)
for variant_name, source_gltf, is_placeholder in VARIANTS:
print(f"\n {variant_name}...")
clear_scene()
if not os.path.exists(source_gltf):
print(f" SKIP: source not found: {source_gltf}")
continue
bpy.ops.import_scene.gltf(filepath=source_gltf)
objects = list(bpy.data.objects)
# Apply placeholder material to placeholder variants only
if is_placeholder:
for obj in objects:
if obj.type == 'MESH':
apply_placeholder_material(obj)
# Select all for export
bpy.ops.object.select_all(action='SELECT')
if objects:
bpy.context.view_layer.objects.active = objects[0]
output_path = os.path.join(OUTPUT_DIR, f"{variant_name}.glb")
bpy.ops.export_scene.gltf(
filepath=output_path,
export_format='GLB',
use_selection=True,
export_apply=False,
export_animations=False,
export_skins=True,
)
size_kb = os.path.getsize(output_path) // 1024
print(f" Exported: {variant_name}.glb ({size_kb} KB)")
print("\n=== Done ===")
for name, _, _ in VARIANTS:
path = os.path.join(OUTPUT_DIR, f"{name}.glb")
status = "[OK]" if os.path.exists(path) else "[MISSING]"
print(f" {status} {name}.glb")