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:
@@ -4,10 +4,12 @@
|
||||
支持所有生成模式:图生3D、文生3D、草图生3D、动画生成、纹理生成、智能拓扑
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Dict, Any, List
|
||||
from urllib.parse import urlparse, unquote
|
||||
from .sign import sign
|
||||
|
||||
BASE_URL = "https://3d.hunyuan.tencent.com"
|
||||
@@ -47,6 +49,24 @@ class Hunyuan3DAPI:
|
||||
TOPO_MEDIUM = 18000
|
||||
TOPO_HIGH = 30000
|
||||
|
||||
# 模型下载格式键名(与腾讯混元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):
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
@@ -124,6 +144,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 cancel_generation(self, creation_id: str) -> Dict[str, Any]:
|
||||
"""取消生成任务"""
|
||||
return self._make_request("POST", "/creations/cancel", data={
|
||||
|
||||
Reference in New Issue
Block a user