Compare commits

...
7 Commits
Author SHA1 Message Date
KawasakiAkasei bd987a2222 feat: ship Claude Code skill in repo with install script
- skill/run-hy3d/SKILL.md: user-level skill now versioned in repo
- skill/install.sh: one-command install to ~/.claude/skills/run-hy3d
- README/README_CN: document skill install/uninstall
2026-08-15 23:29:36 +08:00
KawasakiAkasei 215ed8f7f6 docs: install via git clone only, never publish to PyPI
- README/README_CN: replace pip install with clone + pipx install flow
- document direct 'pipx install git+URL' alternative and browser extra
- user-level skill prerequisite points at repo install path
2026-08-15 23:22:16 +08:00
KawasakiAkasei 5a778d8787 chore: move run-hy3d skill to user scope (~/.claude/skills)
installed via pipx now independent of repo; project-level skill removed
2026-08-15 23:07:12 +08:00
KawasakiAkasei 98280824c5 style: add missing trailing newlines (pyproject.toml, cli.py) 2026-08-15 22:59:52 +08:00
KawasakiAkasei 3e85d6f987 refactor: move config dir to ~/.config/hy3d, replace skill with run-hy3d
- config dir renamed hunyuan3dweb -> hy3d (cookies/profile moved intact,
  auth re-verified: quota 20/20)
- cli.py/login.py/READMEs/doc paths updated to ~/.config/hy3d
- skill: delete run-hunyuan3dweb, add run-hy3d pointing at bin/hy3d
- zero stale old-name references remain (one historical note in skill)
2026-08-15 22:44:56 +08:00
KawasakiAkasei acfe5e7ac2 refactor: rename project to hy3d, full CLI overhaul
- package hunyuan3dweb/ -> hy3d/ (imports: from hy3d import ...)
- CLI: MiniMax-style verb/noun commands (quota/list/status/formats/
  download/text/image/multi-view/sketch/animate/texture/topo/cancel/
  share/auth/config), global --json/--cookies, env HY3D_COOKIES/HY3D_JSON
