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,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
|
||||
Reference in New Issue
Block a user