Land Explorer in HomeCamp with boot loading, journal UI, and CDN publish tooling.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
from pathlib import Path
|
||||
import mimetypes
|
||||
import requests
|
||||
|
||||
BASE = "http://127.0.0.1:8008"
|
||||
OUTPUT_DIR = Path("Tools/output/explorer_3d")
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
REF_IMAGE = OUTPUT_DIR / "explorer_ref.png"
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
def wait_job(url_path, timeout=3600, interval=3):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
r = session.get(f"{BASE}{url_path}", timeout=30)
|
||||
r.raise_for_status()
|
||||
job = r.json()
|
||||
status = job.get("status")
|
||||
stage = job.get("stage", "")
|
||||
progress = job.get("progress", 0)
|
||||
print(f"[{status}] stage={stage} progress={progress}%", flush=True)
|
||||
if status in ("completed", "failed"):
|
||||
if status != "completed" or job.get("error"):
|
||||
raise RuntimeError(f"Job failed: {job.get('error') or job}")
|
||||
return job
|
||||
time.sleep(interval)
|
||||
raise TimeoutError(f"Job timeout: {url_path}")
|
||||
|
||||
def download(url, out_path):
|
||||
r = session.get(f"{BASE}{url}", timeout=300)
|
||||
r.raise_for_status()
|
||||
out = Path(out_path)
|
||||
if r.content[:4] == b"PK\x03\x04":
|
||||
out = out.with_suffix(".zip")
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_bytes(r.content)
|
||||
print(f"Downloaded: {out} ({len(r.content)} bytes)")
|
||||
return out
|
||||
|
||||
# 1. Mesh Generation
|
||||
print("=== Step 1: DGX Hunyuan3D Mesh Generation ===")
|
||||
mime = mimetypes.guess_type(REF_IMAGE.name)[0] or "image/png"
|
||||
with REF_IMAGE.open("rb") as f:
|
||||
resp = session.post(f"{BASE}/api/jobs", timeout=120, data={
|
||||
"engine": "dgx",
|
||||
"mode": "image",
|
||||
"character_type": "humanoid",
|
||||
"prompt": "chibi 3D game character boy adventurer in A-pose",
|
||||
"resolution": 256,
|
||||
"source_faces": 50000,
|
||||
"target_faces": 5000,
|
||||
"texture_size": 1024,
|
||||
"output_format": "fbx"
|
||||
}, files={"image": (REF_IMAGE.name, f, mime)})
|
||||
|
||||
if resp.status_code not in (200, 201, 202):
|
||||
raise RuntimeError(f"Create job failed: {resp.status_code} {resp.text}")
|
||||
|
||||
job = resp.json()
|
||||
job_id = job["id"]
|
||||
print(f"Created Mesh Job ID: {job_id}")
|
||||
|
||||
mesh_job = wait_job(f"/api/jobs/{job_id}", timeout=1800)
|
||||
print(f"Mesh completed. Ready for motion: {mesh_job.get('ready_for_motion')}")
|
||||
(OUTPUT_DIR / "mesh_job.json").write_text(json.dumps(mesh_job, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
# 2. MIA Humanoid Rigging
|
||||
print("\n=== Step 2: Make-It-Animatable (MIA) Humanoid Rigging ===")
|
||||
rig_resp = session.post(f"{BASE}/api/jobs/{job_id}/rig", timeout=120, data={
|
||||
"character_type": "humanoid",
|
||||
"skeleton_template": "auto"
|
||||
})
|
||||
if rig_resp.status_code not in (200, 201, 202):
|
||||
raise RuntimeError(f"Trigger rig failed: {rig_resp.status_code} {rig_resp.text}")
|
||||
|
||||
print("Rig triggered:", rig_resp.json())
|
||||
|
||||
rig_job = wait_job(f"/api/jobs/{job_id}", timeout=1800)
|
||||
print("Rigging completed!")
|
||||
(OUTPUT_DIR / "rig_job.json").write_text(json.dumps(rig_job, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
# Download character assets
|
||||
print("\n=== Downloading Character Assets ===")
|
||||
for key, url in rig_job.get("downloads", {}).items():
|
||||
suffix = ".fbx" if "fbx" in key else ".glb" if "glb" in key else ".bin"
|
||||
download(url, OUTPUT_DIR / f"{key}{suffix}")
|
||||
|
||||
print("Mesh & Rig completed successfully!")
|
||||
|
||||
Reference in New Issue
Block a user