- exit codes: 0 ok / 1 env / 2 auth-expired / 3 cookie-missing
- generation commands support --wait polling with stderr progress
- local images auto-upload to COS in image/multi-view/sketch/animate/texture/topo
- old commands removed (no backwards compat)
- config dir kept at ~/.config/hunyuan3dweb to preserve existing cookies
- deps: requests>=2.32.3, browser extras cloakbrowser>=0.3.31/playwright>=1.40
- skill updated: bin/hy3d shim replaces driver.sh; all commands live-verified
2026-08-15 22:31:50 +08:00
KawasakiAkasei 956b015ee0 feat: add run-hunyuan3dweb skill with verified driver
- driver.sh: PYTHONPATH-based, no-install CLI wrapper with auth diagnostics
- exit codes: 0 ok / 1 env / 2 auth-expired / 3 cookie missing
- verified live 2026-08-15: quota, list, status, formats, download
- text (quota-consuming) documented as request-layer verified only
2026-08-15 22:11:20 +08:00
23 changed files with 988 additions and 700 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"enabledPlugins": {
"open-code-review@open-code-review": true
}
}
+169 -279
View File
@@ -1,304 +1,194 @@
# 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 前端签名算法的逆向实现,与腾讯无关。仅供个人学习与自动化使用。
## 安装
本项目**不发布到 PyPI**——通过克隆仓库安装(推荐先用 [pipx](https://pipx.pypa.io/) 隔离环境;或用 venv + pip):
```bash
pip install -e .
# 1. 克隆仓库
git clone https://gitea.akasei.top/KawasakiAkasei/hy3d.git
cd hy3d
# 2. 安装(pipx 装进独立 venvhy3d 命令进 PATH
pipx install ".[browser]"
# 3. 验证
hy3d auth status
```
For browser automation features:
不想保留仓库副本也能直接装:
```bash
pip install -e ".[browser]"
pipx install "git+https://gitea.akasei.top/KawasakiAkasei/hy3d.git"
```
## Login and Setup
> **「browser」扩展**提供登录/抓包/浏览器自动化(cloakbrowser + playwright);
> 只调用 API 可以省略。已装过纯客户端、想补 browser 扩展:
> `cd hy3d && pipx install --force ".[browser]"`。
> **私有仓库**场景:clone 用 SSH —— `git clone git@gitea.akasei.top:KawasakiAkasei/hy3d.git`git 直装同理。
First-time users need to log in via the browser. Cookies will be saved automatically:
不安装也能用——直接以源码方式运行(无需 pip install):
```bash
hunyuan3dweb-login
cd hy3d
PYTHONPATH=$PWD python3 -m hy3d.cli quota
```
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 login
# → 按提示: 输邮箱 → 收验证码 → 输验证码
# → cookie 自动保存到 ~/.config/hy3d/cookies.txt
```
Or programmatically via browser automation:
```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')}")
```
Or via pure Python upload:
```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="My Model")
```
### 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"
)
cid = result["creationsId"]
final = api.wait_for_completion(cid)
```
### 4. Multi-View Image-to-3D
Use multiple images from different angles to generate a more accurate 3D model.
```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)
```
### 5. Sketch-to-3D
```python
from hunyuan3dweb import Hunyuan3DAPIComplete
api = Hunyuan3DAPIComplete()
result = api.generate_from_sketch(
sketch_url="https://...",
prompt="a sketch of a robot",
title="Sketch Robot"
)
cid = result["creationsId"]
final = api.wait_for_completion(cid)
```
### 6. Animation Generation
Requires a model image URL and a motion type.
```python
from hunyuan3dweb import Hunyuan3DAPIComplete
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
hy3d auth status # 登录体检
hy3d quota # 配额查询
```
## File Reference
## CLI 命令
| 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 |
```
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>"` | 文生 3D4 配额) | `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(走浏览器签名,适合对照验证) |
需要 browser 扩展(安装时加 `[browser]`,见上文安装节)。
## 配置文件与目录
- cookie`~/.config/hy3d/cookies.txt`(全新目录,cookie 已随搬迁保留)
- 浏览器 profile`~/.config/hy3d/profile`
- 环境变量:`HY3D_COOKIES`cookie 路径)、`HY3D_JSON=1`(等同 `--json`
## 文件结构
| 文件 | 说明 |
|---|---|
| `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/` | 登录 / 抓包 / 浏览器自动化工具 |
## Claude Code Skill(可选)
`skill/run-hy3d/` 附带一个 Claude Code skill:让 agent 直接驱动 `hy3d`(查配额、文生 3D、下载模型、诊断登录状态与退出码)。安装:
```bash
./skill/install.sh # 安装到 ~/.claude/skills/run-hy3d
```
新会话中可用 `/run-hy3d` 调用,或当描述命中时自动加载。卸载:`rm -rf ~/.claude/skills/run-hy3d`
## 相关文档
- [reverse engineering 文档](README_REVERSE_ENGINEERING.md) — 签名算法逆向过程与关键常量
- [泛用性分析](UNIVERSALITY_ANALYSIS.md) — 算法能否复用到其他产品
- [API 端点文档](doc/api.md)
+164 -274
View File
@@ -1,304 +1,194 @@
# 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 .
```
如需浏览器自动化功能:
本项目**不发布到 PyPI**——通过克隆仓库安装(推荐先用 [pipx](https://pipx.pypa.io/) 隔离环境;或用 venv + pip):
```bash
pip install -e ".[browser]"
# 1. 克隆仓库
git clone https://gitea.akasei.top/KawasakiAkasei/hy3d.git
cd hy3d
# 2. 安装(pipx 装进独立 venvhy3d 命令进 PATH
pipx install ".[browser]"
# 3. 验证
hy3d auth status
```
## 登录与配置
第一次使用需要先通过浏览器登录,系统会自动保存 Cookie:
不想保留仓库副本也能直接装:
```bash
hunyuan3dweb-login
pipx install "git+https://gitea.akasei.top/KawasakiAkasei/hy3d.git"
```
登录成功后:
- 浏览器 Profile 保存到 `~/.config/hunyuan3dweb/profile`
- Cookie 自动提取到 `~/.config/hunyuan3dweb/cookies.txt`
> **「browser」扩展**提供登录/抓包/浏览器自动化(cloakbrowser + playwright);
> 只调用 API 可以省略。已装过纯客户端、想补 browser 扩展:
> `cd hy3d && pipx install --force ".[browser]"`。
> **私有仓库**场景:clone 用 SSH —— `git clone git@gitea.akasei.top:KawasakiAkasei/hy3d.git`git 直装同理。
之后写 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。
不安装也能用——直接以源码方式运行(无需 pip install):
```bash
hunyuan3dweb-generate /path/to/image.png wait
cd hy3d
PYTHONPATH=$PWD python3 -m hy3d.cli quota
```
或通过浏览器自动化脚本:
## 快速开始
```python
from hunyuan3dweb.browser.generator import generate_3d
首次使用需要登录一次(**在真实终端运行**,邮箱验证码登录,无头浏览器):
result = generate_3d("/path/to/image.png", wait_for_complete=True)
print(f"模型地址: {result.get('modelUrl')}")
```bash
hy3d auth login
# → 按提示: 输邮箱 → 收验证码 → 输验证码
# → cookie 自动保存到 ~/.config/hy3d/cookies.txt
```
或通过纯 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. 图生3DAPI,已有 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)
```bash
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>"` | 文生 3D4 配额) | `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(走浏览器签名,适合对照验证) |
需要 browser 扩展(安装时加 `[browser]`,见上文安装节)。
## 配置与目录
- cookie`~/.config/hy3d/cookies.txt`(全新目录,cookie 已随搬迁保留)
- 浏览器 profile`~/.config/hy3d/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/` | 登录 / 抓包 / 浏览器自动化工具 |
## Claude Code Skill(可选)
`skill/run-hy3d/` 附带一个 Claude Code skill:让 agent 直接驱动 `hy3d`(查配额、文生 3D、下载模型、诊断登录状态与退出码)。安装:
```bash
./skill/install.sh # 安装到 ~/.claude/skills/run-hy3d
```
新会话中可用 `/run-hy3d` 调用,或当描述命中时自动加载。卸载:`rm -rf ~/.claude/skills/run-hy3d`
## 相关文档
- [逆向工程文档](README_REVERSE_ENGINEERING_CN.md) — 签名算法逆向过程与关键常量
- [泛用性分析](UNIVERSALITY_ANALYSIS.md) — 算法能否复用到其他产品
- [API 端点文档](doc/api_complete.md)
+21 -21
View File
@@ -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,10 +88,10 @@ 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
# Login state is automatically saved to ~/.config/hy3d/profile
# Cookies are also exported to ~/.config/hy3d/cookies.txt
```
**Login State Detection Logic** (dual verification):
@@ -102,9 +102,9 @@ hunyuan3dweb-login
### 2. Pure Python 3D Generation
```python
from hunyuan3dweb import Hunyuan3DAPI
from hy3d import Hunyuan3DAPI
# Automatically load cookie from ~/.config/hunyuan3dweb/cookies.txt
# Automatically load cookie from ~/.config/hy3d/cookies.txt
api = Hunyuan3DAPI()
# Check quota
@@ -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
```
---
+21 -21
View File
@@ -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,10 +88,10 @@ resource_url = upload_image("/path/to/image.png")
### 1. 登录获取 Cookie
```bash
hunyuan3dweb-login
hy3d-login
# 按提示输入邮箱和验证码
# 登录状态自动保存到 ~/.config/hunyuan3dweb/profile
# Cookie 同时导出到 ~/.config/hunyuan3dweb/cookies.txt
# 登录状态自动保存到 ~/.config/hy3d/profile
# Cookie 同时导出到 ~/.config/hy3d/cookies.txt
```
**登录状态检测逻辑**(双重验证):
@@ -102,9 +102,9 @@ hunyuan3dweb-login
### 2. 纯 Python 生成3D模型
```python
from hunyuan3dweb import Hunyuan3DAPI
from hy3d import Hunyuan3DAPI
# 自动从 ~/.config/hunyuan3dweb/cookies.txt 加载 cookie
# 自动从 ~/.config/hy3d/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
```
---
Executable
+7
View File
@@ -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
View File
@@ -8,7 +8,7 @@
Cookie 会话认证。登录后由浏览器自动携带 `sessionid` 等 Cookie。
持久化数据保存在 `./hunyuan3d_profile`CloakBrowser 复用该目录即可保持登录态。
持久化数据保存在 `~/.config/hy3d/profile`CloakBrowser 复用该目录即可保持登录态。
---
+1 -1
View File
@@ -1,7 +1,7 @@
# 腾讯混元3D API 完整文档
> 基础地址: `https://3d.hunyuan.tencent.com`
> 签名算法: HMAC-SHA256(已破解,见 `hunyuan3d_sign.py`
> 签名算法: HMAC-SHA256(已破解,见 `hy3d/sign.py`
---
-82
View File
@@ -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",
]
View File
@@ -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)
@@ -3,7 +3,7 @@
腾讯混元3D 邮箱验证码登录 CLI 工具 (CloakBrowser 持久化版本)
第一次输入邮箱回车后自动发送验证码
第二次输入验证码后回车自动点击登录按钮
登录状态会自动保存到 ~/.config/hunyuan3dweb/profile下次运行无需重新登录
登录状态会自动保存到 ~/.config/hy3d/profile下次运行无需重新登录
"""
import argparse
@@ -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
View File
@@ -0,0 +1,471 @@
#!/usr/bin/env python3
"""
hy3d — 腾讯混元 3D 非官方 CLI
用法: hy3d [--json] [--cookies PATH] <command> [args...]
全局选项(必须放在命令前):
--json 机器可读输出(默认 human 格式)
--cookies PATH 指定 cookie 文件(默认 ~/.config/hy3d/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 换取 resourceUrlURL 原样返回。"""
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/hy3d/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())
+3 -3
View File
@@ -6,10 +6,10 @@ from typing import Optional, Tuple
def get_config_dir() -> Path:
"""Return the user configuration directory for hunyuan3dweb."""
"""Return the user configuration directory (~/.config/hy3d)."""
xdg = os.environ.get("XDG_CONFIG_HOME")
base = Path(xdg) if xdg else Path.home() / ".config"
path = base / "hunyuan3dweb"
path = base / "hy3d"
path.mkdir(parents=True, exist_ok=True)
return path
@@ -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
View File
@@ -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*"]
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# 安装 hy3d 的 Claude Code skill 到用户级目录(~/.claude/skills/run-hy3d
# 用法: ./skill/install.sh
set -euo pipefail
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/run-hy3d" && pwd)"
DEST="$HOME/.claude/skills/run-hy3d"
mkdir -p "$(dirname "$DEST")"
rm -rf "$DEST"
cp -r "$SRC" "$DEST"
echo "✓ skill 已安装到 $DEST"
echo " 新会话中即可用 /run-hy3d;描述命中时自动加载"
+91
View File
@@ -0,0 +1,91 @@
---
name: run-hy3d
description: 运行并驱动腾讯混元 3D(hy3d)。当用户要求:跑 hy3d、查混元 3D 配额、列作品、文生 3D / 图生 3D 生成模型、查询生成状态、下载 GLB/OBJ/USDZ 模型,或验证 hy3d 能否工作时使用。
---
hy3d 是腾讯混元 3Dhttps://3d.hunyuan.tencent.com/)的非官方 CLI 与 Python 客户端,通过 `pipx install hy3d`(或 `pip install hy3d`)全局安装,**独立于任何仓库工作**。命令入口即 PATH 上的 **`hy3d`**(源码仓库 bin/hy3d shim 是免安装备选,一般不需要)。
## 前置条件
```bash
hy3d auth status # 先跑这个——验证安装与登录状态
# 退出码 0 + "登录有效 ✓" 即一切就绪
```
若命令不存在(未发布 PyPI,需从仓库安装):
```bash
pipx install "git+https://gitea.akasei.top/KawasakiAkasei/hy3d.git"
# 或克隆后 pipx install ".[browser]";纯源码运行见 README 安装节
```
## Setup
无安装步骤。唯一需要的文件:登录 cookie `~/.config/hy3d/cookies.txt`2026-08-15 曾登录,可能随时过期,过期按下方"重新登录"处理)。
## Runagent 路径)
```bash
hy3d auth status # 登录体检(最先跑)
hy3d quota # 配额(2026-08-15 实测 20/20
hy3d list # 作品列表(实测 7 个作品)
hy3d formats <creationsId>
hy3d download <creationsId> --format glb -o model.glb
hy3d text "a ceramic teapot" --wait # 文生 3D(消耗 4 配额)轮询到完成
# 更多: status/user/count/image/sketch/animate/texture/topo/cancel/share
# 全部支持 --json;生成命令支持 --wait/--style/--title
```
**退出码**(agent 分支判断依据,已实测):
| 码 | 含义 | 处理 |
|---|---|---|
| 0 | 成功 | — |
| 1 | 环境/网络/参数/4xx | 看 stderr |
| 2 | 认证失败(token 无效/过期) | 重新登录(需人工,见下) |
| 3 | cookie 缺失 | 重新登录(需人工,见下) |
实测记录(2026-08-15):`auth status`/`quota`/`list`/`count`/`user`/`formats`/`download`(下载预览图验证过)全部通过;`text --wait` 已验证提交+轮询框架,未跑完整生成(消耗配额)。
## 重新登录(退出码 2/3 时唯一出路)
**必须由人类用户在真实终端**Claude 的 `!` 前缀 stdin 非交互,`input()` 会 EOF)运行:
```bash
hy3d-login # 邮箱验证码登录(browser 扩展)
```
输邮箱 → 收验证码 → 输验证码,cookie 自动存回 `~/.config/hy3d/cookies.txt`。登录后浏览器进程自动退出,无残留(2026-08-15 验证)。
## Python API(同一安装)
```python
from hy3d import Hunyuan3DAPIComplete, upload_image
api = Hunyuan3DAPIComplete()
print(api.get_quota_info())
```
## Test(离线自检)
```bash
python -m hy3d.sign # 在 pipx venv 内(~/.local/share/pipx/venvs/hy3d/bin/python3
# 输出含「派生密钥: Hf6d6KFB3D」即签名算法正确
```
---
## Gotchas
- **`!` 前缀跑登录必 EOF** —— `input()` 在管道 stdin 上直接 `EOFError`。登录必须真实终端;agent 只能引导用户。
- **401 两种码都证明签名正确** —— `"code":"20001"`=token 过期(重登);`"code":"999"`=cookie 无用户。
- **不存在的 creationsId → HTTP 400 空 body**CLI 归为退出码 1),不是 JSON 错误。
- **文生 3D 一次消耗 4 次配额**(`count=4` 固定);图生 3D 等消耗 1 次。
- **配置目录 `~/.config/hy3d` 独立于仓库**——卸载重装/删仓库不影响登录态。
- **generation 类命令(text/image/animate/...)未完整跑通过**——验证到提交与轮询框架;大规模使用前先 `hy3d auth status`
## Troubleshooting
- **`hy3d auth status` 退出码 2**:token 过期。让人在真实终端跑 `hy3d-login`
- **退出码 3**:cookie 缺失。同上,或检查 `HY3D_COOKIES` 是否指向错误路径。
- **`command not found: hy3d`**`pipx install "git+https://gitea.akasei.top/KawasakiAkasei/hy3d.git"`browser 扩展可选,见 README)。
- **`EOFError`(登录时)**:stdin 非交互,换真实终端。
- **`401` + 'token无效'**:重登,不是代码 bug。