Files
tripocli/tripo/features/uv.py
T
Mrpzx 977f97995a tripocli v2.0.0:Tripo AI 3D 生成与加工 CLI(面向 Agent 与开发者,纯 Python 标准库、零第三方依赖)
覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、
纹理、PBR、智能 UV、部件补全、格式导出与下载。

代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
2026-10-13 19:22:54 +08:00

82 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""智能 UV — unfold UVs for a low-poly model.
tripocli uv -p <project_id> --wait
实测流程(在页面上真操作抓到的请求):
POST /v2/studio/operation/uv_edit/generate
{project_id, current_operator_id, action:"generate"} → 生成 UV 候选(新 operator)
POST /v2/studio/operation/uv_edit/apply
{project_id, current_operator_id, candidate_operator_id} → 保存/应用候选
POST /v2/studio/operation/uv_edit/context
{project_id, current_operator_id} → 查询当前处于哪一步
所以这是**两步握手**:先 generate 出候选,再 apply 保存。只调 generate 不 apply,
上下文会停在「待保存」,再调 generate 就会报 `2046 UV Edit action no longer matches the context`。
另外服务端要求网格是低模(约 ≤8 万三角面),否则需要先 `tripocli remesh`。
"""
from . import _common
NAME = "uv"
HELP = "智能 UV:为低模展开 UV(需 ≤8 万三角面)"
def add_arguments(parser):
_common.add_op_options(parser, "应用 UV 后的模型保存到该路径(可选)")
parser.add_argument("--action", default="generate", help="UV 操作动作(默认 generate)")
parser.add_argument("--generate-only", action="store_true", help="只生成候选,不自动保存应用")
def run(ctx):
args = ctx.args
client = ctx.client
operator = client.project_operator(args.project)
current_operator_id = operator.get("operator_id")
if not current_operator_id:
raise ValueError(f"无法读取项目 {args.project} 的 operator_id")
generate_body = {
"project_id": args.project,
"current_operator_id": current_operator_id,
"action": args.action,
}
model_version = _common.project_version(client, args.project, args.model)
if model_version:
generate_body["model_version"] = model_version
ctx.out.line(f"🚀 生成 UV 候选 (current_operator_id={current_operator_id})...")
generated = client.get_data("POST", "/v2/studio/operation/uv_edit/generate", body=generate_body) or {}
candidate_id = generated.get("candidate_operator_id") or generated.get("operator_id")
if not candidate_id:
raise RuntimeError(f"generate 未返回候选 operator: {generated}")
if args.wait and candidate_id:
# The generate step produces a candidate; wait for it, but never hang on it —
# if it does not report a terminal state we still go on to apply.
try:
_common.wait_and_report(ctx, candidate_id, timeout=180)
except Exception as error:
ctx.out.line(f"(候选未在 180s 内就绪,继续尝试保存: {error})")
if args.generate_only:
ctx.out.data(
{"generate": generated, "candidate_operator_id": candidate_id},
human=lambda: ctx.out.line(f"✅ 已生成候选(未保存): {candidate_id}"),
)
return {"generate": generated, "candidate_operator_id": candidate_id}
apply_body = {
"project_id": args.project,
"current_operator_id": current_operator_id,
"candidate_operator_id": candidate_id,
}
ctx.out.line("💾 应用(保存)UV 候选...")
applied = client.get_data("POST", "/v2/studio/operation/uv_edit/apply", body=apply_body) or {}
return _common.run_operator(
ctx, applied, output=args.output, wait=args.wait, label="智能 UV"
)