Files
settled-reach/spikes/quaternius-aesthetic/scripts/blender/create_placeholder_bodies.py
T
jpmschweitzerandClaude Opus 4.6 c380ef8a84 feat(assets): add Quaternius aesthetic validation spike
Complete character pipeline spike validating the Quaternius rig as
foundation for The Settled Reach's 3D character system.

Validated:
- 65-bone skeleton + Universal Animation Library as rig foundation
- Body segmentation into 15 bone-group regions with 1-ring vertex overlap
- Trellis-generated heads via BoneAttachment3D
- Skin tone texture generation pipeline (9 variants from source)
- Toon shader + inverted hull outline at gameplay zoom
- CharacterVisual class as compositor prototype

Failed (documented):
- Trellis clothing auto-rigging (sculptures, not garments)
- Bone scaling for body type variants (catastrophic joint deformation)
- Runtime per-segment clothing scaling (same failure as Blender-side)

Includes: Blender pipeline scripts (segmentation, auto-rigging, Surface
Deform fitting), Godot showcase with interactive controls, automated
screenshot cycle, smoke tests, team reviews, and VERDICT.md.

D-158 through D-164 locked. Q-060 through Q-062 opened.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 13:29:22 +01:00

116 lines
3.6 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:
tooling/blender --background --python \\
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")