Files
settled-reach/tooling/scripts/blender/blender_rebuild_forks.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

105 lines
4.4 KiB
Python

"""
blender_rebuild_forks.py
Usage: reach blender run blender_rebuild_forks <source_dir> <output_base_dir> [body_type ...]
Rebuilds ONLY the fork body types (thin_m/f, heavy_m/f, child) through the same
segmentation pipeline that produced the healthy six (blender_segment_body.py),
using the T-1090 fix: the fork scale is applied to the mesh AND the embedded
armature rest pose so each segment stays internally consistent with its own
skeleton (required by the shared-skeleton compositor in character_visual.gd).
Sources (Source-tier Godot - UE exports):
Regular_Male_FullBody.gltf + scale (0.82, 1.0, 0.88) -> thin_m
Regular_Female_FullBody.gltf + scale (0.82, 1.0, 0.88) -> thin_f
Regular_Male_FullBody.gltf + scale (1.20, 1.08, 1.0) -> heavy_m
Regular_Female_FullBody.gltf + scale (1.20, 1.08, 1.0) -> heavy_f
Teen_Male_FullBody.gltf + scale (0.75, 0.72, 0.75) -> child [gender-neutral]
The fork scale factors and source mapping are unchanged from
blender_process_bodies.py — this driver reuses the same table so the healthy six
are never touched. Output layout matches production: <out>/{body_type}/seg_*.glb.
"""
import sys
import os
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, source_label) — forks only.
FORK_MANIFEST = [
("Regular_Male_FullBody.gltf", "thin_m", (0.82, 1.0, 0.88), "Regular_Male_FullBody.gltf"),
("Regular_Female_FullBody.gltf", "thin_f", (0.82, 1.0, 0.88), "Regular_Female_FullBody.gltf"),
("Regular_Male_FullBody.gltf", "heavy_m", (1.20, 1.08, 1.0), "Regular_Male_FullBody.gltf"),
("Regular_Female_FullBody.gltf", "heavy_f", (1.20, 1.08, 1.0), "Regular_Female_FullBody.gltf"),
("Teen_Male_FullBody.gltf", "child", (0.75, 0.72, 0.75), "Teen_Male_FullBody.gltf"),
]
FORK_README = """\
# Fork body type — auto-generated from mesh + armature scaling (T-1090)
This directory contains **{body_type}** body segments, generated by applying a
proportional scale to the source body ({source}) mesh AND its armature rest pose
together, then segmenting:
Scale: ({sx:.2f}, {sy:.2f}, {sz:.2f})
The scale is applied to the mesh vertices and the embedded armature rest pose
with the SAME affine (T-1090 fix). This keeps each segment internally consistent
with its own skeleton, which the shared-skeleton compositor
(character_visual.gd) requires — a mesh-only scale detaches the head and
explodes the limbs on relocation.
Cross-sectional differentiation (thin = narrow, heavy = wide) survives the
composite; global height normalises to the shared skeleton (see T-1090 report /
Q-060 for the shared-skeleton scale-normalisation note).
Status: auto-generated (rebuilt T-1090), 18 segments incl. seg_hips
"""
def write_fork_readme(output_dir, body_type, source, scale):
sx, sy, sz = scale
with open(os.path.join(output_dir, "README.md"), "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
args = argv[argv.index("--") + 1:] if "--" in argv else []
if len(args) < 2:
print("Usage: -- <source_dir> <output_base_dir> [body_type ...]")
sys.exit(1)
source_dir = args[0]
output_base_dir = args[1]
only = set(args[2:]) # optional subset filter
manifest = [m for m in FORK_MANIFEST if not only or m[1] in only]
unique_sources = {gltf for (gltf, _, _, _) in 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 p in missing:
print(f" {p}")
sys.exit(1)
results = []
for (gltf_filename, body_type, scale, source_label) in manifest:
gltf_path = os.path.join(source_dir, gltf_filename)
output_dir = os.path.join(output_base_dir, body_type)
print(f"\n{'='*60}\n Fork: {body_type} scale={scale}\n Source: {gltf_filename}")
exported, skipped = segment_body(gltf_path, output_dir, scale)
write_fork_readme(output_dir, body_type, source_label, scale)
results.append((body_type, exported, skipped))
print(f"\n{'='*60}\n=== Fork rebuild complete ===")
for body_type, exported, skipped in results:
print(f" {body_type}: {len(exported)} exported, {len(skipped)} skipped")