feat: full format compatibility adaptation for model downloads

- Add get_model_urls() and download_model() to api.py and api_complete.py
  supporting all 14 discovered urlResult format keys (glb, obj, pbr maps, etc.)
- Update generator.py to extract full urlResult dict instead of just modelUrl
- Add CLI subcommands: formats (list available formats) and download (fetch by key)
- Update reverse engineering docs with complete format key table and CLI examples
This commit is contained in:
KawasakiAkasei
2026-05-27 11:59:48 +08:00
parent 58ab0d6655
commit ad3c86b8ba
6 changed files with 261 additions and 9 deletions
+80
View File
@@ -7,7 +7,9 @@
import requests
import json
import time
import os
from typing import Optional, Dict, Any
from urllib.parse import urlparse, unquote
from .sign import sign, sign_with_custom_nonce
BASE_URL = "https://3d.hunyuan.tencent.com"
@@ -17,6 +19,24 @@ API_BASE = f"{BASE_URL}/api/3d"
class Hunyuan3DAPI:
"""腾讯混元3D API客户端"""
# 模型下载格式键名(与腾讯混元3D API返回的 urlResult 字段对应)
MODEL_FORMAT_KEYS = [
"glb", # 带PBR贴图的GLB二进制模型
"obj", # OBJ模型(通常打包在zip中)
"mtl", # MTL材质文件
"obj_url", # 独立OBJ文件URL
"geometryGlb", # 纯几何GLB(无材质)
"textureGlb", # GLB纹理
"textureObj", # OBJ纹理包
"image_url", # 预览图/缩略图
"pbrImage", # PBR综合贴图
"pbrMetallicImage", # PBR金属度贴图
"pbrRoughnessImage", # PBR粗糙度贴图
"pbrNormalImage", # PBR法线贴图
"invisible_wall", # 不可见碰撞墙
"air_wall", # 空气墙(碰撞体)
]
def __init__(self, cookies: Optional[str] = None):
"""
初始化API客户端
@@ -180,6 +200,66 @@ class Hunyuan3DAPI:
"creationsId": creation_id
})
def get_model_urls(self, creation_id: str) -> Dict[str, Optional[str]]:
"""
获取指定创作所有可用格式的下载URL
Returns:
格式键名 -> 下载URL 的字典,空值已被过滤
"""
status = self.get_generation_status(creation_id)
# 兼容两种响应包装格式
if "code" in status:
data = status.get("data", {})
else:
data = status
result_list = data.get("result", [])
if not result_list:
return {}
url_result = result_list[0].get("urlResult", {})
return {
key: val
for key in self.MODEL_FORMAT_KEYS
if (val := url_result.get(key)) and val not in (None, "", {})
}
def download_model(self, creation_id: str, format_key: str = "glb",
output_path: Optional[str] = None) -> str:
"""
下载指定格式的模型文件
Args:
creation_id: 创作ID
format_key: 格式键名,如 'glb' / 'obj' / 'pbrNormalImage'
output_path: 本地保存路径,为空时自动从URL推断文件名
Returns:
实际保存的本地文件路径
"""
urls = self.get_model_urls(creation_id)
if format_key not in urls:
available = ", ".join(urls.keys())
raise ValueError(
f"格式 '{format_key}' 不可用。可用格式: {available}"
)
url = urls[format_key]
resp = self.session.get(url, timeout=120)
resp.raise_for_status()
if output_path is None:
parsed = urlparse(url)
filename = unquote(os.path.basename(parsed.path)) or f"{creation_id}_{format_key}"
output_path = filename
with open(output_path, "wb") as f:
f.write(resp.content)
return os.path.abspath(output_path)
def wait_for_completion(self, creation_id: str, timeout: int = 600,
poll_interval: int = 5) -> Dict[str, Any]:
"""