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:
2026-08-06 15:55:43 -07:00
parent 3363209cac
commit 3ba86b2ea8
558 changed files with 68622 additions and 8 deletions
+123
View File
@@ -0,0 +1,123 @@
# Arrangement-x sweep for bottomTest1 (strand skirt). Diagnostic, exports nothing.
#
# python tools/md_bridge.py --file tools/tailor/md_piupiu_arrsweep.py --timeout 880
#
# WHY: the skirt's waistband folds on ONE SIDE only (turntable side B shows a flap
# jutting out with a grey reverse-face wedge and a strand group folded over itself;
# side A is clean). An asymmetric defect needs an asymmetric input, and the only one
# is SetArrangementPosition x: front=50 / back=0, inherited from md_piupiu.py.
#
# But setting BOTH to 50 — which is exactly what fixed the same one-sided fold on the
# TOP — makes this skirt fall to the floor (band top measured 0.117 m). The top
# arranges on Body_*_Center_1; this arranges on Leg_Skirt_Front/Back, and those points
# evidently do not share a convention. So 50/0 may be load-bearing here rather than a
# copy-paste artefact.
#
# CONTROL: combo (50, 0) must reproduce the shipped drape (band top ~1.09 m). If the
# control fails, the harness is broken and every cell is void — that is exactly how
# md_pari_armsweep.py wasted a run.
#
# Pass/fail is read from GEOMETRY, not images: a garment on the floor measures
# band_top < 0.3 m, so the sweep self-reports which cells even stayed on the body.
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"
M_TEETH = 16
W_TOP, W_HEM = 375.0, 700.0
BAND_H, STRAND_H = 60.0, 530.0
GAP_TOP, GAP_HEM = 6.0, 11.0
PARTICLE_MM, COLLISION_MM = 20.0, 10.0
ARR_Y = 92
COMBOS = [(50, 0), (50, 50), (0, 0), (0, 50)] # (50,0) is the CONTROL
BAND_TOP = STRAND_H + BAND_H
BAND_BOT = STRAND_H
INSET = (W_HEM - W_TOP) / 2.0
pitch_top = W_TOP / M_TEETH
pitch_hem = W_HEM / M_TEETH
report = {"combos": {}, "control": "50_0", "control_expect_band_top_m": 1.09}
def outline(dx):
pts = [(INSET, BAND_TOP), (INSET + W_TOP, BAND_TOP), (INSET + W_TOP, BAND_BOT)]
for j in range(M_TEETH - 1, -1, -1):
ta = INSET + j * pitch_top + GAP_TOP / 2.0
tb = INSET + (j + 1) * pitch_top - GAP_TOP / 2.0
ha = j * pitch_hem + GAP_HEM / 2.0
hb = (j + 1) * pitch_hem - GAP_HEM / 2.0
if j < M_TEETH - 1:
pts.append((tb, BAND_BOT))
pts += [(hb, 0.0), (ha, 0.0), (ta, BAND_BOT)]
return [(x + dx, y, 0) for (x, y) in pts]
for xf, xb in COMBOS:
key = "%d_%d" % (xf, xb)
utility_api.NewProject()
op = ApiTypes.ImportExportOption()
op.scale = 10.0
op.bAddArrangementPoints = True
op.bAutoTranslate = True
import_api.ImportFBX(AVATAR_FBX, op)
pts_f = outline(0.0)
sf = pattern_api.CreatePatternWithPoints(pts_f)
sb = pattern_api.CreatePatternWithPoints(outline(W_HEM + 400.0))
L_RIGHT, L_LEFT = 1, len(pts_f) - 1
# crossed pairing: for a two-panel tube with only side seams this IS the reversal
pattern_api.AddSeamlinePairGroup(sf, L_RIGHT, sb, L_LEFT, False, False)
pattern_api.AddSeamlinePairGroup(sf, L_LEFT, sb, L_RIGHT, False, False)
fab = fabric_api.AddFabric(ZFAB) # PLAIN: judge shape, not motif
for p in (sf, sb):
pattern_api.SetPatternPieceFabricIndex(p, fab)
pattern_api.SetParticleDistanceOfPattern(p, PARTICLE_MM)
pattern_api.SetAddlThicknessCollision(p, COLLISION_MM)
arr = {a["ArrangementName"]: int(a["ArrangementIndex"])
for a in pattern_api.GetArrangementList()}
pattern_api.SetArrangement(sf, arr["Leg_Skirt_Front"])
pattern_api.SetArrangement(sb, arr["Leg_Skirt_Back"])
pattern_api.SetArrangementPosition(sf, xf, ARR_Y, 50)
pattern_api.SetArrangementPosition(sb, xb, ARR_Y, 50)
pattern_api.SetPatternStrengthen(sf, True)
pattern_api.SetPatternStrengthen(sb, True)
utility_api.ResetClothArrangement()
ok1 = utility_api.Simulate(250)
pattern_api.SetPatternStrengthen(sf, False)
pattern_api.SetPatternStrengthen(sb, False)
ok2 = utility_api.Simulate(50)
utility_api.Refresh3DWindow()
# geometry verdict: on the floor measures band_top < 0.3 m
mop = ApiTypes.ImportExportOption()
mop.bExportGarment = True
mop.bExportAvatar = False
probe = os.path.join(tempfile.gettempdir(), "tinqs_arrsweep_%s.obj" % key)
export_api.ExportOBJ(probe, mop)
ys = []
with open(probe) as fh:
for line in fh:
if line.startswith("v "):
ys.append(float(line.split()[2]) * 0.001) # OBJ is MM
ys.sort()
on_body = bool(ys) and ys[-1] > 0.30
shots = export_api.ExportTurntableImages(4)
saved = {}
for i, label in ((0, "front"), (1, "sideA"), (3, "sideB")):
if i < len(shots) and os.path.exists(shots[i]):
dst = os.path.join(tempfile.gettempdir(),
"tinqs_arrsweep_%s_%s.png" % (key, label))
shutil.copyfile(shots[i], dst)
saved[label] = dst
report["combos"][key] = {
"x": [xf, xb], "sim": [ok1, ok2], "on_body": on_body,
"band_top_m": round(ys[-1], 3) if ys else None,
"hem_m": round(ys[0], 3) if ys else None, "shots": saved}
result = report