tailor(mako): kilt v2 on Mako via Lena donor-swap -- MD rejects all Mako meshes

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>
This commit is contained in:
Can Narin
2026-08-18 16:21:16 +03:00
parent 9ac741302e
commit fea96bf582
47 changed files with 1450 additions and 0 deletions
+26
View File
@@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.
+10
View File
@@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

+12
View File
@@ -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
Binary file not shown.
Binary file not shown.
+10
View File
@@ -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
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

+186
View File
@@ -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()
@@ -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)
@@ -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])
@@ -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])
@@ -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")
+122
View File
@@ -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"
}
}
}
}
+166
View File
@@ -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 <this 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
@@ -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
@@ -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
@@ -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
@@ -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