Compare commits
2
Commits
6a61f6423d
...
acfe5e7ac2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acfe5e7ac2 | ||
|
|
956b015ee0 |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"open-code-review@open-code-review": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: run-hunyuan3dweb
|
||||
description: 运行并驱动腾讯混元 3D(hy3d)。当用户要求:跑 hy3d、查混元 3D 配额、列作品、文生 3D / 图生 3D 生成模型、查询生成状态、下载 GLB/OBJ/USDZ 模型,或验证这个项目能否工作时使用。
|
||||
---
|
||||
|
||||
本项目(仓库 gitea.akasei.top/KawasakiAkasei/hy3d)是腾讯混元 3D(https://3d.hunyuan.tencent.com/)的非官方 CLI 与 Python 客户端:签名算法(HMAC-SHA256 密钥派生 `Hf6d6KFB3D`)、纯 HTTP API + CLI(`hy3d`)、浏览器登录工具。
|
||||
|
||||
驱动入口只有一个:**`bin/hy3d`**(免安装 shim,自动设置 PYTHONPATH 并转发到 `python3 -m hy3d.cli`)。也可 `pip install hy3d` 后用 PATH 上的 `hy3d`。下文路径相对仓库根。
|
||||
|
||||
## 前置条件
|
||||
|
||||
```bash
|
||||
python3 --version # ≥3.9 ✅ (本机 3.13.12)
|
||||
python3 -c "import requests" # 缺则: pip install requests
|
||||
```
|
||||
|
||||
浏览器功能(登录/抓包)另需 `playwright`、`cloakbrowser`、`chromium`、`xvfb-run`——本机均已装好。
|
||||
|
||||
## Setup
|
||||
|
||||
无安装步骤。仅需登录 cookie:`~/.config/hunyuan3dweb/cookies.txt`(2026-08-15 曾登录,cookie 可能随时过期,过期用下方 auth 流程处理)。
|
||||
|
||||
## Run(agent 路径)
|
||||
|
||||
```bash
|
||||
bin/hy3d auth status # 登录体检(最先跑这个)
|
||||
bin/hy3d quota # 配额
|
||||
bin/hy3d list # 作品列表(2026-08-15 实测 7 个作品)
|
||||
bin/hy3d formats <creationsId>
|
||||
bin/hy3d download <creationsId> --format glb -o model.glb
|
||||
bin/hy3d text "a ceramic teapot" --wait # 文生 3D(消耗 4 配额)轮询到完成
|
||||
# 更多: status/user/count/image/sketch/animate/texture/topo/cancel/share
|
||||
```
|
||||
|
||||
**退出码**(agent 分支判断的依据,已实测):
|
||||
|
||||
| 码 | 含义 | 处理 |
|
||||
|---|---|---|
|
||||
| 0 | 成功 | — |
|
||||
| 1 | 环境/网络/参数/4xx | 看 stderr |
|
||||
| 2 | 认证失败(token 无效/过期) | `hy3d auth login`(需人工,见下) |
|
||||
| 3 | cookie 缺失 | `hy3d auth login`(需人工,见下) |
|
||||
|
||||
典型工作流(2026-08-15 全部实测通过):`auth status` → `quota` → `list` → 对历史作品 `formats`/`download`(下载 512×512 PNG 预览验证过);`text --wait` 已验证提交+轮询框架,未在容器内跑完整生成(消耗配额)。
|
||||
|
||||
## 重新登录(退出码 2/3 时唯一出路)
|
||||
|
||||
**必须由人类用户在真实终端**(Claude 的 `!` 前缀 stdin 非交互,`input()` 会 EOF)运行:
|
||||
|
||||
```bash
|
||||
python -m hy3d.browser.login # 或安装后: hy3d-login
|
||||
```
|
||||
|
||||
输邮箱 → 收验证码 → 输验证码,cookie 自动存回 `~/.config/hunyuan3dweb/cookies.txt`。登录后浏览器进程自动退出,无残留(2026-08-15 验证)。
|
||||
|
||||
## Run(人类路径)
|
||||
|
||||
```bash
|
||||
pip install -e ".[browser]" # 可选,装好后 hy3d 直接进 PATH
|
||||
hy3d quota
|
||||
```
|
||||
|
||||
## Test(离线自检)
|
||||
|
||||
```bash
|
||||
python3 -m hy3d.sign
|
||||
# 输出含「派生密钥: Hf6d6KFB3D」即签名算法正确
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`!` 前缀跑登录必 EOF** —— `input()` 在管道 stdin 上直接 `EOFError: EOF when reading a line`。登录必须真实终端;agent 只能引导用户。
|
||||
- **401 两种码都证明签名正确** —— `"code":"20001"`=token 过期(重登);`"code":"999"`=cookie 无用户。服务端能分类错误说明请求格式被完全接受。
|
||||
- **不存在的 creationsId → HTTP 400 空 body**(CLI 归为退出码 1),不是 JSON 错误。
|
||||
- **文生 3D 一次消耗 4 次配额**(`count=4` 固定);图生 3D 等消耗 1 次。
|
||||
- **配置目录刻意保持 `~/.config/hunyuan3dweb` 不变**——改名后路径改了会丢掉已登录的 cookie。
|
||||
- **生成类命令(text/image/animate/...)未在容器内完整跑通**——验证到提交与轮询框架;正式大规模使用前先 `hy3d auth status` 确认在线,并接受首单可能失败的调试成本。
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`hy3d auth status` 退出码 2**:token 过期。让人在真实终端跑 `python -m hy3d.browser.login`。
|
||||
- **退出码 3**:cookie 文件不存在/为空。同上,或检查 `HY3D_COOKIES` 是否指向了错误路径。
|
||||
- **`EOFError`(登录时)**:stdin 非交互,换真实终端。
|
||||
- **`401` + 'token无效'**:重登,不是代码 bug。
|
||||
- **`python3 -m hy3d.sign` 的 RuntimeWarning**:runpy 与 `__init__.py` 重复导入的假警报,无害。
|
||||
@@ -1,304 +1,164 @@
|
||||
# hunyuan3dweb
|
||||
# hy3d
|
||||
|
||||
Python API client and browser automation tools for Tencent Hunyuan 3D (https://3d.hunyuan.tencent.com/).
|
||||
非官方腾讯混元 3D CLI 与 Python API 客户端([3d.hunyuan.tencent.com](https://3d.hunyuan.tencent.com/))。
|
||||
|
||||
## Installation
|
||||
纯 Python HTTP 调用腾讯混元 3D 的 AI 能力:**文生 3D、图生 3D、多视角、草图、动画、纹理、智能拓扑减面**,以及配额查询、作品管理、模型下载(GLB/OBJ/FBX/STL/USDZ/MP4/GIF)。
|
||||
|
||||
> ⚠️ 非官方项目:这是对混元 3D Web 前端签名算法的逆向实现,与腾讯无关。仅供个人学习与自动化使用。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
pip install hy3d # 纯 API 客户端
|
||||
pip install "hy3d[browser]" # 额外安装浏览器工具(登录/抓包/浏览器自动化)
|
||||
```
|
||||
|
||||
For browser automation features:
|
||||
不安装也能用——克隆仓库后直接用源码跑(无需 `pip install`):
|
||||
|
||||
```bash
|
||||
pip install -e ".[browser]"
|
||||
cd hy3d
|
||||
PYTHONPATH=$PWD python3 -m hy3d.cli quota
|
||||
```
|
||||
|
||||
## Login and Setup
|
||||
## 快速开始
|
||||
|
||||
First-time users need to log in via the browser. Cookies will be saved automatically:
|
||||
首次使用需要登录一次(**在真实终端运行**,邮箱验证码登录,无头浏览器):
|
||||
|
||||
```bash
|
||||
hunyuan3dweb-login
|
||||
hy3d auth login
|
||||
# → 按提示: 输邮箱 → 收验证码 → 输验证码
|
||||
# → cookie 自动保存到 ~/.config/hunyuan3dweb/cookies.txt
|
||||
```
|
||||
|
||||
After successful login:
|
||||
- Browser profile is saved to `~/.config/hunyuan3dweb/profile`
|
||||
- Cookies are automatically extracted to `~/.config/hunyuan3dweb/cookies.txt`
|
||||
|
||||
Subsequent Python scripts will read cookies automatically — no manual configuration needed.
|
||||
|
||||
## Python Script Usage
|
||||
|
||||
### Quick Start (Auto-read Default Cookie)
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
# Check quota
|
||||
quota = api.get_quota_info()
|
||||
print(f"Remaining: {quota['remainQuota']}/{quota['totalQuota']}")
|
||||
```
|
||||
|
||||
### 1. Text-to-3D
|
||||
|
||||
The simplest mode. No image upload required.
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
# Submit generation job
|
||||
result = api.generate_from_text("a ceramic vase", title="Test")
|
||||
cid = result["creationsId"]
|
||||
|
||||
# Poll until completion (built-in polling with progress output)
|
||||
final = api.wait_for_completion(cid)
|
||||
print("Generation complete")
|
||||
```
|
||||
|
||||
**Full pipeline: Text-to-3D → Extract Download Links**
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
job = api.generate_from_text(
|
||||
"cyberpunk style mechanical dog",
|
||||
style=api.STYLE_CYBERPUNK
|
||||
)
|
||||
cid = job["creationsId"]
|
||||
|
||||
result = api.wait_for_completion(cid, timeout=600, poll_interval=5)
|
||||
|
||||
if result["status"] == "success":
|
||||
models = result.get("result", [])
|
||||
for m in models:
|
||||
if m["status"] == "success":
|
||||
urls = m["urlResult"]
|
||||
print(f"GLB: {urls.get('glb')}")
|
||||
print(f"OBJ: {urls.get('obj')}")
|
||||
print(f"Image: {urls.get('image_url')}")
|
||||
```
|
||||
|
||||
### 2. Image-to-3D (Local File)
|
||||
|
||||
For local image files, you can use either the browser automation tool or the pure Python COS uploader to obtain a `resourceUrl` and then call the API.
|
||||
验证登录与配额:
|
||||
|
||||
```bash
|
||||
hunyuan3dweb-generate /path/to/image.png wait
|
||||
hy3d auth status # 登录体检
|
||||
hy3d quota # 配额查询
|
||||
```
|
||||
|
||||
Or programmatically via browser automation:
|
||||
## CLI 命令
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.browser.generator import generate_3d
|
||||
|
||||
result = generate_3d("/path/to/image.png", wait_for_complete=True)
|
||||
print(f"Model URL: {result.get('modelUrl')}")
|
||||
```
|
||||
hy3d [--json] [--cookies PATH] <command> [args...]
|
||||
```
|
||||
|
||||
Or via pure Python upload:
|
||||
| 命令 | 说明 | 示例 |
|
||||
|---|---|---|
|
||||
| `quota` | 查配额 | `hy3d quota` |
|
||||
| `list` | 作品列表 | `hy3d list --page 2` |
|
||||
| `count` | 作品统计 | `hy3d count` |
|
||||
| `user` | 用户信息 | `hy3d user` |
|
||||
| `status <id>` | 生成状态/详情 | `hy3d status <id> --wait` |
|
||||
| `formats <id>` | 可用下载格式 | `hy3d formats <id> --converted` |
|
||||
| `download <id>` | 下载模型 | `hy3d download <id> --format glb -o model.glb` |
|
||||
| `cancel <id>` | 取消任务 | `hy3d cancel <id>` |
|
||||
| `share <id>` | 生成分享 | `hy3d share <id>` |
|
||||
| `text "<prompt>"` | 文生 3D(4 配额) | `hy3d text "a red teapot" --style cyberpunk --wait` |
|
||||
| `image <file\|url>` | 图生 3D(本地自动上传) | `hy3d image ./photo.png --wait` |
|
||||
| `multi-view <img...>` | 多视角图生 3D(≥2 张) | `hy3d multi-view a.png b.png c.png --wait` |
|
||||
| `sketch <img> --prompt P` | 草图生 3D | `hy3d sketch sketch.png --prompt "a robot" --wait` |
|
||||
| `animate <img> --motion M` | 3D 动画 | `hy3d animate m.png --motion dancing --wait` |
|
||||
| `texture <img> --prompt P` | 白模上纹理 | `hy3d texture white.png --prompt "red metallic rust" --wait` |
|
||||
| `topo <img>` | 智能减面 | `hy3d topo model.png --faces 5000 --wait` |
|
||||
| `auth status\|login` | 登录体检/登录 | `hy3d auth status` |
|
||||
| `config` | 配置与环境变量 | `hy3d config` |
|
||||
|
||||
生成命令都支持 `--wait`(提交后轮询到完成并输出模型链接)、`--style`、`--title`。
|
||||
所有查询命令支持 `--json`(机器可读输出)。
|
||||
|
||||
**动作类型**(`animate`):`capoeira falling jumping kicking sword running dancing`
|
||||
**纹理风格**(`--style`):`sculpture qinghuaci china_style cartoon cyberpunk`
|
||||
**拓扑面数**(`topo --faces`):`5000 18000 30000`
|
||||
**下载格式键**(`download --format`):`glb obj mtl obj_url geometryGlb textureGlb textureObj image_url pbrImage pbrMetallicImage pbrRoughnessImage pbrNormalImage invisible_wall air_wall`,加 `--converted` 可转换出 `fbx stl usdz mp4 gif`
|
||||
|
||||
### 退出码(agent 友好)
|
||||
|
||||
| 码 | 含义 | 处理 |
|
||||
|---|---|---|
|
||||
| 0 | 成功 | — |
|
||||
| 1 | 环境/网络/参数/服务端 4xx | 看 stderr |
|
||||
| 2 | 认证失败(token 无效/过期) | 重新登录:`hy3d auth login` |
|
||||
| 3 | cookie 文件缺失 | 首次登录:`hy3d auth login` |
|
||||
|
||||
## Python API
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.cos_upload import upload_image
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
resource_url = upload_image("/path/to/image.png")
|
||||
from hy3d import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
result = api.generate_from_image(resource_url, title="My Model")
|
||||
```
|
||||
print(api.get_quota_info()) # {'remainQuota': 20, 'totalQuota': 20, ...}
|
||||
|
||||
### 3. Image-to-3D (API with Existing resourceUrl)
|
||||
|
||||
If you already have a `resourceUrl` (e.g. from a previous browser upload), use the pure API client:
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_from_image(
|
||||
image_url="https://3d.hunyuan.tencent.com/api/3d/resource/download?resourceId=...",
|
||||
title="My Model"
|
||||
)
|
||||
result = api.generate_from_text("a ceramic vase", style=api.STYLE_CYBERPUNK)
|
||||
cid = result["creationsId"]
|
||||
final = api.wait_for_completion(cid) # 内置轮询,进度打 stdout
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
# 本地图片 → COS 上传 → 图生 3D 全自动
|
||||
from hy3d import upload_image
|
||||
resource_url = upload_image("/path/to/photo.png")
|
||||
api.generate_from_image(resource_url, title="My Model")
|
||||
|
||||
# 下载模型(原生 + 按需转换 fbx/stl/usdz/mp4/gif)
|
||||
urls = api.get_model_urls(cid, include_converted=True) # 转换会调用 resourceConvert
|
||||
api.download_model(cid, "glb") # → ./model.glb
|
||||
```
|
||||
|
||||
### 4. Multi-View Image-to-3D
|
||||
完整 API 一览(`Hunyuan3DAPIComplete` / `hy3d/api_complete.py`):
|
||||
|
||||
Use multiple images from different angles to generate a more accurate 3D model.
|
||||
| 方法 | 功能 |
|
||||
|---|---|
|
||||
| `generate_from_text` / `generate_from_image` | 文生 3D / 图生 3D |
|
||||
| `generate_from_multi_view` | 多视角图生 3D |
|
||||
| `generate_from_sketch` | 草图生 3D |
|
||||
| `generate_animation` / `generate_texture` | 动画 / 纹理 |
|
||||
| `generate_lowpoly` | 智能拓扑减面 |
|
||||
| `get_quota_info` / `get_creation_list` / `get_creation_count` | 配额 / 作品 / 统计 |
|
||||
| `get_generation_status` / `wait_for_completion` / `cancel_generation` | 任务管理 |
|
||||
| `get_model_urls` / `download_model` / `resource_convert` | 模型下载与格式转换 |
|
||||
| `get_upload_info` / `review_resource` | 资源上传与审核 |
|
||||
| `create_share` | 分享 |
|
||||
|
||||
轻量版客户端 `Hunyuan3DAPI`(`hy3d/api.py`)只含文生/图生/配额/列表等核心接口。
|
||||
|
||||
### 多账号 / 自定义 cookie
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_from_multi_view(
|
||||
image_urls=[
|
||||
"https://.../front.png",
|
||||
"https://.../side.png",
|
||||
"https://.../back.png",
|
||||
],
|
||||
title="Multi-View Model"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
from hy3d import Hunyuan3DAPIComplete, load_cookies_from_file
|
||||
api = Hunyuan3DAPIComplete(cookies=load_cookies_from_file("/path/to/cookies.txt"))
|
||||
```
|
||||
|
||||
### 5. Sketch-to-3D
|
||||
CLI 等价写法:`hy3d --cookies /path/to/cookies.txt quota`,或环境变量 `HY3D_COOKIES`。
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
## 浏览器工具(可选)
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
| 命令 | 用途 |
|
||||
|---|---|
|
||||
| `hy3d-login` | 邮箱验证码登录(无头,自动保存 cookie 与浏览器 profile) |
|
||||
| `hy3d-sniffer` | API 请求/响应抓包(`api_requests.log.json`) |
|
||||
| `hy3d-generate <img> [wait]` | 浏览器内图生 3D(走浏览器签名,适合对照验证) |
|
||||
|
||||
result = api.generate_from_sketch(
|
||||
sketch_url="https://...",
|
||||
prompt="a sketch of a robot",
|
||||
title="Sketch Robot"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
需要 `pip install "hy3d[browser]"`(cloakbrowser + playwright)。
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
## 配置文件与目录
|
||||
|
||||
### 6. Animation Generation
|
||||
- cookie:`~/.config/hunyuan3dweb/cookies.txt`(路径保持不变,避免重新登录)
|
||||
- 浏览器 profile:`~/.config/hunyuan3dweb/profile`
|
||||
- 环境变量:`HY3D_COOKIES`(cookie 路径)、`HY3D_JSON=1`(等同 `--json`)
|
||||
|
||||
Requires a model image URL and a motion type.
|
||||
## 文件结构
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `hy3d/cli.py` | CLI 入口(全部命令) |
|
||||
| `hy3d/api.py` | 轻量 API 客户端(图生 3D / 文生 3D) |
|
||||
| `hy3d/api_complete.py` | 完整 API 客户端(全部生成模式) |
|
||||
| `hy3d/sign.py` | 签名算法(HMAC-SHA256 + 密钥派生) |
|
||||
| `hy3d/cos_upload.py` | 纯 Python COS 上传(免浏览器) |
|
||||
| `hy3d/config.py` | 配置路径管理 |
|
||||
| `hy3d/browser/` | 登录 / 抓包 / 浏览器自动化工具 |
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
## 相关文档
|
||||
|
||||
# Available motions: MOTION_CAPOEIRA, MOTION_FALLING, MOTION_JUMPING,
|
||||
# MOTION_KICKING, MOTION_SWORD, MOTION_RUNNING, MOTION_DANCING
|
||||
result = api.generate_animation(
|
||||
model_image_url="https://...",
|
||||
motion_type=api.MOTION_DANCING,
|
||||
title="Dancing Model"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 7. Texture Generation
|
||||
|
||||
Apply texture to a white/untexured model.
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_texture(
|
||||
white_model_url="https://...",
|
||||
prompt="red metallic texture with rust",
|
||||
title="Textured Model"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 8. Smart Topology (Decimation / Low Poly)
|
||||
|
||||
Reduce polygon count of an existing model.
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_lowpoly(
|
||||
model_url="https://...",
|
||||
face_count=api.TOPO_LOW, # 5000, 18000, or 30000
|
||||
topology_format="glb", # "glb" or "obj"
|
||||
title="Low Poly Model"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### Basic Client (Lightweight)
|
||||
|
||||
For users who only need image-to-3D and text-to-3D:
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPI
|
||||
|
||||
api = Hunyuan3DAPI()
|
||||
|
||||
# Text-to-3D
|
||||
api.generate_text("a cat")
|
||||
|
||||
# Image-to-3D (requires existing resourceUrl)
|
||||
api.generate_3d(image_url="...")
|
||||
```
|
||||
|
||||
### Explicit Cookie Path (Multi-account or Custom Path)
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete, load_cookies_from_file
|
||||
|
||||
cookies = load_cookies_from_file("/path/to/cookies.txt")
|
||||
api = Hunyuan3DAPIComplete(cookies=cookies)
|
||||
```
|
||||
|
||||
## CLI Commands
|
||||
|
||||
- `hunyuan3dweb` - API CLI tool (supports quota / list / text / status subcommands)
|
||||
- `hunyuan3dweb-login` - Browser login (auto-saves cookies)
|
||||
- `hunyuan3dweb-sniffer` - API request/response sniffer
|
||||
- `hunyuan3dweb-generate` - Browser automation for generation
|
||||
|
||||
### CLI Examples
|
||||
|
||||
```bash
|
||||
# Check quota
|
||||
hunyuan3dweb quota
|
||||
|
||||
# List creations
|
||||
hunyuan3dweb list
|
||||
|
||||
# Text-to-3D
|
||||
hunyuan3dweb text "a red apple"
|
||||
|
||||
# Check generation status
|
||||
hunyuan3dweb status <creationsId>
|
||||
|
||||
# Browser login
|
||||
hunyuan3dweb-login
|
||||
|
||||
# Generate from local image (browser automation)
|
||||
hunyuan3dweb-generate /path/to/image.png wait
|
||||
|
||||
# Sniff API traffic
|
||||
hunyuan3dweb-sniffer
|
||||
```
|
||||
|
||||
## File Reference
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `hunyuan3dweb/api.py` | Basic API client (image2model, text2model) |
|
||||
| `hunyuan3dweb/api_complete.py` | Full API client (all generation modes) |
|
||||
| `hunyuan3dweb/sign.py` | Tencent Hunyuan 3D signing algorithm |
|
||||
| `hunyuan3dweb/cos_upload.py` | Pure Python COS upload helper |
|
||||
| `hunyuan3dweb/config.py` | User config path management |
|
||||
| `hunyuan3dweb/cli.py` | CLI entry point |
|
||||
| `hunyuan3dweb/browser/login.py` | Browser login tool |
|
||||
| `hunyuan3dweb/browser/sniffer.py` | API sniffer tool |
|
||||
| `hunyuan3dweb/browser/generator.py` | Browser automation generator |
|
||||
| `doc/api.md` | API endpoint documentation |
|
||||
- [reverse engineering 文档](README_REVERSE_ENGINEERING.md) — 签名算法逆向过程与关键常量
|
||||
- [泛用性分析](UNIVERSALITY_ANALYSIS.md) — 算法能否复用到其他产品
|
||||
- [API 端点文档](doc/api.md)
|
||||
+138
-278
@@ -1,304 +1,164 @@
|
||||
# hunyuan3dweb
|
||||
# hy3d
|
||||
|
||||
腾讯混元3D (https://3d.hunyuan.tencent.com/) 的 Python API 客户端与浏览器自动化工具。
|
||||
非官方腾讯混元 3D CLI 与 Python API 客户端([3d.hunyuan.tencent.com](https://3d.hunyuan.tencent.com/))。
|
||||
|
||||
纯 Python HTTP 调用腾讯混元 3D 的 AI 能力:**文生 3D、图生 3D、多视角、草图、动画、纹理、智能拓扑减面**,以及配额查询、作品管理、模型下载(GLB/OBJ/FBX/STL/USDZ/MP4/GIF)。
|
||||
|
||||
> ⚠️ 非官方项目:对混元 3D Web 前端签名算法的逆向实现,与腾讯无关。仅供个人学习与自动化使用。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
pip install hy3d # 纯 API 客户端
|
||||
pip install "hy3d[browser]" # 额外安装浏览器工具(登录/抓包/浏览器自动化)
|
||||
```
|
||||
|
||||
如需浏览器自动化功能:
|
||||
不安装也能用——克隆仓库后直接用源码跑(无需 `pip install`):
|
||||
|
||||
```bash
|
||||
pip install -e ".[browser]"
|
||||
cd hy3d
|
||||
PYTHONPATH=$PWD python3 -m hy3d.cli quota
|
||||
```
|
||||
|
||||
## 登录与配置
|
||||
## 快速开始
|
||||
|
||||
第一次使用需要先通过浏览器登录,系统会自动保存 Cookie:
|
||||
首次使用需要登录一次(**在真实终端运行**,邮箱验证码登录,无头浏览器):
|
||||
|
||||
```bash
|
||||
hunyuan3dweb-login
|
||||
hy3d auth login
|
||||
# → 按提示: 输邮箱 → 收验证码 → 输验证码
|
||||
# → cookie 自动保存到 ~/.config/hunyuan3dweb/cookies.txt
|
||||
```
|
||||
|
||||
登录成功后:
|
||||
- 浏览器 Profile 保存到 `~/.config/hunyuan3dweb/profile`
|
||||
- Cookie 自动提取到 `~/.config/hunyuan3dweb/cookies.txt`
|
||||
|
||||
之后写 Python 脚本无需再处理 Cookie,库会自动读取。
|
||||
|
||||
## Python 脚本调用
|
||||
|
||||
### 快速开始(自动读取默认 Cookie)
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
# 查配额
|
||||
quota = api.get_quota_info()
|
||||
print(f"剩余: {quota['remainQuota']}/{quota['totalQuota']}")
|
||||
```
|
||||
|
||||
### 1. 文生3D
|
||||
|
||||
最简单的模式,无需上传图片。
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
# 提交生成任务
|
||||
result = api.generate_from_text("一只陶瓷花瓶", title="测试")
|
||||
cid = result["creationsId"]
|
||||
|
||||
# 轮询直到完成(内置轮询,自动打印进度)
|
||||
final = api.wait_for_completion(cid)
|
||||
print("生成完成")
|
||||
```
|
||||
|
||||
**完整流水线:文生3D → 提取下载链接**
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
job = api.generate_from_text(
|
||||
"赛博朋克风格的机械狗",
|
||||
style=api.STYLE_CYBERPUNK
|
||||
)
|
||||
cid = job["creationsId"]
|
||||
|
||||
result = api.wait_for_completion(cid, timeout=600, poll_interval=5)
|
||||
|
||||
if result["status"] == "success":
|
||||
models = result.get("result", [])
|
||||
for m in models:
|
||||
if m["status"] == "success":
|
||||
urls = m["urlResult"]
|
||||
print(f"GLB: {urls.get('glb')}")
|
||||
print(f"OBJ: {urls.get('obj')}")
|
||||
print(f"图片: {urls.get('image_url')}")
|
||||
```
|
||||
|
||||
### 2. 图生3D(本地文件)
|
||||
|
||||
本地图片文件可通过浏览器自动化工具上传,也可使用纯 Python 的 COS 上传模块先拿到 `resourceUrl` 再走 API。
|
||||
验证登录与配额:
|
||||
|
||||
```bash
|
||||
hunyuan3dweb-generate /path/to/image.png wait
|
||||
```
|
||||
|
||||
或通过浏览器自动化脚本:
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.browser.generator import generate_3d
|
||||
|
||||
result = generate_3d("/path/to/image.png", wait_for_complete=True)
|
||||
print(f"模型地址: {result.get('modelUrl')}")
|
||||
```
|
||||
|
||||
或通过纯 Python 上传:
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.cos_upload import upload_image
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
resource_url = upload_image("/path/to/image.png")
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
result = api.generate_from_image(resource_url, title="我的模型")
|
||||
```
|
||||
|
||||
### 3. 图生3D(API,已有 resourceUrl)
|
||||
|
||||
如果你已经有 `resourceUrl`(例如之前通过浏览器上传过),可直接调用 API:
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_from_image(
|
||||
image_url="https://3d.hunyuan.tencent.com/api/3d/resource/download?resourceId=...",
|
||||
title="我的模型"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 4. 多图视角生3D(多视图)
|
||||
|
||||
使用多张不同角度的图片生成更精确的 3D 模型。
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_from_multi_view(
|
||||
image_urls=[
|
||||
"https://.../front.png",
|
||||
"https://.../side.png",
|
||||
"https://.../back.png",
|
||||
],
|
||||
title="多视图模型"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 5. 草图生3D
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_from_sketch(
|
||||
sketch_url="https://...",
|
||||
prompt="一个机器人的草图",
|
||||
title="草图机器人"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 6. 动画生成
|
||||
|
||||
需要模型图片 URL 和动作类型。
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
# 可选动作:MOTION_CAPOEIRA, MOTION_FALLING, MOTION_JUMPING,
|
||||
# MOTION_KICKING, MOTION_SWORD, MOTION_RUNNING, MOTION_DANCING
|
||||
result = api.generate_animation(
|
||||
model_image_url="https://...",
|
||||
motion_type=api.MOTION_DANCING,
|
||||
title="跳舞的模型"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 7. 纹理生成
|
||||
|
||||
为白模/无纹理模型生成贴图。
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_texture(
|
||||
white_model_url="https://...",
|
||||
prompt="红色金属锈迹纹理",
|
||||
title="纹理模型"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 8. 智能拓扑(减面 / Low Poly)
|
||||
|
||||
降低现有模型的面数。
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
|
||||
result = api.generate_lowpoly(
|
||||
model_url="https://...",
|
||||
face_count=api.TOPO_LOW, # 5000 / 18000 / 30000
|
||||
topology_format="glb", # "glb" 或 "obj"
|
||||
title="低模模型"
|
||||
)
|
||||
cid = result["creationsId"]
|
||||
|
||||
final = api.wait_for_completion(cid)
|
||||
```
|
||||
|
||||
### 基础客户端(轻量)
|
||||
|
||||
仅需图生3D和文生3D的用户可使用简化客户端:
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPI
|
||||
|
||||
api = Hunyuan3DAPI()
|
||||
|
||||
# 文生3D
|
||||
api.generate_text("一只猫")
|
||||
|
||||
# 图生3D(需已有 resourceUrl)
|
||||
api.generate_3d(image_url="...")
|
||||
```
|
||||
|
||||
### 显式指定 Cookie(多账户或自定义路径)
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete, load_cookies_from_file
|
||||
|
||||
cookies = load_cookies_from_file("/path/to/cookies.txt")
|
||||
api = Hunyuan3DAPIComplete(cookies=cookies)
|
||||
hy3d auth status # 登录体检
|
||||
hy3d quota # 配额查询
|
||||
```
|
||||
|
||||
## CLI 命令
|
||||
|
||||
- `hunyuan3dweb` - API CLI 工具(支持 quota / list / text / status 子命令)
|
||||
- `hunyuan3dweb-login` - 浏览器登录(自动保存 Cookie)
|
||||
- `hunyuan3dweb-sniffer` - API 拦截分析
|
||||
- `hunyuan3dweb-generate` - 浏览器自动化生成
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
# 查询配额
|
||||
hunyuan3dweb quota
|
||||
|
||||
# 查询作品列表
|
||||
hunyuan3dweb list
|
||||
|
||||
# 文生3D
|
||||
hunyuan3dweb text "一只红色的苹果"
|
||||
|
||||
# 查询生成状态
|
||||
hunyuan3dweb status <creationsId>
|
||||
|
||||
# 浏览器登录
|
||||
hunyuan3dweb-login
|
||||
|
||||
# 本地图片生成(浏览器自动化)
|
||||
hunyuan3dweb-generate /path/to/image.png wait
|
||||
|
||||
# API 拦截
|
||||
hunyuan3dweb-sniffer
|
||||
```
|
||||
hy3d [--json] [--cookies PATH] <command> [args...]
|
||||
```
|
||||
|
||||
## 文件说明
|
||||
| 命令 | 说明 | 示例 |
|
||||
|---|---|---|
|
||||
| `quota` | 查配额 | `hy3d quota` |
|
||||
| `list` | 作品列表 | `hy3d list --page 2` |
|
||||
| `count` | 作品统计 | `hy3d count` |
|
||||
| `user` | 用户信息 | `hy3d user` |
|
||||
| `status <id>` | 生成状态/详情 | `hy3d status <id> --wait` |
|
||||
| `formats <id>` | 可用下载格式 | `hy3d formats <id> --converted` |
|
||||
| `download <id>` | 下载模型 | `hy3d download <id> --format glb -o model.glb` |
|
||||
| `cancel <id>` | 取消任务 | `hy3d cancel <id>` |
|
||||
| `share <id>` | 生成分享 | `hy3d share <id>` |
|
||||
| `text "<prompt>"` | 文生 3D(4 配额) | `hy3d text "a red teapot" --style cyberpunk --wait` |
|
||||
| `image <file\|url>` | 图生 3D(本地自动上传) | `hy3d image ./photo.png --wait` |
|
||||
| `multi-view <img...>` | 多视角图生 3D(≥2 张) | `hy3d multi-view a.png b.png c.png --wait` |
|
||||
| `sketch <img> --prompt P` | 草图生 3D | `hy3d sketch sketch.png --prompt "a robot" --wait` |
|
||||
| `animate <img> --motion M` | 3D 动画 | `hy3d animate m.png --motion dancing --wait` |
|
||||
| `texture <img> --prompt P` | 白模上纹理 | `hy3d texture white.png --prompt "red metallic rust" --wait` |
|
||||
| `topo <img>` | 智能减面 | `hy3d topo model.png --faces 5000 --wait` |
|
||||
| `auth status\|login` | 登录体检/登录 | `hy3d auth status` |
|
||||
| `config` | 配置与环境变量 | `hy3d config` |
|
||||
|
||||
生成命令都支持 `--wait`(提交后轮询到完成并输出模型链接)、`--style`、`--title`。
|
||||
所有命令支持 `--json`(机器可读输出)。
|
||||
|
||||
**动作类型**(`animate`):`capoeira falling jumping kicking sword running dancing`
|
||||
**纹理风格**(`--style`):`sculpture qinghuaci china_style cartoon cyberpunk`
|
||||
**拓扑面数**(`topo --faces`):`5000 18000 30000`
|
||||
**下载格式键**(`download --format`):`glb obj mtl obj_url geometryGlb textureGlb textureObj image_url pbrImage pbrMetallicImage pbrRoughnessImage pbrNormalImage invisible_wall air_wall`,加 `--converted` 可转换出 `fbx stl usdz mp4 gif`
|
||||
|
||||
### 退出码(agent 友好)
|
||||
|
||||
| 码 | 含义 | 处理 |
|
||||
|---|---|---|
|
||||
| 0 | 成功 | — |
|
||||
| 1 | 环境/网络/参数/服务端 4xx | 看 stderr |
|
||||
| 2 | 认证失败(token 无效/过期) | 重新登录:`hy3d auth login` |
|
||||
| 3 | cookie 文件缺失 | 首次登录:`hy3d auth login` |
|
||||
|
||||
## Python API
|
||||
|
||||
```python
|
||||
from hy3d import Hunyuan3DAPIComplete
|
||||
|
||||
api = Hunyuan3DAPIComplete()
|
||||
print(api.get_quota_info()) # {'remainQuota': 20, 'totalQuota': 20, ...}
|
||||
|
||||
result = api.generate_from_text("a ceramic vase", style=api.STYLE_CYBERPUNK)
|
||||
cid = result["creationsId"]
|
||||
final = api.wait_for_completion(cid) # 内置轮询,进度打 stdout
|
||||
|
||||
# 本地图片 → COS 上传 → 图生 3D 全自动
|
||||
from hy3d import upload_image
|
||||
resource_url = upload_image("/path/to/photo.png")
|
||||
api.generate_from_image(resource_url, title="My Model")
|
||||
|
||||
# 下载模型(原生 + 按需转换 fbx/stl/usdz/mp4/gif)
|
||||
urls = api.get_model_urls(cid, include_converted=True) # 转换会调用 resourceConvert
|
||||
api.download_model(cid, "glb") # → ./model.glb
|
||||
```
|
||||
|
||||
完整 API 一览(`Hunyuan3DAPIComplete` / `hy3d/api_complete.py`):
|
||||
|
||||
| 方法 | 功能 |
|
||||
|---|---|
|
||||
| `generate_from_text` / `generate_from_image` | 文生 3D / 图生 3D |
|
||||
| `generate_from_multi_view` | 多视角图生 3D |
|
||||
| `generate_from_sketch` | 草图生 3D |
|
||||
| `generate_animation` / `generate_texture` | 动画 / 纹理 |
|
||||
| `generate_lowpoly` | 智能拓扑减面 |
|
||||
| `get_quota_info` / `get_creation_list` / `get_creation_count` | 配额 / 作品 / 统计 |
|
||||
| `get_generation_status` / `wait_for_completion` / `cancel_generation` | 任务管理 |
|
||||
| `get_model_urls` / `download_model` / `resource_convert` | 模型下载与格式转换 |
|
||||
| `get_upload_info` / `review_resource` | 资源上传与审核 |
|
||||
| `create_share` | 分享 |
|
||||
|
||||
轻量版客户端 `Hunyuan3DAPI`(`hy3d/api.py`)只含文生/图生/配额/列表等核心接口。
|
||||
|
||||
### 多账号 / 自定义 cookie
|
||||
|
||||
```python
|
||||
from hy3d import Hunyuan3DAPIComplete, load_cookies_from_file
|
||||
api = Hunyuan3DAPIComplete(cookies=load_cookies_from_file("/path/to/cookies.txt"))
|
||||
```
|
||||
|
||||
CLI 等价写法:`hy3d --cookies /path/to/cookies.txt quota`,或环境变量 `HY3D_COOKIES`。
|
||||
|
||||
## 浏览器工具(可选)
|
||||
|
||||
| 命令 | 用途 |
|
||||
|---|---|
|
||||
| `hy3d-login` | 邮箱验证码登录(无头,自动保存 cookie 与浏览器 profile) |
|
||||
| `hy3d-sniffer` | API 请求/响应抓包(`api_requests.log.json`) |
|
||||
| `hy3d-generate <img> [wait]` | 浏览器内图生 3D(走浏览器签名,适合对照验证) |
|
||||
|
||||
需要 `pip install "hy3d[browser]"`(cloakbrowser + playwright)。
|
||||
|
||||
## 配置与目录
|
||||
|
||||
- cookie:`~/.config/hunyuan3dweb/cookies.txt`(路径保持不变,避免重新登录)
|
||||
- 浏览器 profile:`~/.config/hunyuan3dweb/profile`
|
||||
- 环境变量:`HY3D_COOKIES`(cookie 路径)、`HY3D_JSON=1`(等同 `--json`)
|
||||
|
||||
## 文件结构
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `hunyuan3dweb/api.py` | 基础 API 客户端(图生3D、文生3D) |
|
||||
| `hunyuan3dweb/api_complete.py` | 完整 API 客户端(所有生成模式) |
|
||||
| `hunyuan3dweb/sign.py` | 腾讯混元3D 签名算法 |
|
||||
| `hunyuan3dweb/cos_upload.py` | 纯 Python COS 上传工具 |
|
||||
| `hunyuan3dweb/config.py` | 用户配置路径管理 |
|
||||
| `hunyuan3dweb/cli.py` | CLI 入口 |
|
||||
| `hunyuan3dweb/browser/login.py` | 浏览器登录工具 |
|
||||
| `hunyuan3dweb/browser/sniffer.py` | API 拦截工具 |
|
||||
| `hunyuan3dweb/browser/generator.py` | 浏览器自动化生成 |
|
||||
| `doc/api.md` | API 接口文档 |
|
||||
|---|---|
|
||||
| `hy3d/cli.py` | CLI 入口(全部命令) |
|
||||
| `hy3d/api.py` | 轻量 API 客户端(图生 3D / 文生 3D) |
|
||||
| `hy3d/api_complete.py` | 完整 API 客户端(全部生成模式) |
|
||||
| `hy3d/sign.py` | 签名算法(HMAC-SHA256 + 密钥派生) |
|
||||
| `hy3d/cos_upload.py` | 纯 Python COS 上传(免浏览器) |
|
||||
| `hy3d/config.py` | 配置路径管理 |
|
||||
| `hy3d/browser/` | 登录 / 抓包 / 浏览器自动化工具 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [逆向工程文档](README_REVERSE_ENGINEERING_CN.md) — 签名算法逆向过程与关键常量
|
||||
- [泛用性分析](UNIVERSALITY_ANALYSIS.md) — 算法能否复用到其他产品
|
||||
- [API 端点文档](doc/api_complete.md)
|
||||
@@ -60,7 +60,7 @@ Upload directly via `cos_upload.py` without a browser:
|
||||
4. Obtain `resourceUrl` for subsequent image-to-3D API calls
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.cos_upload import upload_image
|
||||
from hy3d.cos_upload import upload_image
|
||||
|
||||
resource_url = upload_image("/path/to/image.png")
|
||||
```
|
||||
@@ -71,15 +71,15 @@ resource_url = upload_image("/path/to/image.png")
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `hunyuan3dweb/sign.py` | Signing algorithm in pure Python |
|
||||
| `hunyuan3dweb/api.py` | Basic API client (image2model, text2model) |
|
||||
| `hunyuan3dweb/api_complete.py` | Full API client (all generation modes) |
|
||||
| `hunyuan3dweb/cos_upload.py` | Pure Python COS upload helper (no browser needed) |
|
||||
| `hunyuan3dweb/config.py` | User config path management (cookies, profile) |
|
||||
| `hunyuan3dweb/cli.py` | CLI entry point |
|
||||
| `hunyuan3dweb/browser/login.py` | Browser automation login tool |
|
||||
| `hunyuan3dweb/browser/generator.py` | Browser automation image-to-3D |
|
||||
| `hunyuan3dweb/browser/sniffer.py` | API request sniffer |
|
||||
| `hy3d/sign.py` | Signing algorithm in pure Python |
|
||||
| `hy3d/api.py` | Basic API client (image2model, text2model) |
|
||||
| `hy3d/api_complete.py` | Full API client (all generation modes) |
|
||||
| `hy3d/cos_upload.py` | Pure Python COS upload helper (no browser needed) |
|
||||
| `hy3d/config.py` | User config path management (cookies, profile) |
|
||||
| `hy3d/cli.py` | CLI entry point |
|
||||
| `hy3d/browser/login.py` | Browser automation login tool |
|
||||
| `hy3d/browser/generator.py` | Browser automation image-to-3D |
|
||||
| `hy3d/browser/sniffer.py` | API request sniffer |
|
||||
|
||||
---
|
||||
|
||||
@@ -88,7 +88,7 @@ resource_url = upload_image("/path/to/image.png")
|
||||
### 1. Login to Obtain Cookie
|
||||
|
||||
```bash
|
||||
hunyuan3dweb-login
|
||||
hy3d-login
|
||||
# Follow prompts to enter email and verification code
|
||||
# Login state is automatically saved to ~/.config/hunyuan3dweb/profile
|
||||
# Cookies are also exported to ~/.config/hunyuan3dweb/cookies.txt
|
||||
@@ -102,7 +102,7 @@ hunyuan3dweb-login
|
||||
### 2. Pure Python 3D Generation
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPI
|
||||
from hy3d import Hunyuan3DAPI
|
||||
|
||||
# Automatically load cookie from ~/.config/hunyuan3dweb/cookies.txt
|
||||
api = Hunyuan3DAPI()
|
||||
@@ -120,8 +120,8 @@ status = api.get_generation_status(result["creationsId"])
|
||||
### 3. Local Image Upload + Image-to-3D (Pure Python)
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.cos_upload import upload_image
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
from hy3d.cos_upload import upload_image
|
||||
from hy3d import Hunyuan3DAPIComplete
|
||||
|
||||
# Upload local image to COS
|
||||
resource_url = upload_image("/path/to/image.png")
|
||||
@@ -135,16 +135,16 @@ result = api.generate_from_image(resource_url, title="My Model")
|
||||
|
||||
```bash
|
||||
# List available formats for a creation
|
||||
hunyuan3dweb formats <creation_id>
|
||||
hy3d formats <creation_id>
|
||||
|
||||
# Include converted formats (fbx, stl, usdz, mp4, gif)
|
||||
hunyuan3dweb formats <creation_id> --converted
|
||||
hy3d formats <creation_id> --converted
|
||||
|
||||
# Download a specific format (default: glb)
|
||||
hunyuan3dweb download <creation_id> --format glb -o model.glb
|
||||
hy3d download <creation_id> --format glb -o model.glb
|
||||
|
||||
# Download a converted format
|
||||
hunyuan3dweb download <creation_id> --format usdz --converted -o model.usdz
|
||||
hy3d download <creation_id> --format usdz --converted -o model.usdz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +60,7 @@ sign = HMAC-SHA256(param_str, key="Hf6d6KFB3D")
|
||||
4. 获得 `resourceUrl` 供后续图生3D API 使用
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.cos_upload import upload_image
|
||||
from hy3d.cos_upload import upload_image
|
||||
|
||||
resource_url = upload_image("/path/to/image.png")
|
||||
```
|
||||
@@ -71,15 +71,15 @@ resource_url = upload_image("/path/to/image.png")
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `hunyuan3dweb/sign.py` | 签名算法纯 Python 实现 |
|
||||
| `hunyuan3dweb/api.py` | 基础 API 客户端(图生3D、文生3D) |
|
||||
| `hunyuan3dweb/api_complete.py` | 完整 API 客户端(所有生成模式) |
|
||||
| `hunyuan3dweb/cos_upload.py` | 纯 Python COS 上传工具(无需浏览器) |
|
||||
| `hunyuan3dweb/config.py` | 用户配置路径管理(cookie、profile) |
|
||||
| `hunyuan3dweb/cli.py` | CLI 入口 |
|
||||
| `hunyuan3dweb/browser/login.py` | 浏览器自动化登录工具 |
|
||||
| `hunyuan3dweb/browser/generator.py` | 浏览器自动化图生3D |
|
||||
| `hunyuan3dweb/browser/sniffer.py` | API 请求嗅探工具 |
|
||||
| `hy3d/sign.py` | 签名算法纯 Python 实现 |
|
||||
| `hy3d/api.py` | 基础 API 客户端(图生3D、文生3D) |
|
||||
| `hy3d/api_complete.py` | 完整 API 客户端(所有生成模式) |
|
||||
| `hy3d/cos_upload.py` | 纯 Python COS 上传工具(无需浏览器) |
|
||||
| `hy3d/config.py` | 用户配置路径管理(cookie、profile) |
|
||||
| `hy3d/cli.py` | CLI 入口 |
|
||||
| `hy3d/browser/login.py` | 浏览器自动化登录工具 |
|
||||
| `hy3d/browser/generator.py` | 浏览器自动化图生3D |
|
||||
| `hy3d/browser/sniffer.py` | API 请求嗅探工具 |
|
||||
|
||||
---
|
||||
|
||||
@@ -88,7 +88,7 @@ resource_url = upload_image("/path/to/image.png")
|
||||
### 1. 登录获取 Cookie
|
||||
|
||||
```bash
|
||||
hunyuan3dweb-login
|
||||
hy3d-login
|
||||
# 按提示输入邮箱和验证码
|
||||
# 登录状态自动保存到 ~/.config/hunyuan3dweb/profile
|
||||
# Cookie 同时导出到 ~/.config/hunyuan3dweb/cookies.txt
|
||||
@@ -102,7 +102,7 @@ hunyuan3dweb-login
|
||||
### 2. 纯 Python 生成3D模型
|
||||
|
||||
```python
|
||||
from hunyuan3dweb import Hunyuan3DAPI
|
||||
from hy3d import Hunyuan3DAPI
|
||||
|
||||
# 自动从 ~/.config/hunyuan3dweb/cookies.txt 加载 cookie
|
||||
api = Hunyuan3DAPI()
|
||||
@@ -120,8 +120,8 @@ status = api.get_generation_status(result["creationsId"])
|
||||
### 3. 本地图片上传 + 图生3D(纯 Python)
|
||||
|
||||
```python
|
||||
from hunyuan3dweb.cos_upload import upload_image
|
||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
||||
from hy3d.cos_upload import upload_image
|
||||
from hy3d import Hunyuan3DAPIComplete
|
||||
|
||||
# 上传本地图片到 COS
|
||||
resource_url = upload_image("/path/to/image.png")
|
||||
@@ -135,16 +135,16 @@ result = api.generate_from_image(resource_url, title="我的模型")
|
||||
|
||||
```bash
|
||||
# 列出某个创作所有可用的下载格式
|
||||
hunyuan3dweb formats <creation_id>
|
||||
hy3d formats <creation_id>
|
||||
|
||||
# 包含转换格式(fbx, stl, usdz, mp4, gif)
|
||||
hunyuan3dweb formats <creation_id> --converted
|
||||
hy3d formats <creation_id> --converted
|
||||
|
||||
# 下载指定格式(默认 glb)
|
||||
hunyuan3dweb download <creation_id> --format glb -o model.glb
|
||||
hy3d download <creation_id> --format glb -o model.glb
|
||||
|
||||
# 下载转换格式(如 usdz)
|
||||
hunyuan3dweb download <creation_id> --format usdz --converted -o model.usdz
|
||||
hy3d download <creation_id> --format usdz --converted -o model.usdz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# hy3d 免安装入口:设置 PYTHONPATH 后转交 hy3d CLI。
|
||||
# `pip install hy3d` 之后直接用系统 PATH 上的 hy3d 即可,此 shim 无需存在。
|
||||
set -euo pipefail
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$REPO_ROOT"
|
||||
exec python3 -m hy3d.cli "$@"
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
Cookie 会话认证。登录后由浏览器自动携带 `sessionid` 等 Cookie。
|
||||
|
||||
持久化数据保存在 `./hunyuan3d_profile`,CloakBrowser 复用该目录即可保持登录态。
|
||||
持久化数据保存在 `~/.config/hunyuan3dweb/profile`,CloakBrowser 复用该目录即可保持登录态。
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# 腾讯混元3D API 完整文档
|
||||
|
||||
> 基础地址: `https://3d.hunyuan.tencent.com`
|
||||
> 签名算法: HMAC-SHA256(已破解,见 `hunyuan3d_sign.py`)
|
||||
> 签名算法: HMAC-SHA256(已破解,见 `hy3d/sign.py`)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Hunyuan3D Web CLI
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from .api import Hunyuan3DAPI, load_cookies_from_file
|
||||
from .config import get_cookie_path
|
||||
|
||||
_DEFAULT_COOKIE = str(get_cookie_path())
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Hunyuan3D Web CLI")
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
quota_parser = subparsers.add_parser("quota", help="查询配额")
|
||||
quota_parser.add_argument("--cookies", "-c", default=_DEFAULT_COOKIE, help="Cookie文件路径")
|
||||
|
||||
list_parser = subparsers.add_parser("list", help="查询作品列表")
|
||||
list_parser.add_argument("--cookies", "-c", default=_DEFAULT_COOKIE, help="Cookie文件路径")
|
||||
|
||||
text_parser = subparsers.add_parser("text", help="文生3D")
|
||||
text_parser.add_argument("prompt", help="文本描述")
|
||||
text_parser.add_argument("--cookies", "-c", default=_DEFAULT_COOKIE, help="Cookie文件路径")
|
||||
text_parser.add_argument("--title", "-t", default="", help="作品标题")
|
||||
|
||||
status_parser = subparsers.add_parser("status", help="查询生成状态")
|
||||
status_parser.add_argument("creation_id", help="创作ID")
|
||||
status_parser.add_argument("--cookies", "-c", default=_DEFAULT_COOKIE, help="Cookie文件路径")
|
||||
|
||||
formats_parser = subparsers.add_parser("formats", help="列出创作可用下载格式")
|
||||
formats_parser.add_argument("creation_id", help="创作ID")
|
||||
formats_parser.add_argument("--converted", action="store_true",
|
||||
help="包含转换格式 (fbx/stl/usdz/mp4/gif)")
|
||||
formats_parser.add_argument("--cookies", "-c", default=_DEFAULT_COOKIE, help="Cookie文件路径")
|
||||
|
||||
download_parser = subparsers.add_parser("download", help="下载指定格式模型")
|
||||
download_parser.add_argument("creation_id", help="创作ID")
|
||||
download_parser.add_argument("--format", "-f", default="glb",
|
||||
help="格式键名 (默认: glb)")
|
||||
download_parser.add_argument("--output", "-o", default=None,
|
||||
help="本地保存路径 (默认自动推断)")
|
||||
download_parser.add_argument("--converted", action="store_true",
|
||||
help="若格式为转换格式,自动调用转换接口")
|
||||
download_parser.add_argument("--cookies", "-c", default=_DEFAULT_COOKIE, help="Cookie文件路径")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
cookies = load_cookies_from_file(args.cookies)
|
||||
api = Hunyuan3DAPI(cookies)
|
||||
|
||||
if args.command == "quota":
|
||||
print(json.dumps(api.get_quota_info(), indent=2, ensure_ascii=False))
|
||||
elif args.command == "list":
|
||||
print(json.dumps(api.get_creation_list(), indent=2, ensure_ascii=False))
|
||||
elif args.command == "text":
|
||||
result = api.generate_text(args.prompt, title=args.title)
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
elif args.command == "status":
|
||||
print(json.dumps(api.get_generation_status(args.creation_id), indent=2, ensure_ascii=False))
|
||||
elif args.command == "formats":
|
||||
urls = api.get_model_urls(args.creation_id, include_converted=args.converted)
|
||||
if not urls:
|
||||
print("暂无可用格式,可能生成未完成或失败。", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
for key, url in urls.items():
|
||||
print(f"{key}: {url}")
|
||||
elif args.command == "download":
|
||||
path = api.download_model(args.creation_id, args.format, args.output,
|
||||
include_converted=args.converted)
|
||||
print(f"已下载: {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,11 +1,14 @@
|
||||
from .api import Hunyuan3DAPI
|
||||
from .api_complete import Hunyuan3DAPI as Hunyuan3DAPIComplete
|
||||
from .sign import sign, sign_with_custom_nonce
|
||||
from .cos_upload import upload_image, upload_file_to_cos
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__version__ = "0.2.0"
|
||||
__all__ = [
|
||||
"Hunyuan3DAPI",
|
||||
"Hunyuan3DAPIComplete",
|
||||
"sign",
|
||||
"sign_with_custom_nonce",
|
||||
]
|
||||
"upload_image",
|
||||
"upload_file_to_cos",
|
||||
]
|
||||
@@ -29,7 +29,7 @@ def _temp_persistent_context(headless=True):
|
||||
if not os.path.exists(standard):
|
||||
raise FileNotFoundError(f"未找到登录状态目录: {standard}")
|
||||
|
||||
temp_root = tempfile.mkdtemp(prefix="hunyuan3dweb-profile-")
|
||||
temp_root = tempfile.mkdtemp(prefix="hy3d-profile-")
|
||||
temp_profile = os.path.join(temp_root, "profile")
|
||||
shutil.copytree(standard, temp_profile, dirs_exist_ok=True)
|
||||
|
||||
@@ -72,7 +72,7 @@ def main():
|
||||
|
||||
if not os.path.exists(PROFILE_DIR):
|
||||
print(f"错误: 未找到持久化目录 {PROFILE_DIR}")
|
||||
print("请先运行 hunyuan3dweb-login 完成登录")
|
||||
print("请先运行 hy3d-login(或 python -m hy3d.browser.login)完成登录")
|
||||
sys.exit(1)
|
||||
|
||||
profile_dir, temp_dir = prepare_profile_dir()
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
hy3d — 腾讯混元 3D 非官方 CLI
|
||||
|
||||
用法: hy3d [--json] [--cookies PATH] <command> [args...]
|
||||
|
||||
全局选项(必须放在命令前):
|
||||
--json 机器可读输出(默认 human 格式)
|
||||
--cookies PATH 指定 cookie 文件(默认 ~/.config/hunyuan3dweb/cookies.txt,可用环境变量 HY3D_COOKIES 覆盖)
|
||||
|
||||
退出码:
|
||||
0 成功
|
||||
1 环境/网络/参数错误
|
||||
2 认证失败(token 无效/过期 → 需重新登录)
|
||||
3 cookie 文件缺失
|
||||
|
||||
示例:
|
||||
hy3d quota
|
||||
hy3d text "a red teapot" --style cyberpunk --wait
|
||||
hy3d image ./photo.png --wait
|
||||
hy3d formats <creationsId>
|
||||
hy3d download <creationsId> --format glb -o model.glb
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Callable, Optional
|
||||
|
||||
import requests
|
||||
|
||||
from .config import get_cookie_path
|
||||
|
||||
DEFAULT_COOKIE = str(get_cookie_path())
|
||||
|
||||
# ---------- 常量(与 api_complete.Hunyuan3DAPI 对齐) ----------
|
||||
|
||||
MOTIONS = {
|
||||
"capoeira": 9,
|
||||
"falling": 10,
|
||||
"jumping": 11,
|
||||
"kicking": 12,
|
||||
"sword": 13,
|
||||
"running": 15,
|
||||
"dancing": 16,
|
||||
}
|
||||
STYLES = ["", "sculpture", "qinghuaci", "china_style", "cartoon", "cyberpunk"]
|
||||
FORMAT_KEYS = [
|
||||
"glb", "obj", "mtl", "obj_url", "geometryGlb", "textureGlb", "textureObj",
|
||||
"image_url", "pbrImage", "pbrMetallicImage", "pbrRoughnessImage",
|
||||
"pbrNormalImage", "invisible_wall", "air_wall",
|
||||
"fbx", "stl", "usdz", "mp4", "gif",
|
||||
]
|
||||
|
||||
|
||||
# ---------- 输出与认证 ----------
|
||||
|
||||
def out(obj, args):
|
||||
"""按 --json/human 输出结果。args 可为 None(默认 human)。"""
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps(obj, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(obj)
|
||||
|
||||
|
||||
def make_api(args):
|
||||
"""构造 API 客户端,加载 cookie(--cookies PATH 或 HY3D_COOKIES 覆盖默认)。"""
|
||||
from .api_complete import Hunyuan3DAPI, load_cookies_from_file
|
||||
path = args.cookies or os.environ.get("HY3D_COOKIES") or DEFAULT_COOKIE
|
||||
try:
|
||||
cookies = load_cookies_from_file(path)
|
||||
except FileNotFoundError:
|
||||
print(f"ERR3: cookie 文件不存在: {path}", file=sys.stderr)
|
||||
print(" 首次使用需登录(真实终端,勿用管道):", file=sys.stderr)
|
||||
print(" python -m hy3d.browser.login", file=sys.stderr)
|
||||
sys.exit(3)
|
||||
return Hunyuan3DAPI(cookies)
|
||||
|
||||
|
||||
def classify_http_error(e: requests.HTTPError) -> Optional[int]:
|
||||
"""把 HTTP 错误分类成退出码;返回 None 表示无法分类(调用方按 1 处理)。"""
|
||||
resp = getattr(e, "response", None)
|
||||
if resp is None:
|
||||
return None
|
||||
try:
|
||||
body = resp.json()
|
||||
except Exception:
|
||||
return None
|
||||
code = (body.get("error") or {}).get("code")
|
||||
if resp.status_code == 401:
|
||||
if code == "20001":
|
||||
print("ERR2: token 无效/过期 → 需重新登录", file=sys.stderr)
|
||||
print(" python -m hy3d.browser.login (在真实终端运行,按提示输邮箱+验证码)", file=sys.stderr)
|
||||
elif code == "999":
|
||||
print("ERR2: cookie 中没有用户 → 检查 cookie 或重新登录", file=sys.stderr)
|
||||
else:
|
||||
print(f"ERR2: 认证失败 (HTTP 401, code={code})", file=sys.stderr)
|
||||
return 2
|
||||
if resp.status_code == 400:
|
||||
print(f"ERR1: 请求被拒 (HTTP 400, code={code}) — creationsId 可能不存在或参数无效", file=sys.stderr)
|
||||
return 1
|
||||
return None
|
||||
|
||||
|
||||
def run_checked(fn: Callable, *args, **kw) -> int:
|
||||
"""执行 API 调用并统一处理退出码。成功返回 0。"""
|
||||
try:
|
||||
fn(*args, **kw)
|
||||
except requests.HTTPError as e:
|
||||
code = classify_http_error(e)
|
||||
sys.exit(code if code is not None else 1)
|
||||
except (requests.ConnectionError, requests.Timeout) as e:
|
||||
print(f"ERR1: 网络错误: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"ERR1: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return 0
|
||||
|
||||
|
||||
def submit_and_maybe_wait(api, args, submit: Callable[[], dict]):
|
||||
"""提交生成任务;--wait 时轮询到完成并输出最终结果。"""
|
||||
result = submit()
|
||||
cid = result.get("creationsId")
|
||||
if not cid:
|
||||
out(result, args)
|
||||
return
|
||||
print(f"提交成功: creationsId={cid}")
|
||||
if not args.wait:
|
||||
print("用 `hy3d status <id>` 查询进度,或 `hy3d status <id> --wait` 等待完成")
|
||||
print("用 `hy3d formats <id>` / `hy3d download <id> --format glb` 取模型")
|
||||
return
|
||||
# --wait: 轮询(进度走 stderr,保持 stdout 干净)
|
||||
deadline = time.time() + args.timeout
|
||||
while time.time() < deadline:
|
||||
detail = api.get_generation_status(cid)
|
||||
data = detail.get("data", detail)
|
||||
state = data.get("status") or data.get("state")
|
||||
if state == "success":
|
||||
print(f"\n完成 ✓ (耗时 {args.timeout - (deadline - time.time()):.0f}s)" if args.json else "\n完成 ✓")
|
||||
if args.json:
|
||||
print(json.dumps(detail, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
urls = api.get_model_urls(cid)
|
||||
for k, u in urls.items():
|
||||
print(f" {k}: {u}")
|
||||
return
|
||||
if state == "fail":
|
||||
print(f"ERR1: 生成失败: {data}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f" 状态: {state}, 进度: {data.get('progress', 0)}%", file=sys.stderr)
|
||||
time.sleep(5)
|
||||
print(f"ERR1: 等待超时({args.timeout}s),任务仍在后台运行", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def parse_local_or_url(value: str, uploader) -> str:
|
||||
"""本地文件自动上传 COS 换取 resourceUrl;URL 原样返回。"""
|
||||
if os.path.isfile(value):
|
||||
return uploader(value)
|
||||
return value
|
||||
|
||||
|
||||
# ---------- 各命令 ----------
|
||||
|
||||
def cmd_quota(api, args):
|
||||
q = api.get_quota_info()
|
||||
if args.json:
|
||||
out(q, args)
|
||||
else:
|
||||
out(f"剩余 {q.get('remainQuota', '?')} / 总 {q.get('totalQuota', '?')}"
|
||||
f"(已消耗 {q.get('consumeQuota', 0)})", args)
|
||||
|
||||
|
||||
def cmd_count(api, args):
|
||||
out(api.get_creation_count(), args)
|
||||
|
||||
|
||||
def cmd_list(api, args):
|
||||
data = api.get_creation_list(args.page, args.page_size)
|
||||
if args.json:
|
||||
out(data, args)
|
||||
else:
|
||||
items = data.get("creations", [])
|
||||
print(f"共 {data.get('totalCount', len(items))} 个作品:")
|
||||
for it in items:
|
||||
print(f" {it.get('creationsId', it.get('id'))} {it.get('status', ''):10s} "
|
||||
f"{str(it.get('title'))[:40]}")
|
||||
if not items:
|
||||
print(" (空)")
|
||||
|
||||
|
||||
def cmd_status(api, args):
|
||||
if args.wait:
|
||||
deadline = time.time() + args.timeout
|
||||
while time.time() < deadline:
|
||||
data = api.get_generation_status(args.creation_id)
|
||||
d = data.get("data", data)
|
||||
state = d.get("status") or d.get("state")
|
||||
if state == "success":
|
||||
out(d, args)
|
||||
return
|
||||
if state == "fail":
|
||||
print(f"ERR1: 生成失败: {d}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f" 状态: {state}, 进度: {d.get('progress', 0)}%", file=sys.stderr)
|
||||
time.sleep(5)
|
||||
print(f"ERR1: 等待超时({args.timeout}s),任务仍在后台运行", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
data = api.get_generation_status(args.creation_id)
|
||||
if args.json:
|
||||
out(data, args)
|
||||
else:
|
||||
d = data.get("data", data)
|
||||
state = d.get("status") or d.get("state")
|
||||
print(f"状态: {state} 进度: {d.get('progress', 0)}%")
|
||||
if state == "success":
|
||||
for k, u in api.get_model_urls(args.creation_id).items():
|
||||
print(f" {k}: {u}")
|
||||
|
||||
|
||||
def cmd_formats(api, args):
|
||||
urls = api.get_model_urls(args.creation_id, include_converted=args.converted)
|
||||
if not urls:
|
||||
print("暂无可用格式(生成未完成或失败)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if args.json:
|
||||
out(urls, args)
|
||||
else:
|
||||
for k, u in urls.items():
|
||||
print(f" {k}: {u}")
|
||||
|
||||
|
||||
def cmd_download(api, args):
|
||||
path = api.download_model(args.creation_id, args.format, args.output,
|
||||
include_converted=args.converted)
|
||||
print(f"已下载: {path}")
|
||||
|
||||
|
||||
def cmd_cancel(api, args):
|
||||
out(api.cancel_generation(args.creation_id), args)
|
||||
|
||||
|
||||
def cmd_share(api, args):
|
||||
out(api.create_share(args.creation_id, args.platform), args)
|
||||
|
||||
|
||||
def cmd_user(api, args):
|
||||
out(api.get_user_info(), args)
|
||||
|
||||
|
||||
def cmd_auth(api, args):
|
||||
"""认证体检:cookie 存在性 + 配额接口连通性。"""
|
||||
if args.sub == "login":
|
||||
print("登录需人工在真实终端完成(Claude 的 ! 前缀 stdin 非交互,input() 会 EOF):")
|
||||
print(" python -m hy3d.browser.login")
|
||||
print("流程: 输邮箱 → 收验证码 → 输验证码 → cookie 自动存回 "
|
||||
f"{DEFAULT_COOKIE}")
|
||||
return
|
||||
# auth status
|
||||
import os as _os
|
||||
p = args.cookies or _os.environ.get("HY3D_COOKIES") or DEFAULT_COOKIE
|
||||
if not _os.path.isfile(p):
|
||||
print(f"ERR3: cookie 文件不存在: {p}", file=sys.stderr)
|
||||
sys.exit(3)
|
||||
q = api.get_quota_info()
|
||||
print(f"登录有效 ✓ cookie: {p}")
|
||||
print(f"剩余配额: {q.get('remainQuota')} / {q.get('totalQuota')}")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def cmd_config(api, args):
|
||||
from .config import get_config_dir, get_profile_dir
|
||||
print(f"配置目录: {get_config_dir()}")
|
||||
print(f"cookie 文件: {get_cookie_path()}")
|
||||
print(f"浏览器配置: {get_profile_dir()}")
|
||||
print("环境变量: HY3D_COOKIES(覆盖 cookie 路径)、HY3D_JSON(等同 --json)")
|
||||
|
||||
|
||||
# ---------- 生成命令 ----------
|
||||
|
||||
def cmd_text(api, args):
|
||||
submit_and_maybe_wait(api, args, lambda: api.generate_from_text(
|
||||
args.prompt, title=args.title, style=args.style, count=args.count))
|
||||
|
||||
|
||||
def cmd_image(api, args):
|
||||
from .cos_upload import upload_image
|
||||
def submit():
|
||||
resource = parse_local_or_url(args.image, upload_image)
|
||||
return api.generate_from_image(resource, title=args.title, style=args.style)
|
||||
submit_and_maybe_wait(api, args, submit)
|
||||
|
||||
|
||||
def cmd_multi_view(api, args):
|
||||
from .cos_upload import upload_image
|
||||
def submit():
|
||||
urls = [parse_local_or_url(v, upload_image) for v in args.images]
|
||||
return api.generate_from_multi_view(urls, title=args.title, style=args.style)
|
||||
submit_and_maybe_wait(api, args, submit)
|
||||
|
||||
|
||||
def cmd_sketch(api, args):
|
||||
from .cos_upload import upload_image
|
||||
def submit():
|
||||
resource = parse_local_or_url(args.sketch, upload_image)
|
||||
return api.generate_from_sketch(resource, prompt=args.prompt,
|
||||
title=args.title, style=args.style)
|
||||
submit_and_maybe_wait(api, args, submit)
|
||||
|
||||
|
||||
def cmd_animate(api, args):
|
||||
from .cos_upload import upload_image
|
||||
motion = MOTIONS.get(args.motion)
|
||||
if motion is None:
|
||||
print(f"ERR1: 未知动作 '{args.motion}',可选: {', '.join(MOTIONS)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
def submit():
|
||||
resource = parse_local_or_url(args.model, upload_image)
|
||||
return api.generate_animation(resource, motion_type=motion, title=args.title)
|
||||
submit_and_maybe_wait(api, args, submit)
|
||||
|
||||
|
||||
def cmd_texture(api, args):
|
||||
from .cos_upload import upload_image
|
||||
def submit():
|
||||
resource = parse_local_or_url(args.model, upload_image)
|
||||
return api.generate_texture(resource, prompt=args.prompt, title=args.title)
|
||||
submit_and_maybe_wait(api, args, submit)
|
||||
|
||||
|
||||
def cmd_topo(api, args):
|
||||
from .cos_upload import upload_image
|
||||
def submit():
|
||||
resource = parse_local_or_url(args.model, upload_image)
|
||||
return api.generate_lowpoly(resource, face_count=args.faces,
|
||||
topology_format=args.format, title=args.title)
|
||||
submit_and_maybe_wait(api, args, submit)
|
||||
|
||||
|
||||
# ---------- 主入口 ----------
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser(
|
||||
prog="hy3d",
|
||||
description="腾讯混元 3D 非官方 CLI(需要登录 cookie,见 hy3d auth login)")
|
||||
p.add_argument("--json", action="store_true", help="机器可读 JSON 输出")
|
||||
p.add_argument("--cookies", default=None, help="cookie 文件路径(默认 ~/.config/hunyuan3dweb/cookies.txt)")
|
||||
sub = p.add_subparsers(dest="command", required=True)
|
||||
|
||||
sub.add_parser("quota", help="查询配额")
|
||||
sub.add_parser("count", help="作品数量统计")
|
||||
sub.add_parser("user", help="用户信息")
|
||||
sub.add_parser("config", help="显示配置路径与环境变量")
|
||||
|
||||
sp = sub.add_parser("list", help="作品列表")
|
||||
sp.add_argument("--page", type=int, default=1)
|
||||
sp.add_argument("--page-size", dest="page_size", type=int, default=20)
|
||||
|
||||
sp = sub.add_parser("status", help="查询生成状态/详情")
|
||||
sp.add_argument("creation_id")
|
||||
sp.add_argument("--wait", action="store_true", help="轮询等待完成")
|
||||
sp.add_argument("--timeout", type=int, default=600)
|
||||
|
||||
sp = sub.add_parser("formats", help="列出可用下载格式")
|
||||
sp.add_argument("creation_id")
|
||||
sp.add_argument("--converted", action="store_true", help="包含转换格式 fbx/stl/usdz/mp4/gif")
|
||||
|
||||
sp = sub.add_parser("download", help="下载模型")
|
||||
sp.add_argument("creation_id")
|
||||
sp.add_argument("--format", default="glb", choices=FORMAT_KEYS, help="格式键名(默认 glb)")
|
||||
sp.add_argument("-o", "--output", default=None, help="保存路径(默认 URL 推断)")
|
||||
sp.add_argument("--converted", action="store_true", help="允许调用转换接口获取目标格式")
|
||||
|
||||
sp = sub.add_parser("cancel", help="取消生成任务")
|
||||
sp.add_argument("creation_id")
|
||||
|
||||
sp = sub.add_parser("share", help="生成分享链接")
|
||||
sp.add_argument("creation_id")
|
||||
sp.add_argument("--platform", default="3dPlayground")
|
||||
|
||||
# --- 生成命令(均支持 --wait) ---
|
||||
def generation_flags(sp):
|
||||
sp.add_argument("--wait", action="store_true", help="提交后轮询到完成")
|
||||
sp.add_argument("--timeout", type=int, default=600, help="--wait 超时秒数")
|
||||
sp.add_argument("--title", default="", help="作品标题")
|
||||
sp.add_argument("--style", default="", choices=STYLES,
|
||||
help=f"纹理风格: {', '.join(s or 'default' for s in STYLES[1:])}")
|
||||
|
||||
sp = sub.add_parser("text", help="文生 3D(消耗 4 次配额,出 4 个模型)")
|
||||
sp.add_argument("prompt")
|
||||
sp.add_argument("--count", type=int, default=4, help="生成数量(服务端固定 4)")
|
||||
generation_flags(sp)
|
||||
|
||||
sp = sub.add_parser("image", help="图生 3D(本地图片自动上传 COS)")
|
||||
sp.add_argument("image", help="本地图片路径或 resourceUrl")
|
||||
generation_flags(sp)
|
||||
|
||||
sp = sub.add_parser("multi-view", help="多视角图生 3D(≥2 张)")
|
||||
sp.add_argument("images", nargs="+", help="多张图片路径或 URL(不同角度)")
|
||||
generation_flags(sp)
|
||||
|
||||
sp = sub.add_parser("sketch", help="草图生 3D")
|
||||
sp.add_argument("sketch", help="草图图片路径或 URL")
|
||||
sp.add_argument("--prompt", required=True, help="草图描述提示词")
|
||||
generation_flags(sp)
|
||||
|
||||
sp = sub.add_parser("animate", help="3D 动画生成")
|
||||
sp.add_argument("model", help="3D 模型图片(路径或 URL)")
|
||||
sp.add_argument("--motion", required=True, choices=list(MOTIONS),
|
||||
help=f"动作: {', '.join(MOTIONS)}")
|
||||
generation_flags(sp)
|
||||
|
||||
sp = sub.add_parser("texture", help="3D 纹理生成(白模上色)")
|
||||
sp.add_argument("model", help="白模图片(路径或 URL)")
|
||||
sp.add_argument("--prompt", required=True, help="纹理描述")
|
||||
generation_flags(sp)
|
||||
|
||||
sp = sub.add_parser("topo", help="3D 智能拓扑(减面)")
|
||||
sp.add_argument("model", help="模型图片(路径或 URL)")
|
||||
sp.add_argument("--faces", type=int, default=5000, choices=[5000, 18000, 30000])
|
||||
sp.add_argument("--format", default="glb", choices=["glb", "obj"])
|
||||
generation_flags(sp)
|
||||
|
||||
# --- 认证 ---
|
||||
sp = sub.add_parser("auth", help="认证体检/登录指引")
|
||||
sp.add_argument("sub", nargs="?", default="status", choices=["status", "login"])
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
args = build_parser().parse_args(argv)
|
||||
if os.environ.get("HY3D_JSON") == "1":
|
||||
args.json = True
|
||||
|
||||
# auth login 不需要 API 实例
|
||||
if args.command == "auth" and args.sub == "login":
|
||||
cmd_auth(None, args)
|
||||
return 0
|
||||
if args.command == "config":
|
||||
cmd_config(None, args)
|
||||
return 0
|
||||
|
||||
api = make_api(args)
|
||||
dispatch = {
|
||||
"quota": lambda: cmd_quota(api, args),
|
||||
"count": lambda: cmd_count(api, args),
|
||||
"user": lambda: cmd_user(api, args),
|
||||
"list": lambda: cmd_list(api, args),
|
||||
"status": lambda: cmd_status(api, args),
|
||||
"formats": lambda: cmd_formats(api, args),
|
||||
"download": lambda: cmd_download(api, args),
|
||||
"cancel": lambda: cmd_cancel(api, args),
|
||||
"share": lambda: cmd_share(api, args),
|
||||
"auth": lambda: cmd_auth(api, args),
|
||||
"text": lambda: cmd_text(api, args),
|
||||
"image": lambda: cmd_image(api, args),
|
||||
"multi-view": lambda: cmd_multi_view(api, args),
|
||||
"sketch": lambda: cmd_sketch(api, args),
|
||||
"animate": lambda: cmd_animate(api, args),
|
||||
"texture": lambda: cmd_texture(api, args),
|
||||
"topo": lambda: cmd_topo(api, args),
|
||||
}
|
||||
return run_checked(dispatch.get(args.command, lambda: None))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -6,7 +6,7 @@ from typing import Optional, Tuple
|
||||
|
||||
|
||||
def get_config_dir() -> Path:
|
||||
"""Return the user configuration directory for hunyuan3dweb."""
|
||||
"""Return the user configuration directory (kept as `hunyuan3dweb` for path stability)."""
|
||||
xdg = os.environ.get("XDG_CONFIG_HOME")
|
||||
base = Path(xdg) if xdg else Path.home() / ".config"
|
||||
path = base / "hunyuan3dweb"
|
||||
@@ -38,7 +38,7 @@ def prepare_profile_dir() -> Tuple[str, Optional[str]]:
|
||||
if not os.path.exists(standard):
|
||||
return standard, None
|
||||
|
||||
temp_root = tempfile.mkdtemp(prefix="hunyuan3dweb-profile-")
|
||||
temp_root = tempfile.mkdtemp(prefix="hy3d-profile-")
|
||||
temp_profile = os.path.join(temp_root, "profile")
|
||||
shutil.copytree(standard, temp_profile, dirs_exist_ok=True)
|
||||
return temp_profile, temp_root
|
||||
@@ -145,7 +145,7 @@ def upload_image(image_path, use_accelerate=True):
|
||||
try:
|
||||
cookies = load_cookies_from_file()
|
||||
except FileNotFoundError as exc:
|
||||
raise RuntimeError("Cookies file not found. Please run 'hunyuan3dweb-login' first.") from exc
|
||||
raise RuntimeError("Cookies file not found. Please run real-terminal login first: python -m hy3d.browser.login") from exc
|
||||
api = Hunyuan3DAPIComplete(cookies=cookies)
|
||||
|
||||
filename = Path(image_path).name
|
||||
+12
-12
@@ -3,27 +3,27 @@ requires = ["setuptools>=61.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "hunyuan3dweb"
|
||||
version = "0.1.0"
|
||||
description = "Tencent Hunyuan 3D Web API client and browser automation tools"
|
||||
name = "hy3d"
|
||||
version = "0.2.0"
|
||||
description = "非官方腾讯混元 3D CLI 与 Python API 客户端(3d.hunyuan.tencent.com)"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"requests",
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
browser = [
|
||||
"cloakbrowser",
|
||||
"playwright",
|
||||
"cloakbrowser>=0.3.31",
|
||||
"playwright>=1.40",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
hunyuan3dweb = "hunyuan3dweb.cli:main"
|
||||
hunyuan3dweb-login = "hunyuan3dweb.browser.login:main"
|
||||
hunyuan3dweb-sniffer = "hunyuan3dweb.browser.sniffer:main"
|
||||
hunyuan3dweb-generate = "hunyuan3dweb.browser.generator:main"
|
||||
hy3d = "hy3d.cli:main"
|
||||
hy3d-login = "hy3d.browser.login:main"
|
||||
hy3d-sniffer = "hy3d.browser.sniffer:main"
|
||||
hy3d-generate = "hy3d.browser.generator:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["hunyuan3dweb*"]
|
||||
include = ["hy3d*"]
|
||||
Reference in New Issue
Block a user