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>
@@ -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
|
||||||
|
After Width: | Height: | Size: 129 KiB |
@@ -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
|
||||||
|
After Width: | Height: | Size: 130 KiB |
@@ -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
|
||||||
@@ -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
|
||||||
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 364 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 370 KiB |
|
After Width: | Height: | Size: 292 KiB |
|
After Width: | Height: | Size: 314 KiB |
@@ -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")
|
||||||
|
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||