modelbeast/server/operators/sf3d/run.py
MODELBEAST 605b1ae347 Phase 1 + framework: settings/secrets, queue lanes, job mgmt, inbox, 8 new operators
Framework:
- server/settings.py: key/value settings + secrets, env-injected into operator
  subprocesses, secret values masked in API and redacted from job logs
- runner: gpu/cpu/net concurrency lanes, job cancel/retry/delete, multi-input,
  graceful 'not installed' error when a tool venv is missing
- db: settings table, asset_ids column (migrated), MODELBEAST_DATA test override
- main: settings + job-action endpoints, inbox watch folder auto-ingest
- store: operators can tag output asset kind (splat, colmap_dataset)

Operators (11 total):
- fal_trellis/trellis2/hunyuan3d/rodin via shared _lib/fal_common.py (verified
  params + endpoint ids; recursive result-URL extractor handles per-endpoint keys)
- sf3d, trellis_mac: local MPS image-to-3D, installed with Metal kernels built,
  gated on owner HuggingFace auth
- colmap_poses (COLMAP 4.x + GLOMAP global mapper), brush_train (native Metal 3DGS)
- Scan pipeline validated end-to-end through the UI: frames -> colmap (48/48
  registered, 0.6px) -> brush -> splat.ply -> in-app SplatViewer

Frontend:
- Settings modal, operator gating (lock + disabled run when requires_env unmet),
  job cancel/retry/delete, Compare grid (multi-select side-by-side viewers),
  SplatViewer (gaussian-splats-3d, Ply format forced for extensionless URLs)

Tooling: scripts/install_{colmap,brush,sf3d,trellis_mac}.sh; vendor/ + venvs/
gitignored; tests/smoke.sh (12 checks passing); BENCHMARKS.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:42:27 +10:00

52 lines
1.6 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" / "stable-fast-3d"
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 / "run.py").exists():
print(f"ERROR: SF3D not installed at {VENDOR}. Run scripts/install_sf3d.sh")
sys.exit(1)
outdir = Path(a.outdir)
work = outdir / "sf3d_out"
work.mkdir(parents=True, exist_ok=True)
cmd = [
sys.executable, "run.py", str(Path(a.input[0]).resolve()),
"--device", p.get("device", "mps"),
"--output-dir", str(work.resolve()),
"--texture-resolution", str(p.get("texture_resolution", 1024)),
"--foreground-ratio", str(p.get("foreground_ratio", 0.85)),
"--remesh_option", p.get("remesh_option", "none"),
]
env = os.environ.copy()
env["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
print("+", " ".join(cmd), flush=True)
res = subprocess.run(cmd, cwd=str(VENDOR), env=env, stdout=sys.stdout, stderr=subprocess.STDOUT)
if res.returncode != 0:
sys.exit(res.returncode)
glbs = sorted(work.rglob("*.glb"))
if not glbs:
print("ERROR: SF3D produced no .glb")
sys.exit(1)
name = f"sf3d_{Path(a.input[0]).stem}.glb"
(outdir / "result.json").write_text(json.dumps(
{"outputs": [{"path": str(glbs[0]), "name": name, "meta": {"tool": "sf3d"}}]}))
print("done:", glbs[0], flush=True)