From b416a743f52783c59af298d82bb98d07a4a01d93 Mon Sep 17 00:00:00 2001 From: Mrpzx Date: Tue, 13 Oct 2026 20:56:21 +0800 Subject: [PATCH] =?UTF-8?q?tripocli=20v2.1.0=EF=BC=9A=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=9B=BE=E7=89=874K=E8=B6=85=E5=88=86(img-upscale)=E3=80=81?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E6=8A=A0=E5=9B=BE(img-split)=E3=80=81?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=88=A0=E9=99=A4=E4=B8=8Enanobanana?= =?UTF-8?q?=E5=85=8D=E8=B4=B9=E6=A8=A1=E5=9E=8B=E5=88=AB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++- pyproject.toml | 2 +- tripo/__init__.py | 2 +- tripo/auth.py | 2 +- tripo/client.py | 2 +- tripo/features/__init__.py | 4 ++ tripo/features/image_assets.py | 8 +++ tripo/features/image_gen.py | 6 +- tripo/features/image_split.py | 75 +++++++++++++++++++++++ tripo/features/image_upscale.py | 103 ++++++++++++++++++++++++++++++++ 10 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 tripo/features/image_split.py create mode 100644 tripo/features/image_upscale.py diff --git a/README.md b/README.md index 4a2c1cc..5430598 100644 --- a/README.md +++ b/README.md @@ -149,10 +149,16 @@ tripocli project # 查看项目详情、模型直链与已重定 ### 3. 生成命令 ```bash -# 文生图(支持模型: nano-banana-2 / nano-banana-pro / gpt-image-2 / gpt-image-2.5 / midjourney) +# 文生图(免会员默认模型: nanobanana / nano-banana-2;会员模型: nano-banana-pro / gpt-image-2 / midjourney) tripocli img -p "a stylized treasure chest, game asset" --amount 1 --scale 1:1 --resolution 2K --wait -o ./refs +tripocli img -p "anime knight" -m nanobanana --t-pose --wait -o ./knight.png tripocli img --templates # 查看官方提示词模板 +# 图片 4K 超分放大与智能抠图 +tripocli img-upscale -a --wait -o ./4k.png # 4K 高清放大(对应网页 4K 按钮) +tripocli img-split -a --wait -o ./cutout.webp # 智能抠图透明背景(对应网页剪刀) +tripocli imgs --delete # 删除图片资产(对应网页垃圾桶) + # 单图转 4 视角参考图 tripocli imgmv -i ./refs/image_0.png --wait -o ./views diff --git a/pyproject.toml b/pyproject.toml index ab034fd..89523a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tripocli" -version = "2.0.0" +version = "2.1.0" description = "Tripo AI 3D 生成与加工 CLI(面向 Agent 与开发者,纯标准库零依赖)" readme = "README.md" requires-python = ">=3.8" diff --git a/tripo/__init__.py b/tripo/__init__.py index 7ef4aef..65ebfb8 100644 --- a/tripo/__init__.py +++ b/tripo/__init__.py @@ -6,4 +6,4 @@ file. ``tripo/client.py`` holds the HTTP plumbing, ``tripo/config.py`` the credentials, and ``tripo/cli.py`` wires the feature modules into an argparse CLI. """ -__version__ = "2.0.0" +__version__ = "2.1.0" diff --git a/tripo/auth.py b/tripo/auth.py index 55923ad..674b3b9 100644 --- a/tripo/auth.py +++ b/tripo/auth.py @@ -30,7 +30,7 @@ def _request(api_base, cookie, device_id=None, timeout=30): "Accept": "application/json", "Origin": ORIGIN, "Referer": ORIGIN + "/", - "User-Agent": "tripocli/2.0.0", + "User-Agent": "tripocli/2.1.0", "Cookie": cookie, } if device_id: diff --git a/tripo/client.py b/tripo/client.py index 41a14b8..4f68fa8 100644 --- a/tripo/client.py +++ b/tripo/client.py @@ -26,7 +26,7 @@ RETRY_ATTEMPTS = 4 RETRY_BACKOFF = 1.5 ORIGIN = "https://studio.tripo3d.ai" -USER_AGENT = "tripocli/2.0.0 (Mozilla/5.0; AI Agent Client)" +USER_AGENT = "tripocli/2.1.0 (Mozilla/5.0; AI Agent Client)" # Model formats the studio's "上传 3D 模型" importer accepts (UI: OBJ/FBX/STL/GLB ≤150MB). MODEL_FORMATS = ("glb", "obj", "fbx", "stl") diff --git a/tripo/features/__init__.py b/tripo/features/__init__.py index 53110bb..f76f625 100644 --- a/tripo/features/__init__.py +++ b/tripo/features/__init__.py @@ -20,7 +20,9 @@ from . import ( image_assets, image_gen, image_multiview, + image_split, image_to_3d, + image_upscale, import_model, inspect_model, mesh_edit, @@ -51,6 +53,8 @@ MODULES = [ text_to_3d, # text image_gen, # img (text to image) image_assets, # imgs (list image assets) + image_upscale, # img-upscale (4K image upscale) + image_split, # img-split (auto cutout / background removal) image_multiview, # imgmv (one image -> 4 views) image_to_3d, # image import_model, # import (upload own glb/obj/fbx/stl) diff --git a/tripo/features/image_assets.py b/tripo/features/image_assets.py index f5c25b0..ecb5b7d 100644 --- a/tripo/features/image_assets.py +++ b/tripo/features/image_assets.py @@ -13,10 +13,18 @@ HELP = "列出我的图片资产(生成过的参考图)" def add_arguments(parser): parser.add_argument("--page", type=int, default=1, help="页码(默认 1)") parser.add_argument("--size", type=int, default=8, help="每页数量(默认 8)") + parser.add_argument("--delete", help="删除指定的图片资产 asset_id(对应网页端垃圾桶按钮)") def run(ctx): args = ctx.args + if args.delete: + target_id = args.delete.strip() + body = {"items": [{"asset_id": target_id, "keys": []}]} + ctx.client.get_data("POST", "/v2/studio/image/batch_delete", body=body) + ctx.out.line(f"🗑️ 已成功删除图片资产: {target_id}") + return {"deleted": target_id} + data = ctx.client.get_data( "POST", "/v2/studio/image/image_assets", body={"page_num": args.page, "page_size": args.size}, diff --git a/tripo/features/image_gen.py b/tripo/features/image_gen.py index bde7b89..99461a5 100644 --- a/tripo/features/image_gen.py +++ b/tripo/features/image_gen.py @@ -26,12 +26,16 @@ HELP = "文生图:用文本生成参考图(游戏资产流程的第一步) # 界面上的模型名 → API 的 model_version(逐个在页面上选中并抓包确认) IMAGE_MODELS = { "nano-banana-2": "gemini_3.1_flash_image_preview", + "nanobanana": "gemini_3.1_flash_image_preview", + "nano-banana": "gemini_3.1_flash_image_preview", + "banana": "gemini_3.1_flash_image_preview", "nano-banana-pro": "gemini_3_pro_image_preview", + "nanobanana-pro": "gemini_3_pro_image_preview", "gpt-image-2": "gpt_image_2", "gpt-image-2.5": "gpt_image_2.5_sunburst", "midjourney": "midjourney", } -DEFAULT_MODEL = "nano-banana-2" +DEFAULT_MODEL = "nano-banana-2" # 免费/非会员默认可用模型 (Nano Banana 2 / Gemini 3.1 Flash) SCALES = ["1:1", "16:9", "9:16", "4:3", "3:4"] RESOLUTIONS = ["1K", "2K", "4K"] diff --git a/tripo/features/image_split.py b/tripo/features/image_split.py new file mode 100644 index 0000000..c6d53f0 --- /dev/null +++ b/tripo/features/image_split.py @@ -0,0 +1,75 @@ +"""图片智能抠图/切分 — auto cutout / split image. + + tripocli img-split -a --wait -o ./cutout.png + tripocli img-split (自动取最新一张参考图抠图) + +实测流程(页面上剪刀图标抓到的请求): + POST /v2/studio/image/split + {"asset_id": "...", "resource_key": "generate-image/..."} + → {"task_id": "...", "asset_id": "..."} + +轮询: + POST /v2/studio/image/get_image_asset {"asset_id": "..."} + → status == "success", type == "split_image", output.data[0].url +""" + +import os +import time + +from ..errors import TripoAPIError, TripoTimeout +from .image_upscale import resolve_asset_and_key + +NAME = "img-split" +HELP = "图片智能抠图/切分:主体背景去除与透明化(对应网页端剪刀按钮)" + + +def add_arguments(parser): + parser.add_argument("-a", "--asset", help="图片资产 ID(默认自动取最近生成的一张)") + parser.add_argument("--key", help="指定图片 resource_key(默认自动解析)") + parser.add_argument("-w", "--wait", action="store_true", default=True, help="等待抠图完成(默认等待)") + parser.add_argument("--no-wait", dest="wait", action="store_false", help="不等待,提交后直接退出") + parser.add_argument("-o", "--output", help="抠图后的图片保存路径") + parser.add_argument("--timeout", type=int, default=180, help="等待超时秒数(默认 180)") + + +def run(ctx): + args = ctx.args + asset_id, key = resolve_asset_and_key(ctx, args.asset, args.key) + ctx.out.line(f"✂️ 提交智能抠图任务: asset={asset_id}") + + resp = ctx.client.get_data("POST", "/v2/studio/image/split", body={ + "asset_id": asset_id, + "resource_key": key, + }) or {} + + new_asset_id = resp.get("asset_id") or resp.get("task_id") + if not new_asset_id: + raise TripoAPIError(f"split 未返回 asset_id: {resp}") + + if not args.wait: + ctx.out.data(resp, human=lambda: ctx.out.line(f"✅ 任务已提交,asset_id: {new_asset_id}")) + return resp + + ctx.out.line(f"⏳ 等待智能抠图完成: {new_asset_id}") + start = time.time() + while time.time() - start < args.timeout: + info = ctx.client.get_data("POST", "/v2/studio/image/get_image_asset", body={"asset_id": new_asset_id}) or {} + status = info.get("status") + if status == "success": + ctx.out.line("🎉 智能抠图完成!") + out_imgs = (info.get("output") or {}).get("data") or [] + url = out_imgs[0].get("url") if out_imgs else None + if url: + ctx.out.line(f"抠图结果地址: {url}") + if args.output: + save_path = args.output + if os.path.isdir(save_path): + save_path = os.path.join(save_path, f"{new_asset_id}_cutout.webp") + ctx.client.download_file(url, save_path) + ctx.out.line(f"💾 已保存到: {save_path}") + return info + if status in ("failed", "expired"): + raise TripoAPIError(f"智能抠图失败: status={status}") + time.sleep(2) + + raise TripoTimeout(f"智能抠图超时(超过 {args.timeout} 秒)") diff --git a/tripo/features/image_upscale.py b/tripo/features/image_upscale.py new file mode 100644 index 0000000..b593d21 --- /dev/null +++ b/tripo/features/image_upscale.py @@ -0,0 +1,103 @@ +"""图片 4K 高清放大 — upscale reference image to 4K. + + tripocli img-upscale -a --wait -o ./upscaled.png + tripocli img-upscale (自动取最新一张参考图放大) + +实测流程(页面上 4K 图标抓到的请求): + POST /v2/studio/image/upscale + {"asset_id": "...", "resource_key": "generate-image/..."} + → {"task_id": "...", "asset_id": "..."} + +轮询: + POST /v2/studio/image/get_image_asset {"asset_id": "..."} + → status == "success", type == "upscale_image", output.data[0].url +""" + +import os +import time + +from ..errors import TripoAPIError, TripoTimeout + +NAME = "img-upscale" +HELP = "图片 4K 高清放大:提升参考图分辨率(对应网页端 4K 按钮)" + + +def add_arguments(parser): + parser.add_argument("-a", "--asset", help="图片资产 ID(默认自动取最近生成的一张)") + parser.add_argument("--key", help="指定图片 resource_key(默认自动解析)") + parser.add_argument("-w", "--wait", action="store_true", default=True, help="等待放大完成(默认等待)") + parser.add_argument("--no-wait", dest="wait", action="store_false", help="不等待,提交后直接退出") + parser.add_argument("-o", "--output", help="放大后的图片保存路径") + parser.add_argument("--timeout", type=int, default=180, help="等待超时秒数(默认 180)") + + +def resolve_asset_and_key(ctx, asset_id, resource_key): + if not asset_id: + # 取最新的一张图片资产 + data = ctx.client.get_data( + "POST", "/v2/studio/image/image_assets", + body={"page_num": 1, "page_size": 1}, + ) or {} + assets = data.get("assets") or [] + if not assets: + raise TripoAPIError("未找到任何图片资产,请用 -a 指定 asset_id") + asset_id = assets[0].get("asset_id") + + if not resource_key: + info = ctx.client.get_data("POST", "/v2/studio/image/get_image_asset", body={"asset_id": asset_id}) or {} + out_data = (info.get("output") or {}).get("data") or [] + if out_data and out_data[0].get("key"): + resource_key = out_data[0].get("key") + else: + in_imgs = (info.get("input") or {}).get("prompt_image") or [] + if in_imgs and in_imgs[0].get("key"): + resource_key = in_imgs[0].get("key") + + if not resource_key: + raise TripoAPIError(f"无法从图片资产 {asset_id} 解析 resource_key,请用 --key 显式传入") + + return asset_id, resource_key + + +def run(ctx): + args = ctx.args + asset_id, key = resolve_asset_and_key(ctx, args.asset, args.key) + ctx.out.line(f"🔍 提交 4K 放大任务: asset={asset_id}") + + resp = ctx.client.get_data("POST", "/v2/studio/image/upscale", body={ + "asset_id": asset_id, + "resource_key": key, + }) or {} + + new_asset_id = resp.get("asset_id") or resp.get("task_id") + if not new_asset_id: + raise TripoAPIError(f"upscale 未返回 asset_id: {resp}") + + if not args.wait: + ctx.out.data(resp, human=lambda: ctx.out.line(f"✅ 任务已提交,asset_id: {new_asset_id}")) + return resp + + # 轮询直到完成 + ctx.out.line(f"⏳ 等待 4K 放大完成: {new_asset_id}") + start = time.time() + while time.time() - start < args.timeout: + info = ctx.client.get_data("POST", "/v2/studio/image/get_image_asset", body={"asset_id": new_asset_id}) or {} + status = info.get("status") + if status == "success": + ctx.out.line("🎉 4K 放大完成!") + out_imgs = (info.get("output") or {}).get("data") or [] + url = out_imgs[0].get("url") if out_imgs else None + if url: + ctx.out.line(f"图片地址: {url}") + if args.output: + save_path = args.output + if os.path.isdir(save_path): + save_path = os.path.join(save_path, f"{new_asset_id}_4k.png") + ctx.client.download_file(url, save_path) + ctx.out.line(f"💾 已保存到: {save_path}") + return info + if status in ("failed", "expired"): + raise TripoAPIError(f"4K 放大失败: status={status}") + time.sleep(2) + + raise TripoTimeout(f"4K 放大超时(超过 {args.timeout} 秒)")