tripo-cli 0.3.2 → 0.5.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.
@@ -16,25 +16,28 @@ export const MODEL_PRODUCING_TYPES = new Set([
16
16
  'highpoly_to_lowpoly',
17
17
  'mesh_segmentation',
18
18
  'mesh_completion',
19
+ 'mesh_edit',
19
20
  'animate_rig',
20
21
  'animate_retarget',
21
22
  ]);
22
23
  export const STEPS = [
23
- { name: 'refine', endpoint: '/v3/models/refine', produces: 'refine_model', accepts: ['text_to_model'], acceptsFile: false },
24
- { name: 'texture', endpoint: '/v3/models/texture', produces: 'texture_model', accepts: 'any-model', acceptsFile: true },
25
- { name: 'stylize', endpoint: '/v3/models/stylize', produces: 'stylize_model', accepts: 'any-model', acceptsFile: true },
26
- { name: 'convert', endpoint: '/v3/models/convert', produces: 'convert_model', accepts: 'any-model', acceptsFile: true },
27
- { name: 'import', endpoint: '/v3/models/import', produces: 'import_model', accepts: [], acceptsFile: true },
28
- { name: 'rig-check', endpoint: '/v3/animations/rig-check', produces: 'animate_prerigcheck', accepts: 'any-model', acceptsFile: true, passthrough: true, aliases: ['check', 'rigcheck'] },
29
- { name: 'rig', endpoint: '/v3/animations/rig', produces: 'animate_rig', accepts: 'any-model', acceptsFile: true },
30
- { name: 'retarget', endpoint: '/v3/animations/retarget', produces: 'animate_retarget', accepts: ['animate_rig'], acceptsFile: false, aliases: ['animate'] },
31
- { name: 'segment', endpoint: '/v3/mesh/segment', produces: 'mesh_segmentation', accepts: 'any-model', acceptsFile: true },
32
- { name: 'complete', endpoint: '/v3/mesh/complete', produces: 'mesh_completion', accepts: ['mesh_segmentation'], acceptsFile: false },
33
- { name: 'decimate', endpoint: '/v3/mesh/decimate', produces: 'highpoly_to_lowpoly', accepts: 'any-model', acceptsFile: true, aliases: ['lowpoly'] },
24
+ { name: 'refine', endpoint: '/v3/models/refine', produces: 'refine_model', accepts: ['text_to_model'], acceptsFile: false, describe: { zh: '精修文生模型的几何细节', en: 'refine geometry of a text-to-model result' } },
25
+ { name: 'texture', endpoint: '/v3/models/texture', produces: 'texture_model', accepts: 'any-model', acceptsFile: true, describe: { zh: '重新生成贴图 / PBR 材质', en: 'regenerate textures / PBR materials' } },
26
+ { name: 'stylize', endpoint: '/v3/models/stylize', produces: 'stylize_model', accepts: 'any-model', acceptsFile: true, describe: { zh: '按风格重塑模型(lego/voxel/...)', en: 'restyle the model (lego/voxel/...)' } },
27
+ { name: 'convert', endpoint: '/v3/models/convert', produces: 'convert_model', accepts: 'any-model', acceptsFile: true, describe: { zh: '转换输出格式 / 贴图尺寸 / 朝向', en: 'convert format / texture size / orientation' } },
28
+ { name: 'import', endpoint: '/v3/models/import', produces: 'import_model', accepts: [], acceptsFile: true, describe: { zh: '导入本地模型文件作为加工起点', en: 'import a local model file as the chain source' } },
29
+ { name: 'rig-check', endpoint: '/v3/animations/rig-check', produces: 'animate_prerigcheck', accepts: 'any-model', acceptsFile: true, passthrough: true, aliases: ['check', 'rigcheck'], describe: { zh: '检查是否可绑骨(不产出模型,不可绑则止损)', en: 'check riggability (no model output; stops the chain if not riggable)' } },
30
+ { name: 'rig', endpoint: '/v3/animations/rig', produces: 'animate_rig', accepts: 'any-model', acceptsFile: true, describe: { zh: '自动绑定骨骼', en: 'auto-rig a skeleton' } },
31
+ { name: 'retarget', endpoint: '/v3/animations/retarget', produces: 'animate_retarget', accepts: ['animate_rig'], acceptsFile: false, aliases: ['animate'], describe: { zh: '把预设动画重定向到绑骨模型', en: 'retarget preset animations onto the rigged model' } },
32
+ { name: 'segment', endpoint: '/v3/mesh/segment', produces: 'mesh_segmentation', accepts: 'any-model', acceptsFile: true, describe: { zh: '把网格分割成部件', en: 'segment the mesh into parts' } },
33
+ { name: 'complete', endpoint: '/v3/mesh/complete', produces: 'mesh_completion', accepts: ['mesh_segmentation'], acceptsFile: false, describe: { zh: '补全分割后的部件', en: 'complete the segmented parts' } },
34
+ { 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' } },
35
+ // mesh/edit: local retry/refine inside a bbox, P2 pipeline (model fixed to P2-20260801).
36
+ { 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)' } },
34
37
  // smartsegment's `input` is file_token/URL only (the server does not resolve
35
38
  // task ids here), so it cannot chain after a task. Type is refined to
36
39
  // smartsegment_image|smartsegment_model at request-build time per seg_type.
37
- { name: 'smartsegment', endpoint: '/v3/mesh/smartsegment', produces: 'smartsegment_model', accepts: [], acceptsFile: true },
40
+ { name: 'smartsegment', endpoint: '/v3/mesh/smartsegment', produces: 'smartsegment_model', accepts: [], acceptsFile: true, describe: { zh: '智能语义分割', en: 'smart semantic segmentation' } },
38
41
  ];
39
42
  const stepIndex = new Map();
40
43
  for (const s of STEPS) {
@@ -42,8 +45,25 @@ for (const s of STEPS) {
42
45
  for (const a of s.aliases ?? [])
43
46
  stepIndex.set(a, s);
44
47
  }
48
+ /**
49
+ * `/v3/mesh/edit` is documented but not yet deployed (cn returns 4001). The
50
+ * step stays hidden until the backend ships; set TRIPO_EXPERIMENTAL_MESH_EDIT=1
51
+ * to opt in early. Flip this default once the endpoint is live.
52
+ */
53
+ export function isMeshEditEnabled() {
54
+ return process.env.TRIPO_EXPERIMENTAL_MESH_EDIT === '1';
55
+ }
56
+ const EXPERIMENTAL_STEPS = new Set(['edit']);
57
+ function isStepAvailable(def) {
58
+ return !EXPERIMENTAL_STEPS.has(def.name) || isMeshEditEnabled();
59
+ }
60
+ /** Steps usable right now (experimental ones only when opted in). */
61
+ export function availableSteps() {
62
+ return STEPS.filter(isStepAvailable);
63
+ }
45
64
  export function getStep(name) {
46
- return stepIndex.get(name);
65
+ const def = stepIndex.get(name);
66
+ return def && isStepAvailable(def) ? def : undefined;
47
67
  }
48
68
  /**
49
69
  * Parse a --then expression: "texture,rig,convert:fbx" or
@@ -68,7 +88,7 @@ export function parseThenExpression(expr) {
68
88
  applyArg(prev.args, part);
69
89
  continue;
70
90
  }
71
- throw new ChainError(`unknown step "${stepName}" (valid: ${STEPS.map((s) => s.name).join(', ')})`);
91
+ throw new ChainError(`unknown step "${stepName}" (valid: ${availableSteps().map((s) => s.name).join(', ')})`);
72
92
  }
73
93
  const args = {};
74
94
  const argStr = rest.join(':').trim();
@@ -100,11 +120,13 @@ function applyPrimaryArg(step, args, value) {
100
120
  rig: 'rig_type',
101
121
  segment: 'model',
102
122
  smartsegment: 'granularity',
123
+ // bbox as "x1/y1/z1/x2/y2/z2" (slashes: commas already separate --then tokens)
124
+ edit: 'bbox',
103
125
  };
104
126
  const key = primary[step];
105
127
  if (!key)
106
128
  throw new ChainError(`step "${step}" does not take a bare argument ("${value}")`);
107
- args[key] = coerce(value);
129
+ args[key] = key === 'bbox' ? value : coerce(value);
108
130
  }
109
131
  function coerce(raw) {
110
132
  if (raw === 'true')
@@ -7,12 +7,14 @@
7
7
  */
8
8
  /**
9
9
  * Wire values are what the server actually accepts (backend whitelist:
10
- * v3.1-20260211, v3.0-20250812, v2.5-20250123, P1-20260311). The friendly
11
- * `tripo-*` names from marketing docs are CLI-side aliases only — sending
12
- * them raw returns 400 "invalid model".
10
+ * v3.1-20260211, v3.0-20250812, v2.5-20250123, P1-20260311, P2-20260801).
11
+ * The friendly `tripo-*` names from marketing docs are CLI-side aliases only —
12
+ * sending them raw returns 400 "invalid model".
13
13
  */
14
14
  export const MODEL_V31 = 'v3.1-20260211';
15
15
  export const MODEL_P1 = 'P1-20260311';
16
+ /** P-series upgrade (Preview): adds quad output; explicit --model only, never auto-selected. */
17
+ export const MODEL_P2 = 'P2-20260801';
16
18
  /** User-facing aliases normalized to the wire values above. */
17
19
  export const MODEL_ALIASES = {
18
20
  'v3.1': MODEL_V31,
@@ -22,6 +24,10 @@ export const MODEL_ALIASES = {
22
24
  P1: MODEL_P1,
23
25
  'P1-20260311': MODEL_P1,
24
26
  'tripo-p1': MODEL_P1,
27
+ p2: MODEL_P2,
28
+ P2: MODEL_P2,
29
+ 'P2-20260801': MODEL_P2,
30
+ 'tripo-p2': MODEL_P2,
25
31
  // Older whitelisted versions, alias → wire.
26
32
  'v3.0': 'v3.0-20250812',
27
33
  'tripo-v3.0': 'v3.0-20250812',
@@ -39,6 +45,10 @@ export const P1_FORBIDDEN_PARAMS = [
39
45
  'generate_parts',
40
46
  'geometry_quality',
41
47
  ];
48
+ /** P2 face_limit windows (official docs): 48–50000 triangles, 48–25000 with quad=true. */
49
+ export const P2_FACE_LIMIT = { min: 48, triMax: 50000, quadMax: 25000 };
50
+ /** Parameters the server rejects for P2 (quad is allowed, unlike P1). */
51
+ export const P2_FORBIDDEN_PARAMS = ['smart_low_poly', 'generate_parts', 'geometry_quality'];
42
52
  /** Rig wire versions (backend whitelist; marketing names rig-v1.0/rig-v2.0 are rejected). */
43
53
  export const RIG_MODEL_V1 = 'v1.0-20240301';
44
54
  export const RIG_MODEL_V25 = 'v2.5-20260210';
@@ -138,6 +148,9 @@ export function normalizeModel(input) {
138
148
  export function isP1(model) {
139
149
  return normalizeModel(model) === MODEL_P1;
140
150
  }
151
+ export function isP2(model) {
152
+ return normalizeModel(model) === MODEL_P2;
153
+ }
141
154
  /** The one rule. Returns the model plus a human-readable reason. */
142
155
  export function chooseModel(intent) {
143
156
  if (intent.explicit) {
@@ -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, P1_FACE_LIMIT, P1_FORBIDDEN_PARAMS, TEXTURE_FORMATS, TEXTURE_QUALITIES, isP1, } from './models.js';
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';
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). */
@@ -19,6 +19,26 @@ export function validateGenerationParams(model, input) {
19
19
  if (typeof params.negative_prompt === 'string' && params.negative_prompt.length > NEGATIVE_PROMPT_MAX) {
20
20
  errors.push(`negative_prompt exceeds ${NEGATIVE_PROMPT_MAX} characters`);
21
21
  }
22
+ // [hard] P-series unsupported params are stripped first, so an unsupported
23
+ // generate_parts cannot cascade into disabling texture/pbr/quad below.
24
+ if (isP1(model)) {
25
+ for (const key of P1_FORBIDDEN_PARAMS) {
26
+ if (params[key] !== undefined) {
27
+ delete params[key];
28
+ warnings.push(key === 'quad'
29
+ ? 'P1 does not support quad (removed); need a quad low-poly? use --model tripo-p2'
30
+ : `P1 does not support ${key} (removed)`);
31
+ }
32
+ }
33
+ }
34
+ else if (isP2(model)) {
35
+ for (const key of P2_FORBIDDEN_PARAMS) {
36
+ if (params[key] !== undefined) {
37
+ delete params[key];
38
+ warnings.push(`P2 does not support ${key} (removed)`);
39
+ }
40
+ }
41
+ }
22
42
  // [hard] pbr=true forces texture=true (PBR is a superset of texturing).
23
43
  if (params.pbr === true && params.texture === false) {
24
44
  params.texture = true;
@@ -33,14 +53,8 @@ export function validateGenerationParams(model, input) {
33
53
  }
34
54
  }
35
55
  }
36
- // [hard] P1 constraints: strip forbidden params, enforce face_limit range.
56
+ // [hard] P1 face_limit range.
37
57
  if (isP1(model)) {
38
- for (const key of P1_FORBIDDEN_PARAMS) {
39
- if (params[key] !== undefined) {
40
- delete params[key];
41
- warnings.push(`P1 does not support ${key} (removed)`);
42
- }
43
- }
44
58
  const face = params.face_limit;
45
59
  if (typeof face === 'number') {
46
60
  if (face < P1_FACE_LIMIT.min || face > P1_FACE_LIMIT.max) {
@@ -49,6 +63,18 @@ export function validateGenerationParams(model, input) {
49
63
  }
50
64
  }
51
65
  }
66
+ else if (isP2(model)) {
67
+ // [hard] P2 face_limit range: quad halves the budget.
68
+ const face = params.face_limit;
69
+ if (typeof face === 'number') {
70
+ const quad = params.quad === true;
71
+ const max = quad ? P2_FACE_LIMIT.quadMax : P2_FACE_LIMIT.triMax;
72
+ if (face < P2_FACE_LIMIT.min || face > max) {
73
+ errors.push(`P2 face_limit must be within ${P2_FACE_LIMIT.min}–${max}${quad ? ' with quad=true' : ''} (got ${face}); ` +
74
+ `use tripo-v3.1 for higher budgets`);
75
+ }
76
+ }
77
+ }
52
78
  else {
53
79
  // [hard] smart_low_poly face_limit windows: 500–20000 tri / 500–10000 quad.
54
80
  if (params.smart_low_poly === true && typeof params.face_limit === 'number') {
@@ -201,3 +227,53 @@ export function validateRetargetParams(input) {
201
227
  }
202
228
  return { params, warnings, errors };
203
229
  }
230
+ const MESH_EDIT_FACE_MIN = 100;
231
+ /**
232
+ * Normalize a bbox given as [[x,y,z],[x,y,z]], a flat 6-number array, or a
233
+ * string "x1/y1/z1/x2/y2/z2" (also comma-separated) into the 2×3 wire shape.
234
+ * Returns undefined when the input is not 6 finite numbers.
235
+ */
236
+ function normalizeBbox(raw) {
237
+ let flat;
238
+ if (typeof raw === 'string') {
239
+ flat = raw.split(/[/,;\s]+/).filter(Boolean).map(Number);
240
+ }
241
+ else if (Array.isArray(raw)) {
242
+ flat = raw.flat();
243
+ }
244
+ else {
245
+ return undefined;
246
+ }
247
+ if (flat.length !== 6 || !flat.every((n) => typeof n === 'number' && Number.isFinite(n)))
248
+ return undefined;
249
+ const nums = flat;
250
+ return [nums.slice(0, 3), nums.slice(3, 6)];
251
+ }
252
+ /** Validate parameters for mesh/edit (P2 pipeline): model fixed to P2, bbox required (2×3), face_limit ≥ 100. */
253
+ export function validateMeshEditParams(input) {
254
+ const params = { ...input };
255
+ const warnings = [];
256
+ const errors = [];
257
+ const model = params.model === undefined ? MODEL_P2 : normalizeModel(String(params.model));
258
+ if (model !== MODEL_P2) {
259
+ errors.push(`mesh edit only supports model ${MODEL_P2} (got "${String(params.model)}")`);
260
+ }
261
+ params.model = model;
262
+ if (params.bbox === undefined) {
263
+ errors.push('mesh edit requires bbox (x1/y1/z1/x2/y2/z2 or [[x1,y1,z1],[x2,y2,z2]])');
264
+ }
265
+ else {
266
+ const bbox = normalizeBbox(params.bbox);
267
+ if (!bbox)
268
+ errors.push('bbox must be 6 finite numbers: x1/y1/z1/x2/y2/z2');
269
+ else
270
+ params.bbox = bbox;
271
+ }
272
+ if (params.face_limit !== undefined) {
273
+ const face = params.face_limit;
274
+ if (typeof face !== 'number' || face < MESH_EDIT_FACE_MIN) {
275
+ errors.push(`mesh edit face_limit must be an integer ≥ ${MESH_EDIT_FACE_MIN} (got ${String(face)})`);
276
+ }
277
+ }
278
+ return { params, warnings, errors };
279
+ }
@@ -53,7 +53,7 @@ export async function planMake(client, options) {
53
53
  case 'image':
54
54
  case 'url-image': {
55
55
  const raw = classified.values[0].value;
56
- const { value } = await resolveInputValue(client, raw);
56
+ const { value } = await resolveInputValue(client, raw, { dryRun: options.dryRun });
57
57
  const choice = chooseModel({
58
58
  explicit: options.model ?? scenario?.model,
59
59
  faceLimit: typeof genParams.face_limit === 'number' ? genParams.face_limit : undefined,
@@ -69,7 +69,7 @@ export async function planMake(client, options) {
69
69
  };
70
70
  }
71
71
  case 'multiview': {
72
- const views = await assignViews(client, classified.values.map((v) => v.value));
72
+ const views = await assignViews(client, classified.values.map((v) => v.value), { dryRun: options.dryRun });
73
73
  const choice = chooseModel({
74
74
  explicit: options.model ?? scenario?.model,
75
75
  faceLimit: typeof genParams.face_limit === 'number' ? genParams.face_limit : undefined,
@@ -86,7 +86,7 @@ export async function planMake(client, options) {
86
86
  }
87
87
  case 'model': {
88
88
  const raw = classified.values[0].value;
89
- const { value } = await resolveInputValue(client, raw);
89
+ const { value } = await resolveInputValue(client, raw, { dryRun: options.dryRun });
90
90
  const generation = buildStepRequest('import', value, {});
91
91
  if (chain.length === 0) {
92
92
  warnings.push(t({
@@ -116,24 +116,31 @@ export async function planMake(client, options) {
116
116
  }
117
117
  }
118
118
  }
119
+ /**
120
+ * Chain legality against the plan's upstream task type (throws UsageError).
121
+ * Skipped when the upstream type is unknown (raw task id without local history).
122
+ */
123
+ export function checkPlanChain(plan) {
124
+ if (plan.chain.length === 0)
125
+ return;
126
+ const upstream = plan.generation?.taskType ?? plan.sourceType;
127
+ if (!upstream)
128
+ return;
129
+ try {
130
+ validateChain(plan.chain, upstream);
131
+ }
132
+ catch (error) {
133
+ if (error instanceof ChainError)
134
+ throw new UsageError(error.message);
135
+ throw error;
136
+ }
137
+ }
119
138
  /** Execute a plan produced by planMake. */
120
139
  export async function executeMake(client, plan, options) {
121
140
  for (const w of plan.warnings)
122
141
  warn(w);
123
142
  // Validate chain legality up front so an illegal plan spends zero credits.
124
- if (plan.chain.length > 0) {
125
- const upstream = plan.generation?.taskType ?? plan.sourceType;
126
- if (upstream) {
127
- try {
128
- validateChain(plan.chain, upstream);
129
- }
130
- catch (error) {
131
- if (error instanceof ChainError)
132
- throw new UsageError(error.message);
133
- throw error;
134
- }
135
- }
136
- }
143
+ checkPlanChain(plan);
137
144
  await precheckBalance(client);
138
145
  let sourceTaskId;
139
146
  let sourceType;
@@ -195,7 +202,7 @@ export async function executeMake(client, plan, options) {
195
202
  * Map 2–4 images to views. Filename hints (front/back/left/right, 前/后/左/右)
196
203
  * win; remaining slots fill positionally in [front, left, back, right] order.
197
204
  */
198
- export async function assignViews(client, files) {
205
+ export async function assignViews(client, files, opts = {}) {
199
206
  if (files.length < 2 || files.length > 4) {
200
207
  throw new UsageError(`multiview needs 2–4 images (got ${files.length})`);
201
208
  }
@@ -227,7 +234,7 @@ export async function assignViews(client, files) {
227
234
  }
228
235
  const resolved = {};
229
236
  for (const [view, file] of Object.entries(views)) {
230
- resolved[view] = (await resolveInputValue(client, file)).value;
237
+ resolved[view] = (await resolveInputValue(client, file, { dryRun: opts.dryRun })).value;
231
238
  }
232
239
  return resolved;
233
240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tripo-cli",
3
- "version": "0.3.2",
3
+ "version": "0.5.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
@@ -30,13 +30,14 @@ Regions (ov = international, cn = China mainland) share one CLI: `tripo login --
30
30
 
31
31
  ## Behavior rules (read first)
32
32
 
33
- 1. **`tripo make` is synchronous and blocking.** It submits the task, polls, downloads artifacts, then exits. Wait for the process to finish and read the final JSON from stdout. Do NOT re-implement polling, do NOT add your own timeout shorter than 15 minutes, do NOT stop just because you saw a task_id in the logs.
33
+ 1. **`tripo make` is synchronous and blocking.** It prints a plan card on stderr (one numbered line per API call: endpoint, purpose, args), then submits, polls, downloads artifacts, and exits. In a TTY it asks "Run this plan?" first; `--yes` (auto-enabled when headless) skips the prompt but still prints the card. Wait for the process to finish and read the final JSON from stdout. Do NOT re-implement polling, do NOT add your own timeout shorter than 15 minutes, do NOT stop just because you saw a task_id in the logs.
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
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)
38
- 5. **Never invent parameters.** Unknown `--param key=value` pairs pass through to the API; stick to documented ones (see `commands/`).
39
- 6. **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.
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
+ 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
+ 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").
40
41
 
41
42
  ## The one command you usually need
42
43
 
@@ -51,7 +52,7 @@ tripo make @last --then convert:fbx --json --yes # continue from last t
51
52
  - `--for` scenario presets: `game-mobile` `game-pc` `film` `print` `ar-web` `anim` `toy`
52
53
  - `--then` chain steps: `refine texture stylize convert import rig-check rig retarget segment complete decimate smartsegment`
53
54
  - step args: `convert:format=FBX,texture_size=2048` (bare value = primary arg: `convert:fbx`, `stylize:lego`, `decimate:5000`)
54
- - Output JSON: `{"task_id","type","status","credits_consumed","output_dir","files",["model_file"],["preview"],["chain"]}`
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)
55
56
 
56
57
  ## Pipes (composing steps yourself)
57
58
 
@@ -77,6 +78,7 @@ tripo make cat.png --json | tripo anim rig --json | tripo anim retarget --param
77
78
  ## Detailed docs in this package
78
79
 
79
80
  - `commands/` — one file per command with every flag and parameter
81
+ - model versions · H series (`tripo-v3.x`) · P series (`tripo-p1`, `tripo-p2` Preview) · alias → wire value · GA / Preview / Legacy status · quad → `commands/generate` § "Models & Versions" (also the P Series endpoint entry points)
80
82
  - `examples/` — copy-paste recipes per scenario (game/print/animation/...)
81
83
  - `common-errors.md` — error table with fixes
82
84
 
@@ -1,23 +1,94 @@
1
1
  # tripo generate — deterministic endpoint access
2
2
 
3
3
  Use `tripo make` unless you need exact endpoint control. All subcommands accept the shared task options (`--json --yes -o --no-wait --no-download --timeout --name -p key=value`).
4
+ Everything below is the full parameter reference from the official API docs — you should not need to consult the website.
4
5
 
5
6
  ## 3D
6
7
 
7
8
  ```bash
8
- tripo generate text-to-model "a ceramic teapot" [--model tripo-v3.1|tripo-p1] [-p face_limit=15000] ...
9
+ tripo generate text-to-model "a ceramic teapot" [--model tripo-v3.1|tripo-p1|tripo-p2] [-p face_limit=15000] ...
9
10
  tripo generate image-to-model photo.png [-p texture_alignment=original_image] [-p orientation=align_image]
10
11
  tripo generate multiview-to-model front.png back.png [left.png right.png]
11
12
  ```
12
13
 
13
- Key params (hard rules enforced locally):
14
- - `pbr=true` implies `texture=true`
15
- - `generate_parts=true` excludes texture/pbr/quad (bare geometry; re-texture afterwards)
16
- - `quad=true` forces FBX output (quads cannot be stored in GLB)
17
- - P1: `face_limit` 50–20000; `quad`/`smart_low_poly`/`generate_parts`/`geometry_quality` unsupported
18
- - model aliases (`tripo-v3.1`/`tripo-p1`/`v3.0`/`v2.5`) are normalized to server wire versions automatically
19
- - `smart_low_poly=true`: face_limit 500–20000 (500–10000 with quad)
20
- - seeds: `image_seed` / `model_seed` / `texture_seed` — record them for reproducibility (task.json does this automatically)
14
+ ### P Series (`tripo-p1` / `tripo-p2`) — same endpoints, different model
15
+
16
+ The P series (low-poly, clean topology) uses the **same three endpoints**; only the `model` value changes. The official docs document it as a separate "P Series" tab on each generation page — this is the direct entry point:
17
+
18
+ ```bash
19
+ tripo generate text-to-model "a low poly knight" --model tripo-p1 -p face_limit=8000 # → model: P1-20260311
20
+ tripo generate image-to-model photo.png --model tripo-p2 -p quad=true -p face_limit=12000 # → model: P2-20260801 (Preview)
21
+ tripo generate multiview-to-model front.png back.png --model tripo-p1
22
+ ```
23
+
24
+ | endpoint | H series (standard) page | P series page |
25
+ | --- | --- | --- |
26
+ | `POST /v3/generation/text-to-model` | `…/docs/generation-text-to-model/standard` | <https://developers.tripo3d.ai/en/docs/generation-text-to-model/p> |
27
+ | `POST /v3/generation/image-to-model` | `…/docs/generation-image-to-model/standard` | <https://developers.tripo3d.ai/en/docs/generation-image-to-model/p> |
28
+ | `POST /v3/generation/multiview-to-model` | `…/docs/generation-multiview-to-model/standard` | <https://developers.tripo3d.ai/en/docs/generation-multiview-to-model/p> |
29
+
30
+ (cn: replace `developers.tripo3d.ai/en` with `developers.tripo3d.com/zh`.) P-series constraints — face_limit ranges, unsupported params, P2's `quad` — are in the table below; the CLI enforces them locally.
31
+
32
+ ### 3D models (`--model`) — Models & Versions
33
+
34
+ Keywords: model versions · H series (v3.x) · P series (P1 / P2) · Preview · quad · alias → wire value.
35
+
36
+ | alias (what you type) | wire value (what the API receives) | status | positioning | notes |
37
+ | --- | --- | --- | --- | --- |
38
+ | `tripo-v3.1` (default) | `v3.1-20260211` | GA | latest high-precision; AAA / 3D printing | full feature set |
39
+ | `tripo-p1` | `P1-20260311` | GA | best low-poly; games / UGC / mobile | face_limit 48–20000 (CLI enforces ≥50); **no** `quad` `smart_low_poly` `generate_parts` `geometry_quality`; ~2s mesh; extra `export_uv` |
40
+ | `tripo-p2` | `P2-20260801` | **Preview** (2026-08) | P1 upgrade (P series) | adds `quad` (forces FBX); face_limit 48–50000 tri / 48–25000 quad (validated locally); still **no** `smart_low_poly` `generate_parts` `geometry_quality` (stripped with a warning); credits 100 (no texture) / 110 / 120 / 130 for standard / detailed / extreme. Explicit `--model` only — the CLI never auto-selects it |
41
+ | `tripo-v3.0` / `tripo-v2.5` | `v3.0-20250812` / `v2.5-20250123` | Legacy | previous generations | only when the human asks |
42
+
43
+ **Alias vs wire value.** `tripo-*`, `p1`, `p2`, `v3.1` … are CLI-side aliases; the CLI always sends the dated wire value in the `model` field (`--model tripo-p2` → `"model": "P2-20260801"`). The v3 server whitelist accepts wire values only — sending an alias straight to the API returns 1004 `invalid model` with the allowed list. `task.json` records the wire value that was actually used. `tripo-turbo` / `tripo-v2.0` appear in some docs but are **not** on the v3 whitelist (400).
44
+
45
+ Official docs (ov / cn):
46
+ - Models & Versions: <https://developers.tripo3d.ai/en/docs/models-and-versions> · <https://developers.tripo3d.com/zh/docs/models-and-versions>
47
+ - P Series model detail: <https://developers.tripo3d.ai/en/models/p1> · <https://developers.tripo3d.com/zh/models/p1>
48
+ - P Series endpoints: `…/docs/generation-text-to-model/p`, `…/docs/generation-image-to-model/p`, `…/docs/generation-multiview-to-model/p` (the `/standard` tab on each page is the H-series version)
49
+
50
+ ### Parameters (text / image / multiview-to-model)
51
+
52
+ | param | default | applies to | notes |
53
+ | --- | --- | --- | --- |
54
+ | `prompt` | — | text | ≤1024 chars |
55
+ | `negative_prompt` | — | text | ≤255 chars |
56
+ | `input` | — | image | `file_` token, `http(s)://` URL or `task_` id (e.g. a text-to-image task) — the CLI uploads local files for you |
57
+ | `inputs` | — | multiview | see "multiview inputs" below |
58
+ | `model_seed` / `image_seed` / `texture_seed` | random | all | geometry / text-to-image stage / texture seeds — the key to reproducibility (`task.json` records them) |
59
+ | `face_limit` | adaptive | all | max polycount; P1 48–20000, P2 48–50000 |
60
+ | `texture` | `true` | all | set `false` for bare geometry (print / re-texture later) — saves credits |
61
+ | `pbr` | `true` | all | PBR maps; **forces `texture=true`** |
62
+ | `texture_quality` | `standard` | all | `standard` / `detailed` / `extreme` (price tiers) |
63
+ | `geometry_quality` | `standard` | v3.x | `detailed` = Ultra mode (extra HD credit) |
64
+ | `auto_size` | `false` | all | scale to real-world metres |
65
+ | `quad` | `false` | v3.x, P2 | quad mesh; **forces FBX output** (quads cannot live in GLB) |
66
+ | `smart_low_poly` | `false` | v3.x | hand-crafted-style low-poly; face_limit 500–20000 tri / 500–10000 quad |
67
+ | `generate_parts` | `false` | v3.x | editable segmented parts — see rules below |
68
+ | `compress` | — | v3.x | `geometry` = meshopt compression |
69
+ | `export_orientation` | `+x` | all | forward axis `+x` / `-x` / `+y` / `-y` — see warning below |
70
+ | `export_uv` | `true` | P series only | `false` = faster, smaller file (no UV unwrap) |
71
+ | `enable_image_autofix` | `false` | image | auto-repair low-quality inputs |
72
+ | `texture_alignment` | `original_image` | image, multiview | `original_image` (match the picture) / `geometry` (match the mesh) |
73
+ | `orientation` | `default` | image, multiview | `align_image` = align the model to the input viewpoint |
74
+
75
+ Only valid when `model >= tripo-v3.0`: `texture_quality` `geometry_quality` `auto_size` `quad` `smart_low_poly` `generate_parts` `compress`.
76
+
77
+ **`generate_parts` rules (server-enforced):**
78
+ - `texture=true` or `pbr=true` (including simply omitting `texture`, which defaults to true) → request rejected with `1004`. The CLI flips both to `false` for you and warns; re-texture afterwards with `tripo model texture`.
79
+ - `quad=true` → quad is ignored; parts come back as triangle meshes.
80
+ - `smart_low_poly=true` → smart_low_poly wins and **no parts are produced**.
81
+
82
+ **`export_orientation` warning:** it applies to this generation only. Downstream tasks (texture, rig, retarget, convert, …) do **not** inherit it — they read the model in default orientation and produce a wrongly-oriented result while still reporting `success`. If you will post-process, leave it unset and set `export_orientation` on the final `convert` step instead.
83
+
84
+ ### Multiview inputs
85
+
86
+ The API accepts three `inputs` shapes (never mix them):
87
+ - **view-key (recommended)**: `[{"front":…},{"back":…}]` with keys `front/left/back/right`; `front` required, ≥2 views, order irrelevant.
88
+ - **legacy positional**: exactly 4 strings `[front, left, back, right]`, `""` skips a slot.
89
+ - **task_id**: `[{"task_id":"<image-to-multiview or edit-multiview task>"}]` to reuse a 4-view sheet (source task must be `success`).
90
+
91
+ The CLI builds view-key payloads from your files: filename hints (`front|back|left|right`, or 正面/背面/左/右) win; otherwise positional order front, left, back, right. A front view is mandatory.
21
92
 
22
93
  ## Images (2D pipeline before 3D)
23
94
 
@@ -29,6 +100,32 @@ tripo generate edit-multiview <multiview-task-id> -p 'prompts=[{"view":"front","
29
100
  tripo generate image-to-splat photo.png # gaussian splat
30
101
  ```
31
102
 
32
- - image models: `seedream_v4` (t2i default) `seedream_v5` (i2i default) `banana` `banana_pro` `banana2` `chat_image_1` `chat_image_1.5` `chat_image_2`
33
- - templates — text-to-image: `asset_extraction character_completion t_pose variants figure`; image-to-image: `t_pose character_completion 3d_enhance variants figure`
34
- - `template=t_pose` is the golden pre-step for the animation pipeline
103
+ ### Image models (`--model`)
104
+
105
+ | model | provider | status | notes |
106
+ | --- | --- | --- | --- |
107
+ | `seedream_v4` | ByteDance | GA | text-to-image default (balanced); **not accepted by image-to-image** |
108
+ | `seedream_v5` | ByteDance | GA | image-to-image default; higher quality |
109
+ | `gemini-2.5-flash` / `gemini-3-pro` / `gemini-3.1-flash` | Google | GA | fast / high quality / latest fast. Backend aliases `banana` / `banana_pro` / `banana2` are also accepted |
110
+ | `chat_image_1` / `chat_image_1.5` | OpenAI | retiring 2026-10-23 / 2026-12-01 | GPT image; 1.5 = higher quality — move to `chat_image_2` |
111
+ | `chat_image_2` | OpenAI | GA | GPT latest |
112
+
113
+ Image model ids are sent as-is (no alias normalization). Full list with retirement dates: <https://developers.tripo3d.ai/en/docs/models-and-versions>.
114
+
115
+ ### Image parameters
116
+
117
+ | param | default | endpoint | notes |
118
+ | --- | --- | --- | --- |
119
+ | `prompt` | — | t2i (required), i2i | ≤1024 chars; i2i: required unless `template` is set |
120
+ | `input` | — | i2i, i2mv, edit-mv, splat | `file_` token / URL (i2i also accepts a `task_` id) |
121
+ | `inputs` | — | i2i | multiple reference images: seedream ≤4, gemini ≤10 (gpt ≤16); reference them in the prompt as `[image 1]`, `[image 2]`… |
122
+ | `template` | — | t2i, i2i | `asset_extraction` `character_completion` `t_pose` `head_extraction` `3d_enhance` `variants` `print_clay` `figure` |
123
+ | `t_pose` | `false` | t2i, i2i | boolean shortcut: convert the subject to a T-pose |
124
+ | `sketch_to_render` | `false` | t2i, i2i | turn a sketch into a rendered image |
125
+ | `model_seed` | random | splat | same seed + same image = repeatable `.splat` |
126
+
127
+ - `template=t_pose` (or `t_pose=true`) is the golden pre-step for the animation pipeline; `3d_enhance` cleans a photo before image-to-model; `print_clay` previews a single-colour print look.
128
+ - Sizes are billed in 1K / 2K / 4K tiers; `aspect_ratio` is only honoured by the gemini family.
129
+ - `image-to-multiview` output: `front_view_url` `left_view_url` `back_view_url` `right_view_url` — feed them (or the task id, see above) to `multiview-to-model`.
130
+ - `edit-multiview` takes a successful `image-to-multiview` task; `prompts` is 1–4 `{view, prompt}` objects.
131
+ - `image-to-splat` returns a `.splat` file in `output.model_url` (not a mesh — no texture / rig / convert downstream). Fixed **30 credits**, ~4 minutes. Input PNG / JPEG / WebP ≤20 MB, ≥256×256 px, subject clear and centred. Use image-to-model when you need an editable mesh.