# 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