tripo-cli 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,7 @@ Works with both Tripo regions from the same install — international (`openapi.
35
35
  | `tripo login / logout / whoami / use` | device flow: browser approves, key arrives automatically (paste fallback); stored 0600 in `~/.tripo/config.json`; `use` switches accounts |
36
36
  | `tripo topup / balance / usage` | open the billing page + detect the credits arriving; check spend |
37
37
  | `tripo generate <endpoint>` | deterministic access to all 8 generation endpoints |
38
- | `tripo model / anim / mesh <step>` | refine, texture, stylize, convert, import, rig-check, rig, retarget, segment, complete, decimate, smartsegment |
38
+ | `tripo model / anim / mesh <step>` | refine, texture, stylize, convert, import, rig-check, rig, retarget, segment, complete, decimate, uv, smartsegment |
39
39
  | `tripo task get/list/watch` | query and stream task progress (NDJSON in `--json` mode) |
40
40
  | `tripo files upload` | upload a file, print the `file_token` |
41
41
  | `tripo batch run <manifest.yaml>` | bulk pipelines with concurrency, retries, and resume |
@@ -91,7 +91,7 @@ tripo batch run assets.yaml --concurrency 2
91
91
 
92
92
  - `make`/`watch` are blocking: run them, wait for exit, read the single JSON line on stdout. Do not poll yourself.
93
93
  - The plan card on stderr lists every call before it runs; the final JSON carries `credits_consumed` (total for the run) plus `credits_breakdown` (per task) when a chain ran.
94
- - Exit codes: `0` ok · `2` usage · `3` auth · `4` insufficient credits · `5` content policy · `6` task failed (credits auto-refunded) · `7` network · `8` not found · `9` rate limit.
94
+ - Exit codes: `0` ok · `2` usage · `3` auth · `4` insufficient credits · `5` content policy · `6` task failed (credits auto-refunded; a `timeout` means it is still running) · `7` network (a failed submit is never resent — check `tripo balance` before re-running) · `8` not found · `9` rate limit.
95
95
  - `preview.png` in every output directory is your eyes: look at it, then decide to `tripo redo` or continue the chain.
96
96
  - Full behavior rules: `tripo docs --llm`, per-command docs via `tripo docs --topic commands/make`.
97
97
 
@@ -11,7 +11,7 @@ const SUBS = {
11
11
  generate: ['text-to-model', 'image-to-model', 'multiview-to-model', 'text-to-image', 'image-to-image', 'image-to-multiview', 'edit-multiview', 'image-to-splat'],
12
12
  model: ['refine', 'texture', 'stylize', 'convert', 'import'],
13
13
  anim: ['check', 'rig', 'retarget'],
14
- mesh: ['segment', 'complete', 'decimate', ...(isMeshEditEnabled() ? ['edit'] : []), 'smartsegment'],
14
+ mesh: ['segment', 'complete', 'decimate', 'uv', ...(isMeshEditEnabled() ? ['edit'] : []), 'smartsegment'],
15
15
  task: ['get', 'list', 'watch'],
16
16
  files: ['upload'],
17
17
  batch: ['run'],
@@ -77,7 +77,9 @@ function collectStepArgs(step, flags) {
77
77
  case 'retarget':
78
78
  return pick(['animation', 'out_format', 'animate_in_place']);
79
79
  case 'decimate':
80
- return pick(['face_limit', 'quad', 'bake']);
80
+ return pick(['model', 'face_limit', 'quad', 'smart_uv', 'bake']);
81
+ case 'uv':
82
+ return pick(['bake', 'export_orientation']);
81
83
  case 'segment':
82
84
  return pick(['model']);
83
85
  case 'complete':
@@ -147,7 +149,7 @@ export function registerProcessCommands(program) {
147
149
  await makeStepAction('retarget')(input, flags);
148
150
  });
149
151
  // ---- tripo mesh ----
150
- const mesh = program.command('mesh').description('mesh ops: segment / complete / decimate / smartsegment');
152
+ const mesh = program.command('mesh').description('mesh ops: segment / complete / decimate / uv / smartsegment');
151
153
  withTaskOptions(mesh.command('segment [input]').description('segment a model into parts').option('--model <v>', 'v1.0-20250506|v2.0-20260430')).action(makeStepAction('segment'));
152
154
  withTaskOptions(mesh
153
155
  .command('complete [input]')
@@ -156,9 +158,16 @@ export function registerProcessCommands(program) {
156
158
  withTaskOptions(mesh
157
159
  .command('decimate [input]')
158
160
  .description('high-poly → low-poly (bakes textures by default)')
161
+ .option('--model <v>', 'v3.0|v2.0|v1.0 (default v2.0)')
159
162
  .option('--face-limit <n>', 'target faces 500–20000 tri / 500–10000 quad', (v) => Number(v))
160
163
  .option('--quad', 'quad output')
164
+ .option('--smart-uv', 'unwrap UVs in the same task (v3.0 only; selects v3.0 when --model is omitted)')
161
165
  .option('--no-bake', 'skip baking textures onto the low-poly')).action(makeStepAction('decimate'));
166
+ withTaskOptions(mesh
167
+ .command('uv [input]')
168
+ .description('smart UV unwrap; remaps textures/rig/animation (input ≤80k equivalent triangles)')
169
+ .option('--no-bake', 'skip baking textures onto the new UV layout (geometry + UVs only)')
170
+ .option('--export-orientation <o>', '+x|-x|+y|-y')).action(makeStepAction('uv'));
162
171
  // Hidden until /v3/mesh/edit is live server-side (see isMeshEditEnabled).
163
172
  if (isMeshEditEnabled()) {
164
173
  withTaskOptions(mesh
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Lightweight V3 API client on top of global fetch (Node 20+).
3
3
  * Handles the response envelope, error translation, timeouts, and
4
- * exponential-backoff retries for transient failures (429/5xx/network).
4
+ * exponential-backoff retries for transient failures (429/5xx/network;
5
+ * task creation is only resent on 429).
5
6
  */
6
7
  import { activeProfileName, DEFAULT_PROFILE, listProfiles, loadConfig, resolveApiBaseUrl, resolveApiKey } from '../config/config.js';
7
8
  import { t } from '../i18n.js';
9
+ import { EXIT } from '../knowledge/error-catalog.js';
8
10
  import { ApiError, AuthError, NetworkError } from './errors.js';
9
11
  export class TripoClient {
10
12
  baseUrl;
@@ -37,10 +39,11 @@ export class TripoClient {
37
39
  this.maxRetries = options.maxRetries ?? 3;
38
40
  this.onRetry = options.onRetry;
39
41
  }
40
- async request(method, path, body) {
42
+ async request(method, path, body, options = {}) {
41
43
  return this.send(method, path, {
42
44
  headers: { 'Content-Type': 'application/json' },
43
45
  body: body === undefined ? undefined : JSON.stringify(body),
46
+ idempotent: options.idempotent,
44
47
  });
45
48
  }
46
49
  async uploadFile(fileName, data) {
@@ -61,7 +64,11 @@ export class TripoClient {
61
64
  }
62
65
  catch (err) {
63
66
  lastError = err;
64
- const retryable = err instanceof NetworkError || (err instanceof ApiError && err.retryable);
67
+ // A create that timed out or hit a 5xx may already be accepted (and
68
+ // billed); only a rate-limit rejection proves the server did nothing.
69
+ const retryable = init.idempotent === false
70
+ ? err instanceof ApiError && err.exitCode === EXIT.RATE_LIMIT
71
+ : err instanceof NetworkError || (err instanceof ApiError && err.retryable);
65
72
  if (!retryable || attempt === this.maxRetries)
66
73
  throw err;
67
74
  }
@@ -134,8 +141,19 @@ export class TripoClient {
134
141
  }
135
142
  /** Create a task; returns its id. Response may be {task_id} or a full detail. */
136
143
  async createTask(endpoint, payload) {
137
- const data = await this.request('POST', endpoint, payload);
138
- return data.task_id;
144
+ try {
145
+ const data = await this.request('POST', endpoint, payload, { idempotent: false });
146
+ return data.task_id;
147
+ }
148
+ catch (err) {
149
+ if (err instanceof NetworkError || (err instanceof ApiError && err.exitCode === EXIT.NETWORK)) {
150
+ err.suggestion = t({
151
+ zh: '任务可能已在服务端创建并冻结积分;重试前先用 tripo balance 查看冻结额或到控制台确认,避免重复扣费',
152
+ en: 'the task may already exist server-side (credits frozen); check "tripo balance" or the console before retrying, to avoid a duplicate charge',
153
+ });
154
+ }
155
+ throw err;
156
+ }
139
157
  }
140
158
  }
141
159
  export function sleep(ms) {
@@ -73,7 +73,8 @@ export async function downloadArtifacts(task, outputDir, historyEntry, options =
73
73
  const ext = extname(name).toLowerCase();
74
74
  if (!modelFile && MODEL_EXTS.has(ext))
75
75
  modelFile = dest;
76
- if (!preview && (IMAGE_EXTS.has(ext) || /rendered|preview/i.test(field)))
76
+ // uv_render (mesh/uv) is a flat UV atlas, not a render of the model.
77
+ if (!preview && !/uv_render/.test(field) && (IMAGE_EXTS.has(ext) || /rendered|preview/i.test(field)))
77
78
  preview = dest;
78
79
  }
79
80
  // Normalize the preview name so agents can rely on preview.png existing.
@@ -45,13 +45,17 @@ export class NetworkError extends CliError {
45
45
  }
46
46
  export class TaskFailedError extends CliError {
47
47
  taskId;
48
+ status;
48
49
  constructor(taskId, status, detail) {
49
50
  const refundNote = t({
50
51
  zh: '预扣积分已自动退还',
51
52
  en: 'frozen credits have been refunded automatically',
52
53
  });
53
- super(`task ${taskId} ${status}${detail ? `: ${detail}` : ''} (${refundNote})`, EXIT.TASK_FAILED);
54
+ super(
55
+ // A timed-out task is still running server-side: nothing has been refunded.
56
+ `task ${taskId} ${status}${detail ? `: ${detail}` : ''}${status === 'timeout' ? '' : ` (${refundNote})`}`, EXIT.TASK_FAILED);
54
57
  this.taskId = taskId;
58
+ this.status = status;
55
59
  }
56
60
  }
57
61
  /** Decorate insufficient-balance errors with the topup path and the gap. */
@@ -13,7 +13,7 @@ import { detectInput } from '../util/input-detect.js';
13
13
  import { notifyDone } from '../util/notify.js';
14
14
  import { createSpinner } from '../util/spinner.js';
15
15
  import { defaultOutputDir, downloadArtifacts } from './download.js';
16
- import { ApiError, UsageError, balanceSuggestion } from './errors.js';
16
+ import { ApiError, CliError, TaskFailedError, UsageError, balanceSuggestion } from './errors.js';
17
17
  import { isQuiet, log, warn } from './output.js';
18
18
  import { uploadLocalFile } from './upload.js';
19
19
  /** Placeholder `input` value for a local file that a dry run would upload. */
@@ -115,7 +115,15 @@ export async function runTask(options) {
115
115
  }
116
116
  catch (err) {
117
117
  spinner.fail(`${taskType} · ${taskId}`);
118
- updateHistoryStatus(taskId, 'failed');
118
+ if (err instanceof TaskFailedError && err.status !== 'timeout') {
119
+ updateHistoryStatus(taskId, 'failed');
120
+ }
121
+ else if (err instanceof CliError) {
122
+ err.suggestion = t({
123
+ zh: `任务仍在服务端运行,成功后照常扣费;用 tripo task watch ${taskId} --download 继续等待,不要重新提交`,
124
+ en: `the task keeps running server-side and is billed if it succeeds; resume with "tripo task watch ${taskId} --download" instead of resubmitting`,
125
+ });
126
+ }
119
127
  throw err;
120
128
  }
121
129
  updateHistoryStatus(taskId, 'success', { credits_consumed: task.credits_consumed });
@@ -17,6 +17,7 @@ export const MODEL_PRODUCING_TYPES = new Set([
17
17
  'mesh_segmentation',
18
18
  'mesh_completion',
19
19
  'mesh_edit',
20
+ 'mesh_uv',
20
21
  'animate_rig',
21
22
  'animate_retarget',
22
23
  ]);
@@ -32,6 +33,7 @@ export const STEPS = [
32
33
  { name: 'segment', endpoint: '/v3/mesh/segment', produces: 'mesh_segmentation', accepts: 'any-model', acceptsFile: true, describe: { zh: '把网格分割成部件', en: 'segment the mesh into parts' } },
33
34
  { name: 'complete', endpoint: '/v3/mesh/complete', produces: 'mesh_completion', accepts: ['mesh_segmentation'], acceptsFile: false, describe: { zh: '补全分割后的部件', en: 'complete the segmented parts' } },
34
35
  { name: 'decimate', endpoint: '/v3/mesh/decimate', produces: 'highpoly_to_lowpoly', accepts: 'any-model', acceptsFile: true, aliases: ['lowpoly'], describe: { zh: '减面到目标面数', en: 'decimate to a target face count' } },
36
+ { name: 'uv', endpoint: '/v3/mesh/uv', produces: 'mesh_uv', accepts: 'any-model', acceptsFile: true, describe: { zh: '智能展 UV,原贴图/骨骼/动画映射到新布局', en: 'smart UV unwrap; remaps textures/rig/animation onto the new layout' } },
35
37
  // mesh/edit: local retry/refine inside a bbox, P2 pipeline (model fixed to P2-20260801).
36
38
  { name: 'edit', endpoint: '/v3/mesh/edit', produces: 'mesh_edit', accepts: 'any-model', acceptsFile: true, describe: { zh: '在 bbox 区域内局部重做(P2 管线)', en: 'redo one bbox region locally (P2 pipeline)' } },
37
39
  // smartsegment's `input` is file_token/URL only (the server does not resolve
@@ -109,7 +109,7 @@ export const TEXTURE_MODELS = ['v3.0-20250812', 'v2.5-20250123'];
109
109
  /** mesh_segmentation whitelist (ALLOWED_SEGMENT_MODELS). */
110
110
  export const SEGMENT_MODELS = ['v1.0-20250506', 'v2.0-20260430'];
111
111
  /** mesh/decimate whitelist (ALLOWED_DECIMATE_MODELS; blank/"default" → server default v2.0). */
112
- export const DECIMATE_MODELS = ['v2.0', 'v1.0'];
112
+ export const DECIMATE_MODELS = ['v3.0', 'v2.0', 'v1.0'];
113
113
  /** Server whitelists differ per endpoint (ImageGenerationValidation). */
114
114
  export const TEXT_TO_IMAGE_TEMPLATES = [
115
115
  'asset_extraction',
@@ -5,7 +5,7 @@
5
5
  * Every rule here prevents a guaranteed 400 or a silently wasted credit.
6
6
  * Rules marked [hard] mirror server enforcement; [soft] are CLI smart defaults.
7
7
  */
8
- import { CONVERT_FORMATS, EXPORT_ORIENTATIONS, FBX_PRESETS, MODEL_P2, P1_FACE_LIMIT, P1_FORBIDDEN_PARAMS, P2_FACE_LIMIT, P2_FORBIDDEN_PARAMS, TEXTURE_FORMATS, TEXTURE_QUALITIES, isP1, isP2, normalizeModel, } from './models.js';
8
+ import { CONVERT_FORMATS, DECIMATE_MODELS, EXPORT_ORIENTATIONS, FBX_PRESETS, MODEL_P2, P1_FACE_LIMIT, P1_FORBIDDEN_PARAMS, P2_FACE_LIMIT, P2_FORBIDDEN_PARAMS, TEXTURE_FORMATS, TEXTURE_QUALITIES, isP1, isP2, normalizeModel, } from './models.js';
9
9
  const PROMPT_MAX = 1024;
10
10
  const NEGATIVE_PROMPT_MAX = 255;
11
11
  /** Validate + auto-fix parameters for 3D generation (text/image/multiview-to-model). */
@@ -168,7 +168,8 @@ export function validateConvertParams(input) {
168
168
  }
169
169
  /**
170
170
  * Validate parameters for mesh/decimate (ModelPostProcessValidation):
171
- * v2.0 (default): face_limit 500–20000 tri / 500–10000 quad.
171
+ * v2.0 (default) / v3.0: face_limit 500–20000 tri / 500–10000 quad.
172
+ * v3.0 only: smart_uv (UV unwrap in the same task).
172
173
  * v1.0: face_limit REQUIRED, 500–2,000,000 tri / 500–150,000 quad; no bake/part_names.
173
174
  */
174
175
  export function validateDecimateParams(input) {
@@ -176,8 +177,18 @@ export function validateDecimateParams(input) {
176
177
  const warnings = [];
177
178
  const errors = [];
178
179
  const model = typeof params.model === 'string' ? params.model.toLowerCase() : undefined;
179
- if (model !== undefined && !['v1.0', 'v2.0', 'default'].includes(model)) {
180
- errors.push(`decimate model must be v2.0 or v1.0 (got "${String(params.model)}")`);
180
+ if (model !== undefined && model !== 'default' && !DECIMATE_MODELS.includes(model)) {
181
+ errors.push(`decimate model must be ${DECIMATE_MODELS.join('/')} (got "${String(params.model)}")`);
182
+ }
183
+ // [hard] smart_uv is v3.0-only (server: 1004 on v1.0/v2.0); [soft] no model given → v3.0.
184
+ if (params.smart_uv === true) {
185
+ if (model === undefined) {
186
+ params.model = 'v3.0';
187
+ warnings.push('smart_uv requires decimate v3.0: using model=v3.0 (35 credits tri / 40 quad; v2.0 is 30)');
188
+ }
189
+ else if (model !== 'v3.0') {
190
+ errors.push(`smart_uv only works with decimate model v3.0 (got "${String(params.model)}")`);
191
+ }
181
192
  }
182
193
  const isV1 = model === 'v1.0';
183
194
  const quad = params.quad === true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tripo-cli",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Tripo 3D intelligent CLI — turn a prompt or an image into engine-ready 3D assets from your terminal (Tripo V3 API)",
5
5
  "keywords": [
6
6
  "tripo",
package/skill/SKILL.md CHANGED
@@ -34,7 +34,7 @@ Regions (ov = international, cn = China mainland) share one CLI: `tripo login --
34
34
  2. **stdout is the contract; stderr is commentary.** With `--json` (auto-enabled when piped), stdout carries exactly one final JSON line. Progress/logs go to stderr.
35
35
  3. **Artifacts are local files.** The result JSON has `output_dir`, `model_file`, `preview`. Look at `preview.png` to judge quality; re-roll with `tripo redo` if needed.
36
36
  4. **Exit codes are stable** — branch on them:
37
- - 0 success · 2 usage/params · 3 auth · 4 insufficient credits (tell the human to run `tripo topup`) · 5 content policy · 6 task failed (credits auto-refunded) · 7 network · 8 not found · 9 rate limit (retry with backoff)
37
+ - 0 success · 2 usage/params · 3 auth · 4 insufficient credits (tell the human to run `tripo topup`) · 5 content policy · 6 task failed (credits auto-refunded; on `timeout` it is still running — `tripo task watch <id>`, never redo) · 7 network (a failed submit is never resent and may already exist — check `tripo balance` before re-running) · 8 not found · 9 rate limit (retry with backoff)
38
38
  5. **Never invent parameters.** Unknown `--param key=value` pairs pass through to the API; stick to documented ones. The full parameter/model/preset reference from the official API docs is bundled: `tripo docs --topic commands/generate` (3D + image generation) and `tripo docs --topic commands/process` (texture/convert/rig/retarget/mesh) — read those instead of browsing the website.
39
39
  6. **Validate before spending: `--dry-run --json`.** Same command plus `--dry-run` prints the plan as JSON with zero network calls and no credits: the chosen `model`, the completed `steps[].payload` for every API call, `warnings` (auto-adjustments) and `errors` (blocking; exit 2). When `valid` is true, re-run without `--dry-run`. Details: `tripo docs --topic commands/make` § "Dry run".
40
40
  7. **Don't pick legacy model versions.** The CLI auto-selects `tripo-v3.1` (high fidelity) or `tripo-p1` (low-poly, face budget ≤ 20000). Only override `--model` when the human asks (e.g. `--model tripo-p2` for the P-series **Preview** with quad support, or `tripo-v3.0` / `tripo-v2.5` for reproducing old projects). `tripo-*` names are CLI aliases — the CLI sends the dated wire value (`tripo-p2` → `P2-20260801`); the API rejects the alias itself with 1004. Alias ↔ wire ↔ status table: `tripo docs --topic commands/generate` ("Models & Versions").
@@ -50,7 +50,7 @@ tripo make @last --then convert:fbx --json --yes # continue from last t
50
50
  ```
51
51
 
52
52
  - `--for` scenario presets: `game-mobile` `game-pc` `film` `print` `ar-web` `anim` `toy`
53
- - `--then` chain steps: `refine texture stylize convert import rig-check rig retarget segment complete decimate smartsegment`
53
+ - `--then` chain steps: `refine texture stylize convert import rig-check rig retarget segment complete decimate uv smartsegment`
54
54
  - step args: `convert:format=FBX,texture_size=2048` (bare value = primary arg: `convert:fbx`, `stylize:lego`, `decimate:5000`)
55
55
  - Output JSON: `{"task_id","type","status","credits_consumed","output_dir","files",["model_file"],["preview"],["chain"],["credits_breakdown"]}` — with `--then`, `credits_consumed` is the **total** for the run and `credits_breakdown` itemizes every task (`[{task_id,type,credits_consumed}]`, generation first)
56
56
 
@@ -40,7 +40,7 @@ In a TTY it then asks `Run this plan?`; answering no exits 0 with `cancelled; no
40
40
  ## Options
41
41
 
42
42
  - `--for <scenario>` — `game-mobile` `game-pc` `film` `print` `ar-web` `anim` `toy`. Sets model+params+chain+format from the domain knowledge base.
43
- - `--then <steps>` — processing chain, comma-separated. Steps: `refine texture stylize convert import rig-check rig retarget segment complete decimate smartsegment`. Args: `step:key=value,...`; bare value maps to the step's primary arg (`convert:fbx` → format, `stylize:lego` → style, `decimate:5000` → face_limit, `retarget:preset:walk` → animation). Overrides the scenario chain.
43
+ - `--then <steps>` — processing chain, comma-separated. Steps: `refine texture stylize convert import rig-check rig retarget segment complete decimate uv smartsegment`. Args: `step:key=value,...`; bare value maps to the step's primary arg (`convert:fbx` → format, `stylize:lego` → style, `decimate:5000` → face_limit, `retarget:preset:walk` → animation). Overrides the scenario chain.
44
44
  - `--model <m>` — force `tripo-v3.1` (high fidelity, default), `tripo-p1` (low-poly; face_limit 50–20000; quad/parts/geometry_quality unsupported and stripped) or `tripo-p2` (P-series preview; adds quad, face_limit 48–50000 tri / 48–25000 quad; never auto-selected). The CLI normalizes these aliases to the wire versions (`v3.1-20260211` / `P1-20260311` / `P2-20260801`) the server actually accepts. Full table with status (GA / Preview / Legacy) and official links: `tripo docs --topic commands/generate` → "Models & Versions".
45
45
  - `-n, --candidates <n>` — up to 4 parallel candidates with different seeds (interactive pick when chaining).
46
46
  - `--seed <n>` — fixed model_seed for reproducible geometry.
@@ -92,7 +92,8 @@ All prefixed `preset:biped:`
92
92
  ```bash
93
93
  tripo mesh segment [input] [--model v1.0-20250506|v2.0-20260430]
94
94
  tripo mesh complete [input] [--completion-mode ai_completion|quick_cap] # needs a segment task
95
- tripo mesh decimate [input] --face-limit 5000 [--quad] [--no-bake] # 500–20000 tri / 500–10000 quad
95
+ tripo mesh decimate [input] --face-limit 5000 [--model v3.0|v2.0|v1.0] [--quad] [--smart-uv] [--no-bake] # 500–20000 tri / 500–10000 quad
96
+ tripo mesh uv [input] [--no-bake] [--export-orientation +x|-x|+y|-y] # input ≤ 80,000 equivalent triangles
96
97
  tripo mesh smartsegment <file-or-url> [--seg-type image|model] [--granularity coarse|medium|fine] [--hint "..."]
97
98
  ```
98
99
 
@@ -100,9 +101,12 @@ tripo mesh smartsegment <file-or-url> [--seg-type image|model] [--granularity co
100
101
  | --- | --- | --- |
101
102
  | `segment` | task / file / URL | `model` `v1.0-20250506` (default, geometry-based) / `v2.0-20260430` (Beta, semantic labels + geometry). **v2 only**: `segmentation_granularity` simple/balanced/detailed (default balanced), `split_by_connectivity` (default true), `ref_image` file_token/URL — when `ref_image` is set the other two are ignored; setting `ref_image` on v1 → `1004` |
102
103
  | `complete` | **segment task only** | `completion_mode` `ai_completion` (AI diffusion, default) / `quick_cap` (fast hole-fill, no AI); `part_names` (default: all parts); `model` `v1.0-20250506` (only version) |
103
- | `decimate` | task / file | `face_limit` (docs quote 1000–20000; CLI accepts 500–20000 tri / 500–10000 quad); `quad`; `bake` (default true — bake textures onto the low-poly, right for LODs); `part_names`. `-p model=v1.0`: up to 2,000,000 tri / 150,000 quad but `face_limit` becomes required and `bake`/`part_names` are unsupported |
104
+ | `decimate` | task / file / URL | `model` `v2.0` (default, smart retopology, 30 credits) / `v3.0` (newest retopology, 35 credits tri / 40 quad) / `v1.0` (basic decimation, 10 credits); `face_limit` (v2.0/v3.0: optional, 500–20000 tri / 500–10000 quad, adaptive if omitted); `quad`; `bake` (default true — bake textures onto the low-poly, right for LODs); `part_names`; `smart_uv` (**v3.0 only**, default false — unwrap UVs in the same task, no extra cost, no UV atlas image; `--smart-uv` without `--model` selects v3.0, an explicit v1.0/v2.0 is rejected locally because the server answers `1004`). `--model v1.0`: up to 2,000,000 tri / 150,000 quad but `face_limit` becomes required and `bake`/`part_names` are unsupported |
105
+ | `uv` | task / file / URL | re-unwraps UVs and remaps the existing textures, rig and animation onto the new layout; **20 credits**. `bake` (default true; `--no-bake` returns geometry + UVs only); `export_orientation` `+x`/`-x`/`+y`/`-y`; `compress` `geometry` (meshopt, `-p compress=geometry`); `model` `default` (only value). **Input ≤ 80,000 equivalent triangles** (a quad counts as two): larger inputs are accepted, then the task fails and refunds — decimate first |
104
106
  | `smartsegment` | **file / URL only (no task ids)** | `seg_type` `image` (photo → auto-model → segment, **85 credits**) / `model` (existing **GLB only** → segment, **55 credits**; CLI default); `granularity` coarse/medium/fine (default medium); `hint` free text describing the parts; `transform` 4×4 column-major matrix required for `model` — the CLI auto-fills identity |
105
107
 
106
108
  - `segment` vs `smartsegment`: segment only splits an existing model; smartsegment is the end-to-end pipeline (asset → auto modelling → segmentation).
107
109
  - smartsegment output: `prompt` (comma-separated part labels), `mask_url` (PNG semantic mask), `seg_model_url` (segmented GLB), `seg_task_id` (inner segmentation task), `model_task_id` (inner image_to_model / import task). Task types: `smartsegment_image` / `smartsegment_model`.
108
110
  - `mesh complete` only accepts segmentation tasks; segmented / completed parts can then be exported selectively via `convert -p 'part_names=[...]'`.
111
+ - UVs in one task or two: `decimate --smart-uv` (v3.0) unwraps during retopology — one task, no atlas image. `uv` is its own task that also returns the UV atlas; run it on models that are already under the limit, e.g. `--then decimate:20000,uv`.
112
+ - uv output: the model plus `uv_render.webp` (the UV atlas). It is a flat layout, not a render of the model, so the CLI does not copy it to `preview.png`.
@@ -12,7 +12,7 @@ tripo task get @last --download --json
12
12
 
13
13
  Block until the task finishes. In `--json` mode, progress streams as NDJSON events on stdout (`{"event":"progress",...}` lines) and the final task detail is the last line. `--download` grabs artifacts on success. `--timeout <seconds>` (default 1800).
14
14
 
15
- Rules for agents: treat `watch` as synchronous; read lines until process exit; exit code 6 means the task failed (credits refunded).
15
+ Rules for agents: treat `watch` as synchronous; read lines until process exit; exit code 6 means the task failed (credits refunded) — or, if the error says `timeout`, that it is still running (watch it again; do not resubmit).
16
16
 
17
17
  ## tripo task list [ids...]
18
18
 
@@ -4,7 +4,7 @@
4
4
  | --- | --- | --- | --- |
5
5
  | 3 | 1002 | authentication failed (invalid/missing API key) | set `TRIPO_API_KEY` or run `tripo login`; check the key wasn't revoked |
6
6
  | 3 | 1005 | forbidden (no permission for this resource) | check account permissions |
7
- | 2 | 1003/1004 | malformed request / invalid parameter | check spelling against `tripo docs --topic commands/<cmd>`; the CLI already strips illegal P1 params. Classic 1004s: `generate_parts=true` with texture/pbr on, `ref_image` on segment v1, `quad=true` on P1, non-GLB input to smartsegment `model` / rig-check. `invalid model`: the API only accepts dated wire values (`P2-20260801`, `v3.1-20260211`…) — pass the CLI alias (`--model tripo-p2`) and let the CLI normalize, or the wire value itself; never `tripo-*` straight to the API. Table: `commands/generate` § "Models & Versions" |
7
+ | 2 | 1003/1004 | malformed request / invalid parameter | check spelling against `tripo docs --topic commands/<cmd>`; the CLI already strips illegal P1 params. Classic 1004s: `generate_parts=true` with texture/pbr on, `ref_image` on segment v1, `quad=true` on P1, `smart_uv=true` on decimate v1.0/v2.0, non-GLB input to smartsegment `model` / rig-check. `invalid model`: the API only accepts dated wire values (`P2-20260801`, `v3.1-20260211`…) — pass the CLI alias (`--model tripo-p2`) and let the CLI normalize, or the wire value itself; never `tripo-*` straight to the API. Table: `commands/generate` § "Models & Versions" |
8
8
  | 9 | 1007 | per-API-key rate limit | wait and retry with backoff; honour `Retry-After` / `X-RateLimit-Reset` headers |
9
9
  | 9 | 2000 | per-account concurrency pool full (see table below) | wait for a task in that category to finish, or lower `--concurrency` in batch; other categories are unaffected |
10
10
  | 8 | 2001 | task not found | task ids are account-scoped — same key that created it? right region (ov/cn)? |
@@ -19,9 +19,9 @@
19
19
  | 2 | 2018 | too complex to remesh | lower `face_limit` or simplify the input model |
20
20
  | 8 | 2019 | file not found (token expired?) | re-upload; file tokens are short-lived |
21
21
  | 2 | 2020/2021/2022 | bad image URL / file too large / image too large | fix the URL or shrink the file |
22
- | 7 | 1000/1001 | server-side error | 1000 auto-retries; if persistent, contact support with the `request_id` |
23
- | 6 | — | task failed server-side | frozen credits auto-refund; `tripo redo` often succeeds with a new seed |
24
- | 7 | — | network/5xx | auto-retried 3×; check `tripo doctor`; region mismatch (ov/cn) is a common cause |
22
+ | 7 | 1000/1001 | server-side error | queries auto-retry on 1000, task creation does not (the task may already exist and be billed — check `tripo balance` frozen credits before re-running); if persistent, contact support with the `request_id` |
23
+ | 6 | — | task failed server-side, or the watch timed out | failed: frozen credits auto-refund; `tripo redo` often succeeds with a new seed. `timeout`: the task is still running and is billed if it succeeds — `tripo task watch <id> --download`, never redo |
24
+ | 7 | — | network/5xx | queries auto-retried 3×, task creation is not (follow the `suggestion`: check `tripo balance` before re-running); check `tripo doctor`; region mismatch (ov/cn) is a common cause |
25
25
 
26
26
  ## Concurrency pools (default per account; error 2000 when full)
27
27
 
@@ -42,6 +42,7 @@ Pools are independent: a full H-series pool does not block P-series or image tas
42
42
  - `complete` only accepts a `segment` task; `retarget` only accepts a `rig` task
43
43
  - quad + GLTF export → rejected (quads cannot be stored in glTF); use FBX/OBJ/USDZ
44
44
  - P1 + face_limit outside 50–20000 → rejected with a suggestion to use v3.1
45
+ - decimate `smart_uv` on an explicit v1.0/v2.0 → rejected (v3.0 only); with no model given the CLI selects v3.0
45
46
  - multiview without a front view → rejected (name a file "front" or pass it first)
46
47
  - `-n` with `--then` needs an interactive terminal (pick the winner first, then chain)
47
48
 
@@ -57,3 +58,4 @@ All of these surface in `tripo make ... --dry-run --json` as `errors` (blocking)
57
58
  - A downstream step after a generation that used `export_orientation` succeeds but comes out **wrongly oriented** — no error is raised. Set the orientation on the final `convert` step instead.
58
59
  - `generate_parts` with `smart_low_poly=true` silently produces no parts (smart_low_poly wins); with `quad=true` the parts are triangles.
59
60
  - `image-to-splat` yields a `.splat`, not a mesh — texture / rig / convert steps cannot follow it.
61
+ - `uv` accepts any model at creation, but an input over 80,000 equivalent triangles fails at run time (credits refunded) — decimate first: `--then decimate:20000,uv`.