Files
animation/tools/md_bridge/smoke_test.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

92 lines
2.8 KiB
Python

# TinqsMDBridge smoke test -- run via:
# python tools/md_bridge.py --file tools/md_bridge/smoke_test.py --timeout 120
#
# Defensive by design: MD's api docs are incomplete and signatures unverified,
# so every step is independently try/except'd and the whole thing returns a
# step-by-step report instead of dying on the first surprise. Nothing here
# should be treated as the "right" calling convention until this has passed
# once -- it's a probe, not a recipe.
import inspect
import os
import tempfile
import traceback
report = {"steps": []}
def step(name, fn):
entry = {"step": name}
try:
entry["ok"] = True
entry["value"] = fn()
except Exception:
entry["ok"] = False
entry["error"] = traceback.format_exc().strip().splitlines()[-1]
report["steps"].append(entry)
return entry
def docs_of(mod, names):
out = {}
for n in names:
f = getattr(mod, n, None)
if f is None:
out[n] = "<missing>"
continue
try:
out[n] = str(inspect.signature(f))
except (ValueError, TypeError):
doc = (getattr(f, "__doc__", "") or "").strip()
out[n] = doc.splitlines()[0] if doc else "<no signature/doc>"
return out
# 1. what do the key functions actually look like?
step("signatures.pattern", lambda: docs_of(pattern_api, [
"CreatePatternWithPoints", "AddSeamlinePairGroup", "SetArrangementPosition",
"MovePatternPoint", "DeletePatternPiece"]))
step("signatures.utility", lambda: docs_of(utility_api, [
"NewProject", "Simulate", "GetAvatarCount", "GetAvatarNameList"]))
step("signatures.export", lambda: docs_of(export_api, [
"ExportSnapshot3D", "ExportCustomViewSnapshot", "ExportGLTF", "ExportFBX"]))
# 2. fresh project
step("NewProject", lambda: utility_api.NewProject())
# 3. default avatar present?
step("avatars", lambda: {
"count": utility_api.GetAvatarCount(),
"names": utility_api.GetAvatarNameList(),
})
# 4. create a 50cm square pattern (units unverified -- try mm first, the CLO
# convention; the snapshot will show which interpretation MD used)
SQUARE_MM = [(0.0, 0.0), (500.0, 0.0), (500.0, 500.0), (0.0, 500.0)]
def make_square():
return pattern_api.CreatePatternWithPoints(SQUARE_MM)
step("CreatePatternWithPoints(square)", make_square)
# 5. short drape
step("Simulate(30)", lambda: utility_api.Simulate(30))
# 6. snapshot -- the agent's eyes
SNAP = os.path.join(tempfile.gettempdir(), "tinqs_md_smoke.png")
def snap():
export_api.ExportSnapshot3D(SNAP)
return {"path": SNAP, "exists": os.path.exists(SNAP),
"size": os.path.getsize(SNAP) if os.path.exists(SNAP) else 0}
step("ExportSnapshot3D", snap)
report["passed"] = sum(1 for s in report["steps"] if s["ok"])
report["failed"] = sum(1 for s in report["steps"] if not s["ok"])
result = report