#!/usr/bin/env python3 """ reproduce_test1.py — rebuild the Test1 outfit end to end, in the ONE correct order. python clothing/reproduce_test1.py # both garments python clothing/reproduce_test1.py --only bottom python clothing/reproduce_test1.py --dry-run WHY THIS SCRIPT EXISTS `garment.py` cannot express this build on its own. The skirt's weights are owned by `skirt_garment_weights.py`, which must run **after `export` and before `import`** — `garment_pipeline.py`'s own 2-segment `strand_weights` is overwritten by it, and whichever runs LAST wins. Nothing enforces that, so re-running the pipeline the obvious way (`--from census --to import`) silently ships the 2-segment blend and reads as a rig regression. This script is the enforcement. It reads the tuning from the config's `post_export` block, so the config stays the single source of truth and nobody has to remember four env vars. ORDER (per garment): census -> prepare -> fit -> reduce -> skin -> export [bottom only] skirt_garment_weights.py <-- the step that gets forgotten register -> import The texture is a build input too: bottom_test1.png is generated and must stay V-only (see make_bottom_test1.py). Pass --texture to regenerate it first. AFTERWARDS, to look at it (BODY_OVERRIDE is mandatory — the 4-segment skirt ring exists only on that body copy; on the standard Lena the garment's skirt bones do not exist and the hem collapses): SESSION_ID= MOCK_ONLY=1 SCENE=clothing_test_bed BED_GENDER=1 SKIRT_RIG_DEBUG=1 \\ BODY_OVERRIDE=res://assets/quaternius/derived-bodies/Ariki_Female_QuatSkin_SkirtRig_4seg.glb \\ WAIT=1 bash tools/game.sh spawn """ import argparse import json import os import subprocess import sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PY = sys.executable GARMENT = os.path.join(ROOT, "clothing", "garment.py") CONFIGS = {"bottom": "clothing/configs/bottomTest1.json", "top": "clothing/configs/topTest1.json"} # The bottom must be built first only for tidiness; they are independent. ORDER = ["bottom", "top"] def run(cmd, env=None, dry=False): shown = " ".join(cmd) if env: shown = " ".join("%s=%s" % kv for kv in sorted(env.items())) + " " + shown print("\n$ " + shown, flush=True) if dry: return 0 e = dict(os.environ) if env: e.update(env) return subprocess.call(cmd, cwd=ROOT, env=e) def die(msg): print("[reproduce] FATAL: " + msg, file=sys.stderr) raise SystemExit(1) def main(): ap = argparse.ArgumentParser() ap.add_argument("--only", choices=ORDER, help="build just one garment") ap.add_argument("--dry-run", action="store_true") ap.add_argument("--texture", action="store_true", help="regenerate bottom_test1.png first (must stay V-only)") a = ap.parse_args() names = [a.only] if a.only else ORDER if a.texture: gen = os.path.join(ROOT, "tools", "tailor", "textures", "make_bottom_test1.py") if run([PY, gen], dry=a.dry_run) != 0: die("texture generation failed") for name in names: cfg_rel = CONFIGS[name] cfg = json.load(open(os.path.join(ROOT, cfg_rel))) # 1. Blender half, up to and including export. if run([PY, GARMENT, cfg_rel, "--from", "census", "--to", "export", "--no-gate-stop"], dry=a.dry_run) != 0: die("%s: pipeline census..export failed" % name) # 2. THE STEP THAT GETS FORGOTTEN. Config-driven so the tuning cannot drift. pe = cfg.get("post_export") if pe: tool = os.path.join(ROOT, pe["tool"].replace("/", os.sep)) exp = cfg["export"] target = "%s/%s_%s_%s.gltf" % ( exp["out_dir"].rstrip("/"), exp["gender"], exp["set"], list(cfg["parts"].values())[0]["slot"]) if not a.dry_run and not os.path.exists(target): die("%s: expected export at %s" % (name, target)) if run([PY, tool, target, pe["body"]], env=pe.get("env"), dry=a.dry_run) != 0: die("%s: %s failed" % (name, pe["tool"])) print("[reproduce] %s: post_export weights applied — check the log above for " "'%s'" % (name, pe.get("expect", "no dead strands"))) # 3. Game half. Import LAST so Godot caches the corrected weights. if run([PY, GARMENT, cfg_rel, "--only", "register,import", "--no-gate-stop"], dry=a.dry_run) != 0: die("%s: register/import failed" % name) print("\n[reproduce] done. Spawn the bed with BODY_OVERRIDE=…_SkirtRig_4seg.glb " "(see this file's docstring) — the standard Lena body has no skirt ring.") if __name__ == "__main__": main()