覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、 纹理、PBR、智能 UV、部件补全、格式导出与下载。 代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
"""智能 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"
|
||
)
|