fea96bf582
First male garment through the MD lane. MD's body analyzer rejects every
Tripo-Mako-derived mesh (0 arrangement points, 'avatar must be in T-pose or
A-pose' despite a verified 0.4-deg-perfect skeleton; same wall as ariki-game
e244e8a). Chimera test pins it on the MESH -- Lena's mesh on Mako's armature
gets 109 points. Ruled out: finger weights, decimation, head/hand chops, leg
spread, rename, OBJ import, export settings.
Workaround that ships: arrange+sew+elastic-lock the garment on a 1.07x-scaled
LENA donor, ImportFBX Mako with bAdd, DeleteAvatar(Lena), re-settle. A
seamless single-wrap slides off Mako's legs during the swap -- only SEWN
garments survive it, so the flat pattern's wrap reads are faked: overlap flap
seamed to a split front-waist segment, comb-teeth fringe (piupiu technique)
carved into the hem, rope belt as rigid Blender geometry (build_rope_belt.py).
Other findings baked into work/mako_kilt_v1/ scripts + config:
- fabric texture anchor: pattern y=0 maps to texture v=0.5 and WRAPS
(striped-probe verified; make_kilt_cloth.py mm_from_row)
- waist elastic never reaches TotalLength at default strength; strength 60
moved placement from 1.019 to 1.043
- SetAvatarActivation returns False (unusable); GetArrangementList dies on
any scene avatar mutation
QC PASS (turntable render, alpha mask, height 1.818): top 1.063 vs 1.054
target (tol .03), fringe tips 0.666 vs 0.665 (tol .04).
Avatars: _fingers = full finger weights restored via UV-space transfer from
rig-work/mako_fullhead_rigged.fbx (build_mako_avatar_fingers.py); shipped-body
avatar landed for future diagnosis. v1 (pre-flap/teeth) kept as provenance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
187 lines
6.7 KiB
Python
187 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
make_kilt_cloth.py -- beige woven cloth with black triangle border band for the
|
|
male feather-cape kilt (mako_kilt_v1).
|
|
|
|
python tools/tailor/textures/make_kilt_cloth.py
|
|
|
|
Derived from the reference renders
|
|
`male-clothing-feather-cape-gpt-piece-kilt{,-front,-back}.png`: a light beige
|
|
coarse plain weave (harakeke/tapa in character) with a black triangle border
|
|
band above a short fringe at the hem.
|
|
|
|
UNLIKE hunter_cloth THIS TILE IS POSITIONED, NOT GENERIC. It spans the kilt
|
|
panel's full 390 mm height exactly once (dpi = SIZE / (390 / 25.4)), so the
|
|
band and fringe land at fixed garment heights:
|
|
|
|
garment y (mm, hem=0) zone
|
|
0-30 fringe strands (drawn, not geometry -- v1)
|
|
30-90 black band, beige triangles (6 cm, per flat pattern)
|
|
90-390 plain beige weave
|
|
|
|
Horizontally the same 390 mm tiles ~1.7x across the 650 mm panel; the band is
|
|
horizontal and the triangles repeat 8/tile, so the wrap is seamless.
|
|
|
|
IF THE BAND RENDERS AT THE WAIST INSTEAD OF THE HEM, MD anchored the texture
|
|
the other way up -- regenerate with FLIP=True rather than touching the pattern.
|
|
|
|
Same doctrine as make_hunter_cloth.py: albedo baked in (pipeline has no alpha,
|
|
texture replaces base color), warm ramp r>g>b, deterministic LCG noise.
|
|
"""
|
|
import os
|
|
|
|
from PIL import Image
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
OUT = os.path.join(HERE, "kilt_cloth.png")
|
|
|
|
SIZE = 1024 # px, square
|
|
CLOTH_MM = 390.0 # spans the kilt panel height exactly once
|
|
DPI = SIZE / (CLOTH_MM / 25.4)
|
|
FLIP = False # set True if MD anchors the texture upside-down
|
|
|
|
BASE = (206, 188, 152) # light beige albedo (reference lit mean ~(196,180,148))
|
|
BAND = (44, 38, 30) # near-black band, warm
|
|
THREAD_PITCH = 8 # px per thread; 1024px/390mm -> ~3 mm threads
|
|
OVER_LIFT = 12
|
|
UNDER_DROP = 16
|
|
ROUND_SHADE = 10
|
|
SLUB_RANGE = 8
|
|
MOTTLE = 12
|
|
MOTTLE_CELL = 64
|
|
|
|
# zone boundaries in garment mm (hem = 0)
|
|
# v2: fringe is CUT GEOMETRY now (comb teeth, 0-30 mm) -- the texture paints the
|
|
# teeth as strand-beige instead of drawing fake strands. Band triangles refined
|
|
# 8 -> 12 per tile (32.5 mm) to match the reference's tighter zigzag.
|
|
FRINGE_TOP_MM = 30.0
|
|
BAND_TOP_MM = 90.0
|
|
TRIANGLES_PER_TILE = 12
|
|
|
|
|
|
def lcg(seed):
|
|
state = seed
|
|
while True:
|
|
state = (1103515245 * state + 12345) % (2 ** 31)
|
|
yield state / float(2 ** 31)
|
|
|
|
|
|
def thread_tones(n, seed):
|
|
g = lcg(seed)
|
|
return [int((next(g) * 2.0 - 1.0) * SLUB_RANGE) for _ in range(n)]
|
|
|
|
|
|
def value_noise(cells, seed):
|
|
g = lcg(seed)
|
|
grid = [[(next(g) * 2.0 - 1.0) for _ in range(cells + 1)] for _ in range(cells + 1)]
|
|
|
|
def sample(x, y):
|
|
fx, fy = x * cells / SIZE, y * cells / SIZE
|
|
x0, y0 = int(fx), int(fy)
|
|
tx, ty = fx - x0, fy - y0
|
|
tx, ty = tx * tx * (3 - 2 * tx), ty * ty * (3 - 2 * ty)
|
|
a = grid[y0][x0] * (1 - tx) + grid[y0][x0 + 1] * tx
|
|
b = grid[y0 + 1][x0] * (1 - tx) + grid[y0 + 1][x0 + 1] * tx
|
|
return a * (1 - ty) + b * ty
|
|
|
|
return sample
|
|
|
|
|
|
def mm_from_row(y):
|
|
"""Garment height in mm for an image row.
|
|
|
|
ANCHOR DISCOVERED BY PROBE (2026-08-18, striped texture on the kilt panels,
|
|
scratchpad probe_front/back.png): MD maps pattern y=0 (hem) to image v=0.5
|
|
and WRAPS -- v = (0.5 + (390 - y)/390) mod 1. Both panels map identically.
|
|
Inverted here: row -> garment mm."""
|
|
v = (y + 0.5) / SIZE
|
|
return CLOTH_MM - CLOTH_MM * ((v - 0.5) % 1.0)
|
|
|
|
|
|
def in_triangle(x, y):
|
|
"""Beige zigzag triangles inside the band: alternating up/down, classic
|
|
taniko-style row. Returns True where the pixel is beige (triangle), False
|
|
where it stays black band."""
|
|
band_lo = FRINGE_TOP_MM + 4.0 # thinner black margins (v2, per reference)
|
|
band_hi = BAND_TOP_MM - 4.0
|
|
mm = mm_from_row(y)
|
|
if not (band_lo <= mm <= band_hi):
|
|
return False
|
|
v = (mm - band_lo) / (band_hi - band_lo) # 0 bottom .. 1 top
|
|
tri_w = SIZE / float(TRIANGLES_PER_TILE)
|
|
u = (x % tri_w) / tri_w # 0..1 across one triangle
|
|
k = int(x / tri_w) % 2
|
|
if k == 0: # upward triangle: wide at bottom
|
|
half = (1.0 - v) * 0.5
|
|
else: # downward: wide at top
|
|
half = v * 0.5
|
|
return abs(u - 0.5) < half * 0.88 # slim gutters (v2)
|
|
|
|
|
|
def fringe_strand(x, y, gap_noise):
|
|
"""Fringe zone: vertical beige strands over shadow, ragged tips."""
|
|
mm = mm_from_row(y)
|
|
strand_w = 6
|
|
u = x % strand_w
|
|
# per-strand ragged length: strands end 0..10 mm above the tile bottom
|
|
tip = gap_noise[(x // strand_w) % len(gap_noise)] * 10.0
|
|
if mm < tip:
|
|
return None # past the tip: shadow
|
|
return u >= 1 # 1px gap between strands
|
|
|
|
|
|
def main():
|
|
n_threads = SIZE // THREAD_PITCH
|
|
warp = thread_tones(n_threads, seed=20260818)
|
|
weft = thread_tones(n_threads, seed=31337)
|
|
mottle = value_noise(SIZE // MOTTLE_CELL, seed=777)
|
|
g = lcg(424242)
|
|
gap_noise = [next(g) for _ in range(64)]
|
|
|
|
img = Image.new("RGB", (SIZE, SIZE))
|
|
px = img.load()
|
|
|
|
for y in range(SIZE):
|
|
j = (y // THREAD_PITCH) % n_threads
|
|
vy = ((y % THREAD_PITCH) / (THREAD_PITCH - 1.0)) * 2.0 - 1.0
|
|
mm = mm_from_row(y)
|
|
for x in range(SIZE):
|
|
i = (x // THREAD_PITCH) % n_threads
|
|
vx = ((x % THREAD_PITCH) / (THREAD_PITCH - 1.0)) * 2.0 - 1.0
|
|
|
|
warp_on_top = ((i + j) % 2) == 0
|
|
if warp_on_top:
|
|
lift = OVER_LIFT - int(ROUND_SHADE * vx * vx)
|
|
slub = warp[i]
|
|
else:
|
|
lift = -UNDER_DROP + int(ROUND_SHADE * (1.0 - vy * vy))
|
|
slub = weft[j]
|
|
wear = int(mottle(x, y) * MOTTLE)
|
|
d = lift + slub + wear
|
|
|
|
if mm < FRINGE_TOP_MM:
|
|
# v2: teeth are cut geometry; paint them as strand fibre --
|
|
# duller beige with vertical grain, no fake shadow gaps.
|
|
u = x % 6
|
|
base = (192, 174, 140) if u >= 1 else (160, 143, 112)
|
|
elif mm < BAND_TOP_MM:
|
|
base = BASE if in_triangle(x, y) else BAND
|
|
else:
|
|
base = BASE
|
|
|
|
r = base[0] + d
|
|
gg = base[1] + int(d * 0.82)
|
|
b = base[2] + int(d * 0.66)
|
|
px[x, y] = (max(0, min(255, r)), max(0, min(255, gg)),
|
|
max(0, min(255, b)))
|
|
|
|
if FLIP:
|
|
img = img.transpose(Image.FLIP_TOP_BOTTOM)
|
|
img.save(OUT, dpi=(DPI, DPI))
|
|
print("wrote %s (%dx%d, dpi %.2f -> %.0f mm of cloth, band %d-%d mm, fringe 0-%d mm)"
|
|
% (OUT, SIZE, SIZE, DPI, CLOTH_MM, FRINGE_TOP_MM, BAND_TOP_MM, FRINGE_TOP_MM))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|