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>
116 lines
3.5 KiB
Python
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")
|