tripo-cli 0.7.2 → 0.8.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/CHANGELOG.md +29 -0
- package/README.md +16 -14
- package/dist/ai/experts.js +26 -20
- package/dist/ai/plan-card.js +73 -34
- package/dist/ai/wizard.js +19 -19
- package/dist/commands/account.js +2 -6
- package/dist/commands/ai.js +18 -16
- package/dist/commands/batch.js +58 -18
- package/dist/commands/completion.js +1 -1
- package/dist/commands/config-cmd.js +1 -1
- package/dist/commands/doctor.js +1 -3
- package/dist/commands/generate.js +50 -8
- package/dist/commands/home.js +12 -13
- package/dist/commands/login.js +125 -182
- package/dist/commands/make.js +50 -25
- package/dist/commands/mcp.js +8 -3
- package/dist/commands/process-groups.js +29 -18
- package/dist/commands/shared.js +22 -13
- package/dist/commands/task.js +12 -11
- package/dist/commands/topup.js +6 -10
- package/dist/commands/update.js +23 -11
- package/dist/commands/view.js +3 -4
- package/dist/config/config.js +1 -1
- package/dist/core/analytics.js +39 -16
- package/dist/core/client.js +53 -21
- package/dist/core/device-auth.js +49 -1
- package/dist/core/download.js +2 -1
- package/dist/core/errors.js +24 -14
- package/dist/core/output.js +0 -4
- package/dist/core/requests.js +94 -31
- package/dist/core/task-service.js +21 -20
- package/dist/core/upload.js +23 -5
- package/dist/core/watch.js +3 -0
- package/dist/knowledge/chains.js +16 -17
- package/dist/knowledge/error-catalog.js +39 -37
- package/dist/knowledge/models.js +147 -27
- package/dist/knowledge/params.js +143 -32
- package/dist/knowledge/scenarios.js +32 -55
- package/dist/pipeline/make.js +38 -30
- package/dist/pipeline/runner.js +27 -8
- package/dist/pipeline/stdin.js +40 -3
- package/dist/program.js +3 -10
- package/dist/util/input-detect.js +10 -1
- package/package.json +3 -2
- package/skill/SKILL.md +52 -15
- package/skill/commands/account.md +8 -3
- package/skill/commands/batch.md +13 -3
- package/skill/commands/generate.md +57 -23
- package/skill/commands/make.md +43 -16
- package/skill/commands/process.md +29 -24
- package/skill/commands/task.md +4 -4
- package/skill/commands/view.md +1 -1
- package/skill/common-errors.md +14 -10
- package/skill/examples/animation.md +4 -2
- package/skill/examples/ar-web.md +6 -4
- package/skill/examples/game-asset.md +18 -7
- package/skill/examples/pipes.md +2 -2
- package/skill/examples/print.md +7 -3
- package/dist/i18n.js +0 -19
package/dist/knowledge/models.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Model routing — the single source of truth for 3D generation selection.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* H3.1 is the official default (flagship, highest fidelity) and also covers dense
|
|
5
|
+
* geometry and H-only parameters. Real-time, editable and topology-constrained
|
|
6
|
+
* assets route to P2.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Wire values are what the server actually accepts (backend whitelist:
|
|
@@ -13,24 +13,28 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export const MODEL_V31 = 'v3.1-20260211';
|
|
15
15
|
export const MODEL_P1 = 'P1-20260311';
|
|
16
|
-
/** P-series
|
|
16
|
+
/** P-series model: editable low-poly topology and quad output. */
|
|
17
17
|
export const MODEL_P2 = 'P2-20260801';
|
|
18
|
-
/** User-facing aliases normalized to the wire values above. */
|
|
18
|
+
/** User-facing aliases normalized to the wire values above (matched case-insensitively). */
|
|
19
19
|
export const MODEL_ALIASES = {
|
|
20
|
+
'H3.1': MODEL_V31,
|
|
21
|
+
'P1.0': MODEL_P1,
|
|
22
|
+
'P2.0': MODEL_P2,
|
|
23
|
+
'P2.0 Preview': MODEL_P2,
|
|
20
24
|
'v3.1': MODEL_V31,
|
|
21
25
|
'v3.1-20260211': MODEL_V31,
|
|
22
26
|
'tripo-v3.1': MODEL_V31,
|
|
23
|
-
p1: MODEL_P1,
|
|
24
27
|
P1: MODEL_P1,
|
|
25
28
|
'P1-20260311': MODEL_P1,
|
|
26
29
|
'tripo-p1': MODEL_P1,
|
|
27
|
-
p2: MODEL_P2,
|
|
28
30
|
P2: MODEL_P2,
|
|
29
31
|
'P2-20260801': MODEL_P2,
|
|
30
32
|
'tripo-p2': MODEL_P2,
|
|
31
33
|
// Older whitelisted versions, alias → wire.
|
|
34
|
+
'H3.0': 'v3.0-20250812',
|
|
32
35
|
'v3.0': 'v3.0-20250812',
|
|
33
36
|
'tripo-v3.0': 'v3.0-20250812',
|
|
37
|
+
'H2.5': 'v2.5-20250123',
|
|
34
38
|
'v2.5': 'v2.5-20250123',
|
|
35
39
|
'tripo-v2.5': 'v2.5-20250123',
|
|
36
40
|
};
|
|
@@ -52,6 +56,7 @@ export const P2_FORBIDDEN_PARAMS = ['smart_low_poly', 'generate_parts', 'geometr
|
|
|
52
56
|
/** Rig wire versions (backend whitelist; marketing names rig-v1.0/rig-v2.0 are rejected). */
|
|
53
57
|
export const RIG_MODEL_V1 = 'v1.0-20240301';
|
|
54
58
|
export const RIG_MODEL_V25 = 'v2.5-20260210';
|
|
59
|
+
export const RIG_MODEL_V30 = 'v3.0-20260909';
|
|
55
60
|
/** Marketing rig names → wire versions the server actually accepts. */
|
|
56
61
|
export const RIG_MODEL_ALIASES = {
|
|
57
62
|
'rig-v1.0': RIG_MODEL_V1,
|
|
@@ -59,6 +64,8 @@ export const RIG_MODEL_ALIASES = {
|
|
|
59
64
|
'rig-v2.0': RIG_MODEL_V25,
|
|
60
65
|
'rig-v2.5': RIG_MODEL_V25,
|
|
61
66
|
'v2.5-20260210': RIG_MODEL_V25,
|
|
67
|
+
'rig-v3.0': RIG_MODEL_V30,
|
|
68
|
+
'v3.0-20260909': RIG_MODEL_V30,
|
|
62
69
|
};
|
|
63
70
|
export function normalizeRigModel(input) {
|
|
64
71
|
return RIG_MODEL_ALIASES[input] ?? input;
|
|
@@ -67,8 +74,9 @@ export function normalizeRigModel(input) {
|
|
|
67
74
|
export const CAPABILITY_DEFAULTS = {
|
|
68
75
|
texture: 'v3.0-20250812',
|
|
69
76
|
/** Server default is v1.0 (biped-only presets); the CLI defaults to the
|
|
70
|
-
*
|
|
71
|
-
|
|
77
|
+
* v3.0 rig: recommended for humanoids, v2.5 behavior and presets for the
|
|
78
|
+
* other body types. */
|
|
79
|
+
rig: RIG_MODEL_V30,
|
|
72
80
|
segment: 'v1.0-20250506',
|
|
73
81
|
textToImage: 'seedream_v4',
|
|
74
82
|
imageToImage: 'seedream_v5',
|
|
@@ -81,8 +89,18 @@ export const RIG_TYPES = [
|
|
|
81
89
|
'avian',
|
|
82
90
|
'serpentine',
|
|
83
91
|
'aquatic',
|
|
92
|
+
'others',
|
|
84
93
|
];
|
|
85
|
-
|
|
94
|
+
/** Bone naming specs (biped only); actorcore/unity/unreal/vrm need rig v3.0. */
|
|
95
|
+
export const RIG_SPECS = ['tripo', 'mixamo', 'actorcore', 'unity', 'unreal', 'vrm'];
|
|
96
|
+
/** Retarget presets of non-biped rig types (rig v2.5/v3.0); avian and others have none. */
|
|
97
|
+
export const NON_BIPED_PRESETS = {
|
|
98
|
+
quadruped: ['preset:quadruped:walk'],
|
|
99
|
+
hexapod: ['preset:hexapod:walk'],
|
|
100
|
+
octopod: ['preset:octopod:walk'],
|
|
101
|
+
serpentine: ['preset:serpentine:march'],
|
|
102
|
+
aquatic: ['preset:aquatic:march'],
|
|
103
|
+
};
|
|
86
104
|
/** Per-endpoint image model whitelists (ImageGenerationValidation):
|
|
87
105
|
* image-to-image does NOT accept seedream_v4. */
|
|
88
106
|
export const TEXT_TO_IMAGE_MODELS = [
|
|
@@ -91,21 +109,25 @@ export const TEXT_TO_IMAGE_MODELS = [
|
|
|
91
109
|
'banana',
|
|
92
110
|
'banana_pro',
|
|
93
111
|
'banana2',
|
|
94
|
-
'chat_image_1',
|
|
95
112
|
'chat_image_1.5',
|
|
96
113
|
'chat_image_2',
|
|
114
|
+
'chat_image_2.5_flare',
|
|
115
|
+
'chat_image_2.5_sunburst',
|
|
97
116
|
];
|
|
98
117
|
export const IMAGE_TO_IMAGE_MODELS = [
|
|
99
118
|
'seedream_v5',
|
|
100
119
|
'banana',
|
|
101
120
|
'banana_pro',
|
|
102
121
|
'banana2',
|
|
103
|
-
'chat_image_1',
|
|
104
122
|
'chat_image_1.5',
|
|
105
123
|
'chat_image_2',
|
|
124
|
+
'chat_image_2.5_flare',
|
|
125
|
+
'chat_image_2.5_sunburst',
|
|
106
126
|
];
|
|
127
|
+
/** Texture model required by texture_quality=fast and delight. */
|
|
128
|
+
export const TEXTURE_MODEL_V35 = 'v3.5-20260815';
|
|
107
129
|
/** texture_model whitelist (ModelPostProcessValidation.ALLOWED_TEXTURE_MODELS). */
|
|
108
|
-
export const TEXTURE_MODELS = ['v3.0-20250812', 'v2.5-20250123'];
|
|
130
|
+
export const TEXTURE_MODELS = [TEXTURE_MODEL_V35, 'v3.0-20250812', 'v2.5-20250123'];
|
|
109
131
|
/** mesh_segmentation whitelist (ALLOWED_SEGMENT_MODELS). */
|
|
110
132
|
export const SEGMENT_MODELS = ['v1.0-20250506', 'v2.0-20260430'];
|
|
111
133
|
/** mesh/decimate whitelist (ALLOWED_DECIMATE_MODELS; blank/"default" → server default v2.0). */
|
|
@@ -137,12 +159,14 @@ export const TEXTURE_FORMATS = [
|
|
|
137
159
|
'TIFF',
|
|
138
160
|
'WEBP',
|
|
139
161
|
];
|
|
140
|
-
export const FBX_PRESETS = ['blender', '3dsmax', 'mixamo'];
|
|
162
|
+
export const FBX_PRESETS = ['blender', '3dsmax', 'mixamo', 'bake_scale'];
|
|
141
163
|
export const EXPORT_ORIENTATIONS = ['+x', '-x', '+y', '-y'];
|
|
142
|
-
|
|
143
|
-
|
|
164
|
+
/** `fast` only works with texture model v3.5 (other versions → 1004). */
|
|
165
|
+
export const TEXTURE_QUALITIES = ['fast', 'standard', 'detailed', 'extreme'];
|
|
166
|
+
const MODEL_ALIAS_LOOKUP = new Map(Object.entries(MODEL_ALIASES).map(([alias, wire]) => [alias.toLowerCase(), wire]));
|
|
167
|
+
/** Normalize any accepted alias to the canonical wire value; unknown values pass through for validation to reject. */
|
|
144
168
|
export function normalizeModel(input) {
|
|
145
|
-
return
|
|
169
|
+
return MODEL_ALIAS_LOOKUP.get(input.trim().toLowerCase()) ?? input;
|
|
146
170
|
}
|
|
147
171
|
export function isP1(model) {
|
|
148
172
|
return normalizeModel(model) === MODEL_P1;
|
|
@@ -150,21 +174,117 @@ export function isP1(model) {
|
|
|
150
174
|
export function isP2(model) {
|
|
151
175
|
return normalizeModel(model) === MODEL_P2;
|
|
152
176
|
}
|
|
153
|
-
|
|
177
|
+
function hRequirement(intent) {
|
|
178
|
+
const params = intent.params ?? {};
|
|
179
|
+
const quad = intent.quad === true || params.quad === true;
|
|
180
|
+
if (intent.denseGeometry)
|
|
181
|
+
return 'dense geometry';
|
|
182
|
+
if (params.geometry_quality !== undefined)
|
|
183
|
+
return 'geometry_quality';
|
|
184
|
+
if (params.smart_low_poly === true)
|
|
185
|
+
return 'smart_low_poly';
|
|
186
|
+
if (params.generate_parts === true)
|
|
187
|
+
return 'generate_parts';
|
|
188
|
+
if (typeof intent.faceLimit === 'number' && intent.faceLimit > (quad ? P2_FACE_LIMIT.quadMax : P2_FACE_LIMIT.triMax)) {
|
|
189
|
+
return `face budget exceeds P2 ${quad ? 'quad' : 'triangle'} limit`;
|
|
190
|
+
}
|
|
191
|
+
if (typeof intent.faceLimit === 'number' && intent.faceLimit > 0 && intent.faceLimit < P2_FACE_LIMIT.min) {
|
|
192
|
+
return 'face budget below P2 minimum';
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
/** Select a generation model from explicit choice, geometry requirements and intended use. */
|
|
154
197
|
export function chooseModel(intent) {
|
|
155
|
-
|
|
156
|
-
|
|
198
|
+
const paramModel = typeof intent.params?.model === 'string' ? intent.params.model : undefined;
|
|
199
|
+
const explicit = intent.explicit ?? paramModel;
|
|
200
|
+
if (explicit) {
|
|
201
|
+
return {
|
|
202
|
+
model: normalizeModel(explicit),
|
|
203
|
+
reason: intent.explicit ? 'explicit --model' : 'explicit model parameter',
|
|
204
|
+
};
|
|
157
205
|
}
|
|
158
|
-
|
|
159
|
-
|
|
206
|
+
const hOnly = hRequirement(intent);
|
|
207
|
+
if (hOnly) {
|
|
208
|
+
return { model: MODEL_V31, reason: `${hOnly} → v3.1` };
|
|
160
209
|
}
|
|
161
|
-
if (intent.
|
|
162
|
-
|
|
210
|
+
if (intent.preferred) {
|
|
211
|
+
const preferred = normalizeModel(intent.preferred);
|
|
212
|
+
if (preferred !== MODEL_P1)
|
|
213
|
+
return { model: preferred, reason: 'scenario preference' };
|
|
214
|
+
}
|
|
215
|
+
const params = intent.params ?? {};
|
|
216
|
+
if (intent.lowPoly || intent.realtime || intent.editable || intent.quad === true || params.quad === true || intent.faceLimit !== undefined) {
|
|
217
|
+
return { model: MODEL_P2, reason: 'real-time or editable topology → P2' };
|
|
218
|
+
}
|
|
219
|
+
return { model: MODEL_V31, reason: 'official default → v3.1' };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Official list price per generation task in credits, identical on the ov and cn
|
|
223
|
+
* model pages. Tiers: [no texture, standard/fast, detailed, extreme]. P1 and the
|
|
224
|
+
* legacy models publish no price, so they are never estimated.
|
|
225
|
+
*/
|
|
226
|
+
const GENERATION_CREDITS = {
|
|
227
|
+
[MODEL_V31]: {
|
|
228
|
+
text_to_model: [10, 20, 30, 40],
|
|
229
|
+
image_to_model: [20, 30, 40, 50],
|
|
230
|
+
multiview_to_model: [20, 30, 40, 50],
|
|
231
|
+
},
|
|
232
|
+
[MODEL_P2]: {
|
|
233
|
+
text_to_model: [100, 110, 120, 130],
|
|
234
|
+
image_to_model: [100, 110, 120, 130],
|
|
235
|
+
multiview_to_model: [100, 110, 120, 130],
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
/** List-price credits for one generation task from its validated payload; undefined when no price is published. */
|
|
239
|
+
export function estimateGenerationCredits(model, taskType, params) {
|
|
240
|
+
const wire = normalizeModel(model);
|
|
241
|
+
const tiers = GENERATION_CREDITS[wire]?.[taskType];
|
|
242
|
+
if (!tiers)
|
|
243
|
+
return undefined;
|
|
244
|
+
let credits = tiers[params.texture === false ? 0 : params.texture_quality === 'extreme' ? 3 : params.texture_quality === 'detailed' ? 2 : 1];
|
|
245
|
+
if (wire === MODEL_V31) {
|
|
246
|
+
if (params.geometry_quality === 'detailed')
|
|
247
|
+
credits += 20;
|
|
248
|
+
if (params.quad === true)
|
|
249
|
+
credits += 5;
|
|
250
|
+
if (params.smart_low_poly === true)
|
|
251
|
+
credits += 10;
|
|
252
|
+
if (params.generate_parts === true)
|
|
253
|
+
credits += 20;
|
|
163
254
|
}
|
|
164
|
-
return
|
|
255
|
+
return credits;
|
|
165
256
|
}
|
|
166
257
|
/** Keywords that signal low-poly intent in a prompt (used by make/ai). */
|
|
167
|
-
const LOW_POLY_HINTS = [/low[\s_-]?poly/i, /低模/, /低面/, /手游/, /voxel/i, /像素风/];
|
|
258
|
+
const LOW_POLY_HINTS = [/low[\s_-]?poly/i, /低模/, /低面/, /低多边形/, /手游/, /voxel/i, /像素风/];
|
|
259
|
+
/** Physical games, product nouns (gaming PC, console) and animation *styles* are not real-time intent. */
|
|
260
|
+
const REALTIME_HINTS = [
|
|
261
|
+
/\b(?:(?<!\b(?:board|card|tabletop|dice|party)[\s_-]?)games?|gameplay|real[\s_-]?time|unity|unreal(?:\s+engine)?|godot|three\.?js|webgl)\b/i,
|
|
262
|
+
/\b(?:editable|edit[\s_-]?friendly|topology|retopo(?:logy)?|quad(?:s)?|rig(?:[\s_-]?ready|ged|ging)?|animat(?:e|ions?)(?![\s_-]?styl))\b/i,
|
|
263
|
+
/(?<!棋盘|桌面)游戏|手游|端游|实时|引擎|可编辑|可修改|拓扑|重拓扑|四边面|四边形|绑定|骨骼|动画(?![风片])|虚幻/,
|
|
264
|
+
];
|
|
265
|
+
const DENSE_GEOMETRY_HINTS = [
|
|
266
|
+
/\b(?:dense|high[\s_-]?density|high[\s_-]?detail(?:ed)?)\s+(?:mesh|geometry|topology)\b/i,
|
|
267
|
+
/\bhigh[\s_-]?poly(?:gon(?:al)?)?\b/i,
|
|
268
|
+
/\bnanite\b/i,
|
|
269
|
+
/\b(?:million|millions)\s+of\s+(?:faces|triangles|polygons)\b/i,
|
|
270
|
+
/高模|高面数|高多边形|高密度(?:网格|几何)|密集(?:网格|几何)|稠密(?:网格|几何)|高精度(?:网格|几何)|百万(?:面|三角面)/,
|
|
271
|
+
];
|
|
168
272
|
export function detectLowPolyIntent(text) {
|
|
169
273
|
return LOW_POLY_HINTS.some((re) => re.test(text));
|
|
170
274
|
}
|
|
275
|
+
/** Detect uses that benefit from P-series topology without treating visual quality as mesh density. */
|
|
276
|
+
export function detectRealtimeIntent(text) {
|
|
277
|
+
return REALTIME_HINTS.some((re) => re.test(text));
|
|
278
|
+
}
|
|
279
|
+
/** Detect only explicit dense-geometry requirements; phrases such as "high quality" do not match. */
|
|
280
|
+
export function detectDenseGeometryIntent(text) {
|
|
281
|
+
// Match each mention independently: negating one must not hide a later positive requirement.
|
|
282
|
+
return DENSE_GEOMETRY_HINTS.some((re) => Array.from(text.matchAll(new RegExp(re.source, `${re.flags}g`))).some((match) => {
|
|
283
|
+
const before = text.slice(0, match.index);
|
|
284
|
+
const after = text.slice(match.index + match[0].length);
|
|
285
|
+
const negatedBefore = /(?:\b(?:no|not|without|avoid|exclude)\s+(?:(?:a|an|any|the)\s+)?|\b(?:do\s+not|don't|does\s+not|doesn't)\s+(?:need|want|require|use)\s+(?:(?:a|an|any|the)\s+)?|\bno\s+need\s+for\s+(?:(?:a|an|any|the)\s+)?|(?:不要|不需要|无需|不用|不使用|避免|非)\s*)$/i.test(before);
|
|
286
|
+
const negatedAfter = /^\s*(?:(?:mesh|geometry|topology)\s+)?(?:is\s+not|isn't|not)\s+(?:needed|required|wanted)\b/i.test(after)
|
|
287
|
+
|| /^\s*(?:不需要|不要|无需)/.test(after);
|
|
288
|
+
return !negatedBefore && !negatedAfter;
|
|
289
|
+
}));
|
|
290
|
+
}
|
package/dist/knowledge/params.js
CHANGED
|
@@ -5,14 +5,96 @@
|
|
|
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, 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';
|
|
8
|
+
import { CAPABILITY_DEFAULTS, IMAGE_TO_IMAGE_MODELS, TEXT_TO_IMAGE_MODELS, CONVERT_FORMATS, DECIMATE_MODELS, EXPORT_ORIENTATIONS, FBX_PRESETS, LEGACY_3D_MODELS, MODEL_P1, MODEL_P2, MODEL_V31, P1_FACE_LIMIT, P1_FORBIDDEN_PARAMS, P2_FACE_LIMIT, P2_FORBIDDEN_PARAMS, TEXTURE_FORMATS, TEXTURE_MODEL_V35, TEXTURE_QUALITIES, isP1, isP2, normalizeModel, } from './models.js';
|
|
9
9
|
const PROMPT_MAX = 1024;
|
|
10
10
|
const NEGATIVE_PROMPT_MAX = 255;
|
|
11
|
+
/** Image endpoint contracts; never silently substitute a paid model. */
|
|
12
|
+
export function validateImageParams(input, editing) {
|
|
13
|
+
const params = { ...input };
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const errors = [];
|
|
16
|
+
const model = params.model === undefined
|
|
17
|
+
? (editing ? CAPABILITY_DEFAULTS.imageToImage : CAPABILITY_DEFAULTS.textToImage)
|
|
18
|
+
: params.model;
|
|
19
|
+
params.model = model;
|
|
20
|
+
const models = editing ? IMAGE_TO_IMAGE_MODELS : TEXT_TO_IMAGE_MODELS;
|
|
21
|
+
if (model === 'chat_image_1') {
|
|
22
|
+
errors.push('model chat_image_1 retired on 2026-10-23; explicitly choose chat_image_2 or chat_image_2.5_flare/sunburst (no automatic resubmission)');
|
|
23
|
+
}
|
|
24
|
+
else if (!models.includes(model)) {
|
|
25
|
+
errors.push(`unsupported image model "${String(model)}" for ${editing ? 'image-to-image' : 'text-to-image'}`);
|
|
26
|
+
}
|
|
27
|
+
const chat25 = model === 'chat_image_2.5_flare' || model === 'chat_image_2.5_sunburst';
|
|
28
|
+
if (params.size !== undefined && typeof model === 'string' && !validImageSize(model, params.size)) {
|
|
29
|
+
errors.push(`size is unsupported for model ${model}`);
|
|
30
|
+
}
|
|
31
|
+
if ((model === 'seedream_v4' || model === 'seedream_v5') && typeof params.size === 'string' && params.size.includes('x')) {
|
|
32
|
+
warnings.push('Seedream exact pixel-count limits unverified: official MP ranges are rounded; server validation still applies');
|
|
33
|
+
}
|
|
34
|
+
if (params.quality !== undefined) {
|
|
35
|
+
const qualities = chat25 ? ['low', 'medium', 'high', 'xhigh', 'max']
|
|
36
|
+
: model === 'chat_image_2' ? ['low', 'medium', 'high'] : [];
|
|
37
|
+
if (!qualities.includes(params.quality))
|
|
38
|
+
errors.push(`quality is unsupported for model ${String(model)} (auto is not supported)`);
|
|
39
|
+
}
|
|
40
|
+
if (params.output_format !== undefined && !['png', 'jpeg'].includes(params.output_format)) {
|
|
41
|
+
errors.push('output_format must be png or jpeg');
|
|
42
|
+
}
|
|
43
|
+
if (params.background !== undefined) {
|
|
44
|
+
if (!chat25) {
|
|
45
|
+
delete params.background;
|
|
46
|
+
warnings.push('background is ignored outside chat_image_2.5 models (removed)');
|
|
47
|
+
}
|
|
48
|
+
else if (!['auto', 'opaque', 'transparent'].includes(params.background)) {
|
|
49
|
+
errors.push('background must be auto, opaque or transparent');
|
|
50
|
+
}
|
|
51
|
+
else if (params.background === 'transparent' && params.output_format !== undefined && params.output_format !== 'png') {
|
|
52
|
+
errors.push('background=transparent requires output_format=png');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { params, warnings, errors };
|
|
56
|
+
}
|
|
57
|
+
/** Only documented size rules; rounded Seedream MP limits are not exact pixel thresholds. */
|
|
58
|
+
function validImageSize(model, size) {
|
|
59
|
+
if (typeof size !== 'string')
|
|
60
|
+
return false;
|
|
61
|
+
const match = /^([1-9]\d*)x([1-9]\d*)$/.exec(size);
|
|
62
|
+
const w = Number(match?.[1]);
|
|
63
|
+
const h = Number(match?.[2]);
|
|
64
|
+
const pixels = !!match && Number.isSafeInteger(w) && Number.isSafeInteger(h);
|
|
65
|
+
if (model === 'banana') {
|
|
66
|
+
return ['1024x1024', '832x1248', '1248x832', '864x1184', '1184x864', '896x1152', '1152x896', '768x1344', '1344x768', '1536x672'].includes(size);
|
|
67
|
+
}
|
|
68
|
+
if (model === 'banana2')
|
|
69
|
+
return ['0.5K', '1K', '2K', '4K'].includes(size);
|
|
70
|
+
if (model === 'banana_pro')
|
|
71
|
+
return ['1K', '2K', '4K'].includes(size) || pixels;
|
|
72
|
+
if (model === 'seedream_v4' || model === 'seedream_v5') {
|
|
73
|
+
return ['2K', model === 'seedream_v5' ? '3K' : '4K'].includes(size) || (pixels && w / h >= 1 / 16 && w / h <= 16);
|
|
74
|
+
}
|
|
75
|
+
if (model === 'chat_image_1.5')
|
|
76
|
+
return ['1024x1024', '1024x1536', '1536x1024', 'auto'].includes(size);
|
|
77
|
+
if (model === 'chat_image_2' || model.startsWith('chat_image_2.5_')) {
|
|
78
|
+
return size === 'auto' || (pixels && w <= 3840 && h <= 3840 && w % 16 === 0 && h % 16 === 0 &&
|
|
79
|
+
Math.max(w, h) <= 3 * Math.min(w, h) && w * h >= 655360 && w * h <= 8294400);
|
|
80
|
+
}
|
|
81
|
+
return true; // Unknown models are rejected separately.
|
|
82
|
+
}
|
|
83
|
+
function validateFaceLimit(params, errors) {
|
|
84
|
+
if (params.face_limit !== undefined &&
|
|
85
|
+
(typeof params.face_limit !== 'number' || !Number.isFinite(params.face_limit) || !Number.isInteger(params.face_limit))) {
|
|
86
|
+
errors.push('face_limit must be a finite integer');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
11
89
|
/** Validate + auto-fix parameters for 3D generation (text/image/multiview-to-model). */
|
|
12
90
|
export function validateGenerationParams(model, input) {
|
|
13
91
|
const params = { ...input };
|
|
14
92
|
const warnings = [];
|
|
15
93
|
const errors = [];
|
|
94
|
+
if (![MODEL_V31, MODEL_P1, MODEL_P2, ...LEGACY_3D_MODELS].includes(normalizeModel(model))) {
|
|
95
|
+
errors.push(`unsupported 3D model "${model}"`);
|
|
96
|
+
}
|
|
97
|
+
validateFaceLimit(params, errors);
|
|
16
98
|
if (typeof params.prompt === 'string' && params.prompt.length > PROMPT_MAX) {
|
|
17
99
|
errors.push(`prompt exceeds ${PROMPT_MAX} characters (${params.prompt.length})`);
|
|
18
100
|
}
|
|
@@ -44,10 +126,16 @@ export function validateGenerationParams(model, input) {
|
|
|
44
126
|
params.texture = true;
|
|
45
127
|
warnings.push('pbr=true forces texture=true (adjusted)');
|
|
46
128
|
}
|
|
47
|
-
// [hard]
|
|
129
|
+
// [hard] pbr defaults to true server-side, so texture=false alone would still be textured.
|
|
130
|
+
if (params.texture === false && params.pbr === undefined) {
|
|
131
|
+
params.pbr = false;
|
|
132
|
+
warnings.push('texture=false implies pbr=false (adjusted)');
|
|
133
|
+
}
|
|
134
|
+
// [hard] generate_parts outputs bare geometry: texture/pbr/quad must be off
|
|
135
|
+
// (texture and pbr default to true server-side, so omitting them counts as on).
|
|
48
136
|
if (params.generate_parts === true) {
|
|
49
137
|
for (const key of ['texture', 'pbr', 'quad']) {
|
|
50
|
-
if (params[key] === true) {
|
|
138
|
+
if (key === 'quad' ? params[key] === true : params[key] !== false) {
|
|
51
139
|
params[key] = false;
|
|
52
140
|
warnings.push(`generate_parts=true disables ${key} (adjusted; run "tripo model texture" afterwards)`);
|
|
53
141
|
}
|
|
@@ -59,7 +147,7 @@ export function validateGenerationParams(model, input) {
|
|
|
59
147
|
if (typeof face === 'number') {
|
|
60
148
|
if (face < P1_FACE_LIMIT.min || face > P1_FACE_LIMIT.max) {
|
|
61
149
|
errors.push(`P1 face_limit must be within ${P1_FACE_LIMIT.min}–${P1_FACE_LIMIT.max} (got ${face}); ` +
|
|
62
|
-
`use tripo-v3.1 for
|
|
150
|
+
`use tripo-v3.1 for budgets outside this range`);
|
|
63
151
|
}
|
|
64
152
|
}
|
|
65
153
|
}
|
|
@@ -71,7 +159,7 @@ export function validateGenerationParams(model, input) {
|
|
|
71
159
|
const max = quad ? P2_FACE_LIMIT.quadMax : P2_FACE_LIMIT.triMax;
|
|
72
160
|
if (face < P2_FACE_LIMIT.min || face > max) {
|
|
73
161
|
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
|
|
162
|
+
`use tripo-v3.1 for budgets outside this range`);
|
|
75
163
|
}
|
|
76
164
|
}
|
|
77
165
|
}
|
|
@@ -84,21 +172,44 @@ export function validateGenerationParams(model, input) {
|
|
|
84
172
|
}
|
|
85
173
|
}
|
|
86
174
|
}
|
|
87
|
-
|
|
88
|
-
!TEXTURE_QUALITIES.includes(params.texture_quality)) {
|
|
89
|
-
errors.push(`texture_quality must be one of ${TEXTURE_QUALITIES.join('/')}`);
|
|
90
|
-
}
|
|
175
|
+
applyTextureV35(params, 'texture_version', warnings, errors);
|
|
91
176
|
// [hard] quad topology cannot be stored in GLB → output will be FBX.
|
|
92
177
|
if (params.quad === true) {
|
|
93
178
|
warnings.push('quad=true forces FBX output (quad topology cannot be stored in GLB)');
|
|
94
179
|
}
|
|
95
180
|
return { params, warnings, errors };
|
|
96
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* [hard] texture_quality=fast and delight only exist on texture model v3.5:
|
|
184
|
+
* fast on any other version is a 1004, delight is silently ignored. v3.5 is
|
|
185
|
+
* selected when no version was given. `key` is `texture_version` on the
|
|
186
|
+
* generation endpoints and `model` on /v3/models/texture.
|
|
187
|
+
*/
|
|
188
|
+
export function applyTextureV35(params, key, warnings, errors) {
|
|
189
|
+
if (params.texture_quality !== undefined && !TEXTURE_QUALITIES.includes(params.texture_quality)) {
|
|
190
|
+
errors.push(`texture_quality must be one of ${TEXTURE_QUALITIES.join('/')}`);
|
|
191
|
+
}
|
|
192
|
+
const feature = params.texture_quality === 'fast' ? 'texture_quality=fast' : params.delight !== undefined ? 'delight' : undefined;
|
|
193
|
+
if (!feature)
|
|
194
|
+
return;
|
|
195
|
+
const version = params[key];
|
|
196
|
+
if (version === undefined) {
|
|
197
|
+
params[key] = TEXTURE_MODEL_V35;
|
|
198
|
+
warnings.push(`${feature} needs ${key}=${TEXTURE_MODEL_V35} (selected)`);
|
|
199
|
+
}
|
|
200
|
+
else if (version !== TEXTURE_MODEL_V35) {
|
|
201
|
+
if (feature === 'delight')
|
|
202
|
+
warnings.push(`delight is ignored unless ${key}=${TEXTURE_MODEL_V35}`);
|
|
203
|
+
else
|
|
204
|
+
errors.push(`texture_quality=fast requires ${key}=${TEXTURE_MODEL_V35} (got "${String(version)}")`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
97
207
|
/** Validate parameters for models/convert. */
|
|
98
208
|
export function validateConvertParams(input) {
|
|
99
209
|
const params = { ...input };
|
|
100
210
|
const warnings = [];
|
|
101
211
|
const errors = [];
|
|
212
|
+
validateFaceLimit(params, errors);
|
|
102
213
|
const format = typeof params.format === 'string' ? params.format.toUpperCase() : undefined;
|
|
103
214
|
if (!format) {
|
|
104
215
|
errors.push(`convert requires format (${CONVERT_FORMATS.join('/')})`);
|
|
@@ -109,9 +220,12 @@ export function validateConvertParams(input) {
|
|
|
109
220
|
else {
|
|
110
221
|
params.format = format;
|
|
111
222
|
}
|
|
112
|
-
// [hard] quad topology cannot live in GLTF/GLB.
|
|
223
|
+
// [hard] quad topology cannot live in GLTF/GLB; quad output is always FBX.
|
|
113
224
|
if (params.quad === true && format === 'GLTF') {
|
|
114
|
-
errors.push('quad=true cannot export GLTF (quad topology is not storable in glTF); use FBX
|
|
225
|
+
errors.push('quad=true cannot export GLTF (quad topology is not storable in glTF); quad forces FBX output — use format=FBX');
|
|
226
|
+
}
|
|
227
|
+
else if (params.quad === true && format && format !== 'FBX') {
|
|
228
|
+
warnings.push(`quad=true forces FBX output (format ${format} is overridden)`);
|
|
115
229
|
}
|
|
116
230
|
if (params.force_symmetry === true && params.quad !== true) {
|
|
117
231
|
delete params.force_symmetry;
|
|
@@ -142,30 +256,26 @@ export function validateConvertParams(input) {
|
|
|
142
256
|
!EXPORT_ORIENTATIONS.includes(params.export_orientation)) {
|
|
143
257
|
errors.push(`export_orientation must be one of ${EXPORT_ORIENTATIONS.join('/')}`);
|
|
144
258
|
}
|
|
145
|
-
// [soft] cost transparency
|
|
146
|
-
|
|
147
|
-
const defaults = {
|
|
148
|
-
texture_size: 4096,
|
|
149
|
-
texture_format: 'JPEG',
|
|
150
|
-
bake: true,
|
|
151
|
-
pack_uv: false,
|
|
152
|
-
export_vertex_colors: false,
|
|
153
|
-
pivot_to_center_bottom: false,
|
|
154
|
-
scale_factor: 1,
|
|
155
|
-
with_animation: true,
|
|
156
|
-
animate_in_place: false,
|
|
157
|
-
export_orientation: '+x',
|
|
158
|
-
fbx_preset: 'blender',
|
|
159
|
-
quad: false,
|
|
160
|
-
flatten_bottom: false,
|
|
161
|
-
force_symmetry: false,
|
|
162
|
-
};
|
|
163
|
-
const complex = Object.entries(defaults).some(([k, dv]) => params[k] !== undefined && params[k] !== dv) || params.face_limit !== undefined || params.part_names !== undefined;
|
|
164
|
-
if (complex) {
|
|
259
|
+
// [soft] cost transparency.
|
|
260
|
+
if (isComplexConvert(params)) {
|
|
165
261
|
warnings.push('non-default convert options use the "complex convert" price tier');
|
|
166
262
|
}
|
|
167
263
|
return { params, warnings, errors };
|
|
168
264
|
}
|
|
265
|
+
/** Pricing page: a non-default value of any of these (or any face_limit) bills the complex convert tier. */
|
|
266
|
+
const COMPLEX_CONVERT_DEFAULTS = {
|
|
267
|
+
quad: false,
|
|
268
|
+
flatten_bottom: false,
|
|
269
|
+
flatten_bottom_threshold: 0.01,
|
|
270
|
+
texture_size: 4096,
|
|
271
|
+
texture_format: 'JPEG',
|
|
272
|
+
pivot_to_center_bottom: false,
|
|
273
|
+
scale_factor: 1,
|
|
274
|
+
};
|
|
275
|
+
export function isComplexConvert(params) {
|
|
276
|
+
return (params.face_limit !== undefined ||
|
|
277
|
+
Object.entries(COMPLEX_CONVERT_DEFAULTS).some(([k, dv]) => params[k] !== undefined && String(params[k]).toUpperCase() !== String(dv).toUpperCase()));
|
|
278
|
+
}
|
|
169
279
|
/**
|
|
170
280
|
* Validate parameters for mesh/decimate (ModelPostProcessValidation):
|
|
171
281
|
* v2.0 (default) / v3.0: face_limit 500–20000 tri / 500–10000 quad.
|
|
@@ -176,6 +286,7 @@ export function validateDecimateParams(input) {
|
|
|
176
286
|
const params = { ...input };
|
|
177
287
|
const warnings = [];
|
|
178
288
|
const errors = [];
|
|
289
|
+
validateFaceLimit(params, errors);
|
|
179
290
|
const model = typeof params.model === 'string' ? params.model.toLowerCase() : undefined;
|
|
180
291
|
if (model !== undefined && model !== 'default' && !DECIMATE_MODELS.includes(model)) {
|
|
181
292
|
errors.push(`decimate model must be ${DECIMATE_MODELS.join('/')} (got "${String(params.model)}")`);
|
|
@@ -184,7 +295,7 @@ export function validateDecimateParams(input) {
|
|
|
184
295
|
if (params.smart_uv === true) {
|
|
185
296
|
if (model === undefined) {
|
|
186
297
|
params.model = 'v3.0';
|
|
187
|
-
warnings.push('smart_uv requires decimate v3.0: using model=v3.0 (
|
|
298
|
+
warnings.push('smart_uv requires decimate v3.0: using model=v3.0 (cost not estimated; check current regional pricing)');
|
|
188
299
|
}
|
|
189
300
|
else if (model !== 'v3.0') {
|
|
190
301
|
errors.push(`smart_uv only works with decimate model v3.0 (got "${String(params.model)}")`);
|