""" blender_process_bodies.py Usage: tooling/blender --background --python tooling/blender_process_bodies.py -- Drives the full body segmentation pipeline for all 11 body types. Calls blender_segment_body.py logic inline (same Blender session, one pass per body type). Source GLTF files (pre-exported from Source tier, Godot - UE format): /Regular_Male_FullBody.gltf -> average_m (18 segs) /Regular_Female_FullBody.gltf -> average_f (18 segs) /Superhero_Male_FullBody.gltf -> muscular_m (18 segs) /Superhero_Female_FullBody.gltf -> muscular_f (18 segs) /Teen_Male_FullBody.gltf -> teen_m (18 segs) /Teen_Female_FullBody.gltf -> teen_f (18 segs) Fork body types (vertex-level scale applied before segmentation): Regular_Male + scale (0.82, 1.0, 0.88) -> thin_m Regular_Female + scale (0.82, 1.0, 0.88) -> thin_f Regular_Male + scale (1.20, 1.08, 1.0) -> heavy_m Regular_Female + scale (1.20, 1.08, 1.0) -> heavy_f Teen_Male + scale (0.75, 0.72, 0.75) -> child [gender-neutral] Fork body types are approximate (auto-generated from mesh scaling). They produce visually distinct silhouettes but may need artist refinement for final quality. A README is placed in each fork directory marking this status. Output: /{body_type}/seg_{name}.glb (18 files × 11 types = 198 GLBs) Decisions: D-159 (11 body types), D-160 (18 segments), D-164 (Source .blends as starting point) """ import sys import os # We import the segmentation logic from blender_segment_body.py which must be # in the same directory as this script. script_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, script_dir) from blender_segment_body import segment_body # (gltf_filename, body_type_key, scale, is_fork) BODY_MANIFEST = [ # --- Direct body types (segment as-is) --- ("Regular_Male_FullBody.gltf", "average_m", (1.0, 1.0, 1.0), False), ("Regular_Female_FullBody.gltf", "average_f", (1.0, 1.0, 1.0), False), ("Superhero_Male_FullBody.gltf", "muscular_m", (1.0, 1.0, 1.0), False), ("Superhero_Female_FullBody.gltf","muscular_f", (1.0, 1.0, 1.0), False), ("Teen_Male_FullBody.gltf", "teen_m", (1.0, 1.0, 1.0), False), ("Teen_Female_FullBody.gltf", "teen_f", (1.0, 1.0, 1.0), False), # --- Fork body types (auto-scaled from source) --- # thin: narrow/ectomorph — narrower X, slightly shorter Z ("Regular_Male_FullBody.gltf", "thin_m", (0.82, 1.0, 0.88), True), ("Regular_Female_FullBody.gltf", "thin_f", (0.82, 1.0, 0.88), True), # heavy: wide/endomorph — wider X, slightly taller Y ("Regular_Male_FullBody.gltf", "heavy_m", (1.20, 1.08, 1.0), True), ("Regular_Female_FullBody.gltf", "heavy_f", (1.20, 1.08, 1.0), True), # child: gender-neutral, forked from Teen Male, scaled down ("Teen_Male_FullBody.gltf", "child", (0.75, 0.72, 0.75), True), ] FORK_README = """\ # Fork body type — auto-generated from mesh scaling This directory contains **{body_type}** body segments, generated by applying a proportional mesh scale to the source body ({source}): Scale: ({sx:.2f}, {sy:.2f}, {sz:.2f}) These segments are APPROXIMATE. They produce a visually distinct silhouette but are not artist-authored from scratch. Review the proportions and refine the base mesh manually if the auto-scale result is not satisfactory. Status: auto-generated, needs artist review Sprint 28 — visual team """ def write_fork_readme(output_dir, body_type, source, scale): sx, sy, sz = scale readme_path = os.path.join(output_dir, "README.md") with open(readme_path, 'w') as f: f.write(FORK_README.format(body_type=body_type, source=source, sx=sx, sy=sy, sz=sz)) if __name__ == "__main__": argv = sys.argv if "--" not in argv: print("Usage: tooling/blender --background --python tooling/blender_process_bodies.py -- ") sys.exit(1) args = argv[argv.index("--") + 1:] if len(args) < 2: print("ERROR: Provide and ") sys.exit(1) source_dir = args[0] output_base_dir = args[1] # Preflight: check ALL source files exist before starting any processing, # so all missing files are reported at once rather than failing mid-loop. unique_sources = {gltf_filename for (gltf_filename, _, _, _) in BODY_MANIFEST} missing = [ os.path.join(source_dir, f) for f in sorted(unique_sources) if not os.path.exists(os.path.join(source_dir, f)) ] if missing: print("\nERROR: Missing source GLTF files:") for path in missing: print(f" {path}") sys.exit(1) results = [] for (gltf_filename, body_type, scale, is_fork) in BODY_MANIFEST: gltf_path = os.path.join(source_dir, gltf_filename) output_dir = os.path.join(output_base_dir, body_type) print(f"\n{'='*60}") print(f" Body type: {body_type} {'[FORK]' if is_fork else '[DIRECT]'}") print(f" Source: {gltf_filename}") if is_fork: print(f" Scale: {scale}") exported, skipped = segment_body(gltf_path, output_dir, scale) if is_fork: write_fork_readme(output_dir, body_type, gltf_filename, scale) results.append((body_type, exported, skipped, is_fork)) # Summary print(f"\n{'='*60}") print("=== Body type segmentation complete ===") total_exported = 0 for body_type, exported, skipped, is_fork in results: flag = " [FORK]" if is_fork else "" print(f" {body_type}{flag}: {len(exported)} exported, {len(skipped)} skipped") total_exported += len(exported) print(f" Total GLBs: {total_exported}")