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

+179
View File
@@ -0,0 +1,179 @@
"""Shared argument groups and the submit -> wait -> download flow."""
import os
from ..errors import TripoAPIError, TripoTimeout
from ..versions import default_for, resolve
def add_generation_options(parser):
"""Options shared by text / image / multiview / batch generation."""
parser.add_argument("--smart-mesh", action="store_true", help="智能网格模式 (P2.0/P1.0,四边面低模)")
parser.add_argument("-m", "--model", help="模型版本: P2.0 / P1.0 / H3.1 / v3.0 / v2.5(默认随模式选择)")
parser.add_argument("--face-limit", type=int, help="面数上限(智能网格默认 25000,高精度默认 2000000;游戏场景建议 10000~25000)")
parser.add_argument("--quad", action="store_true", help="四边面拓扑(智能网格默认开启)")
parser.add_argument("--count", type=int, default=1, choices=[1, 2, 4], help="生成候选数量 1/2/4(默认 1)")
parser.add_argument("--no-texture", action="store_true", help="不生成贴图")
parser.add_argument("--no-pbr", action="store_true", help="不生成 PBR 材质")
parser.add_argument("--no-delight", action="store_true", help="保留原图光影(不去光)")
parser.add_argument("--texture-quality", default="detailed", choices=["standard", "detailed"], help="贴图质量")
parser.add_argument("--geometry-quality", default="detailed", choices=["standard", "detailed"], help="几何精细度")
parser.add_argument("--t-pose", action="store_true", help="人形角色输出 T-Pose")
parser.add_argument("--visibility", default="shareable", choices=["shareable", "private"], help="模型可见性")
def add_wait_options(parser, output_help="生成完成后保存到该路径"):
parser.add_argument("-w", "--wait", action="store_true", help="阻塞等待任务完成")
parser.add_argument("-o", "--output", help=output_help)
def add_op_options(parser, output_help="完成后保存到该路径(可选)"):
"""Options shared by post-process operators, which act on an existing project."""
parser.add_argument("-p", "--project", required=True, help="项目 ID (project_id)")
parser.add_argument("-m", "--model", help="覆盖 model_version(默认取项目自身的模型版本)")
add_wait_options(parser, output_help)
def generation_body(args):
"""Turn generation CLI args into the request body fields."""
smart = args.smart_mesh
model_version = resolve(args.model, smart_mesh=smart)
face_limit = args.face_limit or (25000 if smart else 2000000)
quad = True if smart else args.quad
return {
"model_version": model_version,
"face_limit": face_limit,
"quad": quad,
"texture": not args.no_texture,
"pbr": not args.no_pbr,
"delight": not args.no_delight,
"texture_quality": args.texture_quality,
"geometry_quality": args.geometry_quality,
"t_pose": args.t_pose,
"visibility": args.visibility,
"smart_poly": smart,
"model_count": args.count,
}
def wait_and_report(ctx, operator_id, timeout=900):
"""Block on a task, printing progress; returns the finished task."""
def on_progress(status, progress, left_time, _task):
ctx.out.progress(f"⏳ 进度 {progress}% (预估剩余 {left_time}s)")
task = ctx.client.wait_task(operator_id, timeout=timeout, on_progress=on_progress)
ctx.out.progress_done()
return task
def fetch_model_url(ctx, project_id):
detail = ctx.client.project_detail(project_id) or {}
return detail.get("model_url"), detail
def finish_single(ctx, task_data, output=None, wait=False, timeout=900, label="模型"):
"""Standard flow for one generated asset: optionally wait and download."""
project_id = task_data.get("project_id")
operator_id = task_data.get("operator_id")
if not wait:
ctx.out.data(
task_data,
human=lambda: (
ctx.out.line("✅ 任务已提交"),
ctx.out.line(f"project_id : {project_id}"),
ctx.out.line(f"operator_id : {operator_id}"),
ctx.out.line(f"查询进度 : tripocli task status {operator_id}"),
ctx.out.line(f"等待并下载 : tripocli task wait {operator_id} -p {project_id} -o out.glb"),
),
)
return task_data
task = wait_and_report(ctx, operator_id, timeout=timeout)
model_url, detail = fetch_model_url(ctx, project_id)
output_path = output or f"{project_id}.glb"
if model_url:
ctx.client.download(model_url, output_path)
output_path = os.path.abspath(output_path)
payload = {"task": task, "project": detail, "output_file": output_path if model_url else None}
ctx.out.data(
payload,
human=lambda: (
ctx.out.line(f"🎉 {label}完成"),
ctx.out.line(f"模型地址: {model_url}"),
ctx.out.line(f"已保存 : {output_path}") if model_url else ctx.out.line("⚠️ 未取到 model_url"),
),
)
return payload
def project_version(client, project_id, override=None):
"""Model version to send with post-process ops.
The studio sends the project's own version for most operations, so we read it
from the project rather than assuming a global default.
"""
if override:
return resolve(override)
return client.project_operator(project_id).get("model_version")
def node_part_name(client, project_id):
"""The ``part_name_list`` the studio expects for a project's current mesh.
Real names come from the mesh's nodes (only present inside the model file);
when the model cannot be read we fall back to the single-node name
``tripo_node_<operator_id>`` (verified live).
"""
names = client.part_names(project_id)
if names:
return names
operator = client.project_operator(project_id)
operator_id = operator.get("operator_id")
if not operator_id:
raise TripoAPIError(f"无法从项目 {project_id} 读取 operator_id")
return [f"tripo_node_{operator_id}"]
def run_operator(ctx, result, output=None, wait=False, timeout=900, label="处理", multiple=False):
"""Shared flow for post-process operators (they return project_id/operator_id)."""
if multiple:
operator_ids = [item.get("operator_id") for item in result]
project_id = result[0].get("project_id") if result else None
else:
project_id = result.get("project_id")
operator_ids = [result.get("operator_id")]
# Some endpoints answer without a task id; never wait on a missing one.
operator_ids = [oid for oid in operator_ids if oid]
if not wait:
ctx.out.data(
result,
human=lambda: (
ctx.out.line("✅ 任务已提交"),
*[ctx.out.line(f"operator_id : {oid}") for oid in operator_ids],
),
)
return result
for operator_id in operator_ids:
wait_and_report(ctx, operator_id, timeout=timeout)
model_url, detail = fetch_model_url(ctx, project_id) if project_id else (None, None)
output_path = output
if output_path and model_url:
ctx.client.download(model_url, output_path)
output_path = os.path.abspath(output_path)
payload = {"result": result, "project": detail, "model_url": model_url, "output_file": output_path}
ctx.out.data(
payload,
human=lambda: (
ctx.out.line(f"🎉 {label}完成"),
ctx.out.line(f"模型地址: {model_url}"),
),
)
return payload