tripocli v2.0.0:Tripo AI 3D 生成与加工 CLI(面向 Agent 与开发者,纯 Python 标准库、零第三方依赖)

覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、
纹理、PBR、智能 UV、部件补全、格式导出与下载。

代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
This commit is contained in:
Mrpzx committed 2026-10-13 19:22:54 +08:00
commit 977f97995a
60 files changed
+5622

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
"""动画 — retarget a rigged model onto animation presets.
tripocli animate -p <project_id> --preset idle --wait -o anim_idle.glb
tripocli animate --list
"""
import os
from . import _common
from .. import motions
NAME = "animate"
HELP = "动画:为已绑定骨骼的模型套用动作预设"
PRESET_EXAMPLES = ["idle", "walk", "run", "slash", "cast_a_spell", "jump"]
def add_arguments(parser):
parser.add_argument("-p", "--project", help="项目 ID(--list 时可省略)")
parser.add_argument("-m", "--model", help="覆盖 model_version(默认 default)")
_common.add_wait_options(parser, "动画结果保存到该路径(可选)")
parser.add_argument("--type", default="biped", help="骨骼类型,默认 biped")
parser.add_argument(
"--preset", action="append",
help="动作预设名(如 idle / walk / run / slash),可重复;用 --list 查看全部",
)
parser.add_argument(
"--animation", action="append",
help="完整动作标识(如 preset:biped:idle),可重复",
)
parser.add_argument(
"--motion-asset",
help="套用「文字生成动作」产出的动作资产 ID(来自 tripocli motion)",
)
parser.add_argument("--list", action="store_true", help="列出全部可用动作预设(按骨骼类型)")
def run(ctx):
args = ctx.args
if args.list:
data = {
rig: [
{"name": item["name"], "action_type": item.get("action_type"),
"category": item.get("category"), "id": motions.identifier(rig, item["name"])}
for item in motions.catalog().get(rig, [])
]
for rig in motions.rig_types()
}
def human():
for rig, items in data.items():
ctx.out.line(f"== {rig} ({len(items)}) ==")
ctx.out.line(" " + ", ".join(item["name"] for item in items))
ctx.out.data(data, human=human)
return data
animations = list(args.animation or [])
for preset in (args.preset or []):
animations.append(preset if ":" in preset else motions.identifier(args.type, preset))
if not args.project:
raise ValueError("需要 -p/--project 指定项目(用 --list 只查看动作清单)")
body = {
"project_id": args.project,
"rig_type": args.type,
"model_version": args.model or "default",
}
if args.motion_asset:
# 文字生成动作产出的资产:retarget_model 用 motion_asset_id(而不是 animations)
body["motion_asset_id"] = args.motion_asset
elif animations:
body["animations"] = animations
else:
raise ValueError(
"至少需要一个动作:用 --preset afraid、--animation preset:biped:afraid,"
f"或 --motion-asset <id>(示例: {', '.join(PRESET_EXAMPLES)};用 --list 看全部)"
)
data = ctx.client.get_data("POST", "/v2/studio/operation/retarget_model", body=body)
if not args.wait:
return _common.run_operator(ctx, data, output=args.output, wait=False, label="动画重定向")
operator_id = (data or {}).get("operator_id")
project_id = (data or {}).get("project_id") or args.project
if operator_id:
_common.wait_and_report(ctx, operator_id, timeout=900)
# 动画结果不在 project.model_url(那是绑骨结果),而在 operator.retarget[] 里:
# 每个元素 {operator_id, name:"preset:biped:xxx", status, model_url}(实测确认)。
entry = _retarget_entry(ctx.client, project_id, operator_id)
model_url = (entry or {}).get("model_url")
output_path = args.output
if output_path and model_url:
ctx.client.download(model_url, output_path)
output_path = os.path.abspath(output_path)
payload = {"result": data, "animation": (entry or {}).get("name"), "model_url": model_url,
"output_file": output_path if model_url else None}
ctx.out.data(
payload,
human=lambda: (
ctx.out.line(f"🎉 动画重定向完成({(entry or {}).get('name')})"),
ctx.out.line(f"模型地址: {model_url}"),
ctx.out.line(f"已保存 : {output_path}") if model_url else ctx.out.line("⚠️ 未取到动画 model_url"),
),
)
return payload
def _retarget_entry(client, project_id, operator_id=None):
"""Pick the retarget result from the project's operator.retarget list."""
detail = client.project_detail(project_id) or {}
entries = (detail.get("operator") or {}).get("retarget") or []
if operator_id:
for item in entries:
if item.get("operator_id") == operator_id:
return item
return entries[-1] if entries else None