tripocli v2.0.0:Tripo AI 3D 生成与加工 CLI(面向 Agent 与开发者,纯 Python 标准库、零第三方依赖)
覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、 纹理、PBR、智能 UV、部件补全、格式导出与下载。 代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
This commit is contained in:
commit
977f97995a
60 files changed
+5622
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user