Files
animation/tools/tailor/md_pari_armsweep.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

123 lines
5.7 KiB
Python

# Armhole-scoop sweep for topTest1. Diagnostic, exports nothing.
#
# python tools/md_bridge.py --file tools/tailor/md_pari_armsweep.py --timeout 880
#
# HYPOTHESIS (Jeremy's): the persistent diagonal crease across the back is surplus
# WIDTH bunching where the two panels meet. The numbers say he's right, but not at
# the bust or hem — those are matched:
# bust 570 + 510 = 1080 vs bust 1083 (matched)
# hem 380 + 320 = 700 vs waist 675 (+25 ease)
# The surplus is ABOVE the bust. The panels stay near-full width up to the armhole
# while the body narrows, giving ~968 mm of cloth around a much smaller torso at
# underarm height. And the side seam only runs y 0..185, so above that the front and
# back edges are FREE — nothing stops the surplus from overlapping and pushing a
# diagonal fold across the back, which is exactly where the crease sits.
#
# Fix under test: a concave ARMHOLE SCYE. Insert one mid-point per armhole, pulled
# inward by ARM, so the upper panel narrows without touching bust width, hem taper,
# or strap position (moving the straps would re-expose the body's baked-in bra
# straps, which is the whole reason the straps exist).
#
# Inserting points SHIFTS THE LINE INDICES, so indices are computed here rather than
# hardcoded — hardcoded 0/5/7/9 has bitten this recipe twice already.
#
# 4 values x Simulate(250) ~= 4 min. PLAIN so shape is judged, not motif.
import os
import shutil
import tempfile
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"
W_FRONT, W_BACK = 570.0, 510.0
TOP = 350.0
SCOOP_FRONT, SCOOP_BACK = 105.0, 15.0
SIDE_Y = 185.0
TAPER = 95.0
ARM_H = 70.0 # mid-point sits ARM_H above SIDE_Y, i.e. y = 255
SWEEP = [0.0, 30.0, 55.0, 80.0] # mm pulled inward per side. 0 == current shape.
SIDE_FLAGS = (True, True)
SHOULDER_FLAGS = (False, False)
ARR_X = 50
report = {"combos": {}, "hypothesis": "surplus width ABOVE the bust, free edges above SIDE_Y"}
def build(width, scoop, dx, arm):
"""Return (pattern_id, idx) where idx names the line indices we must sew."""
c = width / 2.0
pts = [(c - 210.0, TOP - 25.0), # 0 strapL_outer
(c - 120.0, TOP), # 1 strapL_inner line0 = SHOULDER_L
(c - 70.0, TOP - scoop * 0.75), # 2 neck
(c, TOP - scoop), # 3 neck
(c + 70.0, TOP - scoop * 0.75), # 4 neck
(c + 120.0, TOP), # 5 strapR_inner line5 = SHOULDER_R
(c + 210.0, TOP - 25.0)] # 6 strapR_outer
if arm:
pts.append((width - arm, SIDE_Y + ARM_H)) # 7 armhole R mid (concave scye)
pts += [(width, SIDE_Y), # sideR_top
(width - TAPER, 0.0), # hemR
(TAPER, 0.0), # hemL
(0.0, SIDE_Y)] # sideL_top
if arm:
pts.append((arm, SIDE_Y + ARM_H)) # armhole L mid
# line i runs pts[i] -> pts[i+1]; derive the seam indices from the layout
n_arm = 1 if arm else 0
side_r = 6 + n_arm + 1 # sideR_top -> hemR
hem = side_r + 1
side_l = hem + 1 # hemL -> sideL_top
idx = {"shoulder_l": 0, "shoulder_r": 5, "side_r": side_r, "side_l": side_l,
"hem": hem, "n_points": len(pts)}
pid = pattern_api.CreatePatternWithPoints([(x + dx, y, 0) for (x, y) in pts])
return pid, idx
for arm in SWEEP:
key = "arm%02d" % int(arm)
utility_api.NewProject()
op = ApiTypes.ImportExportOption()
op.scale = 10.0
op.bAddArrangementPoints = True
op.bAutoTranslate = True
import_api.ImportFBX(AVATAR_FBX, op)
pf, ixf = build(W_FRONT, SCOOP_FRONT, 0.0, arm)
pb, ixb = build(W_BACK, SCOOP_BACK, 900.0, arm)
pattern_api.AddSeamlinePairGroup(pf, ixf["shoulder_l"], pb, ixb["shoulder_l"], *SHOULDER_FLAGS)
pattern_api.AddSeamlinePairGroup(pf, ixf["shoulder_r"], pb, ixb["shoulder_r"], *SHOULDER_FLAGS)
pattern_api.AddSeamlinePairGroup(pf, ixf["side_r"], pb, ixb["side_r"], *SIDE_FLAGS)
pattern_api.AddSeamlinePairGroup(pf, ixf["side_l"], pb, ixb["side_l"], *SIDE_FLAGS)
# Verify the computed indices actually point at the edges we think: shoulders
# ~93 mm, sides ~208 mm. If these drift, the index math is wrong, not the cloth.
lens = {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")}
fab = fabric_api.AddFabric(ZFAB)
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, ARR_X, 55, 50)
pattern_api.SetArrangementPosition(pb, ARR_X, 55, 50)
utility_api.ResetClothArrangement()
ok = utility_api.Simulate(250)
utility_api.Refresh3DWindow()
shots = export_api.ExportTurntableImages(4)
saved = {}
for i, label in ((0, "front"), (2, "back")):
if i < len(shots) and os.path.exists(shots[i]):
dst = os.path.join(tempfile.gettempdir(),
"tinqs_armsweep_%s_%s.png" % (key, label))
shutil.copyfile(shots[i], dst)
saved[label] = dst
report["combos"][key] = {"sim": ok, "arm": arm, "idx_front": ixf,
"seam_lengths": lens, "shots": saved}
result = report