feat: clothing lane, character sources, and DCC bridges

Bulk import of the working lanes that were living untracked on the PC.

Content:
- characters/  Lena/male body lanes, bakes, texture work, run logs
- clothing/    garment pipeline, configs, gates, contract docs
- garments/    MD-authored garment sources (.zprj/.zpac)
- UAL-Lib/     Universal Animation Library 2 source (.blend/.fbx/.glb)
- tools/       blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent
- docs/, plans/, dev/, .agents/plans/

Repo hygiene:
- .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the
  Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the
  ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out
  on purpose — ~250 are already tracked raw and converting them would
  rewrite every one without shrinking history.
- .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable
  from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies
  dropped by a `>/dev/null` redirect on Windows).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 15:55:43 -07:00
parent 3363209cac
commit 3ba86b2ea8
558 changed files with 68622 additions and 8 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
make_bottom_test1.py — flax-strand skirt texture for bottomTest1.
python tools/tailor/textures/make_bottom_test1.py
WHY THIS EXISTS
The first bottom_test1.png (kept as bottom_test1_prestrand.png) was authored for a
SOLID skirt panel and has two properties that fight the strand geometry:
1. It paints its own VERTICAL STRAND LINES. The garment is now 32 discrete
geometric strands, so those painted lines double up — strands-within-strands.
2. Its band region VARIES ALONG U (measured horizontal stdev 68-83 per row, vs
0-13 on the clean rows). Every geometric strand samples a different slice of
that region, so the horizontal bands do not line up strand-to-strand and the
skirt reads as broken noise instead of woven rows.
THE RULE, and it is the whole trick: for a strand garment the texture must be a
function of V ONLY. Constant along U means every strand samples the same colour at
the same height, so bands align across all 32 strands automatically — regardless of
each strand's UV position, its width, or how the drape stretched it. No UV surgery
needed, and it stays true if the strand count changes.
METHOD
Collapse the reference to V-only by taking each row's MEDIAN colour. That keeps the
original vertical rhythm (waistband, band groups, plain flax field) — the design
intent — and throws away only the horizontal variation that was causing the noise.
Then add back a very low-contrast vertical striation for fibre feel, small enough
(+/- STRIATION levels) that it cannot resurrect the misalignment.
Re-sampling happens through the EXISTING UVs, so this needs no MD session: rebuild
the PNG, copy it next to the exported glTF, reimport the texture.
"""
import os
import statistics
from PIL import Image
HERE = os.path.dirname(os.path.abspath(__file__))
REF = os.path.join(HERE, "bottom_test1_prestrand.png") # the pre-strand original
OUT = os.path.join(HERE, "bottom_test1.png")
SIZE = 1024
STRIATION = 6 # +/- levels of vertical fibre variation. Keep SMALL.
STRIATION_PERIOD = 7 # px between fibre lines
DPI = 43.3 # matches the original: 1024 px at 43.3 dpi = 600 mm of cloth
def main():
ref = Image.open(REF).convert("RGB").resize((SIZE, SIZE), Image.LANCZOS)
px = ref.load()
out = Image.new("RGB", (SIZE, SIZE))
op = out.load()
row_var_before, row_var_after = [], []
for y in range(SIZE):
row = [px[x, y] for x in range(SIZE)]
med = tuple(int(statistics.median(c[i] for c in row)) for i in range(3))
row_var_before.append(statistics.pstdev([sum(c) / 3 for c in row]))
for x in range(SIZE):
# deterministic, seed-free striation: a fixed comb, not noise, so the
# result is byte-identical run to run (Date/random are avoided on
# purpose — this file is a build input).
d = STRIATION if (x % STRIATION_PERIOD) < STRIATION_PERIOD // 2 else -STRIATION
op[x, y] = tuple(max(0, min(255, med[i] + d)) for i in range(3))
row_var_after.append(
statistics.pstdev([sum(op[x, y]) / 3 for x in range(0, SIZE, 4)]))
out.save(OUT, dpi=(DPI, DPI))
print("wrote %s (%dx%d, dpi %.1f)" % (OUT, SIZE, SIZE, DPI))
print("mean horizontal stdev per row: %.1f -> %.1f (lower = bands align)"
% (sum(row_var_before) / SIZE, sum(row_var_after) / SIZE))
print("worst row stdev: %.1f -> %.1f"
% (max(row_var_before), max(row_var_after)))
if __name__ == "__main__":
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB