Files
animation/tools/tailor/md_pari_v4.py
T
jeremy 3ba86b2ea8 feat: clothing lane, character sources, and DCC bridges
Bulk import of the working lanes that were living untracked on the PC.

Content:
- characters/  Lena/male body lanes, bakes, texture work, run logs
- clothing/    garment pipeline, configs, gates, contract docs
- garments/    MD-authored garment sources (.zprj/.zpac)
- UAL-Lib/     Universal Animation Library 2 source (.blend/.fbx/.glb)
- tools/       blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent
- docs/, plans/, dev/, .agents/plans/

Repo hygiene:
- .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the
  Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the
  ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out
  on purpose — ~250 are already tracked raw and converting them would
  rewrite every one without shrinking history.
- .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable
  from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies
  dropped by a `>/dev/null` redirect on Windows).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:55:43 -07:00

280 lines
17 KiB
Python

# topTest1 v4 (MD source keeps the old "pari" filename on purpose — sources are
# versioned and referenced by backup/2026-07-31-pre-v3/MANIFEST.md).
#
# python tools/md_bridge.py --file tools/tailor/md_pari_v4.py --timeout 880
#
# ── ROOT CAUSE: A TWISTED SIDE SEAM ───────────────────────────────────
# The shipped top's back does not lie flat. md_pari.py sews all four seams with
# AddSeamlinePairGroup(..., False, False), and for the SIDE seams that is wrong: it
# twists the panel, so part of the back turns inside out and rucks up, leaving the
# lower back bare. The giveaway only shows on an UNTEXTURED render — the cloth's
# REVERSE FACE appears as a big pale diagonal flap.
#
# The skill's rule reads "pair the SAME line index on MIRRORED front/back panels
# with (False, False)". The operative word is *mirrored*. These panels are NOT
# mirrored — panel() returns the identical point list for both, only offset by dx —
# so front-right and back-right run in opposite directions once wrapped onto the
# body, and joining them needs both edges reversed: (True, True).
# md_piupiu_v3.py never hit this because it pairs right<->left, which is equivalent.
#
# Proven by sweeping all four flag combos on the side seams at fixed geometry
# (tools/tailor/md_pari_seamsweep.py): (0,0), (0,1) and (1,0) all produce the
# reverse-face flap; only (1,1) gives a smooth back. SHOULDER seams stay at
# (False, False) — they are correct, which is why the front always looked right.
#
# ── ALSO TRIED AND REVERTED (the reasons are the value) ───────────────────
# 1. WIDTH REBALANCE. Both panels were 550 mm, splitting a 1083 mm bust evenly;
# the bust takes arc from the front, so the back looked like it held surplus.
# 570/510 is KEPT because it is correct tailoring — the side seam belongs
# slightly rearward — but it was not the cause and did not fix the back.
# 600/480 was worse: a 480 back cannot reach the side seams, so it dragged the
# front panel and its centre motif around onto the back.
# 2. STRENGTHEN. md_pari.py never calls SetPatternStrengthen and the failure
# catalogue blames soft-from-frame-0 for bunching, so this looked like THE fix.
# It flattens the back but ROTATES the garment: sides are sewn only over
# y 0-185 and straps are 90 mm, so a stiffened panel is under-constrained.
# Flat but twisted, hem uneven, skin showing. Pre-sim was clean, so it develops
# in the sim. The catalogue rule is real but not universal — it applies to
# garments whose sides are fully sewn.
# 3. SHORTER BACK PANEL (TOP_BACK 315). The front path runs shoulder -> over the
# bust -> waist while the back runs straight, so surplus back LENGTH was a good
# theory. Changed almost nothing; the fold survived. Left equal at 350.
# 4. CROSSING EVERY SEAM (7<->9 and 0<->5). Garment slides to the hips —
# cross-wired straps pull against each other, cancel, and it falls off the
# shoulders. MD does not flip pattern-x for a back arrangement; the fix is the
# reversal flags, not the pairing.
#
# ── THE QC HABIT THIS EXPOSED ────────────────────────────────────
# SetCamViewPoint has NO REAR VIEW (all 8 shot: 0 bottom, 1 and 3 front quadrants,
# 2 front, 4/6/7 sides, 5 top), so ExportSnapshot3D cannot show the back and every
# screenshot in this repo was a front view. That is how a twisted seam shipped. Use
# export_api.ExportTurntableImages(4) — index 2 is the back; it ignores its path arg
# in the (int) overload and writes into MD's own output folder, so the return value
# is the only way to find the files. And judge SHAPE with PLAIN=True first.
#
# QC targets: band top ~1.31 m (above bust), hem ~1.05 m (waist) +-3 cm. G1 measured
# v3's hem +3.9 cm high, so check placement too.
#
# Lines: 0 shoulder_L | 1-4 scoop (open) | 5 shoulder_R |
# 6 armhole_R | 7 side_R | 8 hem | 9 side_L | 10 armhole_L
import os
import tempfile
# ── parameters ───────────────────────────────────────────────────────────────
W_FRONT = 570.0 # mm at the bust.
W_BACK = 510.0 # total 1080 vs a 1083 mm bust. See ALSO TRIED #1.
TOP = 350.0 # front: shoulder -> over the bust -> waist
TOP_BACK = 350.0 # equal; see ALSO TRIED #3.
SCOOP_FRONT = 105.0
SCOOP_BACK = 15.0 # was 45. A 45 mm scoop puts the back neckline BELOW the body's
# baked-in tank neckline, so the tank shows as a cream band at the
# back neck - which reads as the shirt not meeting the back. Covering
# that baked-in underwear is the whole reason the straps exist
# (see the skill: 'straps ... cover the avatar's baked-in bra straps').
SIDE_Y = 185.0 # side seams run y=0..185; above that is open armhole. y=185 is
# about the bust line (bust z 1.264 maps to y~199), y=0 the waist.
ARMHOLE_IN = 0.0 # TESTED at 55: the diagonal crease survives unchanged, so surplus
# upper width is NOT the cause. Left disabled. Was 55.0: # mm pulled inward at mid-armhole, making a concave scye. Jeremy's
# hypothesis: the persistent diagonal back crease is surplus WIDTH
# bunching where the panels meet. The numbers back it, but ABOVE the
# bust: bust 570+510=1080 vs 1083 and hem 380+320=700 vs waist 675 are
# both matched, while the panels stay near-full width up to the armhole
# as the body narrows (~968 mm of cloth at underarm height). The side
# seam only runs y 0..185, so above that the edges are FREE and the
# surplus can overlap. Narrowing here leaves bust width, hem taper and
# strap position alone - moving the straps would re-expose the body's
# baked-in bra straps, which is what the straps are for. 0 disables.
ARMHOLE_H = 70.0 # mid-armhole point sits this far above SIDE_Y (so y = 255)
TAPER = 95.0 # mm taken off EACH side edge at the hem, so the panel narrows from
# bust width to waist width. Without it the panels are rectangles
# cut for a 1083 mm bust while the hem sits on a 675 mm waist - some
# 400 mm of surplus that can only fold, which is the residual
# diagonal flap at the lower back (it GREW from 200 to 300 sim
# frames, i.e. never settles). Hems become 380 + 320 = 700 vs waist
# 675 + 25 ease. The SAME taper on both panels is deliberate: side
# seam length is sqrt(TAPER^2 + SIDE_Y^2) = 208 mm on both, so the
# whole-edge pairing stays matched. Different tapers would skew it.
MIRROR_BACK = False # THE CREASE'S REAL CAUSE, but NOT SHIPPED - read this before touching it.
# Mirror + index remap (the PAIRS block below) DOES eliminate the
# diagonal back crease completely: the back comes out flat and smooth.
# Proof that the crease was the inside-out back panel, not surplus
# cloth. But it changes the effective fit and coverage regresses: at
# W_BACK 510 the back panel under-covers (body's tank shows at the
# lower back and the front wraps round the sides), and at 560 it
# collapses into a diagonal band - worse than either. A correct v5
# would enable this and then re-fit W_BACK / TAPER / TOP_BACK from
# scratch, somewhere between 510 and 560. Shipping the un-mirrored
# version because coverage beats a crease. Mirroring PERMUTES the line
# indices, so same-index pairing stops joining the right edges and the
# garment falls off the body entirely. The winding observation below
# is REAL but cosmetic here - the back panel does render its back face
# outward, and Blender can recalculate normals downstream. Kept as a
# flag because a correct fix would mirror AND remap the pair indices.
# Original note: THE ACTUAL ROOT CAUSE. Cloth renders WHITE on its front face and
# GREY on its back face. Untextured, the FRONT view is white (correct,
# front face outward) but the BACK view is GREY - the back panel is
# INSIDE OUT, and every 'reverse-face flap' chased below was that.
# Both panels were drafted with the SAME winding, so when MD arranges
# one behind the body its front face points INTO the body. Mirroring
# the back panel's x flips the winding; the outline is symmetric about
# its centre, so the geometry is unchanged. This is also what makes the
# skill's rule apply AS WRITTEN - it says 'same line index on MIRRORED
# front/back panels with (False, False)'. Mine were never mirrored.
SIDE_FLAGS = (True, True) # required while MIRROR_BACK is False. See above.
SHOULDER_FLAGS = (False, False)
ARR_X_FRONT = 50 # md_pari.py used front=50 / back=0, which is ASYMMETRIC and gave a
ARR_X_BACK = 50 # folded flap on the RIGHT shoulder only (left shoulder clean).
# An asymmetric defect needs an asymmetric input, and this is the only
# one. 50 reads as centred; 0 puts the back panel off-centre so the two
# shoulder seams do not line up. md_piupiu.py carries the same 50/0
# pair, so it looks inherited rather than intended.
STRENGTHEN = False # see ALSO TRIED #2 — it rotates this garment.
PLAIN = False # Skip the texture. The taniko motif is busy enough to hide a fold
# in the geometry - the "second fabric layer" read off the textured
# back render may be motif, not an overlap. Judge SHAPE untextured,
# then re-enable for the shipping render.
EXPORT = True # front, side and back all verified flat 2026-07-31
VERSION = "v4"
AVATAR_FBX = r"C:\Users\Jeremy\tinqs\animation\tools\tailor\avatar\Lena_QuatSkin_Avatar.fbx"
ZFAB = r"C:\Users\Public\Documents\MarvelousDesigner\New Assets\Fabric\(Default for Simulation).zfab"
TEX = r"C:\Users\Jeremy\tinqs\animation\tools\tailor\textures\taniko.png"
OUT = r"C:\Users\Jeremy\tinqs\animation\tools\tailor"
report = {"params": {"W_FRONT": W_FRONT, "W_BACK": W_BACK,
"TOP": TOP, "TOP_BACK": TOP_BACK,
"SCOOP_FRONT": SCOOP_FRONT, "SCOOP_BACK": SCOOP_BACK,
"STRENGTHEN": STRENGTHEN, "EXPORT": EXPORT}}
utility_api.NewProject() # NB: deletes the avatar — import after
op = ApiTypes.ImportExportOption()
op.scale = 10.0
op.bAddArrangementPoints = True
op.bAutoTranslate = True
report["avatar"] = import_api.ImportFBX(AVATAR_FBX, op)
def panel(width, scoop, dx, top=TOP, mirror=False, arm=0.0):
"""Return (pattern_id, line_index_map).
Line indices are COMPUTED, not hardcoded: inserting the armhole mid-point shifts
every index after it, and hardcoded 0/5/7/9 has already bitten this recipe twice.
"""
c = width / 2.0
pts = [(c - 210.0, top - 25.0), # strapL_outer
(c - 120.0, top), # strapL_inner -> line 0 = shoulder L
(c - 70.0, top - scoop * 0.75),
(c, top - scoop),
(c + 70.0, top - scoop * 0.75),
(c + 120.0, top), # strapR_inner -> line 5 = shoulder R
(c + 210.0, top - 25.0)] # strapR_outer
if arm:
pts.append((width - arm, SIDE_Y + ARMHOLE_H)) # concave scye, right
pts += [(width, SIDE_Y), (width - TAPER, 0.0), (TAPER, 0.0), (0.0, SIDE_Y)]
if arm:
pts.append((arm, SIDE_Y + ARMHOLE_H)) # concave scye, left
n_arm = 1 if arm else 0
side_r = 6 + n_arm + 1 # sideR_top -> hemR
idx = {"shoulder_l": 0, "shoulder_r": 5,
"side_r": side_r, "hem": side_r + 1, "side_l": side_r + 2}
if mirror:
# x -> width - x, list order kept: same geometry (symmetric outline), opposite
# winding. DISPROVED as a fix on its own - see MIRROR_BACK above.
pts = [(width - x, y) for (x, y) in pts]
return pattern_api.CreatePatternWithPoints([(x + dx, y, 0) for (x, y) in pts]), idx
pf, ixf = panel(W_FRONT, SCOOP_FRONT, 0.0, TOP, False, ARMHOLE_IN)
pb, ixb = panel(W_BACK, SCOOP_BACK, 900.0, TOP_BACK, MIRROR_BACK, ARMHOLE_IN)
report["ids"] = [pf, pb]
# 0/5 = shoulders, 7/9 = sides. All four must use the SAME convention: if the back
# panel's pattern-x is flipped when arranged behind the body, every pair crosses;
# if it isn't, none do. Mixing them is geometrically impossible.
# Mirroring the back panel reverses its edge order, so the back's "shoulder_l" edge
# is physically the RIGHT shoulder. Pair across, and drop the reversal flags — the
# mirror already supplies the reversal. Mirror WITHOUT this remap was tried first and
# dropped the garment on the floor.
if MIRROR_BACK:
PAIRS = [("shoulder_l", "shoulder_r"), ("shoulder_r", "shoulder_l"),
("side_r", "side_l"), ("side_l", "side_r")]
FLAGS = {"shoulder": (False, False), "side": (False, False)}
else:
PAIRS = [("shoulder_l", "shoulder_l"), ("shoulder_r", "shoulder_r"),
("side_r", "side_r"), ("side_l", "side_l")]
FLAGS = {"shoulder": SHOULDER_FLAGS, "side": SIDE_FLAGS}
for kf, kb in PAIRS:
fl = FLAGS["shoulder" if kf.startswith("shoulder") else "side"]
pattern_api.AddSeamlinePairGroup(pf, ixf[kf], pb, ixb[kb], *fl)
report["pairs"] = PAIRS
# Fingerprint the paired edges — mismatched lengths here is what skews a shoulder.
try:
# Shoulders must read ~93 mm and sides ~208 mm. If they drift, the index maths
# is wrong, not the cloth.
report["seam_lengths"] = {
k: [round(pattern_api.GetLineLength(pf, ixf[k]), 1),
round(pattern_api.GetLineLength(pb, ixb[k]), 1)]
for k in ("shoulder_l", "shoulder_r", "side_r", "side_l")}
except Exception as e:
report["seam_length_error"] = "%s: %s" % (type(e).__name__, e)
fab = fabric_api.AddFabric(ZFAB)
if not PLAIN:
fabric_api.SetBaseTextureMapImageGivenFilePath(TEX, fab)
for p in (pf, pb):
pattern_api.SetPatternPieceFabricIndex(p, fab)
arr = {a["ArrangementName"]: int(a["ArrangementIndex"])
for a in pattern_api.GetArrangementList()}
pattern_api.SetArrangement(pf, arr["Body_Front_Center_1"])
pattern_api.SetArrangement(pb, arr["Body_Back_Center_1"])
pattern_api.SetArrangementPosition(pf, 50, 55, 50)
pattern_api.SetArrangementPosition(pb, 0, 55, 50)
if STRENGTHEN:
pattern_api.SetPatternStrengthen(pf, True)
pattern_api.SetPatternStrengthen(pb, True)
utility_api.ResetClothArrangement()
utility_api.Refresh3DWindow()
utility_api.SetCamViewPoint(2)
pre = os.path.join(tempfile.gettempdir(), "tinqs_md_pari_%s_presim.png" % VERSION)
export_api.ExportSnapshot3D(pre)
report["presim_snap"] = pre
report["sim1"] = utility_api.Simulate(300) # main settle, stiff
if STRENGTHEN:
pattern_api.SetPatternStrengthen(pf, False)
pattern_api.SetPatternStrengthen(pb, False)
report["sim2"] = utility_api.Simulate(50) # relax into natural folds
utility_api.Refresh3DWindow()
utility_api.SetCamViewPoint(2)
front = os.path.join(tempfile.gettempdir(), "tinqs_md_pari_%s_front.png" % VERSION)
export_api.ExportSnapshot3D(front)
report["front_snap"] = front
# THE GOTCHA THAT LET THIS BUG SHIP: SetCamViewPoint has NO REAR VIEW. Verified by
# shooting all 8 — 0 bottom, 1 and 3 front quadrants, 2 front, 4/6/7 sides, 5 top.
# So ExportSnapshot3D can never show the back, and every screenshot in this repo is
# a front view. Use the turntable instead: index 0 front, 1 side, 2 BACK, 3 side.
# NB it ignores its path argument in the (int) overload and writes into MD's own
# output folder, so the return value is the only way to find the files.
utility_api.SetCamViewPoint(5) # 5 = TOP-DOWN: the clearest view of a shoulder join
utility_api.Refresh3DWindow()
topv = os.path.join(tempfile.gettempdir(), "tinqs_md_pari_%s_top.png" % VERSION)
export_api.ExportSnapshot3D(topv)
report["top_snap"] = topv
utility_api.SetCamViewPoint(2)
report["turntable"] = export_api.ExportTurntableImages(4)
if EXPORT:
base = os.path.join(OUT, "lena_pari_%s" % VERSION)
eop = ApiTypes.ImportExportOption()
eop.bExportGarment = True
eop.bExportAvatar = False
report["zprj"] = export_api.ExportZPrj(base + ".zprj")
report["fbx"] = export_api.ExportFBX(base + "_garment.fbx", eop)
report["obj"] = export_api.ExportOBJ(base + "_garment.obj", eop)
report["zpac"] = export_api.ExportZPac(base + ".zpac")
result = report