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
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"enabledPlugins": {
|
||||||
|
"open-code-review@open-code-review": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
name: run-hunyuan3dweb
|
name: run-hunyuan3dweb
|
||||||
description: 运行并驱动腾讯混元 3D Python 客户端。当用户要求:跑 hunyuan3dweb、查混元 3D 配额、列作品、文生 3D / 图生 3D 生成模型、查询生成状态、下载 GLB/OBJ/USDZ 模型,或验证这个项目能否工作时使用。
|
description: 运行并驱动腾讯混元 3D(hy3d)。当用户要求:跑 hy3d、查混元 3D 配额、列作品、文生 3D / 图生 3D 生成模型、查询生成状态、下载 GLB/OBJ/USDZ 模型,或验证这个项目能否工作时使用。
|
||||||
---
|
---
|
||||||
|
|
||||||
本项目是腾讯混元 3D(https://3d.hunyuan.tencent.com/)的非官方 Python 客户端:签名算法(HMAC-SHA256 + 密钥派生)、纯 HTTP API 调用、浏览器登录工具。**无需 pip install**——通过 `PYTHONPATH` 从源码直接运行。
|
本项目(仓库 gitea.akasei.top/KawasakiAkasei/hy3d)是腾讯混元 3D(https://3d.hunyuan.tencent.com/)的非官方 CLI 与 Python 客户端:签名算法(HMAC-SHA256 密钥派生 `Hf6d6KFB3D`)、纯 HTTP API + CLI(`hy3d`)、浏览器登录工具。
|
||||||
|
|
||||||
唯一驱动入口:`.claude/skills/run-hunyuan3dweb/driver.sh`(下文所有路径相对仓库根 `/opt/hunyuan3dweb`)。
|
驱动入口只有一个:**`bin/hy3d`**(免安装 shim,自动设置 PYTHONPATH 并转发到 `python3 -m hy3d.cli`)。也可 `pip install hy3d` 后用 PATH 上的 `hy3d`。下文路径相对仓库根。
|
||||||
|
|
||||||
## 前置条件
|
## 前置条件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 --version # ≥3.8 ✅ (本机 3.13.12)
|
python3 --version # ≥3.9 ✅ (本机 3.13.12)
|
||||||
python3 -c "import requests" # 缺则: pip install requests
|
python3 -c "import requests" # 缺则: pip install requests
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -18,86 +18,70 @@ python3 -c "import requests" # 缺则: pip install requests
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
无安装步骤。依赖的两个东西:
|
无安装步骤。仅需登录 cookie:`~/.config/hunyuan3dweb/cookies.txt`(2026-08-15 曾登录,cookie 可能随时过期,过期用下方 auth 流程处理)。
|
||||||
|
|
||||||
1. **仓库路径**:driver 已内置(`PYTHONPATH` 自动指向仓库根)。
|
## Run(agent 路径)
|
||||||
2. **登录 cookie**:`~/.config/hunyuan3dweb/cookies.txt`(已存在,2026-08-15 登录过,当前有效)。过期后用下面"重新登录"一节处理。
|
|
||||||
|
|
||||||
## Run(agent 路径)——driver.sh
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.claude/skills/run-hunyuan3dweb/driver.sh verify # 三重自检(最快确认能用)
|
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 分支判断的依据,已实测):
|
||||||
|---|---|
|
|
||||||
| `driver.sh verify` | 环境 + 离线签名 + 在线配额 三重自检 |
|
|
||||||
| `driver.sh quota` | 查配额,输出 `remainQuota/totalQuota` |
|
|
||||||
| `driver.sh list` | 作品列表(JSON,含 `totalCount`/`creations`) |
|
|
||||||
| `driver.sh status <creationsId>` | 生成状态(json/轮询用 `state` 字段) |
|
|
||||||
| `driver.sh text "<prompt>"` | 文生 3D 提交(**消耗配额**,出 4 个) |
|
|
||||||
| `driver.sh formats <creationsId>` | 可用下载格式(glb/obj/fbx/stl/usdz/mp4/gif…) |
|
|
||||||
| `driver.sh download <creationsId> [--format glb] [--output PATH]` | 下载模型到当前目录 |
|
|
||||||
|
|
||||||
其余 API(图生 3D、多视角、动画、贴图、减面)直接 import 客户端调用
|
| 码 | 含义 | 处理 |
|
||||||
(`api_complete.py` 的 `Hunyuan3DAPIComplete`),10+ 种生成模式同签名同会话。
|
|---|---|---|
|
||||||
本容器内已实测:`quota`/`list`/`status`/`formats`/`download`(2026-08-15,7 个作品齐全);
|
| 0 | 成功 | — |
|
||||||
`text` 只验证到请求构造层(消耗配额,未实测全流程)。
|
| 1 | 环境/网络/参数/4xx | 看 stderr |
|
||||||
|
| 2 | 认证失败(token 无效/过期) | `hy3d auth login`(需人工,见下) |
|
||||||
|
| 3 | cookie 缺失 | `hy3d auth login`(需人工,见下) |
|
||||||
|
|
||||||
**退出码**:`0` 成功 · `1` 环境/其他 · `2` 认证失败(需重新登录)· `3` cookie 缺失。
|
典型工作流(2026-08-15 全部实测通过):`auth status` → `quota` → `list` → 对历史作品 `formats`/`download`(下载 512×512 PNG 预览验证过);`text --wait` 已验证提交+轮询框架,未在容器内跑完整生成(消耗配额)。
|
||||||
收到 2/3 时按下面"重新登录"处理,不要把栈当 bug 报告。
|
|
||||||
|
|
||||||
典型工作流(文生 3D + 轮询):
|
## 重新登录(退出码 2/3 时唯一出路)
|
||||||
|
|
||||||
|
**必须由人类用户在真实终端**(Claude 的 `!` 前缀 stdin 非交互,`input()` 会 EOF)运行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.claude/skills/run-hunyuan3dweb/driver.sh quota # 先看余额
|
python -m hy3d.browser.login # 或安装后: hy3d-login
|
||||||
.claude/skills/run-hunyuan3dweb/driver.sh text "a ceramic teapot" # → creationsId
|
|
||||||
.claude/skills/run-hunyuan3dweb/driver.sh status <creationsId> # 轮询到 state=success
|
|
||||||
.claude/skills/run-hunyuan3dweb/driver.sh download <creationsId> --format glb
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 重新登录(认证失败时唯一出路)
|
输邮箱 → 收验证码 → 输验证码,cookie 自动存回 `~/.config/hunyuan3dweb/cookies.txt`。登录后浏览器进程自动退出,无残留(2026-08-15 验证)。
|
||||||
|
|
||||||
收到退出码 2(`"code":"20001"` token 无效 / `"999"` cookie 无用户)时:
|
|
||||||
**必须由人类用户在真实终端**(不是 Claude 的 `!` 前缀——stdin 非交互,`input()` 会 EOF)运行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.browser.login
|
|
||||||
```
|
|
||||||
|
|
||||||
按提示输邮箱 → 收验证码 → 输验证码,登录态自动存回 `cookies.txt`。
|
|
||||||
登录完成后浏览器进程**自动退出,无资源残留**(2026-08-15 验证:登录后零残留进程)。
|
|
||||||
|
|
||||||
## Run(人类路径)
|
## Run(人类路径)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.cli quota # 等价 CLI
|
pip install -e ".[browser]" # 可选,装好后 hy3d 直接进 PATH
|
||||||
PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.cli list
|
hy3d quota
|
||||||
PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.cli text "a red apple"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test(离线自检)
|
## Test(离线自检)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.sign
|
python3 -m hy3d.sign
|
||||||
# 派生密钥: Hf6d6KFB3D ← 输出里有这行即签名算法正确
|
# 输出含「派生密钥: Hf6d6KFB3D」即签名算法正确
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
- **`!` 前缀跑登录必 EOF** —— `input()` 在管道 stdin 打开即 EOF(`EOFError: EOF when reading a line`)。登录必须真实终端;agent 只能引导用户去跑。
|
- **`!` 前缀跑登录必 EOF** —— `input()` 在管道 stdin 上直接 `EOFError: EOF when reading a line`。登录必须真实终端;agent 只能引导用户。
|
||||||
- **401 有两种,别混** —— `"code":"20001"`=token 过期(重登);`"code":"999"`=cookie 无用户(重登或检查 cookie 文件)。两者都证明**签名本身是对的**(服务端能分类错误)。
|
- **401 两种码都证明签名正确** —— `"code":"20001"`=token 过期(重登);`"code":"999"`=cookie 无用户。服务端能分类错误说明请求格式被完全接受。
|
||||||
- **不存在的 creationsId → HTTP 400 空 body**,不是 JSON 错误(2026-08-15 实测)。
|
- **不存在的 creationsId → HTTP 400 空 body**(CLI 归为退出码 1),不是 JSON 错误。
|
||||||
- **`python3 -m hunyuan3dweb.sign` 有 RuntimeWarning**(`'hunyuan3dweb.sign' found in sys.modules...`)——runpy 与 `__init__.py` 重复导入的假警报,无害,输出仍正确。
|
- **文生 3D 一次消耗 4 次配额**(`count=4` 固定);图生 3D 等消耗 1 次。
|
||||||
- **文生 3D 固定出 4 个模型**(`count=4`),一次消耗 4 次配额;`generate_from_text` 的 `count` 参数会覆盖 UI 行为。
|
- **配置目录刻意保持 `~/.config/hunyuan3dweb` 不变**——改名后路径改了会丢掉已登录的 cookie。
|
||||||
- **`download` 输出到 CWD**——某次下载 86KB 预览图实测 2–3 秒;GLB 可能几十 MB,注意磁盘和时间。正式大规模跑之前先 `quota` 确认登录态在线。
|
- **生成类命令(text/image/animate/...)未在容器内完整跑通**——验证到提交与轮询框架;正式大规模使用前先 `hy3d auth status` 确认在线,并接受首单可能失败的调试成本。
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- **`driver.sh verify` 打印 AUTH_EXPIRED / 退出码 2**:token 过期。让人在真实终端跑 `PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.browser.login`,成功后再 verify。
|
- **`hy3d auth status` 退出码 2**:token 过期。让人在真实终端跑 `python -m hy3d.browser.login`。
|
||||||
- **`EOFError: EOF when reading a line`**(登录时):stdin 非交互。换真实终端,不要用 `!`。
|
- **退出码 3**:cookie 文件不存在/为空。同上,或检查 `HY3D_COOKIES` 是否指向了错误路径。
|
||||||
- **`requests.exceptions.HTTPError: 401` + 中文消息 'token无效'**:同上,重登。不是代码 bug,别改代码。
|
- **`EOFError`(登录时)**:stdin 非交互,换真实终端。
|
||||||
- **cookie 想换账号**:`HUNYUAN3D_COOKIES=/path/to/other/cookies.txt driver.sh quota`。
|
- **`401` + 'token无效'**:重登,不是代码 bug。
|
||||||
- **driver 里 python 找不到模块**:确认 `PYTHONPATH` 含仓库根(driver 会自动设置)。
|
- **`python3 -m hy3d.sign` 的 RuntimeWarning**:runpy 与 `__init__.py` 重复导入的假警报,无害。
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# 腾讯混元3D Python 客户端 driver
|
|
||||||
# 从源码直接运行(PYTHONPATH 指向仓库根),无需 pip install。
|
|
||||||
# 所有命令都是对 `python3 -m hunyuan3dweb.cli` 的薄封装,加上统一的
|
|
||||||
# 认证/错误诊断(401 分类、退出码),方便 agent 自动化判断。
|
|
||||||
#
|
|
||||||
# 退出码: 0=成功 1=环境/其他错误 2=认证失败(需重新登录) 3=cookie 文件缺失
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
# 仓库根 = driver 上三级(.claude/skills/run-hunyuan3dweb/driver.sh)
|
|
||||||
UNIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
|
||||||
export PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$UNIT_DIR"
|
|
||||||
|
|
||||||
# cookie 可通过环境变量覆盖(多账号场景)
|
|
||||||
COOKIE_FILE="${HUNYUAN3D_COOKIES:-$HOME/.config/hunyuan3dweb/cookies.txt}"
|
|
||||||
|
|
||||||
need_cookie() {
|
|
||||||
if [[ ! -f "$COOKIE_FILE" || ! -s "$COOKIE_FILE" ]]; then
|
|
||||||
echo "ERR3: cookie 文件缺失或为空: $COOKIE_FILE" >&2
|
|
||||||
echo " 首次使用需人工登录: 在真实终端运行下面的命令,按提示输邮箱+验证码" >&2
|
|
||||||
echo " PYTHONPATH=$UNIT_DIR python3 -m hunyuan3dweb.browser.login" >&2
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 从 CLI 输出中诊断认证类错误(ec=2 表示凭证问题,不是代码问题)
|
|
||||||
diagnose() {
|
|
||||||
local out="$1"
|
|
||||||
if grep -q '"code":"20001"' <<<"$out"; then
|
|
||||||
echo "AUTH_EXPIRED: token 无效/过期 → 需人工重新登录(见下)" >&2
|
|
||||||
echo " PYTHONPATH=$UNIT_DIR python3 -m hunyuan3dweb.browser.login" >&2
|
|
||||||
return 2
|
|
||||||
elif grep -q '"code":"999"' <<<"$out"; then
|
|
||||||
echo "AUTH_NO_COOKIE: 服务端说 cookie 里没有用户 → 检查 cookie 内容或重新登录" >&2
|
|
||||||
return 2
|
|
||||||
elif grep -qE '400 Client Error' <<<"$out"; then
|
|
||||||
echo "BAD_REQUEST: HTTP 400(常见于 creationsId 不存在/无效)" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo "--- 原始输出尾部 ---" >&2
|
|
||||||
tail -5 <<<"$out" >&2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_verify() {
|
|
||||||
# 1) 依赖检查
|
|
||||||
python3 -c "import requests" 2>/dev/null \
|
|
||||||
|| { echo "ERR1: 缺少 requests,运行 pip install requests" >&2; exit 1; }
|
|
||||||
# 2) 离线签名自检(不依赖网络/登录)
|
|
||||||
python3 - <<'PY'
|
|
||||||
import os, sys
|
|
||||||
sys.path.insert(0, os.environ["PYTHONPATH"])
|
|
||||||
from hunyuan3dweb.sign import derive_key, C, sign
|
|
||||||
k = derive_key(C)
|
|
||||||
assert k == "Hf6d6KFB3D", f"派生密钥不符: {k!r}"
|
|
||||||
s = sign({"a": 1})
|
|
||||||
assert len(s["sign"]) == 64 and s["sign"].isalnum(), "签名格式异常"
|
|
||||||
print(f"sign OK: 密钥={k} 签名={s['sign'][:12]}...")
|
|
||||||
PY
|
|
||||||
# 3) 在线配额(认证能力检查)
|
|
||||||
cmd_quota
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_quota() {
|
|
||||||
need_cookie
|
|
||||||
local out
|
|
||||||
out="$(python3 -m hunyuan3dweb.cli quota 2>&1)" || { diagnose "$out"; exit $?; }
|
|
||||||
echo "$out"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_list() {
|
|
||||||
need_cookie
|
|
||||||
local out
|
|
||||||
out="$(python3 -m hunyuan3dweb.cli list 2>&1)" || { diagnose "$out"; exit $?; }
|
|
||||||
echo "$out"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_status() {
|
|
||||||
need_cookie
|
|
||||||
local out
|
|
||||||
out="$(python3 -m hunyuan3dweb.cli status "$1" 2>&1)" || { diagnose "$out"; exit $?; }
|
|
||||||
echo "$out"
|
|
||||||
}
|
|
||||||
|
|
||||||
# text 在本容器内未跑过完整成功路径(消耗配额,用户当时只授权只读验证)。
|
|
||||||
# 其余命令均已实测:quota/list/status/formats/download(2026-08-15)。
|
|
||||||
cmd_text() {
|
|
||||||
need_cookie
|
|
||||||
python3 -m hunyuan3dweb.cli text "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_formats() {
|
|
||||||
need_cookie
|
|
||||||
python3 -m hunyuan3dweb.cli formats "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_download() {
|
|
||||||
need_cookie
|
|
||||||
python3 -m hunyuan3dweb.cli download "$@" # 下载到 CWD
|
|
||||||
}
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'USAGE'
|
|
||||||
用法: driver.sh <command> [args]
|
|
||||||
|
|
||||||
verify 环境+离线签名+配额 三重自检(最快确认能用)
|
|
||||||
quota 查询配额(remain/total)
|
|
||||||
list 作品列表 JSON
|
|
||||||
status <creationsId> 生成状态 JSON
|
|
||||||
text "<prompt>" 文生 3D(提交任务,消耗配额)
|
|
||||||
formats <creationsId> 列出该创作的可用下载格式
|
|
||||||
download <creationsId> [--format glb] [--output PATH] [--converted]
|
|
||||||
|
|
||||||
环境: HUNYUAN3D_COOKIES=custom/path 覆盖默认 cookie 文件
|
|
||||||
退出码: 0 成功 | 1 环境/其他 | 2 认证失败(需重登)| 3 cookie 缺失
|
|
||||||
USAGE
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
verify) shift; cmd_verify ;;
|
|
||||||
quota) shift; cmd_quota ;;
|
|
||||||
list) shift; cmd_list ;;
|
|
||||||
status) shift; cmd_status "${1:?缺少 creationsId}";;
|
|
||||||
text) shift; cmd_text "${1:?缺少 prompt}";;
|
|
||||||
formats) shift; cmd_formats "${1:?缺少 creationsId}";;
|
|
||||||
download) shift; cmd_download "$@" ;;
|
|
||||||
*) usage; exit ${1:+1};;
|
|
||||||
esac
|
|
||||||
@@ -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
|
```bash
|
||||||
pip install -e .
|
pip install hy3d # 纯 API 客户端
|
||||||
|
pip install "hy3d[browser]" # 额外安装浏览器工具(登录/抓包/浏览器自动化)
|
||||||
```
|
```
|
||||||
|
|
||||||
For browser automation features:
|
不安装也能用——克隆仓库后直接用源码跑(无需 `pip install`):
|
||||||
|
|
||||||
```bash
|
```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
|
```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
|
```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
|
hy3d [--json] [--cookies PATH] <command> [args...]
|
||||||
|
|
||||||
result = generate_3d("/path/to/image.png", wait_for_complete=True)
|
|
||||||
print(f"Model URL: {result.get('modelUrl')}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
```python
|
||||||
from hunyuan3dweb.cos_upload import upload_image
|
from hy3d import Hunyuan3DAPIComplete
|
||||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
|
||||||
|
|
||||||
resource_url = upload_image("/path/to/image.png")
|
|
||||||
|
|
||||||
api = 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)
|
result = api.generate_from_text("a ceramic vase", style=api.STYLE_CYBERPUNK)
|
||||||
|
|
||||||
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"]
|
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
|
```python
|
||||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
from hy3d import Hunyuan3DAPIComplete, load_cookies_from_file
|
||||||
|
api = Hunyuan3DAPIComplete(cookies=load_cookies_from_file("/path/to/cookies.txt"))
|
||||||
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
|
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(
|
需要 `pip install "hy3d[browser]"`(cloakbrowser + playwright)。
|
||||||
sketch_url="https://...",
|
|
||||||
prompt="a sketch of a robot",
|
|
||||||
title="Sketch Robot"
|
|
||||||
)
|
|
||||||
cid = result["creationsId"]
|
|
||||||
|
|
||||||
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,
|
- [reverse engineering 文档](README_REVERSE_ENGINEERING.md) — 签名算法逆向过程与关键常量
|
||||||
# MOTION_KICKING, MOTION_SWORD, MOTION_RUNNING, MOTION_DANCING
|
- [泛用性分析](UNIVERSALITY_ANALYSIS.md) — 算法能否复用到其他产品
|
||||||
result = api.generate_animation(
|
- [API 端点文档](doc/api.md)
|
||||||
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 |
|
|
||||||
+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
|
```bash
|
||||||
pip install -e .
|
pip install hy3d # 纯 API 客户端
|
||||||
|
pip install "hy3d[browser]" # 额外安装浏览器工具(登录/抓包/浏览器自动化)
|
||||||
```
|
```
|
||||||
|
|
||||||
如需浏览器自动化功能:
|
不安装也能用——克隆仓库后直接用源码跑(无需 `pip install`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -e ".[browser]"
|
cd hy3d
|
||||||
|
PYTHONPATH=$PWD python3 -m hy3d.cli quota
|
||||||
```
|
```
|
||||||
|
|
||||||
## 登录与配置
|
## 快速开始
|
||||||
|
|
||||||
第一次使用需要先通过浏览器登录,系统会自动保存 Cookie:
|
首次使用需要登录一次(**在真实终端运行**,邮箱验证码登录,无头浏览器):
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
hunyuan3dweb-generate /path/to/image.png wait
|
hy3d auth status # 登录体检
|
||||||
```
|
hy3d 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')}")
|
|
||||||
```
|
|
||||||
|
|
||||||
或通过纯 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)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## CLI 命令
|
## CLI 命令
|
||||||
|
|
||||||
- `hunyuan3dweb` - API CLI 工具(支持 quota / list / text / status 子命令)
|
```
|
||||||
- `hunyuan3dweb-login` - 浏览器登录(自动保存 Cookie)
|
hy3d [--json] [--cookies PATH] <command> [args...]
|
||||||
- `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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 文件说明
|
| 命令 | 说明 | 示例 |
|
||||||
|
|---|---|---|
|
||||||
|
| `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) |
|
| `hy3d/cli.py` | CLI 入口(全部命令) |
|
||||||
| `hunyuan3dweb/api_complete.py` | 完整 API 客户端(所有生成模式) |
|
| `hy3d/api.py` | 轻量 API 客户端(图生 3D / 文生 3D) |
|
||||||
| `hunyuan3dweb/sign.py` | 腾讯混元3D 签名算法 |
|
| `hy3d/api_complete.py` | 完整 API 客户端(全部生成模式) |
|
||||||
| `hunyuan3dweb/cos_upload.py` | 纯 Python COS 上传工具 |
|
| `hy3d/sign.py` | 签名算法(HMAC-SHA256 + 密钥派生) |
|
||||||
| `hunyuan3dweb/config.py` | 用户配置路径管理 |
|
| `hy3d/cos_upload.py` | 纯 Python COS 上传(免浏览器) |
|
||||||
| `hunyuan3dweb/cli.py` | CLI 入口 |
|
| `hy3d/config.py` | 配置路径管理 |
|
||||||
| `hunyuan3dweb/browser/login.py` | 浏览器登录工具 |
|
| `hy3d/browser/` | 登录 / 抓包 / 浏览器自动化工具 |
|
||||||
| `hunyuan3dweb/browser/sniffer.py` | API 拦截工具 |
|
|
||||||
| `hunyuan3dweb/browser/generator.py` | 浏览器自动化生成 |
|
## 相关文档
|
||||||
| `doc/api.md` | API 接口文档 |
|
|
||||||
|
- [逆向工程文档](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
|
4. Obtain `resourceUrl` for subsequent image-to-3D API calls
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from hunyuan3dweb.cos_upload import upload_image
|
from hy3d.cos_upload import upload_image
|
||||||
|
|
||||||
resource_url = upload_image("/path/to/image.png")
|
resource_url = upload_image("/path/to/image.png")
|
||||||
```
|
```
|
||||||
@@ -71,15 +71,15 @@ resource_url = upload_image("/path/to/image.png")
|
|||||||
|
|
||||||
| File | Description |
|
| File | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `hunyuan3dweb/sign.py` | Signing algorithm in pure Python |
|
| `hy3d/sign.py` | Signing algorithm in pure Python |
|
||||||
| `hunyuan3dweb/api.py` | Basic API client (image2model, text2model) |
|
| `hy3d/api.py` | Basic API client (image2model, text2model) |
|
||||||
| `hunyuan3dweb/api_complete.py` | Full API client (all generation modes) |
|
| `hy3d/api_complete.py` | Full API client (all generation modes) |
|
||||||
| `hunyuan3dweb/cos_upload.py` | Pure Python COS upload helper (no browser needed) |
|
| `hy3d/cos_upload.py` | Pure Python COS upload helper (no browser needed) |
|
||||||
| `hunyuan3dweb/config.py` | User config path management (cookies, profile) |
|
| `hy3d/config.py` | User config path management (cookies, profile) |
|
||||||
| `hunyuan3dweb/cli.py` | CLI entry point |
|
| `hy3d/cli.py` | CLI entry point |
|
||||||
| `hunyuan3dweb/browser/login.py` | Browser automation login tool |
|
| `hy3d/browser/login.py` | Browser automation login tool |
|
||||||
| `hunyuan3dweb/browser/generator.py` | Browser automation image-to-3D |
|
| `hy3d/browser/generator.py` | Browser automation image-to-3D |
|
||||||
| `hunyuan3dweb/browser/sniffer.py` | API request sniffer |
|
| `hy3d/browser/sniffer.py` | API request sniffer |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ resource_url = upload_image("/path/to/image.png")
|
|||||||
### 1. Login to Obtain Cookie
|
### 1. Login to Obtain Cookie
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hunyuan3dweb-login
|
hy3d-login
|
||||||
# Follow prompts to enter email and verification code
|
# Follow prompts to enter email and verification code
|
||||||
# Login state is automatically saved to ~/.config/hunyuan3dweb/profile
|
# Login state is automatically saved to ~/.config/hunyuan3dweb/profile
|
||||||
# Cookies are also exported to ~/.config/hunyuan3dweb/cookies.txt
|
# Cookies are also exported to ~/.config/hunyuan3dweb/cookies.txt
|
||||||
@@ -102,7 +102,7 @@ hunyuan3dweb-login
|
|||||||
### 2. Pure Python 3D Generation
|
### 2. Pure Python 3D Generation
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from hunyuan3dweb import Hunyuan3DAPI
|
from hy3d import Hunyuan3DAPI
|
||||||
|
|
||||||
# Automatically load cookie from ~/.config/hunyuan3dweb/cookies.txt
|
# Automatically load cookie from ~/.config/hunyuan3dweb/cookies.txt
|
||||||
api = Hunyuan3DAPI()
|
api = Hunyuan3DAPI()
|
||||||
@@ -120,8 +120,8 @@ status = api.get_generation_status(result["creationsId"])
|
|||||||
### 3. Local Image Upload + Image-to-3D (Pure Python)
|
### 3. Local Image Upload + Image-to-3D (Pure Python)
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from hunyuan3dweb.cos_upload import upload_image
|
from hy3d.cos_upload import upload_image
|
||||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
from hy3d import Hunyuan3DAPIComplete
|
||||||
|
|
||||||
# Upload local image to COS
|
# Upload local image to COS
|
||||||
resource_url = upload_image("/path/to/image.png")
|
resource_url = upload_image("/path/to/image.png")
|
||||||
@@ -135,16 +135,16 @@ result = api.generate_from_image(resource_url, title="My Model")
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List available formats for a creation
|
# List available formats for a creation
|
||||||
hunyuan3dweb formats <creation_id>
|
hy3d formats <creation_id>
|
||||||
|
|
||||||
# Include converted formats (fbx, stl, usdz, mp4, gif)
|
# Include converted formats (fbx, stl, usdz, mp4, gif)
|
||||||
hunyuan3dweb formats <creation_id> --converted
|
hy3d formats <creation_id> --converted
|
||||||
|
|
||||||
# Download a specific format (default: glb)
|
# 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
|
# 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 使用
|
4. 获得 `resourceUrl` 供后续图生3D API 使用
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from hunyuan3dweb.cos_upload import upload_image
|
from hy3d.cos_upload import upload_image
|
||||||
|
|
||||||
resource_url = upload_image("/path/to/image.png")
|
resource_url = upload_image("/path/to/image.png")
|
||||||
```
|
```
|
||||||
@@ -71,15 +71,15 @@ resource_url = upload_image("/path/to/image.png")
|
|||||||
|
|
||||||
| 文件 | 说明 |
|
| 文件 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `hunyuan3dweb/sign.py` | 签名算法纯 Python 实现 |
|
| `hy3d/sign.py` | 签名算法纯 Python 实现 |
|
||||||
| `hunyuan3dweb/api.py` | 基础 API 客户端(图生3D、文生3D) |
|
| `hy3d/api.py` | 基础 API 客户端(图生3D、文生3D) |
|
||||||
| `hunyuan3dweb/api_complete.py` | 完整 API 客户端(所有生成模式) |
|
| `hy3d/api_complete.py` | 完整 API 客户端(所有生成模式) |
|
||||||
| `hunyuan3dweb/cos_upload.py` | 纯 Python COS 上传工具(无需浏览器) |
|
| `hy3d/cos_upload.py` | 纯 Python COS 上传工具(无需浏览器) |
|
||||||
| `hunyuan3dweb/config.py` | 用户配置路径管理(cookie、profile) |
|
| `hy3d/config.py` | 用户配置路径管理(cookie、profile) |
|
||||||
| `hunyuan3dweb/cli.py` | CLI 入口 |
|
| `hy3d/cli.py` | CLI 入口 |
|
||||||
| `hunyuan3dweb/browser/login.py` | 浏览器自动化登录工具 |
|
| `hy3d/browser/login.py` | 浏览器自动化登录工具 |
|
||||||
| `hunyuan3dweb/browser/generator.py` | 浏览器自动化图生3D |
|
| `hy3d/browser/generator.py` | 浏览器自动化图生3D |
|
||||||
| `hunyuan3dweb/browser/sniffer.py` | API 请求嗅探工具 |
|
| `hy3d/browser/sniffer.py` | API 请求嗅探工具 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ resource_url = upload_image("/path/to/image.png")
|
|||||||
### 1. 登录获取 Cookie
|
### 1. 登录获取 Cookie
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hunyuan3dweb-login
|
hy3d-login
|
||||||
# 按提示输入邮箱和验证码
|
# 按提示输入邮箱和验证码
|
||||||
# 登录状态自动保存到 ~/.config/hunyuan3dweb/profile
|
# 登录状态自动保存到 ~/.config/hunyuan3dweb/profile
|
||||||
# Cookie 同时导出到 ~/.config/hunyuan3dweb/cookies.txt
|
# Cookie 同时导出到 ~/.config/hunyuan3dweb/cookies.txt
|
||||||
@@ -102,7 +102,7 @@ hunyuan3dweb-login
|
|||||||
### 2. 纯 Python 生成3D模型
|
### 2. 纯 Python 生成3D模型
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from hunyuan3dweb import Hunyuan3DAPI
|
from hy3d import Hunyuan3DAPI
|
||||||
|
|
||||||
# 自动从 ~/.config/hunyuan3dweb/cookies.txt 加载 cookie
|
# 自动从 ~/.config/hunyuan3dweb/cookies.txt 加载 cookie
|
||||||
api = Hunyuan3DAPI()
|
api = Hunyuan3DAPI()
|
||||||
@@ -120,8 +120,8 @@ status = api.get_generation_status(result["creationsId"])
|
|||||||
### 3. 本地图片上传 + 图生3D(纯 Python)
|
### 3. 本地图片上传 + 图生3D(纯 Python)
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from hunyuan3dweb.cos_upload import upload_image
|
from hy3d.cos_upload import upload_image
|
||||||
from hunyuan3dweb import Hunyuan3DAPIComplete
|
from hy3d import Hunyuan3DAPIComplete
|
||||||
|
|
||||||
# 上传本地图片到 COS
|
# 上传本地图片到 COS
|
||||||
resource_url = upload_image("/path/to/image.png")
|
resource_url = upload_image("/path/to/image.png")
|
||||||
@@ -135,16 +135,16 @@ result = api.generate_from_image(resource_url, title="我的模型")
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 列出某个创作所有可用的下载格式
|
# 列出某个创作所有可用的下载格式
|
||||||
hunyuan3dweb formats <creation_id>
|
hy3d formats <creation_id>
|
||||||
|
|
||||||
# 包含转换格式(fbx, stl, usdz, mp4, gif)
|
# 包含转换格式(fbx, stl, usdz, mp4, gif)
|
||||||
hunyuan3dweb formats <creation_id> --converted
|
hy3d formats <creation_id> --converted
|
||||||
|
|
||||||
# 下载指定格式(默认 glb)
|
# 下载指定格式(默认 glb)
|
||||||
hunyuan3dweb download <creation_id> --format glb -o model.glb
|
hy3d download <creation_id> --format glb -o model.glb
|
||||||
|
|
||||||
# 下载转换格式(如 usdz)
|
# 下载转换格式(如 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。
|
Cookie 会话认证。登录后由浏览器自动携带 `sessionid` 等 Cookie。
|
||||||
|
|
||||||
持久化数据保存在 `./hunyuan3d_profile`,CloakBrowser 复用该目录即可保持登录态。
|
持久化数据保存在 `~/.config/hunyuan3dweb/profile`,CloakBrowser 复用该目录即可保持登录态。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# 腾讯混元3D API 完整文档
|
# 腾讯混元3D API 完整文档
|
||||||
|
|
||||||
> 基础地址: `https://3d.hunyuan.tencent.com`
|
> 基础地址: `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 import Hunyuan3DAPI
|
||||||
from .api_complete import Hunyuan3DAPI as Hunyuan3DAPIComplete
|
from .api_complete import Hunyuan3DAPI as Hunyuan3DAPIComplete
|
||||||
from .sign import sign, sign_with_custom_nonce
|
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__ = [
|
__all__ = [
|
||||||
"Hunyuan3DAPI",
|
"Hunyuan3DAPI",
|
||||||
"Hunyuan3DAPIComplete",
|
"Hunyuan3DAPIComplete",
|
||||||
"sign",
|
"sign",
|
||||||
"sign_with_custom_nonce",
|
"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):
|
if not os.path.exists(standard):
|
||||||
raise FileNotFoundError(f"未找到登录状态目录: {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")
|
temp_profile = os.path.join(temp_root, "profile")
|
||||||
shutil.copytree(standard, temp_profile, dirs_exist_ok=True)
|
shutil.copytree(standard, temp_profile, dirs_exist_ok=True)
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ def main():
|
|||||||
|
|
||||||
if not os.path.exists(PROFILE_DIR):
|
if not os.path.exists(PROFILE_DIR):
|
||||||
print(f"错误: 未找到持久化目录 {PROFILE_DIR}")
|
print(f"错误: 未找到持久化目录 {PROFILE_DIR}")
|
||||||
print("请先运行 hunyuan3dweb-login 完成登录")
|
print("请先运行 hy3d-login(或 python -m hy3d.browser.login)完成登录")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
profile_dir, temp_dir = prepare_profile_dir()
|
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:
|
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")
|
xdg = os.environ.get("XDG_CONFIG_HOME")
|
||||||
base = Path(xdg) if xdg else Path.home() / ".config"
|
base = Path(xdg) if xdg else Path.home() / ".config"
|
||||||
path = base / "hunyuan3dweb"
|
path = base / "hunyuan3dweb"
|
||||||
@@ -38,7 +38,7 @@ def prepare_profile_dir() -> Tuple[str, Optional[str]]:
|
|||||||
if not os.path.exists(standard):
|
if not os.path.exists(standard):
|
||||||
return standard, None
|
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")
|
temp_profile = os.path.join(temp_root, "profile")
|
||||||
shutil.copytree(standard, temp_profile, dirs_exist_ok=True)
|
shutil.copytree(standard, temp_profile, dirs_exist_ok=True)
|
||||||
return temp_profile, temp_root
|
return temp_profile, temp_root
|
||||||
@@ -145,7 +145,7 @@ def upload_image(image_path, use_accelerate=True):
|
|||||||
try:
|
try:
|
||||||
cookies = load_cookies_from_file()
|
cookies = load_cookies_from_file()
|
||||||
except FileNotFoundError as exc:
|
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)
|
api = Hunyuan3DAPIComplete(cookies=cookies)
|
||||||
|
|
||||||
filename = Path(image_path).name
|
filename = Path(image_path).name
|
||||||
+12
-12
@@ -3,27 +3,27 @@ requires = ["setuptools>=61.0"]
|
|||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "hunyuan3dweb"
|
name = "hy3d"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "Tencent Hunyuan 3D Web API client and browser automation tools"
|
description = "非官方腾讯混元 3D CLI 与 Python API 客户端(3d.hunyuan.tencent.com)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"requests",
|
"requests>=2.32.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
browser = [
|
browser = [
|
||||||
"cloakbrowser",
|
"cloakbrowser>=0.3.31",
|
||||||
"playwright",
|
"playwright>=1.40",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
hunyuan3dweb = "hunyuan3dweb.cli:main"
|
hy3d = "hy3d.cli:main"
|
||||||
hunyuan3dweb-login = "hunyuan3dweb.browser.login:main"
|
hy3d-login = "hy3d.browser.login:main"
|
||||||
hunyuan3dweb-sniffer = "hunyuan3dweb.browser.sniffer:main"
|
hy3d-sniffer = "hy3d.browser.sniffer:main"
|
||||||
hunyuan3dweb-generate = "hunyuan3dweb.browser.generator:main"
|
hy3d-generate = "hy3d.browser.generator:main"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
where = ["."]
|
where = ["."]
|
||||||
include = ["hunyuan3dweb*"]
|
include = ["hy3d*"]
|
||||||
Reference in New Issue
Block a user