Files
settled-reach/tooling/scripts/blender/blender_process_bodies.py
T
jpmschweitzerandClaude Opus 5.5 c597ec9131 docs(tooling): T-1253 — sweep the live references to retired tool paths
A script scanned every tracked doc, rule, skill, agent, hook and source file
for tooling/ paths that no longer exist, skipping historical records (sprints,
discussions, workshops, governance, generated wiki pages). It found 62. The
ones that tell a reader what to RUN now name the reach verb:

- The atlas skill still sent agents to tooling/atlas, atlas-verify,
  atlas-update-field and atlas-commit-and-sync — about forty lines, all
  retired in T-1285. They now name the `reach atlas` verbs, and the skill
  records that commit-and-sync STAGES by default (--commit to commit) and
  takes --corridor as an option.
- The clerk agent named tooling/clerk-review (now `reach dev clerk`). The Si
  and clerk briefings sent those agents to the retired tooling/db/decision
  and sqlite-query CLIs and to decisions/*.md paths that moved to
  governance/ in the pql migration. They now name pql.
- The ticket-cli rule documented `pql decisions read`, which does not exist;
  `show` already includes the body.
- The culture authoring guide and the RON sources name
  `reach validate ron`, with the same arguments as before.
- The 41 Blender payloads' usage lines ran the retired tooling/blender
  wrapper, and the docstrings still cited pre-carve-out paths. They now read
  `reach blender run <payload>`.
- Doc comments in server/, client/, wiki TOMLs and the domain modules.

What is left is deliberate: "Formerly …" provenance, dated plans and findings
docs, the retired-pipeline doc, and a build-artefact path.

project.yaml 0.4.14 (mirrored to the client). Comment-only, but four touched
files are in the canvas-version registry (trait_catalog_reader.rs, since
T-1289, canvas_sources.py itself, and two client files). The gate is
path-based and has no override. The previous push was rejected on exactly
this.

Three of the edits are stamped ledger sources, so systems.db is regenerated
and the stamp is fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:13:58 +02:00

141 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
blender_process_bodies.py
Usage: reach blender run blender_process_bodies <source_dir> <output_base_dir>
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):
<source_dir>/Regular_Male_FullBody.gltf -> average_m (18 segs)
<source_dir>/Regular_Female_FullBody.gltf -> average_f (18 segs)
<source_dir>/Superhero_Male_FullBody.gltf -> muscular_m (18 segs)
<source_dir>/Superhero_Female_FullBody.gltf -> muscular_f (18 segs)
<source_dir>/Teen_Male_FullBody.gltf -> teen_m (18 segs)
<source_dir>/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: <output_base_dir>/{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: reach blender run blender_process_bodies <source_dir> <output_base_dir>")
sys.exit(1)
args = argv[argv.index("--") + 1:]
if len(args) < 2:
print("ERROR: Provide <source_dir> and <output_base_dir>")
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}")