"""智能 UV — unfold UVs for a low-poly model. tripocli uv -p --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" )