diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..18ded69 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "open-code-review@open-code-review": true + } +} diff --git a/.claude/skills/run-hunyuan3dweb/SKILL.md b/.claude/skills/run-hunyuan3dweb/SKILL.md index 3324182..6ae6319 100644 --- a/.claude/skills/run-hunyuan3dweb/SKILL.md +++ b/.claude/skills/run-hunyuan3dweb/SKILL.md @@ -1,103 +1,87 @@ --- 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 -python3 --version # ≥3.8 ✅ (本机 3.13.12) -python3 -c "import requests" # 缺则: pip install requests +python3 --version # ≥3.9 ✅ (本机 3.13.12) +python3 -c "import requests" # 缺则: pip install requests ``` 浏览器功能(登录/抓包)另需 `playwright`、`cloakbrowser`、`chromium`、`xvfb-run`——本机均已装好。 ## Setup -无安装步骤。依赖的两个东西: +无安装步骤。仅需登录 cookie:`~/.config/hunyuan3dweb/cookies.txt`(2026-08-15 曾登录,cookie 可能随时过期,过期用下方 auth 流程处理)。 -1. **仓库路径**:driver 已内置(`PYTHONPATH` 自动指向仓库根)。 -2. **登录 cookie**:`~/.config/hunyuan3dweb/cookies.txt`(已存在,2026-08-15 登录过,当前有效)。过期后用下面"重新登录"一节处理。 - -## Run(agent 路径)——driver.sh +## Run(agent 路径) ```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 +bin/hy3d download --format glb -o model.glb +bin/hy3d text "a ceramic teapot" --wait # 文生 3D(消耗 4 配额)轮询到完成 +# 更多: status/user/count/image/sketch/animate/texture/topo/cancel/share ``` -| 命令 | 作用 | -|---|---| -| `driver.sh verify` | 环境 + 离线签名 + 在线配额 三重自检 | -| `driver.sh quota` | 查配额,输出 `remainQuota/totalQuota` | -| `driver.sh list` | 作品列表(JSON,含 `totalCount`/`creations`) | -| `driver.sh status ` | 生成状态(json/轮询用 `state` 字段) | -| `driver.sh text ""` | 文生 3D 提交(**消耗配额**,出 4 个) | -| `driver.sh formats ` | 可用下载格式(glb/obj/fbx/stl/usdz/mp4/gif…) | -| `driver.sh download [--format glb] [--output PATH]` | 下载模型到当前目录 | +**退出码**(agent 分支判断的依据,已实测): -其余 API(图生 3D、多视角、动画、贴图、减面)直接 import 客户端调用 -(`api_complete.py` 的 `Hunyuan3DAPIComplete`),10+ 种生成模式同签名同会话。 -本容器内已实测:`quota`/`list`/`status`/`formats`/`download`(2026-08-15,7 个作品齐全); -`text` 只验证到请求构造层(消耗配额,未实测全流程)。 +| 码 | 含义 | 处理 | +|---|---|---| +| 0 | 成功 | — | +| 1 | 环境/网络/参数/4xx | 看 stderr | +| 2 | 认证失败(token 无效/过期) | `hy3d auth login`(需人工,见下) | +| 3 | cookie 缺失 | `hy3d auth login`(需人工,见下) | -**退出码**:`0` 成功 · `1` 环境/其他 · `2` 认证失败(需重新登录)· `3` cookie 缺失。 -收到 2/3 时按下面"重新登录"处理,不要把栈当 bug 报告。 +典型工作流(2026-08-15 全部实测通过):`auth status` → `quota` → `list` → 对历史作品 `formats`/`download`(下载 512×512 PNG 预览验证过);`text --wait` 已验证提交+轮询框架,未在容器内跑完整生成(消耗配额)。 -典型工作流(文生 3D + 轮询): +## 重新登录(退出码 2/3 时唯一出路) + +**必须由人类用户在真实终端**(Claude 的 `!` 前缀 stdin 非交互,`input()` 会 EOF)运行: ```bash -.claude/skills/run-hunyuan3dweb/driver.sh quota # 先看余额 -.claude/skills/run-hunyuan3dweb/driver.sh text "a ceramic teapot" # → creationsId -.claude/skills/run-hunyuan3dweb/driver.sh status # 轮询到 state=success -.claude/skills/run-hunyuan3dweb/driver.sh download --format glb +python -m hy3d.browser.login # 或安装后: hy3d-login ``` -## 重新登录(认证失败时唯一出路) - -收到退出码 2(`"code":"20001"` token 无效 / `"999"` cookie 无用户)时: -**必须由人类用户在真实终端**(不是 Claude 的 `!` 前缀——stdin 非交互,`input()` 会 EOF)运行: - -```bash -PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.browser.login -``` - -按提示输邮箱 → 收验证码 → 输验证码,登录态自动存回 `cookies.txt`。 -登录完成后浏览器进程**自动退出,无资源残留**(2026-08-15 验证:登录后零残留进程)。 +输邮箱 → 收验证码 → 输验证码,cookie 自动存回 `~/.config/hunyuan3dweb/cookies.txt`。登录后浏览器进程自动退出,无残留(2026-08-15 验证)。 ## Run(人类路径) ```bash -PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.cli quota # 等价 CLI -PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.cli list -PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.cli text "a red apple" +pip install -e ".[browser]" # 可选,装好后 hy3d 直接进 PATH +hy3d quota ``` ## Test(离线自检) ```bash -PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.sign -# 派生密钥: Hf6d6KFB3D ← 输出里有这行即签名算法正确 +python3 -m hy3d.sign +# 输出含「派生密钥: Hf6d6KFB3D」即签名算法正确 ``` --- ## Gotchas -- **`!` 前缀跑登录必 EOF** —— `input()` 在管道 stdin 打开即 EOF(`EOFError: EOF when reading a line`)。登录必须真实终端;agent 只能引导用户去跑。 -- **401 有两种,别混** —— `"code":"20001"`=token 过期(重登);`"code":"999"`=cookie 无用户(重登或检查 cookie 文件)。两者都证明**签名本身是对的**(服务端能分类错误)。 -- **不存在的 creationsId → HTTP 400 空 body**,不是 JSON 错误(2026-08-15 实测)。 -- **`python3 -m hunyuan3dweb.sign` 有 RuntimeWarning**(`'hunyuan3dweb.sign' found in sys.modules...`)——runpy 与 `__init__.py` 重复导入的假警报,无害,输出仍正确。 -- **文生 3D 固定出 4 个模型**(`count=4`),一次消耗 4 次配额;`generate_from_text` 的 `count` 参数会覆盖 UI 行为。 -- **`download` 输出到 CWD**——某次下载 86KB 预览图实测 2–3 秒;GLB 可能几十 MB,注意磁盘和时间。正式大规模跑之前先 `quota` 确认登录态在线。 +- **`!` 前缀跑登录必 EOF** —— `input()` 在管道 stdin 上直接 `EOFError: EOF when reading a line`。登录必须真实终端;agent 只能引导用户。 +- **401 两种码都证明签名正确** —— `"code":"20001"`=token 过期(重登);`"code":"999"`=cookie 无用户。服务端能分类错误说明请求格式被完全接受。 +- **不存在的 creationsId → HTTP 400 空 body**(CLI 归为退出码 1),不是 JSON 错误。 +- **文生 3D 一次消耗 4 次配额**(`count=4` 固定);图生 3D 等消耗 1 次。 +- **配置目录刻意保持 `~/.config/hunyuan3dweb` 不变**——改名后路径改了会丢掉已登录的 cookie。 +- **生成类命令(text/image/animate/...)未在容器内完整跑通**——验证到提交与轮询框架;正式大规模使用前先 `hy3d auth status` 确认在线,并接受首单可能失败的调试成本。 ## Troubleshooting -- **`driver.sh verify` 打印 AUTH_EXPIRED / 退出码 2**:token 过期。让人在真实终端跑 `PYTHONPATH=/opt/hunyuan3dweb python3 -m hunyuan3dweb.browser.login`,成功后再 verify。 -- **`EOFError: EOF when reading a line`**(登录时):stdin 非交互。换真实终端,不要用 `!`。 -- **`requests.exceptions.HTTPError: 401` + 中文消息 'token无效'**:同上,重登。不是代码 bug,别改代码。 -- **cookie 想换账号**:`HUNYUAN3D_COOKIES=/path/to/other/cookies.txt driver.sh quota`。 -- **driver 里 python 找不到模块**:确认 `PYTHONPATH` 含仓库根(driver 会自动设置)。 \ No newline at end of file +- **`hy3d auth status` 退出码 2**:token 过期。让人在真实终端跑 `python -m hy3d.browser.login`。 +- **退出码 3**:cookie 文件不存在/为空。同上,或检查 `HY3D_COOKIES` 是否指向了错误路径。 +- **`EOFError`(登录时)**:stdin 非交互,换真实终端。 +- **`401` + 'token无效'**:重登,不是代码 bug。 +- **`python3 -m hy3d.sign` 的 RuntimeWarning**:runpy 与 `__init__.py` 重复导入的假警报,无害。 \ No newline at end of file diff --git a/.claude/skills/run-hunyuan3dweb/driver.sh b/.claude/skills/run-hunyuan3dweb/driver.sh deleted file mode 100755 index 507b3cb..0000000 --- a/.claude/skills/run-hunyuan3dweb/driver.sh +++ /dev/null @@ -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 [args] - - verify 环境+离线签名+配额 三重自检(最快确认能用) - quota 查询配额(remain/total) - list 作品列表 JSON - status 生成状态 JSON - text "" 文生 3D(提交任务,消耗配额) - formats 列出该创作的可用下载格式 - download [--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 \ No newline at end of file diff --git a/README.md b/README.md index 4b01a90..4ab840c 100644 --- a/README.md +++ b/README.md @@ -1,304 +1,164 @@ -# hunyuan3dweb +# hy3d -Python API client and browser automation tools for Tencent Hunyuan 3D (https://3d.hunyuan.tencent.com/). +非官方腾讯混元 3D CLI 与 Python API 客户端([3d.hunyuan.tencent.com](https://3d.hunyuan.tencent.com/))。 -## Installation +纯 Python HTTP 调用腾讯混元 3D 的 AI 能力:**文生 3D、图生 3D、多视角、草图、动画、纹理、智能拓扑减面**,以及配额查询、作品管理、模型下载(GLB/OBJ/FBX/STL/USDZ/MP4/GIF)。 + +> ⚠️ 非官方项目:这是对混元 3D Web 前端签名算法的逆向实现,与腾讯无关。仅供个人学习与自动化使用。 + +## 安装 ```bash -pip install -e . +pip install hy3d # 纯 API 客户端 +pip install "hy3d[browser]" # 额外安装浏览器工具(登录/抓包/浏览器自动化) ``` -For browser automation features: +不安装也能用——克隆仓库后直接用源码跑(无需 `pip install`): ```bash -pip install -e ".[browser]" +cd hy3d +PYTHONPATH=$PWD python3 -m hy3d.cli quota ``` -## Login and Setup +## 快速开始 -First-time users need to log in via the browser. Cookies will be saved automatically: +首次使用需要登录一次(**在真实终端运行**,邮箱验证码登录,无头浏览器): ```bash -hunyuan3dweb-login +hy3d auth login +# → 按提示: 输邮箱 → 收验证码 → 输验证码 +# → cookie 自动保存到 ~/.config/hunyuan3dweb/cookies.txt ``` -After successful login: -- Browser profile is saved to `~/.config/hunyuan3dweb/profile` -- Cookies are automatically extracted to `~/.config/hunyuan3dweb/cookies.txt` - -Subsequent Python scripts will read cookies automatically — no manual configuration needed. - -## Python Script Usage - -### Quick Start (Auto-read Default Cookie) - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -# Check quota -quota = api.get_quota_info() -print(f"Remaining: {quota['remainQuota']}/{quota['totalQuota']}") -``` - -### 1. Text-to-3D - -The simplest mode. No image upload required. - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -# Submit generation job -result = api.generate_from_text("a ceramic vase", title="Test") -cid = result["creationsId"] - -# Poll until completion (built-in polling with progress output) -final = api.wait_for_completion(cid) -print("Generation complete") -``` - -**Full pipeline: Text-to-3D → Extract Download Links** - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -job = api.generate_from_text( - "cyberpunk style mechanical dog", - style=api.STYLE_CYBERPUNK -) -cid = job["creationsId"] - -result = api.wait_for_completion(cid, timeout=600, poll_interval=5) - -if result["status"] == "success": - models = result.get("result", []) - for m in models: - if m["status"] == "success": - urls = m["urlResult"] - print(f"GLB: {urls.get('glb')}") - print(f"OBJ: {urls.get('obj')}") - print(f"Image: {urls.get('image_url')}") -``` - -### 2. Image-to-3D (Local File) - -For local image files, you can use either the browser automation tool or the pure Python COS uploader to obtain a `resourceUrl` and then call the API. +验证登录与配额: ```bash -hunyuan3dweb-generate /path/to/image.png wait +hy3d auth status # 登录体检 +hy3d quota # 配额查询 ``` -Or programmatically via browser automation: +## CLI 命令 -```python -from hunyuan3dweb.browser.generator import generate_3d - -result = generate_3d("/path/to/image.png", wait_for_complete=True) -print(f"Model URL: {result.get('modelUrl')}") +``` +hy3d [--json] [--cookies PATH] [args...] ``` -Or via pure Python upload: +| 命令 | 说明 | 示例 | +|---|---|---| +| `quota` | 查配额 | `hy3d quota` | +| `list` | 作品列表 | `hy3d list --page 2` | +| `count` | 作品统计 | `hy3d count` | +| `user` | 用户信息 | `hy3d user` | +| `status ` | 生成状态/详情 | `hy3d status --wait` | +| `formats ` | 可用下载格式 | `hy3d formats --converted` | +| `download ` | 下载模型 | `hy3d download --format glb -o model.glb` | +| `cancel ` | 取消任务 | `hy3d cancel ` | +| `share ` | 生成分享 | `hy3d share ` | +| `text ""` | 文生 3D(4 配额) | `hy3d text "a red teapot" --style cyberpunk --wait` | +| `image ` | 图生 3D(本地自动上传) | `hy3d image ./photo.png --wait` | +| `multi-view ` | 多视角图生 3D(≥2 张) | `hy3d multi-view a.png b.png c.png --wait` | +| `sketch --prompt P` | 草图生 3D | `hy3d sketch sketch.png --prompt "a robot" --wait` | +| `animate --motion M` | 3D 动画 | `hy3d animate m.png --motion dancing --wait` | +| `texture --prompt P` | 白模上纹理 | `hy3d texture white.png --prompt "red metallic rust" --wait` | +| `topo ` | 智能减面 | `hy3d topo model.png --faces 5000 --wait` | +| `auth status\|login` | 登录体检/登录 | `hy3d auth status` | +| `config` | 配置与环境变量 | `hy3d config` | + +生成命令都支持 `--wait`(提交后轮询到完成并输出模型链接)、`--style`、`--title`。 +所有查询命令支持 `--json`(机器可读输出)。 + +**动作类型**(`animate`):`capoeira falling jumping kicking sword running dancing` +**纹理风格**(`--style`):`sculpture qinghuaci china_style cartoon cyberpunk` +**拓扑面数**(`topo --faces`):`5000 18000 30000` +**下载格式键**(`download --format`):`glb obj mtl obj_url geometryGlb textureGlb textureObj image_url pbrImage pbrMetallicImage pbrRoughnessImage pbrNormalImage invisible_wall air_wall`,加 `--converted` 可转换出 `fbx stl usdz mp4 gif` + +### 退出码(agent 友好) + +| 码 | 含义 | 处理 | +|---|---|---| +| 0 | 成功 | — | +| 1 | 环境/网络/参数/服务端 4xx | 看 stderr | +| 2 | 认证失败(token 无效/过期) | 重新登录:`hy3d auth login` | +| 3 | cookie 文件缺失 | 首次登录:`hy3d auth login` | + +## Python API ```python -from hunyuan3dweb.cos_upload import upload_image -from hunyuan3dweb import Hunyuan3DAPIComplete - -resource_url = upload_image("/path/to/image.png") +from hy3d import Hunyuan3DAPIComplete api = Hunyuan3DAPIComplete() -result = api.generate_from_image(resource_url, title="My Model") -``` +print(api.get_quota_info()) # {'remainQuota': 20, 'totalQuota': 20, ...} -### 3. Image-to-3D (API with Existing resourceUrl) - -If you already have a `resourceUrl` (e.g. from a previous browser upload), use the pure API client: - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_from_image( - image_url="https://3d.hunyuan.tencent.com/api/3d/resource/download?resourceId=...", - title="My Model" -) +result = api.generate_from_text("a ceramic vase", style=api.STYLE_CYBERPUNK) cid = result["creationsId"] +final = api.wait_for_completion(cid) # 内置轮询,进度打 stdout -final = api.wait_for_completion(cid) +# 本地图片 → COS 上传 → 图生 3D 全自动 +from hy3d import upload_image +resource_url = upload_image("/path/to/photo.png") +api.generate_from_image(resource_url, title="My Model") + +# 下载模型(原生 + 按需转换 fbx/stl/usdz/mp4/gif) +urls = api.get_model_urls(cid, include_converted=True) # 转换会调用 resourceConvert +api.download_model(cid, "glb") # → ./model.glb ``` -### 4. Multi-View Image-to-3D +完整 API 一览(`Hunyuan3DAPIComplete` / `hy3d/api_complete.py`): -Use multiple images from different angles to generate a more accurate 3D model. +| 方法 | 功能 | +|---|---| +| `generate_from_text` / `generate_from_image` | 文生 3D / 图生 3D | +| `generate_from_multi_view` | 多视角图生 3D | +| `generate_from_sketch` | 草图生 3D | +| `generate_animation` / `generate_texture` | 动画 / 纹理 | +| `generate_lowpoly` | 智能拓扑减面 | +| `get_quota_info` / `get_creation_list` / `get_creation_count` | 配额 / 作品 / 统计 | +| `get_generation_status` / `wait_for_completion` / `cancel_generation` | 任务管理 | +| `get_model_urls` / `download_model` / `resource_convert` | 模型下载与格式转换 | +| `get_upload_info` / `review_resource` | 资源上传与审核 | +| `create_share` | 分享 | + +轻量版客户端 `Hunyuan3DAPI`(`hy3d/api.py`)只含文生/图生/配额/列表等核心接口。 + +### 多账号 / 自定义 cookie ```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_from_multi_view( - image_urls=[ - "https://.../front.png", - "https://.../side.png", - "https://.../back.png", - ], - title="Multi-View Model" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) +from hy3d import Hunyuan3DAPIComplete, load_cookies_from_file +api = Hunyuan3DAPIComplete(cookies=load_cookies_from_file("/path/to/cookies.txt")) ``` -### 5. Sketch-to-3D +CLI 等价写法:`hy3d --cookies /path/to/cookies.txt quota`,或环境变量 `HY3D_COOKIES`。 -```python -from hunyuan3dweb import Hunyuan3DAPIComplete +## 浏览器工具(可选) -api = Hunyuan3DAPIComplete() +| 命令 | 用途 | +|---|---| +| `hy3d-login` | 邮箱验证码登录(无头,自动保存 cookie 与浏览器 profile) | +| `hy3d-sniffer` | API 请求/响应抓包(`api_requests.log.json`) | +| `hy3d-generate [wait]` | 浏览器内图生 3D(走浏览器签名,适合对照验证) | -result = api.generate_from_sketch( - sketch_url="https://...", - prompt="a sketch of a robot", - title="Sketch Robot" -) -cid = result["creationsId"] +需要 `pip install "hy3d[browser]"`(cloakbrowser + playwright)。 -final = api.wait_for_completion(cid) -``` +## 配置文件与目录 -### 6. Animation Generation +- cookie:`~/.config/hunyuan3dweb/cookies.txt`(路径保持不变,避免重新登录) +- 浏览器 profile:`~/.config/hunyuan3dweb/profile` +- 环境变量:`HY3D_COOKIES`(cookie 路径)、`HY3D_JSON=1`(等同 `--json`) -Requires a model image URL and a motion type. +## 文件结构 -```python -from hunyuan3dweb import Hunyuan3DAPIComplete +| 文件 | 说明 | +|---|---| +| `hy3d/cli.py` | CLI 入口(全部命令) | +| `hy3d/api.py` | 轻量 API 客户端(图生 3D / 文生 3D) | +| `hy3d/api_complete.py` | 完整 API 客户端(全部生成模式) | +| `hy3d/sign.py` | 签名算法(HMAC-SHA256 + 密钥派生) | +| `hy3d/cos_upload.py` | 纯 Python COS 上传(免浏览器) | +| `hy3d/config.py` | 配置路径管理 | +| `hy3d/browser/` | 登录 / 抓包 / 浏览器自动化工具 | -api = Hunyuan3DAPIComplete() +## 相关文档 -# Available motions: MOTION_CAPOEIRA, MOTION_FALLING, MOTION_JUMPING, -# MOTION_KICKING, MOTION_SWORD, MOTION_RUNNING, MOTION_DANCING -result = api.generate_animation( - model_image_url="https://...", - motion_type=api.MOTION_DANCING, - title="Dancing Model" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 7. Texture Generation - -Apply texture to a white/untexured model. - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_texture( - white_model_url="https://...", - prompt="red metallic texture with rust", - title="Textured Model" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 8. Smart Topology (Decimation / Low Poly) - -Reduce polygon count of an existing model. - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_lowpoly( - model_url="https://...", - face_count=api.TOPO_LOW, # 5000, 18000, or 30000 - topology_format="glb", # "glb" or "obj" - title="Low Poly Model" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### Basic Client (Lightweight) - -For users who only need image-to-3D and text-to-3D: - -```python -from hunyuan3dweb import Hunyuan3DAPI - -api = Hunyuan3DAPI() - -# Text-to-3D -api.generate_text("a cat") - -# Image-to-3D (requires existing resourceUrl) -api.generate_3d(image_url="...") -``` - -### Explicit Cookie Path (Multi-account or Custom Path) - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete, load_cookies_from_file - -cookies = load_cookies_from_file("/path/to/cookies.txt") -api = Hunyuan3DAPIComplete(cookies=cookies) -``` - -## CLI Commands - -- `hunyuan3dweb` - API CLI tool (supports quota / list / text / status subcommands) -- `hunyuan3dweb-login` - Browser login (auto-saves cookies) -- `hunyuan3dweb-sniffer` - API request/response sniffer -- `hunyuan3dweb-generate` - Browser automation for generation - -### CLI Examples - -```bash -# Check quota -hunyuan3dweb quota - -# List creations -hunyuan3dweb list - -# Text-to-3D -hunyuan3dweb text "a red apple" - -# Check generation status -hunyuan3dweb status - -# Browser login -hunyuan3dweb-login - -# Generate from local image (browser automation) -hunyuan3dweb-generate /path/to/image.png wait - -# Sniff API traffic -hunyuan3dweb-sniffer -``` - -## File Reference - -| File | Description | -|------|-------------| -| `hunyuan3dweb/api.py` | Basic API client (image2model, text2model) | -| `hunyuan3dweb/api_complete.py` | Full API client (all generation modes) | -| `hunyuan3dweb/sign.py` | Tencent Hunyuan 3D signing algorithm | -| `hunyuan3dweb/cos_upload.py` | Pure Python COS upload helper | -| `hunyuan3dweb/config.py` | User config path management | -| `hunyuan3dweb/cli.py` | CLI entry point | -| `hunyuan3dweb/browser/login.py` | Browser login tool | -| `hunyuan3dweb/browser/sniffer.py` | API sniffer tool | -| `hunyuan3dweb/browser/generator.py` | Browser automation generator | -| `doc/api.md` | API endpoint documentation | +- [reverse engineering 文档](README_REVERSE_ENGINEERING.md) — 签名算法逆向过程与关键常量 +- [泛用性分析](UNIVERSALITY_ANALYSIS.md) — 算法能否复用到其他产品 +- [API 端点文档](doc/api.md) \ No newline at end of file diff --git a/README_CN.md b/README_CN.md index a99c82f..98698e4 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,304 +1,164 @@ -# hunyuan3dweb +# hy3d -腾讯混元3D (https://3d.hunyuan.tencent.com/) 的 Python API 客户端与浏览器自动化工具。 +非官方腾讯混元 3D CLI 与 Python API 客户端([3d.hunyuan.tencent.com](https://3d.hunyuan.tencent.com/))。 + +纯 Python HTTP 调用腾讯混元 3D 的 AI 能力:**文生 3D、图生 3D、多视角、草图、动画、纹理、智能拓扑减面**,以及配额查询、作品管理、模型下载(GLB/OBJ/FBX/STL/USDZ/MP4/GIF)。 + +> ⚠️ 非官方项目:对混元 3D Web 前端签名算法的逆向实现,与腾讯无关。仅供个人学习与自动化使用。 ## 安装 ```bash -pip install -e . +pip install hy3d # 纯 API 客户端 +pip install "hy3d[browser]" # 额外安装浏览器工具(登录/抓包/浏览器自动化) ``` -如需浏览器自动化功能: +不安装也能用——克隆仓库后直接用源码跑(无需 `pip install`): ```bash -pip install -e ".[browser]" +cd hy3d +PYTHONPATH=$PWD python3 -m hy3d.cli quota ``` -## 登录与配置 +## 快速开始 -第一次使用需要先通过浏览器登录,系统会自动保存 Cookie: +首次使用需要登录一次(**在真实终端运行**,邮箱验证码登录,无头浏览器): ```bash -hunyuan3dweb-login +hy3d auth login +# → 按提示: 输邮箱 → 收验证码 → 输验证码 +# → cookie 自动保存到 ~/.config/hunyuan3dweb/cookies.txt ``` -登录成功后: -- 浏览器 Profile 保存到 `~/.config/hunyuan3dweb/profile` -- Cookie 自动提取到 `~/.config/hunyuan3dweb/cookies.txt` - -之后写 Python 脚本无需再处理 Cookie,库会自动读取。 - -## Python 脚本调用 - -### 快速开始(自动读取默认 Cookie) - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -# 查配额 -quota = api.get_quota_info() -print(f"剩余: {quota['remainQuota']}/{quota['totalQuota']}") -``` - -### 1. 文生3D - -最简单的模式,无需上传图片。 - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -# 提交生成任务 -result = api.generate_from_text("一只陶瓷花瓶", title="测试") -cid = result["creationsId"] - -# 轮询直到完成(内置轮询,自动打印进度) -final = api.wait_for_completion(cid) -print("生成完成") -``` - -**完整流水线:文生3D → 提取下载链接** - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -job = api.generate_from_text( - "赛博朋克风格的机械狗", - style=api.STYLE_CYBERPUNK -) -cid = job["creationsId"] - -result = api.wait_for_completion(cid, timeout=600, poll_interval=5) - -if result["status"] == "success": - models = result.get("result", []) - for m in models: - if m["status"] == "success": - urls = m["urlResult"] - print(f"GLB: {urls.get('glb')}") - print(f"OBJ: {urls.get('obj')}") - print(f"图片: {urls.get('image_url')}") -``` - -### 2. 图生3D(本地文件) - -本地图片文件可通过浏览器自动化工具上传,也可使用纯 Python 的 COS 上传模块先拿到 `resourceUrl` 再走 API。 +验证登录与配额: ```bash -hunyuan3dweb-generate /path/to/image.png wait -``` - -或通过浏览器自动化脚本: - -```python -from hunyuan3dweb.browser.generator import generate_3d - -result = generate_3d("/path/to/image.png", wait_for_complete=True) -print(f"模型地址: {result.get('modelUrl')}") -``` - -或通过纯 Python 上传: - -```python -from hunyuan3dweb.cos_upload import upload_image -from hunyuan3dweb import Hunyuan3DAPIComplete - -resource_url = upload_image("/path/to/image.png") - -api = Hunyuan3DAPIComplete() -result = api.generate_from_image(resource_url, title="我的模型") -``` - -### 3. 图生3D(API,已有 resourceUrl) - -如果你已经有 `resourceUrl`(例如之前通过浏览器上传过),可直接调用 API: - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_from_image( - image_url="https://3d.hunyuan.tencent.com/api/3d/resource/download?resourceId=...", - title="我的模型" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 4. 多图视角生3D(多视图) - -使用多张不同角度的图片生成更精确的 3D 模型。 - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_from_multi_view( - image_urls=[ - "https://.../front.png", - "https://.../side.png", - "https://.../back.png", - ], - title="多视图模型" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 5. 草图生3D - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_from_sketch( - sketch_url="https://...", - prompt="一个机器人的草图", - title="草图机器人" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 6. 动画生成 - -需要模型图片 URL 和动作类型。 - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -# 可选动作:MOTION_CAPOEIRA, MOTION_FALLING, MOTION_JUMPING, -# MOTION_KICKING, MOTION_SWORD, MOTION_RUNNING, MOTION_DANCING -result = api.generate_animation( - model_image_url="https://...", - motion_type=api.MOTION_DANCING, - title="跳舞的模型" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 7. 纹理生成 - -为白模/无纹理模型生成贴图。 - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_texture( - white_model_url="https://...", - prompt="红色金属锈迹纹理", - title="纹理模型" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 8. 智能拓扑(减面 / Low Poly) - -降低现有模型的面数。 - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete - -api = Hunyuan3DAPIComplete() - -result = api.generate_lowpoly( - model_url="https://...", - face_count=api.TOPO_LOW, # 5000 / 18000 / 30000 - topology_format="glb", # "glb" 或 "obj" - title="低模模型" -) -cid = result["creationsId"] - -final = api.wait_for_completion(cid) -``` - -### 基础客户端(轻量) - -仅需图生3D和文生3D的用户可使用简化客户端: - -```python -from hunyuan3dweb import Hunyuan3DAPI - -api = Hunyuan3DAPI() - -# 文生3D -api.generate_text("一只猫") - -# 图生3D(需已有 resourceUrl) -api.generate_3d(image_url="...") -``` - -### 显式指定 Cookie(多账户或自定义路径) - -```python -from hunyuan3dweb import Hunyuan3DAPIComplete, load_cookies_from_file - -cookies = load_cookies_from_file("/path/to/cookies.txt") -api = Hunyuan3DAPIComplete(cookies=cookies) +hy3d auth status # 登录体检 +hy3d quota # 配额查询 ``` ## CLI 命令 -- `hunyuan3dweb` - API CLI 工具(支持 quota / list / text / status 子命令) -- `hunyuan3dweb-login` - 浏览器登录(自动保存 Cookie) -- `hunyuan3dweb-sniffer` - API 拦截分析 -- `hunyuan3dweb-generate` - 浏览器自动化生成 - -### CLI 示例 - -```bash -# 查询配额 -hunyuan3dweb quota - -# 查询作品列表 -hunyuan3dweb list - -# 文生3D -hunyuan3dweb text "一只红色的苹果" - -# 查询生成状态 -hunyuan3dweb status - -# 浏览器登录 -hunyuan3dweb-login - -# 本地图片生成(浏览器自动化) -hunyuan3dweb-generate /path/to/image.png wait - -# API 拦截 -hunyuan3dweb-sniffer +``` +hy3d [--json] [--cookies PATH] [args...] ``` -## 文件说明 +| 命令 | 说明 | 示例 | +|---|---|---| +| `quota` | 查配额 | `hy3d quota` | +| `list` | 作品列表 | `hy3d list --page 2` | +| `count` | 作品统计 | `hy3d count` | +| `user` | 用户信息 | `hy3d user` | +| `status ` | 生成状态/详情 | `hy3d status --wait` | +| `formats ` | 可用下载格式 | `hy3d formats --converted` | +| `download ` | 下载模型 | `hy3d download --format glb -o model.glb` | +| `cancel ` | 取消任务 | `hy3d cancel ` | +| `share ` | 生成分享 | `hy3d share ` | +| `text ""` | 文生 3D(4 配额) | `hy3d text "a red teapot" --style cyberpunk --wait` | +| `image ` | 图生 3D(本地自动上传) | `hy3d image ./photo.png --wait` | +| `multi-view ` | 多视角图生 3D(≥2 张) | `hy3d multi-view a.png b.png c.png --wait` | +| `sketch --prompt P` | 草图生 3D | `hy3d sketch sketch.png --prompt "a robot" --wait` | +| `animate --motion M` | 3D 动画 | `hy3d animate m.png --motion dancing --wait` | +| `texture --prompt P` | 白模上纹理 | `hy3d texture white.png --prompt "red metallic rust" --wait` | +| `topo ` | 智能减面 | `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 [wait]` | 浏览器内图生 3D(走浏览器签名,适合对照验证) | + +需要 `pip install "hy3d[browser]"`(cloakbrowser + playwright)。 + +## 配置与目录 + +- cookie:`~/.config/hunyuan3dweb/cookies.txt`(路径保持不变,避免重新登录) +- 浏览器 profile:`~/.config/hunyuan3dweb/profile` +- 环境变量:`HY3D_COOKIES`(cookie 路径)、`HY3D_JSON=1`(等同 `--json`) + +## 文件结构 | 文件 | 说明 | -|------|------| -| `hunyuan3dweb/api.py` | 基础 API 客户端(图生3D、文生3D) | -| `hunyuan3dweb/api_complete.py` | 完整 API 客户端(所有生成模式) | -| `hunyuan3dweb/sign.py` | 腾讯混元3D 签名算法 | -| `hunyuan3dweb/cos_upload.py` | 纯 Python COS 上传工具 | -| `hunyuan3dweb/config.py` | 用户配置路径管理 | -| `hunyuan3dweb/cli.py` | CLI 入口 | -| `hunyuan3dweb/browser/login.py` | 浏览器登录工具 | -| `hunyuan3dweb/browser/sniffer.py` | API 拦截工具 | -| `hunyuan3dweb/browser/generator.py` | 浏览器自动化生成 | -| `doc/api.md` | API 接口文档 | +|---|---| +| `hy3d/cli.py` | CLI 入口(全部命令) | +| `hy3d/api.py` | 轻量 API 客户端(图生 3D / 文生 3D) | +| `hy3d/api_complete.py` | 完整 API 客户端(全部生成模式) | +| `hy3d/sign.py` | 签名算法(HMAC-SHA256 + 密钥派生) | +| `hy3d/cos_upload.py` | 纯 Python COS 上传(免浏览器) | +| `hy3d/config.py` | 配置路径管理 | +| `hy3d/browser/` | 登录 / 抓包 / 浏览器自动化工具 | + +## 相关文档 + +- [逆向工程文档](README_REVERSE_ENGINEERING_CN.md) — 签名算法逆向过程与关键常量 +- [泛用性分析](UNIVERSALITY_ANALYSIS.md) — 算法能否复用到其他产品 +- [API 端点文档](doc/api_complete.md) \ No newline at end of file diff --git a/README_REVERSE_ENGINEERING.md b/README_REVERSE_ENGINEERING.md index 479d27f..71652ff 100644 --- a/README_REVERSE_ENGINEERING.md +++ b/README_REVERSE_ENGINEERING.md @@ -60,7 +60,7 @@ Upload directly via `cos_upload.py` without a browser: 4. Obtain `resourceUrl` for subsequent image-to-3D API calls ```python -from hunyuan3dweb.cos_upload import upload_image +from hy3d.cos_upload import upload_image resource_url = upload_image("/path/to/image.png") ``` @@ -71,15 +71,15 @@ resource_url = upload_image("/path/to/image.png") | File | Description | |------|-------------| -| `hunyuan3dweb/sign.py` | Signing algorithm in pure Python | -| `hunyuan3dweb/api.py` | Basic API client (image2model, text2model) | -| `hunyuan3dweb/api_complete.py` | Full API client (all generation modes) | -| `hunyuan3dweb/cos_upload.py` | Pure Python COS upload helper (no browser needed) | -| `hunyuan3dweb/config.py` | User config path management (cookies, profile) | -| `hunyuan3dweb/cli.py` | CLI entry point | -| `hunyuan3dweb/browser/login.py` | Browser automation login tool | -| `hunyuan3dweb/browser/generator.py` | Browser automation image-to-3D | -| `hunyuan3dweb/browser/sniffer.py` | API request sniffer | +| `hy3d/sign.py` | Signing algorithm in pure Python | +| `hy3d/api.py` | Basic API client (image2model, text2model) | +| `hy3d/api_complete.py` | Full API client (all generation modes) | +| `hy3d/cos_upload.py` | Pure Python COS upload helper (no browser needed) | +| `hy3d/config.py` | User config path management (cookies, profile) | +| `hy3d/cli.py` | CLI entry point | +| `hy3d/browser/login.py` | Browser automation login tool | +| `hy3d/browser/generator.py` | Browser automation image-to-3D | +| `hy3d/browser/sniffer.py` | API request sniffer | --- @@ -88,7 +88,7 @@ resource_url = upload_image("/path/to/image.png") ### 1. Login to Obtain Cookie ```bash -hunyuan3dweb-login +hy3d-login # Follow prompts to enter email and verification code # Login state is automatically saved to ~/.config/hunyuan3dweb/profile # Cookies are also exported to ~/.config/hunyuan3dweb/cookies.txt @@ -102,7 +102,7 @@ hunyuan3dweb-login ### 2. Pure Python 3D Generation ```python -from hunyuan3dweb import Hunyuan3DAPI +from hy3d import Hunyuan3DAPI # Automatically load cookie from ~/.config/hunyuan3dweb/cookies.txt api = Hunyuan3DAPI() @@ -120,8 +120,8 @@ status = api.get_generation_status(result["creationsId"]) ### 3. Local Image Upload + Image-to-3D (Pure Python) ```python -from hunyuan3dweb.cos_upload import upload_image -from hunyuan3dweb import Hunyuan3DAPIComplete +from hy3d.cos_upload import upload_image +from hy3d import Hunyuan3DAPIComplete # Upload local image to COS resource_url = upload_image("/path/to/image.png") @@ -135,16 +135,16 @@ result = api.generate_from_image(resource_url, title="My Model") ```bash # List available formats for a creation -hunyuan3dweb formats +hy3d formats # Include converted formats (fbx, stl, usdz, mp4, gif) -hunyuan3dweb formats --converted +hy3d formats --converted # Download a specific format (default: glb) -hunyuan3dweb download --format glb -o model.glb +hy3d download --format glb -o model.glb # Download a converted format -hunyuan3dweb download --format usdz --converted -o model.usdz +hy3d download --format usdz --converted -o model.usdz ``` --- diff --git a/README_REVERSE_ENGINEERING_CN.md b/README_REVERSE_ENGINEERING_CN.md index d47395c..fc03fff 100644 --- a/README_REVERSE_ENGINEERING_CN.md +++ b/README_REVERSE_ENGINEERING_CN.md @@ -60,7 +60,7 @@ sign = HMAC-SHA256(param_str, key="Hf6d6KFB3D") 4. 获得 `resourceUrl` 供后续图生3D API 使用 ```python -from hunyuan3dweb.cos_upload import upload_image +from hy3d.cos_upload import upload_image resource_url = upload_image("/path/to/image.png") ``` @@ -71,15 +71,15 @@ resource_url = upload_image("/path/to/image.png") | 文件 | 说明 | |------|------| -| `hunyuan3dweb/sign.py` | 签名算法纯 Python 实现 | -| `hunyuan3dweb/api.py` | 基础 API 客户端(图生3D、文生3D) | -| `hunyuan3dweb/api_complete.py` | 完整 API 客户端(所有生成模式) | -| `hunyuan3dweb/cos_upload.py` | 纯 Python COS 上传工具(无需浏览器) | -| `hunyuan3dweb/config.py` | 用户配置路径管理(cookie、profile) | -| `hunyuan3dweb/cli.py` | CLI 入口 | -| `hunyuan3dweb/browser/login.py` | 浏览器自动化登录工具 | -| `hunyuan3dweb/browser/generator.py` | 浏览器自动化图生3D | -| `hunyuan3dweb/browser/sniffer.py` | API 请求嗅探工具 | +| `hy3d/sign.py` | 签名算法纯 Python 实现 | +| `hy3d/api.py` | 基础 API 客户端(图生3D、文生3D) | +| `hy3d/api_complete.py` | 完整 API 客户端(所有生成模式) | +| `hy3d/cos_upload.py` | 纯 Python COS 上传工具(无需浏览器) | +| `hy3d/config.py` | 用户配置路径管理(cookie、profile) | +| `hy3d/cli.py` | CLI 入口 | +| `hy3d/browser/login.py` | 浏览器自动化登录工具 | +| `hy3d/browser/generator.py` | 浏览器自动化图生3D | +| `hy3d/browser/sniffer.py` | API 请求嗅探工具 | --- @@ -88,7 +88,7 @@ resource_url = upload_image("/path/to/image.png") ### 1. 登录获取 Cookie ```bash -hunyuan3dweb-login +hy3d-login # 按提示输入邮箱和验证码 # 登录状态自动保存到 ~/.config/hunyuan3dweb/profile # Cookie 同时导出到 ~/.config/hunyuan3dweb/cookies.txt @@ -102,7 +102,7 @@ hunyuan3dweb-login ### 2. 纯 Python 生成3D模型 ```python -from hunyuan3dweb import Hunyuan3DAPI +from hy3d import Hunyuan3DAPI # 自动从 ~/.config/hunyuan3dweb/cookies.txt 加载 cookie api = Hunyuan3DAPI() @@ -120,8 +120,8 @@ status = api.get_generation_status(result["creationsId"]) ### 3. 本地图片上传 + 图生3D(纯 Python) ```python -from hunyuan3dweb.cos_upload import upload_image -from hunyuan3dweb import Hunyuan3DAPIComplete +from hy3d.cos_upload import upload_image +from hy3d import Hunyuan3DAPIComplete # 上传本地图片到 COS resource_url = upload_image("/path/to/image.png") @@ -135,16 +135,16 @@ result = api.generate_from_image(resource_url, title="我的模型") ```bash # 列出某个创作所有可用的下载格式 -hunyuan3dweb formats +hy3d formats # 包含转换格式(fbx, stl, usdz, mp4, gif) -hunyuan3dweb formats --converted +hy3d formats --converted # 下载指定格式(默认 glb) -hunyuan3dweb download --format glb -o model.glb +hy3d download --format glb -o model.glb # 下载转换格式(如 usdz) -hunyuan3dweb download --format usdz --converted -o model.usdz +hy3d download --format usdz --converted -o model.usdz ``` --- diff --git a/bin/hy3d b/bin/hy3d new file mode 100755 index 0000000..4d923e1 --- /dev/null +++ b/bin/hy3d @@ -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 "$@" \ No newline at end of file diff --git a/doc/api.md b/doc/api.md index 364692d..490be96 100644 --- a/doc/api.md +++ b/doc/api.md @@ -8,7 +8,7 @@ Cookie 会话认证。登录后由浏览器自动携带 `sessionid` 等 Cookie。 -持久化数据保存在 `./hunyuan3d_profile`,CloakBrowser 复用该目录即可保持登录态。 +持久化数据保存在 `~/.config/hunyuan3dweb/profile`,CloakBrowser 复用该目录即可保持登录态。 --- diff --git a/doc/api_complete.md b/doc/api_complete.md index aef1f36..1d863a0 100644 --- a/doc/api_complete.md +++ b/doc/api_complete.md @@ -1,7 +1,7 @@ # 腾讯混元3D API 完整文档 > 基础地址: `https://3d.hunyuan.tencent.com` -> 签名算法: HMAC-SHA256(已破解,见 `hunyuan3d_sign.py`) +> 签名算法: HMAC-SHA256(已破解,见 `hy3d/sign.py`) --- diff --git a/hunyuan3dweb/cli.py b/hunyuan3dweb/cli.py deleted file mode 100644 index 99a7bb5..0000000 --- a/hunyuan3dweb/cli.py +++ /dev/null @@ -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() diff --git a/hunyuan3dweb/__init__.py b/hy3d/__init__.py similarity index 65% rename from hunyuan3dweb/__init__.py rename to hy3d/__init__.py index 08c77e4..1fdd0b3 100644 --- a/hunyuan3dweb/__init__.py +++ b/hy3d/__init__.py @@ -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", +] \ No newline at end of file diff --git a/hunyuan3dweb/api.py b/hy3d/api.py similarity index 100% rename from hunyuan3dweb/api.py rename to hy3d/api.py diff --git a/hunyuan3dweb/api_complete.py b/hy3d/api_complete.py similarity index 100% rename from hunyuan3dweb/api_complete.py rename to hy3d/api_complete.py diff --git a/hunyuan3dweb/browser/__init__.py b/hy3d/browser/__init__.py similarity index 100% rename from hunyuan3dweb/browser/__init__.py rename to hy3d/browser/__init__.py diff --git a/hunyuan3dweb/browser/generator.py b/hy3d/browser/generator.py similarity index 99% rename from hunyuan3dweb/browser/generator.py rename to hy3d/browser/generator.py index a48cfb2..db0006d 100644 --- a/hunyuan3dweb/browser/generator.py +++ b/hy3d/browser/generator.py @@ -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) diff --git a/hunyuan3dweb/browser/login.py b/hy3d/browser/login.py similarity index 100% rename from hunyuan3dweb/browser/login.py rename to hy3d/browser/login.py diff --git a/hunyuan3dweb/browser/sniffer.py b/hy3d/browser/sniffer.py similarity index 98% rename from hunyuan3dweb/browser/sniffer.py rename to hy3d/browser/sniffer.py index 75f72a9..6d3ef9d 100644 --- a/hunyuan3dweb/browser/sniffer.py +++ b/hy3d/browser/sniffer.py @@ -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() diff --git a/hy3d/cli.py b/hy3d/cli.py new file mode 100644 index 0000000..18d66b5 --- /dev/null +++ b/hy3d/cli.py @@ -0,0 +1,471 @@ +#!/usr/bin/env python3 +""" +hy3d — 腾讯混元 3D 非官方 CLI + +用法: hy3d [--json] [--cookies PATH] [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 + hy3d download --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 ` 查询进度,或 `hy3d status --wait` 等待完成") + print("用 `hy3d formats ` / `hy3d download --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()) \ No newline at end of file diff --git a/hunyuan3dweb/config.py b/hy3d/config.py similarity index 88% rename from hunyuan3dweb/config.py rename to hy3d/config.py index 2f310bf..ec4b2b6 100644 --- a/hunyuan3dweb/config.py +++ b/hy3d/config.py @@ -6,7 +6,7 @@ from typing import Optional, Tuple def get_config_dir() -> Path: - """Return the user configuration directory for hunyuan3dweb.""" + """Return the user configuration directory (kept as `hunyuan3dweb` for path stability).""" xdg = os.environ.get("XDG_CONFIG_HOME") base = Path(xdg) if xdg else Path.home() / ".config" path = base / "hunyuan3dweb" @@ -38,7 +38,7 @@ def prepare_profile_dir() -> Tuple[str, Optional[str]]: if not os.path.exists(standard): return standard, None - temp_root = tempfile.mkdtemp(prefix="hunyuan3dweb-profile-") + temp_root = tempfile.mkdtemp(prefix="hy3d-profile-") temp_profile = os.path.join(temp_root, "profile") shutil.copytree(standard, temp_profile, dirs_exist_ok=True) return temp_profile, temp_root diff --git a/hunyuan3dweb/cos_upload.py b/hy3d/cos_upload.py similarity index 97% rename from hunyuan3dweb/cos_upload.py rename to hy3d/cos_upload.py index a2d7507..3a81dc1 100644 --- a/hunyuan3dweb/cos_upload.py +++ b/hy3d/cos_upload.py @@ -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 diff --git a/hunyuan3dweb/sign.py b/hy3d/sign.py similarity index 100% rename from hunyuan3dweb/sign.py rename to hy3d/sign.py diff --git a/pyproject.toml b/pyproject.toml index b669d6b..8927e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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*"] \ No newline at end of file