Remote runs exec the repo-relative manifest python via bash with no
existence check ('vendor/trellis2-mlx/.venv/bin/python: No such file'),
killing trellis2_mlx on the validated m1 lane (its fork lives at
~/trellis2-mlx). Both run.py launchers are stdlib-only and already
resolve the per-box fork checkout + venv themselves — run them on the
server/system python instead. trellis_mac/run.py now resolves its vendor
venv explicitly (sys.executable was only correct under the old pin).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
VENDOR = ROOT / "vendor" / "trellis-mac"
|
|
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--input", action="append", default=[])
|
|
ap.add_argument("--outdir", required=True)
|
|
ap.add_argument("--params", default="{}")
|
|
a = ap.parse_args()
|
|
p = json.loads(a.params)
|
|
|
|
if not a.input:
|
|
print("ERROR: no input image")
|
|
sys.exit(1)
|
|
if not (VENDOR / "generate.py").exists():
|
|
print(f"ERROR: trellis-mac not installed at {VENDOR}. Run scripts/install_trellis_mac.sh")
|
|
sys.exit(1)
|
|
# manifest no longer pins "python" (broke remote dispatch when the venv path
|
|
# didn't exist on the worker) — resolve the vendor venv here instead.
|
|
VENV_PY = VENDOR / ".venv" / "bin" / "python"
|
|
if not VENV_PY.exists():
|
|
print(f"ERROR: no venv at {VENV_PY} — run scripts/install_trellis_mac.sh")
|
|
sys.exit(1)
|
|
|
|
outdir = Path(a.outdir)
|
|
out_stem = outdir / f"trellis2_{Path(a.input[0]).stem}"
|
|
|
|
cmd = [
|
|
str(VENV_PY), "generate.py", str(Path(a.input[0]).resolve()),
|
|
"--pipeline-type", str(p.get("pipeline_type", "1024")),
|
|
"--texture-size", str(p.get("texture_size", 2048)),
|
|
"--seed", str(p.get("seed", 0)),
|
|
"--output", str(out_stem.resolve()),
|
|
]
|
|
if p.get("no_texture"):
|
|
cmd.append("--no-texture")
|
|
if p.get("steps"):
|
|
cmd += ["--steps", str(p["steps"])]
|
|
|
|
env = os.environ.copy()
|
|
# xet chunked downloader fails on these BFL/Meta repos ("Unable to parse string
|
|
# as hex hash value") — force the reliable HTTP path (same fix as flux_local).
|
|
env["HF_HUB_DISABLE_XET"] = "1"
|
|
# guard the torch + compiled-kernel OpenMP collision like sf3d does.
|
|
env["KMP_DUPLICATE_LIB_OK"] = "TRUE"
|
|
env.setdefault("OMP_NUM_THREADS", "1")
|
|
env.setdefault("MKL_NUM_THREADS", "1")
|
|
# Pre-bake simplify cap. The 200k generate.py default is a laptop-derived
|
|
# mtlbvh guard that costs most of the mesh detail (2.2M-tri decode → 95k GLB
|
|
# vs fal's 495k). 500k verified crash-free on the M3 Ultra Studio 2026-07-22.
|
|
# The server passes only client-sent params (manifest defaults are UI-only),
|
|
# so default here, sized by node RAM: big-memory Studios get the quality cap.
|
|
if p.get("max_bake_faces") is not None:
|
|
env["TRELLIS2_MAX_BAKE_FACES"] = str(p["max_bake_faces"])
|
|
else:
|
|
mem_gb = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") / 1e9
|
|
env.setdefault("TRELLIS2_MAX_BAKE_FACES", "500000" if mem_gb >= 96 else "200000")
|
|
if p.get("baker"):
|
|
env["TRELLIS2_BAKER"] = str(p["baker"])
|
|
# "auto" keeps o_voxel's baked-alpha BLEND detection (glass etc.); default
|
|
# opaque — the Metal baker's noisy alpha turns solid hair into speckles.
|
|
if p.get("alpha_mode"):
|
|
env["TRELLIS2_ALPHA_MODE"] = str(p["alpha_mode"])
|
|
|
|
print("+", " ".join(cmd), flush=True)
|
|
print("(first run downloads TRELLIS.2-4B + dinov3 + RMBG-2.0 weights)", flush=True)
|
|
res = subprocess.run(cmd, cwd=str(VENDOR), stdout=sys.stdout, stderr=subprocess.STDOUT, env=env)
|
|
if res.returncode != 0:
|
|
sys.exit(res.returncode)
|
|
|
|
# generate.py writes <output>.glb (and often a .obj); collect any glb under outdir
|
|
glbs = sorted(outdir.rglob("*.glb"))
|
|
if not glbs:
|
|
print("ERROR: trellis-mac produced no .glb")
|
|
sys.exit(1)
|
|
outputs = [{"path": str(glbs[0]), "name": f"trellis2_{Path(a.input[0]).stem}.glb",
|
|
"meta": {"tool": "trellis_mac"}}]
|
|
(outdir / "result.json").write_text(json.dumps({"outputs": outputs}))
|
|
print("done:", glbs[0], flush=True)
|