3ba86b2ea8
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>
79 lines
3.5 KiB
Python
79 lines
3.5 KiB
Python
#!/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()
|