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>
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user