diff --git a/tools/tailor/_import_mako_avatar.py b/tools/tailor/_import_mako_avatar.py new file mode 100644 index 0000000..d8f96b5 --- /dev/null +++ b/tools/tailor/_import_mako_avatar.py @@ -0,0 +1,26 @@ +# Import Mako (latest male base, mako_base_v01) as the MD avatar. +# Run via: python tools/md_bridge.py --file tools/tailor/_import_mako_avatar.py --timeout 300 +import os + +AVATAR_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar.fbx" +SNAP = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots\_mako_import_check.png" + +report = {} + +utility_api.NewProject() +op = ApiTypes.ImportExportOption() +op.scale = 10.0 # Blender-exported FBX lands 10x small +op.bAddArrangementPoints = True # ~98 named points to hang cloth on +op.bAutoTranslate = True +report["avatar_imported"] = import_api.ImportFBX(AVATAR_FBX, op) + +report["avatar_count"] = export_api.GetAvatarCount() +report["avatar_names"] = export_api.GetAvatarNameList() +arr = pattern_api.GetArrangementList() +report["arrangement_points"] = len(arr) if arr else 0 + +utility_api.SetCamViewPoint(2) # front view for comparable QC shots +snaps = export_api.ExportSnapshot3D(SNAP) +report["snapshot"] = SNAP if os.path.exists(SNAP) else snaps + +result = report diff --git a/tools/tailor/avatar/Mako_Fullhead_QuatSkin_Avatar_fingers.fbx b/tools/tailor/avatar/Mako_Fullhead_QuatSkin_Avatar_fingers.fbx new file mode 100644 index 0000000..32a64cf --- /dev/null +++ b/tools/tailor/avatar/Mako_Fullhead_QuatSkin_Avatar_fingers.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eec2d2e11231f4619bd3d86ed82485ac938ef6ff07e4c373262cbbbff0065fb1 +size 7676588 diff --git a/tools/tailor/avatar/Mako_Shipped_QuatSkin_Avatar.fbx b/tools/tailor/avatar/Mako_Shipped_QuatSkin_Avatar.fbx new file mode 100644 index 0000000..51c3df0 --- /dev/null +++ b/tools/tailor/avatar/Mako_Shipped_QuatSkin_Avatar.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9562ddb4fb9b42df39f1f8b5de6b190fdda1fd692f8f0539cba7fa632ad62bb +size 5455132 diff --git a/tools/tailor/mako_kilt_v1.zpac b/tools/tailor/mako_kilt_v1.zpac new file mode 100644 index 0000000..df1ed6d --- /dev/null +++ b/tools/tailor/mako_kilt_v1.zpac @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50acf21755e054071e87b77cc27b904e3ff79d44974884a685305b795bb8fd90 +size 1454459 diff --git a/tools/tailor/mako_kilt_v1.zprj b/tools/tailor/mako_kilt_v1.zprj new file mode 100644 index 0000000..509d036 --- /dev/null +++ b/tools/tailor/mako_kilt_v1.zprj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b657e875ef53515de7aaf6ac16b5e32392ec2c5bdacf8c9a71a972906c6466f +size 9339780 diff --git a/tools/tailor/mako_kilt_v1_Default Colorway.png b/tools/tailor/mako_kilt_v1_Default Colorway.png new file mode 100644 index 0000000..7519f4a Binary files /dev/null and b/tools/tailor/mako_kilt_v1_Default Colorway.png differ diff --git a/tools/tailor/mako_kilt_v1_garment.fbx b/tools/tailor/mako_kilt_v1_garment.fbx new file mode 100644 index 0000000..a5a4017 --- /dev/null +++ b/tools/tailor/mako_kilt_v1_garment.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0995343e71e122a9c4d46562e0f769f64628bf238ff5618d20302b829a2d315 +size 161776 diff --git a/tools/tailor/mako_kilt_v1_garment.mtl b/tools/tailor/mako_kilt_v1_garment.mtl new file mode 100644 index 0000000..5bde46d --- /dev/null +++ b/tools/tailor/mako_kilt_v1_garment.mtl @@ -0,0 +1,10 @@ +# MTL Exporter v1.0 by Seungwoo Oh at CLO - Virtual Fashion Inc. +newmtl (Default_for_Simulation)_FRONT_50521 +Ka 0.257132 0.257132 0.257132 +Kd 1.000000 1.000000 1.000000 +Ks 0.075000 0.075000 0.075000 +Ns 28.763239 +illum 2 +d 1.000000 +map_Ka C:/Users/CAN/tinqs-ltd/animation/tools/tailor/kilt_cloth.png +map_Kd C:/Users/CAN/tinqs-ltd/animation/tools/tailor/kilt_cloth.png diff --git a/tools/tailor/mako_kilt_v1_garment.obj b/tools/tailor/mako_kilt_v1_garment.obj new file mode 100644 index 0000000..30a5645 --- /dev/null +++ b/tools/tailor/mako_kilt_v1_garment.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9422f4bac1fbfe1790b15932f73f4aef36f48cc7e904a48d545eb6fc0a1a35eb +size 360315 diff --git a/tools/tailor/mako_kilt_v2.zpac b/tools/tailor/mako_kilt_v2.zpac new file mode 100644 index 0000000..a661b94 --- /dev/null +++ b/tools/tailor/mako_kilt_v2.zpac @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:096c27a61a26170131d3360cc0fd7f6b7d0e9df1888052f45ae1848564d424a4 +size 1500203 diff --git a/tools/tailor/mako_kilt_v2.zprj b/tools/tailor/mako_kilt_v2.zprj new file mode 100644 index 0000000..18901c7 --- /dev/null +++ b/tools/tailor/mako_kilt_v2.zprj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f28fe5a3cde484c5472402baab385a220dcedd300ca1a8ed06a9a424e468a73f +size 9370302 diff --git a/tools/tailor/mako_kilt_v2_Default Colorway.png b/tools/tailor/mako_kilt_v2_Default Colorway.png new file mode 100644 index 0000000..62c0b42 Binary files /dev/null and b/tools/tailor/mako_kilt_v2_Default Colorway.png differ diff --git a/tools/tailor/mako_kilt_v2_belt.mtl b/tools/tailor/mako_kilt_v2_belt.mtl new file mode 100644 index 0000000..a595b09 --- /dev/null +++ b/tools/tailor/mako_kilt_v2_belt.mtl @@ -0,0 +1,12 @@ +# Blender 5.1.2 MTL File: 'None' +# www.blender.org + +newmtl RopeBrown +Ns 10.000005 +Ka 1.000000 1.000000 1.000000 +Kd 0.420000 0.300000 0.170000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.500000 +d 1.000000 +illum 2 diff --git a/tools/tailor/mako_kilt_v2_belt.obj b/tools/tailor/mako_kilt_v2_belt.obj new file mode 100644 index 0000000..e1c1ae2 --- /dev/null +++ b/tools/tailor/mako_kilt_v2_belt.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88afafb51d919bd3fdae4e6ca331bf5da39fe71b10ed453d09d8e55ef25a956a +size 11503719 diff --git a/tools/tailor/mako_kilt_v2_garment.fbx b/tools/tailor/mako_kilt_v2_garment.fbx new file mode 100644 index 0000000..ee33aab --- /dev/null +++ b/tools/tailor/mako_kilt_v2_garment.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92ab47c70dda90c95c525d71f7d2016961502e0b58d214dcf357b3321bc869e0 +size 203728 diff --git a/tools/tailor/mako_kilt_v2_garment.mtl b/tools/tailor/mako_kilt_v2_garment.mtl new file mode 100644 index 0000000..21b284a --- /dev/null +++ b/tools/tailor/mako_kilt_v2_garment.mtl @@ -0,0 +1,10 @@ +# MTL Exporter v1.0 by Seungwoo Oh at CLO - Virtual Fashion Inc. +newmtl (Default_for_Simulation)_FRONT_88393 +Ka 0.257132 0.257132 0.257132 +Kd 1.000000 1.000000 1.000000 +Ks 0.075000 0.075000 0.075000 +Ns 28.763239 +illum 2 +d 1.000000 +map_Ka C:/Users/CAN/tinqs-ltd/animation/tools/tailor/kilt_cloth.png +map_Kd C:/Users/CAN/tinqs-ltd/animation/tools/tailor/kilt_cloth.png diff --git a/tools/tailor/mako_kilt_v2_garment.obj b/tools/tailor/mako_kilt_v2_garment.obj new file mode 100644 index 0000000..2526334 --- /dev/null +++ b/tools/tailor/mako_kilt_v2_garment.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1d2f041cd8cf47d62dc3e874b242cff6fc0b0bd74f55464322ad4641f28f153 +size 474457 diff --git a/tools/tailor/screenshots/kilt_v2_fitted_lena.png b/tools/tailor/screenshots/kilt_v2_fitted_lena.png new file mode 100644 index 0000000..19ea274 Binary files /dev/null and b/tools/tailor/screenshots/kilt_v2_fitted_lena.png differ diff --git a/tools/tailor/screenshots/kilt_v2_presim.png b/tools/tailor/screenshots/kilt_v2_presim.png new file mode 100644 index 0000000..27611dd Binary files /dev/null and b/tools/tailor/screenshots/kilt_v2_presim.png differ diff --git a/tools/tailor/screenshots/kilt_v2_probe_front.png b/tools/tailor/screenshots/kilt_v2_probe_front.png new file mode 100644 index 0000000..b4988d0 Binary files /dev/null and b/tools/tailor/screenshots/kilt_v2_probe_front.png differ diff --git a/tools/tailor/screenshots/kilt_v2_probe_top.png b/tools/tailor/screenshots/kilt_v2_probe_top.png new file mode 100644 index 0000000..1078969 Binary files /dev/null and b/tools/tailor/screenshots/kilt_v2_probe_top.png differ diff --git a/tools/tailor/screenshots/kilt_v2b_presim_lena.png b/tools/tailor/screenshots/kilt_v2b_presim_lena.png new file mode 100644 index 0000000..74d6529 Binary files /dev/null and b/tools/tailor/screenshots/kilt_v2b_presim_lena.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v1_back.png b/tools/tailor/screenshots/mako_kilt_v1_back.png new file mode 100644 index 0000000..c2cffb4 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v1_back.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v1_fitted_lena.png b/tools/tailor/screenshots/mako_kilt_v1_fitted_lena.png new file mode 100644 index 0000000..1c9429c Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v1_fitted_lena.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v1_front.png b/tools/tailor/screenshots/mako_kilt_v1_front.png new file mode 100644 index 0000000..7d8ab44 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v1_front.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v1_front_hd.png b/tools/tailor/screenshots/mako_kilt_v1_front_hd.png new file mode 100644 index 0000000..81085de Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v1_front_hd.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v1_presim.png b/tools/tailor/screenshots/mako_kilt_v1_presim.png new file mode 100644 index 0000000..e21b610 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v1_presim.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v1_presim_lena.png b/tools/tailor/screenshots/mako_kilt_v1_presim_lena.png new file mode 100644 index 0000000..054afcf Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v1_presim_lena.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v1_side_hd.png b/tools/tailor/screenshots/mako_kilt_v1_side_hd.png new file mode 100644 index 0000000..621b2c4 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v1_side_hd.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v2_back.png b/tools/tailor/screenshots/mako_kilt_v2_back.png new file mode 100644 index 0000000..ee23863 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v2_back.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v2_belt_front.png b/tools/tailor/screenshots/mako_kilt_v2_belt_front.png new file mode 100644 index 0000000..e4840ea Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v2_belt_front.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v2_belt_threequarter.png b/tools/tailor/screenshots/mako_kilt_v2_belt_threequarter.png new file mode 100644 index 0000000..3e631bc Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v2_belt_threequarter.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v2_front.png b/tools/tailor/screenshots/mako_kilt_v2_front.png new file mode 100644 index 0000000..ce2fac3 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v2_front.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v2_front_hd.png b/tools/tailor/screenshots/mako_kilt_v2_front_hd.png new file mode 100644 index 0000000..3bce919 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v2_front_hd.png differ diff --git a/tools/tailor/screenshots/mako_kilt_v2_side_hd.png b/tools/tailor/screenshots/mako_kilt_v2_side_hd.png new file mode 100644 index 0000000..5064a69 Binary files /dev/null and b/tools/tailor/screenshots/mako_kilt_v2_side_hd.png differ diff --git a/tools/tailor/textures/kilt_cloth.png b/tools/tailor/textures/kilt_cloth.png new file mode 100644 index 0000000..3f2daa3 Binary files /dev/null and b/tools/tailor/textures/kilt_cloth.png differ diff --git a/tools/tailor/textures/make_kilt_cloth.py b/tools/tailor/textures/make_kilt_cloth.py new file mode 100644 index 0000000..30f5d7c --- /dev/null +++ b/tools/tailor/textures/make_kilt_cloth.py @@ -0,0 +1,186 @@ +#!/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() diff --git a/tools/tailor/work/mako_kilt_v1/_diag_chimera_lena_mesh_mako_arm.py b/tools/tailor/work/mako_kilt_v1/_diag_chimera_lena_mesh_mako_arm.py new file mode 100644 index 0000000..8932e56 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/_diag_chimera_lena_mesh_mako_arm.py @@ -0,0 +1,37 @@ +"""Chimera A: Lena's mesh bound to Mako's armature. Tests which half kills MD's +arrangement-point generation (bone names are identical, so weights just work).""" +import bpy + +MAKO = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx" +LENA = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Lena_QuatSkin_Avatar.fbx" +OUT = r"C:\Users\CAN\AppData\Local\Temp\Chimera_LenaMesh_MakoArm.fbx" + +bpy.ops.wm.read_factory_settings(use_empty=True) +bpy.ops.import_scene.fbx(filepath=MAKO) +mako_arm = next(o for o in bpy.data.objects if o.type == "ARMATURE") +mako_mesh = next(o for o in bpy.data.objects if o.type == "MESH") +bpy.data.objects.remove(mako_mesh, do_unlink=True) + +pre = set(bpy.data.objects) +bpy.ops.import_scene.fbx(filepath=LENA) +new = [o for o in bpy.data.objects if o not in pre] +lena_mesh = next(o for o in new if o.type == "MESH") +lena_arm = next((o for o in new if o.type == "ARMATURE"), None) + +# rebind lena mesh to mako armature +lena_mesh.parent = mako_arm +lena_mesh.matrix_parent_inverse = mako_arm.matrix_world.inverted() +for m in list(lena_mesh.modifiers): + lena_mesh.modifiers.remove(m) +mod = lena_mesh.modifiers.new("Armature", "ARMATURE") +mod.object = mako_arm +if lena_arm: + bpy.data.objects.remove(lena_arm, do_unlink=True) + +bpy.ops.object.select_all(action="DESELECT") +mako_arm.select_set(True) +lena_mesh.select_set(True) +bpy.context.view_layer.objects.active = mako_arm +bpy.ops.export_scene.fbx(filepath=OUT, use_selection=True, + add_leaf_bones=False, bake_anim=False) +print("EXPORTED", OUT) diff --git a/tools/tailor/work/mako_kilt_v1/_diag_repose_mako_tpose.py b/tools/tailor/work/mako_kilt_v1/_diag_repose_mako_tpose.py new file mode 100644 index 0000000..3eab412 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/_diag_repose_mako_tpose.py @@ -0,0 +1,109 @@ +"""Repose Mako into a strict T-pose for MD's pose detector. + +Measures current limb angles, swings legs outward and levels the arms via the +armature, applies the deformation to the mesh, bakes the pose as rest, exports. +""" +import bpy, json, sys, math +from mathutils import Matrix, Vector + +SRC = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx" +OUT = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_tpose.fbx" +LEG_SPREAD_DEG = 6.0 + +report = {} +bpy.ops.wm.read_factory_settings(use_empty=True) +bpy.ops.import_scene.fbx(filepath=SRC) +arm = next(o for o in bpy.data.objects if o.type == "ARMATURE") +mesh = next(o for o in bpy.data.objects if o.type == "MESH") + +def bone_world_dir(pb): + m = arm.matrix_world @ pb.matrix + head = m.translation + tail = m @ Vector((0, pb.length, 0)) + return (tail - head).normalized(), head + +bpy.context.view_layer.objects.active = arm +bpy.ops.object.mode_set(mode="POSE") + +# --- measure current angles --- +angles = {} +for name in ("thigh_l", "thigh_r", "upperarm_l", "upperarm_r", "lowerarm_l", "lowerarm_r"): + pb = arm.pose.bones[name] + d, _ = bone_world_dir(pb) + angles[name] = { + "dir": [round(v, 3) for v in d], + "off_vertical_deg": round(math.degrees(d.angle(Vector((0, 0, -1)))), 2), + "off_horiz_deg": round(math.degrees(min(d.angle(Vector((1, 0, 0))), d.angle(Vector((-1, 0, 0))))), 2), + } +report["before"] = angles + +def rotate_world(pb, axis, deg): + """Rotate a pose bone around a world axis through its own head.""" + d, head = bone_world_dir(pb) + R = Matrix.Translation(head) @ Matrix.Rotation(math.radians(deg), 4, axis) @ Matrix.Translation(-head) + pb.matrix = arm.matrix_world.inverted() @ R @ arm.matrix_world @ pb.matrix + bpy.context.view_layer.update() + +# --- legs: swing outward around the forward (Y) axis --- +for name, sign in (("thigh_l", 1), ("thigh_r", -1)): + pb = arm.pose.bones[name] + d, _ = bone_world_dir(pb) + side = 1 if d.x >= 0 or "l" in name[-1] else -1 + # positive X side leg rotates -Y ... determine empirically: we want tail.x to move AWAY from 0 + for trial in (sign * LEG_SPREAD_DEG, -sign * LEG_SPREAD_DEG): + before_x = abs(bone_world_dir(pb)[0].x) + rotate_world(pb, "Y", trial) + after_x = abs(bone_world_dir(pb)[0].x) + if after_x > before_x: + break + rotate_world(pb, "Y", -trial) # undo, try other sign + +# --- arms: level each segment to exactly horizontal (world XZ -> X) --- +for name in ("upperarm_l", "lowerarm_l", "hand_l", "upperarm_r", "lowerarm_r", "hand_r"): + pb = arm.pose.bones[name] + d, _ = bone_world_dir(pb) + target = Vector((1 if d.x >= 0 else -1, 0, 0)) + flat = Vector((d.x, d.y, d.z)) + axis = flat.cross(target) + if axis.length < 1e-8: + continue + ang = math.degrees(flat.angle(target)) + if ang < 0.05: + continue + R = Matrix.Rotation(math.radians(ang), 4, axis.normalized()) + head = (arm.matrix_world @ pb.matrix).translation + T = Matrix.Translation(head) @ R @ Matrix.Translation(-head) + pb.matrix = arm.matrix_world.inverted() @ T @ arm.matrix_world @ pb.matrix + bpy.context.view_layer.update() + +# --- re-measure --- +angles2 = {} +for name in ("thigh_l", "thigh_r", "upperarm_l", "upperarm_r", "lowerarm_l", "lowerarm_r"): + pb = arm.pose.bones[name] + d, _ = bone_world_dir(pb) + angles2[name] = { + "off_vertical_deg": round(math.degrees(d.angle(Vector((0, 0, -1)))), 2), + "off_horiz_deg": round(math.degrees(min(d.angle(Vector((1, 0, 0))), d.angle(Vector((-1, 0, 0))))), 2), + } +report["after"] = angles2 + +bpy.ops.object.mode_set(mode="OBJECT") + +# --- bake: apply armature modifier to mesh, then pose as rest --- +bpy.context.view_layer.objects.active = mesh +amod = next(m for m in mesh.modifiers if m.type == "ARMATURE") +bpy.ops.object.modifier_apply(modifier=amod.name) +bpy.context.view_layer.objects.active = arm +bpy.ops.object.mode_set(mode="POSE") +bpy.ops.pose.armature_apply(selected=False) +bpy.ops.object.mode_set(mode="OBJECT") +# re-add armature modifier so the export binds skin +mod = mesh.modifiers.new("Armature", "ARMATURE") +mod.object = arm + +bpy.ops.object.select_all(action="SELECT") +bpy.ops.export_scene.fbx(filepath=OUT, use_selection=True, add_leaf_bones=False, bake_anim=False) +report["out"] = OUT +with open(sys.argv[-1], "w") as f: + json.dump(report, f, indent=1) +print("WROTE", sys.argv[-1]) diff --git a/tools/tailor/work/mako_kilt_v1/build_mako_avatar_fingers.py b/tools/tailor/work/mako_kilt_v1/build_mako_avatar_fingers.py new file mode 100644 index 0000000..877d524 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/build_mako_avatar_fingers.py @@ -0,0 +1,113 @@ +"""Rebuild Mako MD avatar with finger weights — v3, UV-space transfer. + +Vertex order differs and the shape was resculpted, but both meshes carry the same +Tripo UV atlas. Match target loops to source loops in UV space (KDTree), copy +hand+finger weights (CC -> quat names), average per target vertex, normalize. +""" +import bpy, json, sys +from mathutils import kdtree, Vector + +TARGET_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar.fbx" +SOURCE_FBX = r"C:\Users\CAN\tinqs-ltd\animation\characters\rig-work\mako_fullhead_rigged.fbx" +OUT_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx" + +XFER_MAP = {} +for S, T in (("L", "l"), ("R", "r")): + XFER_MAP[f"CC_Base_{S}_Hand"] = f"hand_{T}" + for F, Q in (("Thumb", "thumb"), ("Index", "index"), ("Mid", "middle"), + ("Ring", "ring"), ("Pinky", "pinky")): + for i in (1, 2, 3): + XFER_MAP[f"CC_Base_{S}_{F}{i}"] = f"{Q}_0{i}_{T}" + +report = {} +bpy.ops.wm.read_factory_settings(use_empty=True) +bpy.ops.import_scene.fbx(filepath=TARGET_FBX) +tgt = next(o for o in bpy.data.objects if o.type == "MESH") +pre = {o.name for o in bpy.data.objects} +bpy.ops.import_scene.fbx(filepath=SOURCE_FBX) +new = [o for o in bpy.data.objects if o.name not in pre] +src = max((o for o in new if o.type == "MESH"), key=lambda o: len(o.data.vertices)) + +# --- source loop UV KDTree -> vertex index --- +s_layer = src.data.uv_layers.active.data +s_loops = src.data.loops +kd = kdtree.KDTree(len(s_loops)) +loop_vert = [0] * len(s_loops) +for lp in s_loops: + uv = s_layer[lp.index].uv + kd.insert(Vector((uv.x, uv.y, 0.0)), lp.index) + loop_vert[lp.index] = lp.vertex_index +kd.balance() + +# --- source per-vertex weights for transfer groups --- +src_gidx = {g.index: XFER_MAP[g.name] for g in src.vertex_groups if g.name in XFER_MAP} +report["src_transfer_groups"] = len(src_gidx) +src_w = [None] * len(src.data.vertices) +for i, v in enumerate(src.data.vertices): + d = {} + for g in v.groups: + q = src_gidx.get(g.group) + if q and g.weight > 0.0001: + d[q] = d.get(q, 0.0) + g.weight + src_w[i] = d or None + +# --- match target loops by UV, accumulate weights per target vertex --- +t_layer = tgt.data.uv_layers.active.data +acc = [None] * len(tgt.data.vertices) # vertex -> {qname: (sum, count)} +cnt = [0] * len(tgt.data.vertices) +dist_sum, dist_n, dist_max = 0.0, 0, 0.0 +for lp in tgt.data.loops: + uv = t_layer[lp.index].uv + _, sl, d = kd.find(Vector((uv.x, uv.y, 0.0))) + dist_sum += d; dist_n += 1; dist_max = max(dist_max, d) + vi = lp.vertex_index + cnt[vi] += 1 + w = src_w[loop_vert[sl]] + if w: + a = acc[vi] or {} + for q, val in w.items(): + a[q] = a.get(q, 0.0) + val + acc[vi] = a +report["uv_dist_mean"] = round(dist_sum / dist_n, 6) +report["uv_dist_max"] = round(dist_max, 6) + +# --- wipe old hand groups, write averaged weights --- +for name in ("hand_l", "hand_r"): + g = tgt.vertex_groups.get(name) + if g: + tgt.vertex_groups.remove(g) +dst = {} +for qname in set(XFER_MAP.values()): + dst[qname] = tgt.vertex_groups.get(qname) or tgt.vertex_groups.new(name=qname) +touched = 0 +for vi, a in enumerate(acc): + if not a: + continue + touched += 1 + for q, s in a.items(): + dst[q].add([vi], s / cnt[vi], "REPLACE") +report["tgt_verts_touched"] = touched + +bpy.ops.object.select_all(action="DESELECT") +tgt.select_set(True) +bpy.context.view_layer.objects.active = tgt +bpy.ops.object.vertex_group_normalize_all(lock_active=False) + +weighted = set() +for v in tgt.data.vertices: + for g in v.groups: + if g.weight > 0.0001: + weighted.add(tgt.vertex_groups[g.group].name) +report["tgt_weighted_groups"] = len(weighted) +report["tgt_finger_groups"] = len([x for x in weighted if any( + f in x for f in ("index", "middle", "ring", "pinky", "thumb"))]) + +for o in new: + bpy.data.objects.remove(o, do_unlink=True) +bpy.ops.object.select_all(action="SELECT") +bpy.ops.export_scene.fbx(filepath=OUT_FBX, use_selection=True, + add_leaf_bones=False, bake_anim=False) +report["out"] = OUT_FBX +with open(sys.argv[-1], "w") as f: + json.dump(report, f, indent=1) +print("WROTE", sys.argv[-1]) diff --git a/tools/tailor/work/mako_kilt_v1/build_rope_belt.py b/tools/tailor/work/mako_kilt_v1/build_rope_belt.py new file mode 100644 index 0000000..400c67a --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/build_rope_belt.py @@ -0,0 +1,163 @@ +"""Rope belt for the Mako kilt -- braided torus + knot + two hanging tie ends. + +Rigid geometry, not cloth (piupiu lesson: a waistband that swings reads broken). +Flat pattern: rope 150 x 1.5 cm at high hip, knot + ~22 cm ends on the wearer's +right hip; tie ends 40 x 1.2 cm beside the front overlap edge. + +Run: blender --background --python build_rope_belt.py +Out: tools/tailor/mako_kilt_v2_belt.obj + composite review renders. +""" +import bpy, math +from mathutils import Vector + +OUTD = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor" +AVATAR = OUTD + r"\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx" +KILT_OBJ = OUTD + r"\mako_kilt_v2_garment.obj" +KILT_TEX = OUTD + r"\textures\kilt_cloth.png" +BELT_OBJ = OUTD + r"\mako_kilt_v2_belt.obj" +SHOTS = OUTD + r"\screenshots" + +BELT_Z = 1.055 # sits ON the kilt's top edge (QC: top ~1.06) +BELT_R = 0.172 # outside the draped cloth, not just the body +ROPE_R = 0.0075 # rope radius (1.5 cm dia) +KNOT_ANG = math.radians(-38) # wearer's right-front, beside the overlap edge + +bpy.ops.wm.read_factory_settings(use_empty=True) + +def rope_material(): + m = bpy.data.materials.new("RopeBrown") + m.use_nodes = True + b = m.node_tree.nodes["Principled BSDF"] + b.inputs["Base Color"].default_value = (0.42, 0.30, 0.17, 1.0) + b.inputs["Roughness"].default_value = 0.9 + return m + +MAT = rope_material() + +def strand_curve(name, pts, bevel, taper_end=False): + cu = bpy.data.curves.new(name, "CURVE") + cu.dimensions = "3D" + cu.bevel_depth = bevel + cu.bevel_resolution = 3 + sp = cu.splines.new("NURBS") + sp.points.add(len(pts) - 1) + for i, p in enumerate(pts): + sp.points[i].co = (p[0], p[1], p[2], 1.0) + if taper_end: + sp.points[i].radius = 1.0 - 0.85 * (i / (len(pts) - 1)) ** 2 + sp.use_endpoint_u = True + ob = bpy.data.objects.new(name, cu) + ob.data.materials.append(MAT) + bpy.context.collection.objects.link(ob) + return ob + +# --- braided ring: 3 strands weaving around the belt circle ------------------- +N = 220 +belt_parts = [] +for k in range(3): + phase = k * 2.0 * math.pi / 3.0 + pts = [] + for i in range(N + 1): + t = 2.0 * math.pi * i / N + wob = 0.55 * ROPE_R + r = BELT_R + wob * math.cos(9 * t + phase) + z = BELT_Z + wob * math.sin(9 * t + phase) + pts.append((r * math.sin(t), -r * math.cos(t), z)) + belt_parts.append(strand_curve("belt_strand_%d" % k, pts, ROPE_R * 0.62)) + +# --- knot: short fat wrap at the front-right ---------------------------------- +kx, ky = BELT_R * math.sin(KNOT_ANG), -BELT_R * math.cos(KNOT_ANG) +knot_pts = [] +for i in range(25): + t = i / 24.0 + a = KNOT_ANG + (t - 0.5) * 0.28 + r = BELT_R + 0.011 * math.sin(t * math.pi) + knot_pts.append((r * math.sin(a), -r * math.cos(a), + BELT_Z + 0.012 * math.sin(t * 2 * math.pi))) +belt_parts.append(strand_curve("belt_knot", knot_pts, ROPE_R * 0.9)) + +# --- two hanging tie ends (40 cm, tapered, slight sway) ------------------------ +out_x, out_y = math.sin(KNOT_ANG), -math.cos(KNOT_ANG) +for j, (side, sway) in enumerate(((0.014, 0.035), (-0.012, -0.025))): + pts = [] + L = 0.40 + for i in range(14): + t = i / 13.0 + # hang OUTSIDE the kilt cloth: push radially out as they fall + r_out = 0.012 + 0.010 * t + pts.append((kx + out_x * r_out + side + sway * math.sin(t * 2.6), + ky + out_y * r_out, + BELT_Z - 0.012 - L * t)) + belt_parts.append(strand_curve("belt_tie_%d" % j, pts, 0.006, taper_end=True)) + +# convert curves to mesh and join +for ob in belt_parts: + ob.select_set(True) +bpy.context.view_layer.objects.active = belt_parts[0] +bpy.ops.object.convert(target="MESH") +bpy.ops.object.join() +belt = bpy.context.view_layer.objects.active +belt.name = "MakoKiltBelt" +bpy.ops.wm.obj_export(filepath=BELT_OBJ, export_selected_objects=True, + export_materials=True) +print("BELT verts", len(belt.data.vertices)) + +# --- composite review render ---------------------------------------------------- +bpy.ops.import_scene.fbx(filepath=AVATAR) +# the avatar's own textures fail to load headless (magenta) -- flat grey body +grey = bpy.data.materials.new("BodyGrey") +grey.use_nodes = True +gb = grey.node_tree.nodes["Principled BSDF"] +gb.inputs["Base Color"].default_value = (0.62, 0.60, 0.58, 1.0) +gb.inputs["Roughness"].default_value = 0.7 +for ob in bpy.data.objects: + if ob.type == "MESH" and "belt" not in ob.name.lower() and ob.name != "MakoKiltBelt": + ob.data.materials.clear() + ob.data.materials.append(grey) +bpy.ops.wm.obj_import(filepath=KILT_OBJ) +kilt = bpy.context.view_layer.objects.active +km = bpy.data.materials.new("KiltCloth") +km.use_nodes = True +bsdf = km.node_tree.nodes["Principled BSDF"] +tex = km.node_tree.nodes.new("ShaderNodeTexImage") +tex.image = bpy.data.images.load(KILT_TEX) +km.node_tree.links.new(tex.outputs["Color"], bsdf.inputs["Base Color"]) +bsdf.inputs["Roughness"].default_value = 0.85 +kilt.data.materials.clear() +kilt.data.materials.append(km) +# MD OBJ exports in mm -- scale to metres if huge +if max(kilt.dimensions) > 10: + kilt.scale = (0.001, 0.001, 0.001) + +sun = bpy.data.objects.new("Sun", bpy.data.lights.new("Sun", "SUN")) +sun.data.energy = 1.2 +sun.rotation_euler = (math.radians(55), 0, math.radians(25)) +bpy.context.collection.objects.link(sun) +fill = bpy.data.objects.new("Fill", bpy.data.lights.new("Fill", "AREA")) +fill.data.energy = 220 +fill.data.size = 3.0 +fill.location = (-1.2, -2.4, 1.5) +fill.rotation_euler = (math.radians(75), 0, math.radians(-25)) +bpy.context.collection.objects.link(fill) +front = bpy.data.objects.new("Front", bpy.data.lights.new("Front", "AREA")) +front.data.energy = 160 +front.data.size = 3.0 +front.location = (0.8, -2.8, 1.2) +front.rotation_euler = (math.radians(80), 0, math.radians(15)) +bpy.context.collection.objects.link(front) + +cam = bpy.data.objects.new("Cam", bpy.data.cameras.new("Cam")) +bpy.context.collection.objects.link(cam) +bpy.context.scene.camera = cam +sc = bpy.context.scene +sc.render.engine = "BLENDER_EEVEE" +sc.render.resolution_x = sc.render.resolution_y = 1600 +sc.render.film_transparent = False +for name, loc, rot in (("front", (0, -3.4, 1.05), (math.radians(88), 0, 0)), + ("threequarter", (2.2, -2.6, 1.1), (math.radians(86), 0, math.radians(40)))): + cam.location = loc + cam.rotation_euler = rot + sc.render.filepath = SHOTS + r"\mako_kilt_v2_belt_%s.png" % name + bpy.ops.render.render(write_still=True) +print("RENDERS done") + diff --git a/tools/tailor/work/mako_kilt_v1/config.json b/tools/tailor/work/mako_kilt_v1/config.json new file mode 100644 index 0000000..e207a7c --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/config.json @@ -0,0 +1,122 @@ +{ + "name": "mako_kilt_v1", + "_worksheet": { + "block": "hunter_skirt_v8 topology (12-point shaped A-line, cross-paired sides ONE reversed), renumbered to Mako", + "reference": "male-clothing-feather-cape-gpt-piece-kilt{,-front,-back,-pattern-annotated}.png", + "flat_pattern_source": "annotated pattern sized to male Lena 1.777 m: worn top 1.03, hem 0.65, panel 118x38 (incl 30 cm wrap overlap), hem ~150. Scaled to Mako 1.818 m (x1.0231): top 1.054, hem 0.665, worn length 39 cm.", + "measured_targets": { + "waist_top_m": 1.054, + "solid_hem_m": 0.665, + "note": "Mako card: waist 88.7 @ 1.065, hip 101.4 @ 0.932. Body at worn top (z=1.054, interp) = 89.8 cm." + }, + "ease_profile_cm": { + "top_z1.054": 3.2, + "z0.990": 6.0, + "hip_z0.926": 6.1 + }, + "tension_note": "Garment waist 93 cm vs 89.8 body (+3.2, same margin that held v8) and 8.4 cm smaller than the 101.4 hip below it -- cannot slide down. No elastic, no strengthen, per v8 findings.", + "silhouette_note": "hem 130 cm/waist 93 = 1.40 vs reference's ~1.27 worn. Mako's gentle 8 cm waist-to-hip drop needs far less flare than Lena's 42 cm; extra 10 cm is walk clearance.", + "not_built_v1": [ + "30 cm wrap overlap flap (reference front-left diagonal edge)", + "rope belt + 2 tie ends (geometry; candidate for a downstream prop)", + "geometric fringe teeth (drawn in the texture for now, piupiu technique later)" + ], + "texture_note": "kilt_cloth.png spans the 390 mm panel height exactly once (dpi 66.69): fringe 0-30 mm, black triangle band 30-90 mm, plain beige above. PROBE-VERIFIED anchor: MD maps pattern y=0 (hem) to texture v=0.5 and WRAPS -- see mm_from_row() in make_kilt_cloth.py.", + "SHIPPED_BY": "md_script_swap.py, NOT the generated md_script.py -- MD's body analyzer rejects every Mako-derived mesh (0 arrangement points, 'avatar must be in T-pose or A-pose' despite a verified perfect T), so the kilt is arranged/sewn/elastic-locked on a 1.07x-scaled LENA donor, then avatars are swapped and the fitted garment re-settles onto Mako. Tried and ruled out: finger weights, decimation, head/hand chops, leg spread, mesh rename, OBJ import.", + "v6_findings": { + "swap_drop": "the avatar swap drops loose arranged panels ~10 cm; a sewn+cinched garment survives it (drops ~5 cm, countered by the 1.07 donor scale)", + "elastic": "waist line 0, total 450 mm/panel, strength 60 -- DEFAULT STRENGTH NEVER REACHES TARGET; strength 60 moved the top from 1.019 to 1.043", + "qc_pass": "top 1.043 (target 1.054 +/-0.03), hem 0.662 (target 0.665 +/-0.04), span 0.381 vs 0.390 drafted; measured on mako_kilt_v1_front_hd.png (turntable render, alpha mask, warm-pixel classifier, --height 1.818)", + "known_flaw": "slightly scalloped top edge at center front from the stiff elastic; the reference's rope belt would cover it -- candidate for the belt prop pass" + } + }, + "md": { + "reset": "new_project", + "avatar_fbx": "C:/Users/CAN/tinqs-ltd/animation/tools/tailor/avatar/Mako_Fullhead_QuatSkin_Avatar_fingers.fbx", + "avatar_scale": 10.0, + "add_arrangement_points": true, + "auto_translate": true, + "zfab": "C:/Users/Public/Documents/MarvelousDesigner/New Assets/Fabric/(Default for Simulation).zfab", + "texture": "C:/Users/CAN/tinqs-ltd/animation/tools/tailor/textures/kilt_cloth.png", + "texture_dpi": 66.69, + "panels": [ + { + "name": "front", + "dx": 0.0, + "note": "Widths vs Mako body interp +ease: y390/465 (body 89.8+3.2), y325/510 (95.9+6), y260.5/537 (hip 101.4+6), y215/555, y135/600 (thigh hull guess ~108, single thigh 67.3), y0/650. Lines: 0 waist | 1-5 side_R | 6 hem | 7-11 side_L.", + "points": [ + [92.5, 390.0], + [557.5, 390.0], + [580.0, 325.0], + [593.5, 260.5], + [602.5, 215.0], + [625.0, 135.0], + [650.0, 0.0], + [0.0, 0.0], + [25.0, 135.0], + [47.5, 215.0], + [56.5, 260.5], + [70.0, 325.0] + ] + }, + { + "name": "back", + "dx": 900.0, + "note": "identical panel offset by dx (NOT mirrored) -> sides CROSS-pair, ONE reversed (v8 discovery)", + "points": [ + [92.5, 390.0], + [557.5, 390.0], + [580.0, 325.0], + [593.5, 260.5], + [602.5, 215.0], + [625.0, 135.0], + [650.0, 0.0], + [0.0, 0.0], + [25.0, 135.0], + [47.5, 215.0], + [56.5, 260.5], + [70.0, 325.0] + ] + } + ], + "seams": [ + {"a": "front", "a_line": 1, "b": "back", "b_line": 11, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 2, "b": "back", "b_line": 10, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 3, "b": "back", "b_line": 9, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 4, "b": "back", "b_line": 8, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 5, "b": "back", "b_line": 7, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 11, "b": "back", "b_line": 1, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 10, "b": "back", "b_line": 2, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 9, "b": "back", "b_line": 3, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 8, "b": "back", "b_line": 4, "reverse_a": false, "reverse_b": true}, + {"a": "front", "a_line": 7, "b": "back", "b_line": 5, "reverse_a": false, "reverse_b": true} + ], + "arrangements": [ + {"panel": "front", "point": "Leg_Skirt_Front", "offset": [50, 92, 50]}, + {"panel": "back", "point": "Leg_Skirt_Back", "offset": [0, 92, 50]} + ], + "sim": { + "strengthen": false, + "settle_frames": 200, + "relax_frames": 60 + }, + "cam_viewpoint": 2, + "presim_snapshot": "C:/Users/CAN/tinqs-ltd/animation/tools/tailor/screenshots/mako_kilt_v1_presim.png", + "snapshot": "C:/Users/CAN/tinqs-ltd/animation/tools/tailor/screenshots/mako_kilt_v1_front.png", + "back_snapshot": "C:/Users/CAN/tinqs-ltd/animation/tools/tailor/screenshots/mako_kilt_v1_back.png", + "export_dir": "C:/Users/CAN/tinqs-ltd/animation/tools/tailor", + "export_basename": "mako_kilt_v1", + "exports": ["zprj", "fbx", "obj", "zpac"] + }, + "expect": { + "bands": { + "mako_kilt_v1": { + "top_m": 1.054, + "bottom_m": 0.665, + "tol_m": 0.03, + "bottom_tol_m": 0.04, + "from": "flat pattern annotation scaled 1.777 -> 1.818" + } + } + } +} diff --git a/tools/tailor/work/mako_kilt_v1/md_script.py b/tools/tailor/work/mako_kilt_v1/md_script.py new file mode 100644 index 0000000..7de2547 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/md_script.py @@ -0,0 +1,166 @@ +# GENERATED by tools/tailor/draft_garment.py -- DO NOT EDIT. +# garment : mako_kilt_v1 +# stage : all +# config : C:\Users\CAN\tinqs-ltd\animation\tools\tailor\work\mako_kilt_v1\config.json +# emitted : 2026-08-18T15:14:53 +# Edit the config's `md` block and re-emit; edits here are lost. +# +# Run inside an MD bridge session (a human must click Plugin > TinqsMDBridge first): +# python tools/md_bridge.py --file --timeout 250 +# +# HARD-WON MD FACTS (do not rediscover -- see tools/tailor/draft_garment.py): +# - 2D pattern space: units are mm, +y is UP in the 3D mapping. Straps/neck at +# HIGH y, hem at y=0. Panels built y-down drape upside-down over the head and +# tangle -- it looks like a seam bug, it isn't. +# - MD's 3D unit is mm (gravity -9800). Blender-exported FBX avatars land 10x +# small -- import with op.scale = 10. ImportAvatar() is .avt only (False on FBX). +# - op.bAddArrangementPoints = True generates the ~98 named arrangement points; +# without it there is nowhere to hang cloth. +# - CreatePatternWithPoints takes (x, y, type) triples, type 0 = corner. Returned +# id is the pattern index; line i = edge point[i] -> point[i+1]. +# - Seams are WHOLE-EDGE only: AddSeamlinePairGroup(a, lineA, b, lineB, False, +# False) with the SAME line index on mirrored front/back panels. Cross-pairing +# sews the garment over the face. Neck gaps and armholes are extra points in +# the outline, not partial seams. +# - SetArrangement() only ASSIGNS; utility_api.ResetClothArrangement() APPLIES it +# (ReDrape3DArrangement only materializes not-yet-draped cloth). +# - Arrangement INDICES REGENERATE on every avatar import -- always look up by +# NAME from GetArrangementList(). Offsets aren't stable either; verify with a +# 0-frame snapshot (the diagnostic that unsticks everything). +# - utility_api.NewProject() DELETES the avatar -- re-import after. +# - AddFabric() needs a .zfab FILE PATH (a name string silently fails); fabric 0 +# is the shared default, coloring it dyes every garment. Assign with +# SetPatternPieceFabricIndex -- AssignFabricToPattern() never worked. +# - SetBaseTextureMapImageGivenFilePath(PATH, fabricIdx) -- path is arg 0. +# - PNG DPI sets a texture's physical size in MD; control tiling via dpi in PIL. +# - Strengthen through the WHOLE settle, relax only at the end; elastic (if any) +# mid-settle, never from frame 0. One garment per scene. +# - Simulate(n) is synchronous, ~1 min per 300 frames -- raise the client timeout. +# - GetClothPositions() stays empty: no mesh introspection, QC is image-based. + +import os + +report = {"garment": 'mako_kilt_v1', "stage": 'all'} + +# ---- scene --------------------------------------------------------------- +utility_api.NewProject() # NOTE: this DELETES the avatar; re-import below +AVATAR_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx" +op = ApiTypes.ImportExportOption() +op.scale = 10.0 # Blender-exported FBX lands 10x small +op.bAddArrangementPoints = True # ~98 named points to hang cloth on +op.bAutoTranslate = True +report["avatar"] = import_api.ImportFBX(AVATAR_FBX, op) # ImportAvatar() is .avt only + +# ---- pattern (mm; +y is UP in 3D -- hem at y=0, neck/straps at high y) ---- +# front: Widths vs Mako body interp +ease: y390/465 (body 89.8+3.2), y325/510 (95.9+6), y260.5/537 (hip 101.4+6), y215/555, y135/600 (thigh hull guess ~108, single thigh 67.3), y0/650. Lines: 0 waist | 1-5 side_R | 6 hem | 7-11 side_L. +_pts_p_front = [ + (92.5, 390.0), (557.5, 390.0), (580.0, 325.0), + (593.5, 260.5), (602.5, 215.0), (625.0, 135.0), + (650.0, 0.0), (0.0, 0.0), (25.0, 135.0), + (47.5, 215.0), (56.5, 260.5), (70.0, 325.0), +] +p_front = pattern_api.CreatePatternWithPoints([(x + 0.0, y + 0.0, 0) for (x, y) in _pts_p_front]) +# back: identical panel offset by dx (NOT mirrored) -> sides CROSS-pair, ONE reversed (v8 discovery) +_pts_p_back = [ + (92.5, 390.0), (557.5, 390.0), (580.0, 325.0), + (593.5, 260.5), (602.5, 215.0), (625.0, 135.0), + (650.0, 0.0), (0.0, 0.0), (25.0, 135.0), + (47.5, 215.0), (56.5, 260.5), (70.0, 325.0), +] +p_back = pattern_api.CreatePatternWithPoints([(x + 900.0, y + 0.0, 0) for (x, y) in _pts_p_back]) +report["ids"] = [p_front, p_back] + +# ---- seams (WHOLE-EDGE only; same line index on mirrored panels) --------- +pattern_api.AddSeamlinePairGroup(p_front, 1, p_back, 11, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 2, p_back, 10, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 3, p_back, 9, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 4, p_back, 8, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 5, p_back, 7, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 11, p_back, 1, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 10, p_back, 2, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 9, p_back, 3, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 8, p_back, 4, False, True) +pattern_api.AddSeamlinePairGroup(p_front, 7, p_back, 5, False, True) + +# Paired edges must be EQUAL, not close: a 0.075 mm 'rounding' difference was +# a slanted edge that notched a waistband. Fail loudly BEFORE wasting a sim. +_pairs = [(p_front, 1, p_back, 11), (p_front, 2, p_back, 10), (p_front, 3, p_back, 9), (p_front, 4, p_back, 8), (p_front, 5, p_back, 7), (p_front, 11, p_back, 1), (p_front, 10, p_back, 2), (p_front, 9, p_back, 3), (p_front, 8, p_back, 4), (p_front, 7, p_back, 5)] +report["seam_lengths"] = [] +_bad = [] +for _pa, _la, _pb, _lb in _pairs: + _fa = pattern_api.GetLineLength(_pa, _la) + _fb = pattern_api.GetLineLength(_pb, _lb) + report["seam_lengths"].append((_pa, _la, round(_fa, 3), _pb, _lb, round(_fb, 3))) + if abs(_fa - _fb) > 0.1: + _bad.append("%s.%s=%.3f vs %s.%s=%.3f" % (_pa, _la, _fa, _pb, _lb, _fb)) +if _bad: + raise RuntimeError("seam length mismatch (construction bug, not rounding): " + + "; ".join(_bad)) + +# ---- fabric (never touch index 0: it is the scene-wide default) --------- +ZFAB = r"C:\Users\Public\Documents\MarvelousDesigner\New Assets\Fabric\(Default for Simulation).zfab" +fab = fabric_api.AddFabric(ZFAB) # a name string silently fails +TEX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\textures\kilt_cloth.png" # (dpi 66.69 -> physical size) +fabric_api.SetBaseTextureMapImageGivenFilePath(TEX, fab) # path is arg 0 +for _p in (p_front, p_back,): + pattern_api.SetPatternPieceFabricIndex(_p, fab) # AssignFabricToPattern never worked + +# ---- arrangement (BY NAME: indices regenerate on every avatar import) ---- +arr = {a["ArrangementName"]: int(a["ArrangementIndex"]) + for a in pattern_api.GetArrangementList()} +pattern_api.SetArrangement(p_front, arr['Leg_Skirt_Front']) +pattern_api.SetArrangement(p_back, arr['Leg_Skirt_Back']) +pattern_api.SetArrangementPosition(p_front, 50, 92, 50) +pattern_api.SetArrangementPosition(p_back, 0, 92, 50) + +# Hand the pattern ids to a later --stage drape/publish in the SAME session. +TINQS_GARMENT = {"garment": 'mako_kilt_v1', "fabric": fab, "patterns": {'front': p_front, 'back': p_back}} + +# THE diagnostic: apply the arrangement and shoot with ZERO sim frames. It shows +# where the panels actually start, before physics muddies it. Reach for this the +# moment a drape misbehaves -- upside-down panels look exactly like seam bugs. +utility_api.ResetClothArrangement() +utility_api.SetCamViewPoint(2) # SetViewPoint() does nothing; 2 = front +presim_snap = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots\mako_kilt_v1_presim.png" +os.makedirs(os.path.dirname(presim_snap), exist_ok=True) +export_api.ExportSnapshot3D(presim_snap) +report['presim_snap'] = presim_snap + +# ---- drape --------------------------------------------------------------- +utility_api.ResetClothArrangement() # SetArrangement only assigns; THIS applies it +report["sim1"] = utility_api.Simulate(200) # synchronous, ~1 min / 300 frames +# Relax only at the end, so the settled shape falls into natural folds. +report["sim2"] = utility_api.Simulate(60) +utility_api.Refresh3DWindow() +utility_api.SetCamViewPoint(2) # SetViewPoint() does nothing; 2 = front +snap = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots\mako_kilt_v1_front.png" +os.makedirs(os.path.dirname(snap), exist_ok=True) +export_api.ExportSnapshot3D(snap) +report['snap'] = snap +# ExportSnapshot3D has NO rear view (SetCamViewPoint 0-7: none is the back). +# Turntable ignores any path arg and reuses the same output names -- copy now. +import shutil +_tt = export_api.ExportTurntableImages(4) # 0 front, 1 side, 2 BACK, 3 side +if _tt and len(_tt) > 2: + back_snap = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots\mako_kilt_v1_back.png" + os.makedirs(os.path.dirname(back_snap), exist_ok=True) + shutil.copyfile(_tt[2], back_snap) + report["back_snapshot"] = back_snap +else: + report["back_snapshot"] = "TURNTABLE RETURNED NOTHING -- rear unverified" + +# ---- publish (all scriptable; the FBX/OBJ flags are the load-bearing part) - +os.makedirs(r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor", exist_ok=True) +xop = ApiTypes.ImportExportOption() +xop.bExportGarment = True +xop.bExportAvatar = False # the avatar is the game body; ship cloth only +report["zprj"] = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\mako_kilt_v1.zprj" +export_api.ExportZPrj(report["zprj"]) # editable source of truth; no SaveProjectFile exists +report["fbx"] = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\mako_kilt_v1_garment.fbx" +export_api.ExportFBX(report["fbx"], xop) # -> clothing/garment_pipeline.py +report["obj"] = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\mako_kilt_v1_garment.obj" +export_api.ExportOBJ(report["obj"], xop) +report["zpac"] = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\mako_kilt_v1.zpac" +export_api.ExportZPac(report["zpac"]) # for outfit assembly (ImportZpac op.bAdd=True) + +result = report diff --git a/tools/tailor/work/mako_kilt_v1/md_script_swap.py b/tools/tailor/work/mako_kilt_v1/md_script_swap.py new file mode 100644 index 0000000..faeee73 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/md_script_swap.py @@ -0,0 +1,148 @@ +# DERIVED from the generated md_script.py (draft_garment.py cannot express this). +# garment : mako_kilt_v1 -- DONOR-ARRANGEMENT VARIANT +# +# WHY: MD's pose/body analyzer rejects every Mako-derived mesh ("Fitting Suit +# cannot be created / avatar must be in T-pose or A-pose") and generates 0 +# arrangement points, so nothing can be arranged on him directly. Lena's mesh +# passes (109 points) and her hips (109.5 cm) are WIDER than Mako's (101.4), so +# cloth arranged on her Leg_Skirt shell circles outside his body too. +# +# FLOW: arrange the kilt on LENA -> add Mako (bAdd, NO arrangement flag, avoids +# the failing analyzer) -> DeleteAvatar(Lena) -> simulate: seams contract the +# kilt onto Mako. Arrangement points may die with Lena; the cloth is already +# positioned and the sim does not need them. +# +# python tools/md_bridge.py --file tools/tailor/work/mako_kilt_v1/md_script_swap.py --timeout 900 + +import os +import shutil + +report = {"garment": 'mako_kilt_v1', "stage": 'swap'} + +# ---- scene: LENA is the arrangement donor --------------------------------- +utility_api.NewProject() +LENA_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Lena_QuatSkin_Avatar.fbx" +MAKO_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx" +op = ApiTypes.ImportExportOption() +# v4: donor scaled +5% -- the avatar swap consistently drops the fitted kilt +# ~8 cm, so fit it ~6 cm higher on a bigger Lena and let the drop land it on +# Mako's target (top 1.054). Elastic still cinches to the same 90 cm. +op.scale = 10.7 # v5: 10.5 measured top=1.022 (2 mm under tol) -- nudge up +op.bAddArrangementPoints = True +op.bAutoTranslate = True +report["lena"] = import_api.ImportFBX(LENA_FBX, op) + +# ---- pattern (identical to generated script) ------------------------------- +# v2: waist narrowed 465 -> 455/panel (91 cm total vs 89.8 body at z=1.054). +# v1 measured 9.6 cm low: the 93 cm waist slid until Mako's hip bulge caught it +# (his 8 cm waist-to-hip drop cannot arrest a slide the way Lena's flare does). +_pts = [ + (97.5, 390.0), (552.5, 390.0), (580.0, 325.0), + (593.5, 260.5), (602.5, 215.0), (625.0, 135.0), + (650.0, 0.0), (0.0, 0.0), (25.0, 135.0), + (47.5, 215.0), (56.5, 260.5), (70.0, 325.0), +] +p_front = pattern_api.CreatePatternWithPoints([(x + 0.0, y + 0.0, 0) for (x, y) in _pts]) +p_back = pattern_api.CreatePatternWithPoints([(x + 900.0, y + 0.0, 0) for (x, y) in _pts]) +report["ids"] = [p_front, p_back] + +for a_l, b_l in ((1, 11), (2, 10), (3, 9), (4, 8), (5, 7), + (11, 1), (10, 2), (9, 3), (8, 4), (7, 5)): + pattern_api.AddSeamlinePairGroup(p_front, a_l, p_back, b_l, False, True) + +_bad = [] +for a_l, b_l in ((1, 11), (2, 10), (3, 9), (4, 8), (5, 7), + (11, 1), (10, 2), (9, 3), (8, 4), (7, 5)): + _fa = pattern_api.GetLineLength(p_front, a_l) + _fb = pattern_api.GetLineLength(p_back, b_l) + if abs(_fa - _fb) > 0.1: + _bad.append("front.%d=%.3f vs back.%d=%.3f" % (a_l, _fa, b_l, _fb)) +if _bad: + raise RuntimeError("seam length mismatch: " + "; ".join(_bad)) + +# ---- fabric ---------------------------------------------------------------- +ZFAB = r"C:\Users\Public\Documents\MarvelousDesigner\New Assets\Fabric\(Default for Simulation).zfab" +fab = fabric_api.AddFabric(ZFAB) +TEX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\textures\kilt_cloth.png" +fabric_api.SetBaseTextureMapImageGivenFilePath(TEX, fab) +for _p in (p_front, p_back): + pattern_api.SetPatternPieceFabricIndex(_p, fab) + +# ---- arrangement on Lena ---------------------------------------------------- +arr = {a["ArrangementName"]: int(a["ArrangementIndex"]) + for a in pattern_api.GetArrangementList()} +report["arrangement_count"] = len(arr) +pattern_api.SetArrangement(p_front, arr['Leg_Skirt_Front']) +pattern_api.SetArrangement(p_back, arr['Leg_Skirt_Back']) +# v2: y raised 92 -> 96 to start the drape higher (v1 settled 9.6 cm low); +# verify against the presim snapshot before trusting the sim. +pattern_api.SetArrangementPosition(p_front, 50, 96, 50) +pattern_api.SetArrangementPosition(p_back, 0, 96, 50) +utility_api.ResetClothArrangement() + +SHOTS = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots" +os.makedirs(SHOTS, exist_ok=True) +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "mako_kilt_v1_presim_lena.png")) + +# ---- v3: FIT ON LENA FIRST --------------------------------------------------- +# v2 finding: the avatar swap itself drops loose arranged panels ~10 cm before +# any sim runs. So sew + settle + elastic-lock the kilt on Lena (known-good +# v8-style drape), and only THEN swap bodies -- a fitted, cinched garment +# survives the swap and just re-settles onto Mako. +report["sim_lena_a"] = utility_api.Simulate(30) +for _p in (p_front, p_back): + pattern_api.SetPatternPieceElastic(_p, 0, True) + pattern_api.SetPatternPieceElasticTotalLength(_p, 0, 450) + # v6: default strength never reached the 90 cm target -- the kilt rested + # where Mako's body fills ~93 cm (z=1.02). Stiffen the cinch. + pattern_api.SetPatternPieceElasticStrength(_p, 0, 60) +report["sim_lena_b"] = utility_api.Simulate(120) +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "mako_kilt_v1_fitted_lena.png")) + +# ---- THE SWAP --------------------------------------------------------------- +op2 = ApiTypes.ImportExportOption() +op2.scale = 10.0 +op2.bAdd = True # add alongside Lena +op2.bAutoTranslate = True +op2.bAddArrangementPoints = False # skip the analyzer that fails on Mako +report["mako"] = import_api.ImportFBX(MAKO_FBX, op2) +names = [str(n) for n in export_api.GetAvatarNameList()] +report["avatars_before_delete"] = names +lena_idx = next(i for i, n in enumerate(names) if "Lena" in n) +report["deleted_lena"] = utility_api.DeleteAvatar([lena_idx]) +report["avatars_after_delete"] = [str(n) for n in export_api.GetAvatarNameList()] +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "mako_kilt_v1_presim.png")) + +# ---- re-settle onto Mako ------------------------------------------------------ +report["sim1"] = utility_api.Simulate(120) +report["sim2"] = utility_api.Simulate(40) +utility_api.Refresh3DWindow() +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "mako_kilt_v1_front.png")) +_tt = export_api.ExportTurntableImages(4) # 0 front, 1 side, 2 BACK, 3 side +if _tt and len(_tt) > 2: + shutil.copyfile(_tt[0], os.path.join(SHOTS, "mako_kilt_v1_front_hd.png")) + shutil.copyfile(_tt[1], os.path.join(SHOTS, "mako_kilt_v1_side_hd.png")) + shutil.copyfile(_tt[2], os.path.join(SHOTS, "mako_kilt_v1_back.png")) + report["back_snapshot"] = "ok" +else: + report["back_snapshot"] = "TURNTABLE RETURNED NOTHING" + +# ---- publish ----------------------------------------------------------------- +OUTD = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor" +xop = ApiTypes.ImportExportOption() +xop.bExportGarment = True +xop.bExportAvatar = False +report["zprj"] = os.path.join(OUTD, "mako_kilt_v1.zprj") +export_api.ExportZPrj(report["zprj"]) +report["fbx"] = os.path.join(OUTD, "mako_kilt_v1_garment.fbx") +export_api.ExportFBX(report["fbx"], xop) +report["obj"] = os.path.join(OUTD, "mako_kilt_v1_garment.obj") +export_api.ExportOBJ(report["obj"], xop) +report["zpac"] = os.path.join(OUTD, "mako_kilt_v1.zpac") +export_api.ExportZPac(report["zpac"]) + +result = report diff --git a/tools/tailor/work/mako_kilt_v1/md_script_v2b.py b/tools/tailor/work/mako_kilt_v1/md_script_v2b.py new file mode 100644 index 0000000..9d0edb2 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/md_script_v2b.py @@ -0,0 +1,168 @@ +# mako_kilt_v2b -- sewn tube (v6-proven) + comb-teeth fringe + overlap flap. +# +# The seamless single-wrap (true flat pattern) slides off Mako's legs during the +# avatar swap -- only a SEWN garment survives it (v1-v6 evidence). So: front/back +# panels cross-pair-seamed as in v6, teeth carved INTO the bottom 30 mm (solid +# hem 0.695, tips 0.665 -- exactly the flat pattern's "3 cm fringe at bottom +# edge"), and the wrap slit faked with a flap panel sewn to a 300 mm segment of +# the front waist, hanging free over the front-right. +# +# Run: python tools/md_bridge.py --file tools/tailor/work/mako_kilt_v1/md_script_v2b.py --timeout 900 +import os +import shutil + +report = {"garment": "mako_kilt_v2b"} + +utility_api.NewProject() +op = ApiTypes.ImportExportOption() +op.scale = 10.7 # donor upscale counters the ~5 cm swap drop +op.bAddArrangementPoints = True +op.bAutoTranslate = True +report["lena"] = import_api.ImportFBX( + r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Lena_QuatSkin_Avatar.fbx", op) + +# ---- panel builder: v6 shape, teeth carved into the hem ---------------------- +PITCH, STRAND, TEETH_D = 24.0, 17.0, 30.0 + +def teeth(x_from, x_to, y_solid): + """Comb teeth from x_from rightward-edge to x_to (x_from > x_to).""" + pts = [] + x = x_from + while x - PITCH >= x_to - 0.5: + pts.append((x, 0.0, 0)) + pts.append((x - STRAND, 0.0, 0)) + pts.append((x - STRAND, y_solid, 0)) + pts.append((x - PITCH, y_solid, 0)) + x -= PITCH + return pts + +def kilt_panel(dx, split_waist): + """v6 outline with teeth. Returns (points, line_index_map).""" + pts = [] + if split_waist: + pts += [(97.5, 390.0, 0), (252.5, 390.0, 0), (552.5, 390.0, 0)] + waist_lines = [0, 1] # 155 mm + 300 mm (flap sews to line 1) + else: + pts += [(97.5, 390.0, 0), (552.5, 390.0, 0)] + waist_lines = [0] + right_start = len(pts) - 1 + pts += [(580.0, 325.0, 0), (593.5, 260.5, 0), (602.5, 215.0, 0), + (625.0, 135.0, 0), (650.0, TEETH_D, 0)] + right_lines = list(range(right_start, right_start + 5)) + pts += teeth(650.0, 0.0, TEETH_D) + pts.append((0.0, TEETH_D, 0)) + n = len(pts) # lines: n total (closing edge = last) + pts_final = [(x + dx, y, t) for (x, y, t) in pts] + left_lines = [n - 1, n - 2, n - 3, n - 4] + # left side has 4 shaping pts + closing edge = 5 segments (mirror of right) + pts_left = [(25.0 + dx, 135.0, 0), (47.5 + dx, 215.0, 0), + (56.5 + dx, 260.5, 0), (70.0 + dx, 325.0, 0)] + pts_final += pts_left + total = len(pts_final) + left_lines = [total - 5, total - 4, total - 3, total - 2, total - 1] + return pts_final, waist_lines, right_lines, left_lines + +f_pts, f_waist, f_right, f_left = kilt_panel(0.0, split_waist=True) +b_pts, b_waist, b_right, b_left = kilt_panel(900.0, split_waist=False) +p_front = pattern_api.CreatePatternWithPoints(f_pts) +p_back = pattern_api.CreatePatternWithPoints(b_pts) +report["front_lines"] = {"waist": f_waist, "right": f_right, "left_last5": f_left} +report["back_lines"] = {"waist": b_waist, "right": b_right, "left_last5": b_left} + +# ---- overlap flap: 300 top / 340 hem trapezoid, same teeth + band ------------ +fl = [(0.0, 390.0, 0), (300.0, 390.0, 0), (320.0, TEETH_D, 0)] +fl += teeth(320.0, -20.0, TEETH_D) +fl.append((-20.0, TEETH_D, 0)) +fl = [(x + 1800.0, y, t) for (x, y, t) in fl] +p_flap = pattern_api.CreatePatternWithPoints(fl) +report["ids"] = [p_front, p_back, p_flap] + +# ---- seams ------------------------------------------------------------------- +# sides: cross-paired, ONE reversed (v8 discovery). right side of front pairs +# with the left side of back, top-to-top. +_bad = [] +for i in range(5): + a_l, b_l = f_right[i], b_left[4 - i] + pattern_api.AddSeamlinePairGroup(p_front, a_l, p_back, b_l, False, True) + if abs(pattern_api.GetLineLength(p_front, a_l) - pattern_api.GetLineLength(p_back, b_l)) > 0.1: + _bad.append("R%d" % i) +for i in range(5): + a_l, b_l = f_left[4 - i], b_right[i] + pattern_api.AddSeamlinePairGroup(p_front, a_l, p_back, b_l, False, True) + if abs(pattern_api.GetLineLength(p_front, a_l) - pattern_api.GetLineLength(p_back, b_l)) > 0.1: + _bad.append("L%d" % i) +# flap top (line 0, 300 mm) onto front waist segment (line 1, 300 mm) +pattern_api.AddSeamlinePairGroup(p_flap, 0, p_front, 1, False, False) +if abs(pattern_api.GetLineLength(p_flap, 0) - pattern_api.GetLineLength(p_front, 1)) > 0.1: + _bad.append("FLAP") +if _bad: + raise RuntimeError("seam length mismatch: " + ",".join(_bad)) + +# ---- fabric ------------------------------------------------------------------- +fab = fabric_api.AddFabric(r"C:\Users\Public\Documents\MarvelousDesigner\New Assets\Fabric\(Default for Simulation).zfab") +fabric_api.SetBaseTextureMapImageGivenFilePath( + r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\textures\kilt_cloth.png", fab) +for _p in (p_front, p_back, p_flap): + pattern_api.SetPatternPieceFabricIndex(_p, fab) + +# ---- arrangement --------------------------------------------------------------- +arr = {a["ArrangementName"]: int(a["ArrangementIndex"]) + for a in pattern_api.GetArrangementList()} +pattern_api.SetArrangement(p_front, arr["Leg_Skirt_Front"]) +pattern_api.SetArrangement(p_back, arr["Leg_Skirt_Back"]) +pattern_api.SetArrangement(p_flap, arr["Leg_Skirt_Front"]) +pattern_api.SetArrangementPosition(p_front, 50, 96, 50) +pattern_api.SetArrangementPosition(p_back, 0, 96, 50) +pattern_api.SetArrangementPosition(p_flap, 65, 96, 70) # right of front, farther out +utility_api.ResetClothArrangement() + +SHOTS = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots" +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "kilt_v2b_presim_lena.png")) + +# ---- fit on donor: settle then elastic (v6 recipe, strength 60) --------------- +report["sim_a"] = utility_api.Simulate(30) +for _p, lines_totals in ((p_front, ((0, 152), (1, 298))), (p_back, ((0, 450),))): + for ln, total in lines_totals: + pattern_api.SetPatternPieceElastic(_p, ln, True) + pattern_api.SetPatternPieceElasticTotalLength(_p, ln, total) + pattern_api.SetPatternPieceElasticStrength(_p, ln, 60) +report["sim_b"] = utility_api.Simulate(120) + +# ---- swap ---------------------------------------------------------------------- +op2 = ApiTypes.ImportExportOption() +op2.scale = 10.0 +op2.bAdd = True +op2.bAutoTranslate = True +op2.bAddArrangementPoints = False +report["mako"] = import_api.ImportFBX( + r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx", op2) +names = [str(n) for n in export_api.GetAvatarNameList()] +lena_idx = next(i for i, n in enumerate(names) if "Lena" in n) +report["deleted_lena"] = utility_api.DeleteAvatar([lena_idx]) +report["sim1"] = utility_api.Simulate(120) +report["sim2"] = utility_api.Simulate(40) +utility_api.Refresh3DWindow() +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "mako_kilt_v2_front.png")) +_tt = export_api.ExportTurntableImages(4) +if _tt and len(_tt) > 2: + shutil.copyfile(_tt[0], os.path.join(SHOTS, "mako_kilt_v2_front_hd.png")) + shutil.copyfile(_tt[1], os.path.join(SHOTS, "mako_kilt_v2_side_hd.png")) + shutil.copyfile(_tt[2], os.path.join(SHOTS, "mako_kilt_v2_back.png")) + report["back_snapshot"] = "ok" + +OUTD = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor" +xop = ApiTypes.ImportExportOption() +xop.bExportGarment = True +xop.bExportAvatar = False +report["zprj"] = os.path.join(OUTD, "mako_kilt_v2.zprj") +export_api.ExportZPrj(report["zprj"]) +report["fbx"] = os.path.join(OUTD, "mako_kilt_v2_garment.fbx") +export_api.ExportFBX(report["fbx"], xop) +report["obj"] = os.path.join(OUTD, "mako_kilt_v2_garment.obj") +export_api.ExportOBJ(report["obj"], xop) +report["zpac"] = os.path.join(OUTD, "mako_kilt_v2.zpac") +export_api.ExportZPac(report["zpac"]) + +result = report diff --git a/tools/tailor/work/mako_kilt_v1/wrap_finish.py b/tools/tailor/work/mako_kilt_v1/wrap_finish.py new file mode 100644 index 0000000..c420435 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/wrap_finish.py @@ -0,0 +1,80 @@ +# mako_kilt_v2 STAGE 2: continue the SAME session after wrap_probe.py (exec +# namespace persists -- p_wrap and fab come from the probe). Elastic-lock the +# waist, settle on the Lena donor, swap to Mako, re-settle, QC shots, export. +# Run: python tools/md_bridge.py --file tools/tailor/work/mako_kilt_v1/wrap_finish.py --timeout 900 +import os +import shutil + +report = {"stage": "wrap_finish", "pattern": p_wrap} + +# finer sim mesh so the 17 mm teeth actually simulate (default 20 mm skips them) +for fname in ("SetPatternParticleDistance", "SetParticleDistance"): + f = getattr(pattern_api, fname, None) + if f: + try: + f(p_wrap, 10.0) + report["particle_distance"] = fname + break + except Exception as e: + report["particle_distance_error"] = "%s: %s" % (fname, e) + +# elastic waist: single wrap panel, line 0 = full top edge. +# total 910 mm = 91 cm target waist; strength 60 (v6 finding: default never gets there) +pattern_api.SetPatternPieceElastic(p_wrap, 0, True) +pattern_api.SetPatternPieceElasticTotalLength(p_wrap, 0, 910) +pattern_api.SetPatternPieceElasticStrength(p_wrap, 0, 60) + +report["sim_lena_a"] = utility_api.Simulate(40) +report["sim_lena_b"] = utility_api.Simulate(120) +SHOTS = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots" +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "kilt_v2_fitted_lena.png")) + +# ---- swap to Mako ------------------------------------------------------------ +MAKO_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Mako_Fullhead_QuatSkin_Avatar_fingers.fbx" +op2 = ApiTypes.ImportExportOption() +op2.scale = 10.0 +op2.bAdd = True +op2.bAutoTranslate = True +op2.bAddArrangementPoints = False +report["mako"] = import_api.ImportFBX(MAKO_FBX, op2) +names = [str(n) for n in export_api.GetAvatarNameList()] +lena_name = next(n for n in names if "Lena" in n) +# v2b: DEACTIVATE the donor instead of deleting her. DeleteAvatar drops the +# cloth into free fall and the seamless wrap slid off Mako's legs to the floor. +# Deactivation should kill her collision in place, letting the cinched ring +# contract through her ghost body onto Mako with no fall. +report["deactivated_lena"] = utility_api.SetAvatarActivation(lena_name, False) +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "kilt_v2_presim.png")) + +report["sim1"] = utility_api.Simulate(120) +report["sim2"] = utility_api.Simulate(60) +# only now remove her from the scene for clean QC renders +lena_idx = next(i for i, n in enumerate([str(n) for n in export_api.GetAvatarNameList()]) if "Lena" in n) +report["deleted_lena"] = utility_api.DeleteAvatar([lena_idx]) +report["sim3"] = utility_api.Simulate(30) +utility_api.Refresh3DWindow() +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "mako_kilt_v2_front.png")) +_tt = export_api.ExportTurntableImages(4) +if _tt and len(_tt) > 2: + shutil.copyfile(_tt[0], os.path.join(SHOTS, "mako_kilt_v2_front_hd.png")) + shutil.copyfile(_tt[1], os.path.join(SHOTS, "mako_kilt_v2_side_hd.png")) + shutil.copyfile(_tt[2], os.path.join(SHOTS, "mako_kilt_v2_back.png")) + report["back_snapshot"] = "ok" + +OUTD = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor" +xop = ApiTypes.ImportExportOption() +xop.bExportGarment = True +xop.bExportAvatar = False +report["zprj"] = os.path.join(OUTD, "mako_kilt_v2.zprj") +export_api.ExportZPrj(report["zprj"]) +report["fbx"] = os.path.join(OUTD, "mako_kilt_v2_garment.fbx") +export_api.ExportFBX(report["fbx"], xop) +report["obj"] = os.path.join(OUTD, "mako_kilt_v2_garment.obj") +export_api.ExportOBJ(report["obj"], xop) +report["zpac"] = os.path.join(OUTD, "mako_kilt_v2.zpac") +export_api.ExportZPac(report["zpac"]) + +result = report diff --git a/tools/tailor/work/mako_kilt_v1/wrap_probe.py b/tools/tailor/work/mako_kilt_v1/wrap_probe.py new file mode 100644 index 0000000..e10e0a9 --- /dev/null +++ b/tools/tailor/work/mako_kilt_v1/wrap_probe.py @@ -0,0 +1,67 @@ +# mako_kilt_v2 STAGE 1: single-wrap-panel probe (TRUE flat-pattern construction). +# One 1210 mm panel (91 cm waist + 30 cm overlap), A-line to 1550 mm, comb-teeth +# fringe hem (piupiu technique). Arrange on the Lena donor, ZERO sim frames, +# snapshot front + top -- the top view shows how far the arrangement wraps. +# Run: python tools/md_bridge.py --file tools/tailor/work/mako_kilt_v1/wrap_probe.py --timeout 600 +import os + +report = {"stage": "wrap_probe"} + +utility_api.NewProject() +LENA_FBX = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\avatar\Lena_QuatSkin_Avatar.fbx" +op = ApiTypes.ImportExportOption() +op.scale = 10.7 # donor upscale (v6 finding: swap drops ~5 cm) +op.bAddArrangementPoints = True +op.bAutoTranslate = True +report["lena"] = import_api.ImportFBX(LENA_FBX, op) + +# ---- single wrap panel with comb-teeth hem --------------------------------- +W_TOP, W_HEM, H, TEETH_D = 1210.0, 1550.0, 390.0, 30.0 +PITCH, STRAND = 24.0, 17.0 # tooth pitch / strand width (gap 7 mm) +flare = (W_HEM - W_TOP) / 2.0 + +pts = [(0.0, H, 0), (W_TOP, H, 0)] # line 0 = waist (top edge) +pts.append((W_TOP + flare, TEETH_D, 0)) # right edge down to teeth top +# teeth right -> left along the hem +x = W_TOP + flare +left_end = -flare +n_teeth = 0 +while x - PITCH >= left_end - 0.5: + pts.append((x, 0.0, 0)) # strand outer-bottom + pts.append((x - STRAND, 0.0, 0)) # strand inner-bottom + pts.append((x - STRAND, TEETH_D, 0)) # back up + pts.append((x - PITCH, TEETH_D, 0)) # gap to next strand + x -= PITCH + n_teeth += 1 +pts.append((left_end, TEETH_D, 0)) # ensure left hem corner +# left edge back up to the waist closes the polygon automatically +report["n_teeth"] = n_teeth +report["n_points"] = len(pts) + +p_wrap = pattern_api.CreatePatternWithPoints(pts) +report["pattern_id"] = p_wrap + +# fabric + v2 texture +fab = fabric_api.AddFabric(r"C:\Users\Public\Documents\MarvelousDesigner\New Assets\Fabric\(Default for Simulation).zfab") +fabric_api.SetBaseTextureMapImageGivenFilePath( + r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\textures\kilt_cloth.png", fab) +pattern_api.SetPatternPieceFabricIndex(p_wrap, fab) + +# arrange -- x=50 centres the panel on the front; overlap ends land at the back. +# If the top view shows a half-shell or wrong overlap angle, sweep x next. +arr = {a["ArrangementName"]: int(a["ArrangementIndex"]) + for a in pattern_api.GetArrangementList()} +report["arrangement_count"] = len(arr) +pattern_api.SetArrangement(p_wrap, arr["Leg_Skirt_Front"]) +pattern_api.SetArrangementPosition(p_wrap, 50, 96, 50) +utility_api.ResetClothArrangement() + +SHOTS = r"C:\Users\CAN\tinqs-ltd\animation\tools\tailor\screenshots" +os.makedirs(SHOTS, exist_ok=True) +utility_api.SetCamViewPoint(2) +export_api.ExportSnapshot3D(os.path.join(SHOTS, "kilt_v2_probe_front.png")) +utility_api.SetCamViewPoint(5) # top-down: the ONLY view that shows wrap +export_api.ExportSnapshot3D(os.path.join(SHOTS, "kilt_v2_probe_top.png")) +utility_api.SetCamViewPoint(2) + +result = report