完善Tripo枚举与参数组合校验
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m19s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m22s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m26s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m38s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m1s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m54s
Project CI / Native shell tests (pull_request) Failing after 3m5s
Project CI / AI game creator shell web tests (pull_request) Failing after 2m13s
Project CI / Frontend tests (pull_request) Successful in 5m44s

按提供的 Tripo 文档补齐 compression、texture quality 与 texture version 枚举

移除未在三个生成接口出现的 style 占位字段并同步 Rust/TypeScript contracts

增加模型版本、纹理版本、生成部件、quad、smart-low-poly 与 face limit 的副作用前校验

同步 Provider 技术方案和 ADR
This commit is contained in:
2026-09-18 17:23:55 +08:00
parent 37b355a814
commit 5be4a51ac1
25 changed files with 472 additions and 60 deletions
@@ -4,4 +4,4 @@
`tripo3d-sdk` 只允许出现在 `platform-tripo` 内部。该 crate 以显式必填配置创建可复用 provider client,只公开自己的 text-to-model、image-to-model、multiview-to-model DTO、通用单次 task 查询、显式模型下载和结构化错误;不暴露 SDK 类型、`wait_for_task` 或后台轮询。共享产品契约与 provider task 结果分层,provider adapter 不伪造 `resourceId`/`assetId`shared-contracts 的 Rust 类型始终通过 ts-rs 生成目录化 TypeScript binding。
选择这个边界是为了避免 API 层绑定第三方 SDK 的任务/错误模型,同时让未来的资源持久化、幂等和 provider 替换由应用层负责。`compress/style` 的正式 enum 和完整组合校验等待 Tripo API 文档,当前只保留编译占位和 TODO,不通过裸字符串逃生
选择这个边界是为了避免 API 层绑定第三方 SDK 的任务/错误模型,同时让未来的资源持久化、幂等和 provider 替换由应用层负责。请求 enum 以 Tripo API 文档为准;当前 `compress=geometry`、纹理质量 `fast/standard/detailed/extreme` 及纹理版本已经纳入目录化 Rust/TypeScript contracts。文档确认不存在于这三个 endpoint 的 `style` 字段不再保留占位类型,组合约束在 provider 副作用前执行
@@ -20,7 +20,9 @@ provider task 结果与产品资源结果分离。provider adapter 不生成 `re
## 请求与类型
text-to-model 请求包含 SDK 已公开的字段。稳定且已由 SDK 常量定义的字段使用 Rust/TypeScript enum`compress``style` 暂时使用带示例 variant 的 enum,并保留官方 API 文档补全 TODO。完整组合校验等待官方 API 文档后一次性落地,不复制不完整的规则集合
三个生成请求使用官方文档已确认的 enum:模型版本、纹理版本、纹理质量(含 `fast`)、几何质量、纹理对齐、输入方向、导出方向和压缩类型(`geometry`)。文档未将 `style` 列为这三个 endpoint 的请求字段,因此不再保留占位 enum
提交前由 provider 统一执行组合校验:模型必填;`fast` 必须配 `v3.5-20260815`;几何质量 / 压缩 / 自动尺寸 / P 系列与 H 系列能力按模型版本限制;`quad``generate_parts``smart_low_poly``texture/pbr` 组合按文档的互斥关系拒绝;`face_limit` 按模型、quad 和 smart-low-poly 模式检查范围。SDK 返回的参数错误仍统一归一为 `TripoError`
## 验收
@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Model3dCompression = 'example';
export type Model3dCompression = 'geometry';
@@ -1,3 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Model3dTextureQuality = 'standard' | 'detailed' | 'extreme';
export type Model3dTextureQuality =
| 'fast'
| 'standard'
| 'detailed'
| 'extreme';
@@ -1,3 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Model3dStyle = 'example';
export type Model3dTextureVersion =
| 'v3.5-20260815'
| 'v3.0-20250812'
| 'v2.5-20250123';
@@ -4,9 +4,9 @@ import type { Model3dExportOrientation } from '../common/Model3dExportOrientatio
import type { Model3dGeometryQuality } from '../common/Model3dGeometryQuality';
import type { Model3dInputOrientation } from '../common/Model3dInputOrientation';
import type { Model3dModelVersion } from '../common/Model3dModelVersion';
import type { Model3dStyle } from '../common/Model3dStyle';
import type { Model3dTextureAlignment } from '../common/Model3dTextureAlignment';
import type { Model3dTextureQuality } from '../common/Model3dTextureQuality';
import type { Model3dTextureVersion } from '../common/Model3dTextureVersion';
export type Model3dImageToModelRequest = {
input: string;
@@ -17,6 +17,8 @@ export type Model3dImageToModelRequest = {
texture: boolean | null;
pbr: boolean | null;
textureQuality: Model3dTextureQuality | null;
textureVersion: Model3dTextureVersion | null;
delight: boolean | null;
textureAlignment: Model3dTextureAlignment | null;
geometryQuality: Model3dGeometryQuality | null;
faceLimit: number | null;
@@ -28,5 +30,4 @@ export type Model3dImageToModelRequest = {
compress: Model3dCompression | null;
exportUv: boolean | null;
exportOrientation: Model3dExportOrientation | null;
style: Model3dStyle | null;
};
@@ -6,6 +6,7 @@ import type { Model3dInputOrientation } from '../common/Model3dInputOrientation'
import type { Model3dModelVersion } from '../common/Model3dModelVersion';
import type { Model3dTextureAlignment } from '../common/Model3dTextureAlignment';
import type { Model3dTextureQuality } from '../common/Model3dTextureQuality';
import type { Model3dTextureVersion } from '../common/Model3dTextureVersion';
import type { Model3dMultiviewInputs } from './Model3dMultiviewInputs';
export type Model3dMultiviewToModelRequest = {
@@ -16,6 +17,8 @@ export type Model3dMultiviewToModelRequest = {
texture: boolean | null;
pbr: boolean | null;
textureQuality: Model3dTextureQuality | null;
textureVersion: Model3dTextureVersion | null;
delight: boolean | null;
geometryQuality: Model3dGeometryQuality | null;
textureAlignment: Model3dTextureAlignment | null;
faceLimit: number | null;
@@ -3,8 +3,8 @@ import type { Model3dCompression } from '../common/Model3dCompression';
import type { Model3dExportOrientation } from '../common/Model3dExportOrientation';
import type { Model3dGeometryQuality } from '../common/Model3dGeometryQuality';
import type { Model3dModelVersion } from '../common/Model3dModelVersion';
import type { Model3dStyle } from '../common/Model3dStyle';
import type { Model3dTextureQuality } from '../common/Model3dTextureQuality';
import type { Model3dTextureVersion } from '../common/Model3dTextureVersion';
export type Model3dTextToModelRequest = {
prompt: string;
@@ -16,6 +16,8 @@ export type Model3dTextToModelRequest = {
texture: boolean | null;
pbr: boolean | null;
textureQuality: Model3dTextureQuality | null;
textureVersion: Model3dTextureVersion | null;
delight: boolean | null;
geometryQuality: Model3dGeometryQuality | null;
faceLimit: number | null;
autoSize: boolean | null;
@@ -25,5 +27,4 @@ export type Model3dTextToModelRequest = {
compress: Model3dCompression | null;
exportUv: boolean | null;
exportOrientation: Model3dExportOrientation | null;
style: Model3dStyle | null;
};
+1
View File
@@ -4159,6 +4159,7 @@ dependencies = [
name = "platform-tripo"
version = "0.1.0"
dependencies = [
"serde_json",
"shared-contracts",
"tripo3d-sdk",
"url",
@@ -6,5 +6,6 @@ license.workspace = true
[dependencies]
shared-contracts = { workspace = true }
serde_json = { workspace = true }
tripo3d-sdk = { workspace = true }
url = { workspace = true }
@@ -5,12 +5,15 @@ pub enum TripoField {
Prompt,
Model,
NegativePrompt,
Input,
Inputs,
ImageSeed,
ModelSeed,
TextureSeed,
Texture,
Pbr,
TextureQuality,
TextureVersion,
GeometryQuality,
FaceLimit,
AutoSize,
@@ -20,7 +23,7 @@ pub enum TripoField {
Compress,
ExportUv,
ExportOrientation,
Style,
Delight,
TaskId,
}
@@ -30,12 +33,15 @@ impl fmt::Display for TripoField {
Self::Prompt => "prompt",
Self::Model => "model",
Self::NegativePrompt => "negative_prompt",
Self::Input => "input",
Self::Inputs => "inputs",
Self::ImageSeed => "image_seed",
Self::ModelSeed => "model_seed",
Self::TextureSeed => "texture_seed",
Self::Texture => "texture",
Self::Pbr => "pbr",
Self::TextureQuality => "texture_quality",
Self::TextureVersion => "texture_version",
Self::GeometryQuality => "geometry_quality",
Self::FaceLimit => "face_limit",
Self::AutoSize => "auto_size",
@@ -45,7 +51,7 @@ impl fmt::Display for TripoField {
Self::Compress => "compress",
Self::ExportUv => "export_uv",
Self::ExportOrientation => "export_orientation",
Self::Style => "style",
Self::Delight => "delight",
Self::TaskId => "task_id",
};
f.write_str(name)
@@ -13,4 +13,6 @@ pub use types::{
TripoDownloadedModel, TripoModelUrl, TripoTaskFailure, TripoTaskHandle, TripoTaskOutput,
TripoTaskSnapshot, TripoTaskType,
};
pub(crate) use validation::validate_task_id;
pub(crate) use validation::{
TripoGenerationOptions, validate_generation_options, validate_task_id,
};
@@ -1,5 +1,225 @@
use shared_contracts::model3d::common::{
Model3dCompression, Model3dGeometryQuality, Model3dModelVersion, Model3dTextureQuality,
Model3dTextureVersion,
};
use super::{TripoError, TripoField, TripoValidationReason};
pub(crate) struct TripoGenerationOptions {
pub model: Option<Model3dModelVersion>,
pub texture: Option<bool>,
pub pbr: Option<bool>,
pub texture_quality: Option<Model3dTextureQuality>,
pub texture_version: Option<Model3dTextureVersion>,
pub geometry_quality: Option<Model3dGeometryQuality>,
pub face_limit: Option<i64>,
pub auto_size: Option<bool>,
pub quad: Option<bool>,
pub smart_low_poly: Option<bool>,
pub generate_parts: Option<bool>,
pub compress: Option<Model3dCompression>,
}
pub(crate) fn validate_generation_options(
options: &TripoGenerationOptions,
) -> Result<(), TripoError> {
let model = options.model.ok_or_else(|| {
invalid(
Some(TripoField::Model),
TripoValidationReason::Required,
"model is required",
)
})?;
let family = ModelFamily::from(model);
if options.texture_quality.is_some() && matches!(family, ModelFamily::H25) {
return Err(invalid(
Some(TripoField::TextureQuality),
TripoValidationReason::InvalidCombination,
"texture_quality is not supported by v2.5-20250123",
));
}
if options.texture_quality.is_some_and(|quality| {
matches!(quality, Model3dTextureQuality::Fast)
&& options.texture_version != Some(Model3dTextureVersion::V35)
}) {
return Err(invalid(
Some(TripoField::TextureQuality),
TripoValidationReason::InvalidCombination,
"texture_quality=fast requires texture_version=v3.5-20260815",
));
}
if options.geometry_quality.is_some() && !matches!(family, ModelFamily::H31 | ModelFamily::H30)
{
return Err(invalid(
Some(TripoField::GeometryQuality),
TripoValidationReason::InvalidCombination,
"geometry_quality is only supported by v3.1-20260211 and v3.0-20250812",
));
}
if options.compress.is_some() && !matches!(family, ModelFamily::H31 | ModelFamily::H30) {
return Err(invalid(
Some(TripoField::Compress),
TripoValidationReason::InvalidCombination,
"compress is only supported by v3.1-20260211 and v3.0-20250812",
));
}
if options.auto_size.is_some() && matches!(family, ModelFamily::H25) {
return Err(invalid(
Some(TripoField::AutoSize),
TripoValidationReason::InvalidCombination,
"auto_size is not supported by v2.5-20250123",
));
}
if options
.smart_low_poly
.is_some_and(|enabled| enabled && !matches!(family, ModelFamily::H31 | ModelFamily::H30))
{
return Err(invalid(
Some(TripoField::SmartLowPoly),
TripoValidationReason::InvalidCombination,
"smart_low_poly is only supported by v3.1-20260211 and v3.0-20250812",
));
}
if options
.generate_parts
.is_some_and(|enabled| enabled && !matches!(family, ModelFamily::H31 | ModelFamily::H30))
{
return Err(invalid(
Some(TripoField::GenerateParts),
TripoValidationReason::InvalidCombination,
"generate_parts is only supported by v3.1-20260211 and v3.0-20250812",
));
}
if options.quad == Some(true)
&& !matches!(
family,
ModelFamily::H31 | ModelFamily::H30 | ModelFamily::P2
)
{
return Err(invalid(
Some(TripoField::Quad),
TripoValidationReason::InvalidCombination,
"quad=true is only supported by v3.x models and P2-20260801",
));
}
if options.generate_parts == Some(true) {
if options.texture != Some(false) {
return Err(invalid(
Some(TripoField::GenerateParts),
TripoValidationReason::InvalidCombination,
"generate_parts=true requires texture=false",
));
}
if options.pbr != Some(false) {
return Err(invalid(
Some(TripoField::GenerateParts),
TripoValidationReason::InvalidCombination,
"generate_parts=true requires pbr=false",
));
}
if options.quad == Some(true) || options.smart_low_poly == Some(true) {
return Err(invalid(
Some(TripoField::GenerateParts),
TripoValidationReason::InvalidCombination,
"generate_parts=true cannot be combined with quad or smart_low_poly",
));
}
}
if let Some(face_limit) = options.face_limit {
let (minimum, maximum) = face_limit_bounds(family, options);
if face_limit < minimum || face_limit > maximum {
return Err(invalid(
Some(TripoField::FaceLimit),
TripoValidationReason::InvalidCombination,
&format!(
"face_limit must be between {minimum} and {maximum} for the selected model and options"
),
));
}
}
Ok(())
}
#[derive(Clone, Copy)]
enum ModelFamily {
H25,
H30,
H31,
P1,
P2,
}
impl From<Model3dModelVersion> for ModelFamily {
fn from(model: Model3dModelVersion) -> Self {
match model {
Model3dModelVersion::H25 => Self::H25,
Model3dModelVersion::H30 => Self::H30,
Model3dModelVersion::H31 => Self::H31,
Model3dModelVersion::P1 => Self::P1,
Model3dModelVersion::P2 => Self::P2,
}
}
}
fn face_limit_bounds(family: ModelFamily, options: &TripoGenerationOptions) -> (i64, i64) {
if options.smart_low_poly == Some(true) {
return if options.quad == Some(true) {
(500, 10_000)
} else {
(500, 20_000)
};
}
match family {
ModelFamily::H25 => (1, 500_000),
ModelFamily::H30 => {
if options.quad == Some(true) {
(1, 150_000)
} else if options.geometry_quality == Some(Model3dGeometryQuality::Detailed) {
(1, 2_000_000)
} else {
(1, 1_000_000)
}
}
ModelFamily::H31 => {
if options.quad == Some(true) {
(1, 150_000)
} else if options.geometry_quality == Some(Model3dGeometryQuality::Detailed) {
(1, 2_000_000)
} else {
(1, 1_500_000)
}
}
ModelFamily::P1 => (50, 20_000),
ModelFamily::P2 => {
if options.quad == Some(true) {
(48, 25_000)
} else {
(48, 50_000)
}
}
}
}
fn invalid(field: Option<TripoField>, reason: TripoValidationReason, message: &str) -> TripoError {
TripoError::InvalidParameters {
field,
reason,
message: message.into(),
}
}
pub(crate) fn validate_task_id(task_id: &str) -> Result<(), TripoError> {
if task_id.trim().is_empty() {
return Err(TripoError::InvalidParameters {
@@ -1,11 +1,14 @@
use shared_contracts::model3d::common::{
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dInputOrientation,
Model3dModelVersion, Model3dStyle, Model3dTextureAlignment, Model3dTextureQuality,
Model3dModelVersion, Model3dTextureAlignment, Model3dTextureQuality, Model3dTextureVersion,
};
use shared_contracts::model3d::image_to_model::Model3dImageToModelRequest;
use tripo3d_sdk::{models::FileInput, params::ImageToModelParams};
use crate::common::{TripoError, TripoProviderClient, TripoTaskHandle};
use crate::common::{
TripoError, TripoField, TripoGenerationOptions, TripoProviderClient, TripoTaskHandle,
TripoValidationReason, validate_generation_options,
};
impl TripoProviderClient {
pub async fn submit_image_to_model(
@@ -13,10 +16,26 @@ impl TripoProviderClient {
request: &Model3dImageToModelRequest,
) -> Result<TripoTaskHandle, TripoError> {
if request.input.trim().is_empty() {
return Err(TripoError::SdkInvalidArgument(
"image_to_model: input is required".into(),
));
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Input),
reason: TripoValidationReason::Required,
message: "input must not be blank".into(),
});
}
validate_generation_options(&TripoGenerationOptions {
model: request.model,
texture: request.texture,
pbr: request.pbr,
texture_quality: request.texture_quality,
texture_version: request.texture_version,
geometry_quality: request.geometry_quality,
face_limit: request.face_limit,
auto_size: request.auto_size,
quad: request.quad,
smart_low_poly: request.smart_low_poly,
generate_parts: request.generate_parts,
compress: request.compress,
})?;
let task_id = self
.client
@@ -43,6 +62,7 @@ fn to_sdk_params(request: &Model3dImageToModelRequest) -> ImageToModelParams {
.texture_quality
.map(Model3dTextureQuality::as_str)
.map(str::to_owned),
extra: extra_fields(request.texture_version, request.delight),
texture_alignment: request
.texture_alignment
.map(Model3dTextureAlignment::as_str)
@@ -69,7 +89,23 @@ fn to_sdk_params(request: &Model3dImageToModelRequest) -> ImageToModelParams {
.export_orientation
.map(Model3dExportOrientation::as_str)
.map(str::to_owned),
style: request.style.map(Model3dStyle::as_str).map(str::to_owned),
..Default::default()
style: None,
}
}
fn extra_fields(
texture_version: Option<Model3dTextureVersion>,
delight: Option<bool>,
) -> std::collections::HashMap<String, serde_json::Value> {
let mut extra = std::collections::HashMap::new();
if let Some(value) = texture_version {
extra.insert(
"texture_version".into(),
serde_json::Value::String(value.as_str().into()),
);
}
if let Some(value) = delight {
extra.insert("delight".into(), serde_json::Value::Bool(value));
}
extra
}
@@ -7,13 +7,70 @@ use shared_contracts::model3d::multiview_to_model::{
};
use tripo3d_sdk::{models::FileInput, params::MultiviewToModelParams};
use crate::common::{TripoError, TripoProviderClient, TripoTaskHandle};
use crate::common::{
TripoError, TripoField, TripoGenerationOptions, TripoProviderClient, TripoTaskHandle,
TripoValidationReason, validate_generation_options,
};
impl TripoProviderClient {
pub async fn submit_multiview_to_model(
&self,
request: &Model3dMultiviewToModelRequest,
) -> Result<TripoTaskHandle, TripoError> {
match &request.inputs {
Model3dMultiviewInputs::Views {
front,
left,
back,
right,
} => {
if front.trim().is_empty() {
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Inputs),
reason: TripoValidationReason::Required,
message: "front view is required".into(),
});
}
if [left, back, right]
.into_iter()
.filter(|value| {
value
.as_deref()
.is_some_and(|value| !value.trim().is_empty())
})
.count()
== 0
{
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Inputs),
reason: TripoValidationReason::InvalidCombination,
message: "at least two views are required".into(),
});
}
}
Model3dMultiviewInputs::TaskId { task_id } if task_id.trim().is_empty() => {
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Inputs),
reason: TripoValidationReason::Required,
message: "task_id must not be blank".into(),
});
}
Model3dMultiviewInputs::TaskId { .. } => {}
}
validate_generation_options(&TripoGenerationOptions {
model: request.model,
texture: request.texture,
pbr: request.pbr,
texture_quality: request.texture_quality,
texture_version: request.texture_version,
geometry_quality: request.geometry_quality,
face_limit: request.face_limit,
auto_size: request.auto_size,
quad: request.quad,
smart_low_poly: request.smart_low_poly,
generate_parts: request.generate_parts,
compress: request.compress,
})?;
let task_id = self
.client
.multiview_to_model(to_sdk_params(request))
@@ -52,6 +109,17 @@ fn to_sdk_params(request: &Model3dMultiviewToModelRequest) -> MultiviewToModelPa
.texture_quality
.map(Model3dTextureQuality::as_str)
.map(str::to_owned);
if let Some(value) = request.texture_version {
params.extra.insert(
"texture_version".into(),
serde_json::Value::String(value.as_str().into()),
);
}
if let Some(value) = request.delight {
params
.extra
.insert("delight".into(), serde_json::Value::Bool(value));
}
params.geometry_quality = request
.geometry_quality
.map(Model3dGeometryQuality::as_str)
@@ -1,7 +1,7 @@
use crate::common::{TripoError, TripoProviderClient, TripoTaskHandle};
use shared_contracts::model3d::common::{
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dModelVersion,
Model3dStyle, Model3dTextureQuality,
Model3dTextureQuality, Model3dTextureVersion,
};
use shared_contracts::model3d::text_to_model::Model3dTextToModelRequest;
@@ -39,6 +39,7 @@ fn to_sdk_params(request: &Model3dTextToModelRequest) -> tripo3d_sdk::params::Te
.texture_quality
.map(Model3dTextureQuality::as_str)
.map(str::to_owned),
extra: extra_fields(request.texture_version, request.delight),
geometry_quality: request
.geometry_quality
.map(Model3dGeometryQuality::as_str)
@@ -57,7 +58,23 @@ fn to_sdk_params(request: &Model3dTextToModelRequest) -> tripo3d_sdk::params::Te
.export_orientation
.map(Model3dExportOrientation::as_str)
.map(str::to_owned),
style: request.style.map(Model3dStyle::as_str).map(str::to_owned),
..Default::default()
style: None,
}
}
fn extra_fields(
texture_version: Option<Model3dTextureVersion>,
delight: Option<bool>,
) -> std::collections::HashMap<String, serde_json::Value> {
let mut extra = std::collections::HashMap::new();
if let Some(value) = texture_version {
extra.insert(
"texture_version".into(),
serde_json::Value::String(value.as_str().into()),
);
}
if let Some(value) = delight {
extra.insert("delight".into(), serde_json::Value::Bool(value));
}
extra
}
@@ -1,6 +1,9 @@
use shared_contracts::model3d::text_to_model::Model3dTextToModelRequest;
use crate::common::{TripoError, TripoField, TripoValidationReason};
use crate::common::{
TripoError, TripoField, TripoGenerationOptions, TripoValidationReason,
validate_generation_options,
};
pub(crate) fn validate_text_to_model_request(
request: &Model3dTextToModelRequest,
@@ -13,7 +16,39 @@ pub(crate) fn validate_text_to_model_request(
});
}
// TODO(tripo-docs): add the complete documented cross-field validation
// matrix in one pass when the official text-to-model API contract arrives.
if request.prompt.chars().count() > 1024 {
return Err(TripoError::InvalidParameters {
field: Some(TripoField::Prompt),
reason: TripoValidationReason::InvalidCombination,
message: "prompt must not exceed 1024 characters".into(),
});
}
if request
.negative_prompt
.as_deref()
.is_some_and(|value| value.chars().count() > 255)
{
return Err(TripoError::InvalidParameters {
field: Some(TripoField::NegativePrompt),
reason: TripoValidationReason::InvalidCombination,
message: "negative_prompt must not exceed 255 characters".into(),
});
}
validate_generation_options(&TripoGenerationOptions {
model: request.model,
texture: request.texture,
pbr: request.pbr,
texture_quality: request.texture_quality,
texture_version: request.texture_version,
geometry_quality: request.geometry_quality,
face_limit: request.face_limit,
auto_size: request.auto_size,
quad: request.quad,
smart_low_poly: request.smart_low_poly,
generate_parts: request.generate_parts,
compress: request.compress,
})?;
Ok(())
}
@@ -5,16 +5,14 @@ use super::MODEL3D_TS_EXPORT_DIR;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ts_rs::TS)]
#[ts(export, export_to = MODEL3D_TS_EXPORT_DIR)]
pub enum Model3dCompression {
/// TODO(tripo-docs): replace this compile-time placeholder with the
/// official text-to-model compression enum values.
#[serde(rename = "example")]
Example,
#[serde(rename = "geometry")]
Geometry,
}
impl Model3dCompression {
pub const fn as_str(self) -> &'static str {
match self {
Self::Example => "example",
Self::Geometry => "geometry",
}
}
}
@@ -9,10 +9,10 @@ mod geometry_quality;
mod input_orientation;
mod model_version;
mod output_format;
mod style;
mod task_status;
mod texture_alignment;
mod texture_quality;
mod texture_version;
pub use compression::Model3dCompression;
pub use export_orientation::Model3dExportOrientation;
@@ -20,7 +20,7 @@ pub use geometry_quality::Model3dGeometryQuality;
pub use input_orientation::Model3dInputOrientation;
pub use model_version::Model3dModelVersion;
pub use output_format::Model3dOutputFormat;
pub use style::Model3dStyle;
pub use task_status::Model3dTaskStatus;
pub use texture_alignment::Model3dTextureAlignment;
pub use texture_quality::Model3dTextureQuality;
pub use texture_version::Model3dTextureVersion;
@@ -1,20 +0,0 @@
use serde::{Deserialize, Serialize};
use super::MODEL3D_TS_EXPORT_DIR;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ts_rs::TS)]
#[ts(export, export_to = MODEL3D_TS_EXPORT_DIR)]
pub enum Model3dStyle {
/// TODO(tripo-docs): replace this compile-time placeholder with the
/// official text-to-model style enum values.
#[serde(rename = "example")]
Example,
}
impl Model3dStyle {
pub const fn as_str(self) -> &'static str {
match self {
Self::Example => "example",
}
}
}
@@ -7,6 +7,7 @@ use super::MODEL3D_TS_EXPORT_DIR;
#[derive(ts_rs::TS)]
#[ts(export, export_to = MODEL3D_TS_EXPORT_DIR)]
pub enum Model3dTextureQuality {
Fast,
Standard,
Detailed,
Extreme,
@@ -15,6 +16,7 @@ pub enum Model3dTextureQuality {
impl Model3dTextureQuality {
pub const fn as_str(self) -> &'static str {
match self {
Self::Fast => "fast",
Self::Standard => "standard",
Self::Detailed => "detailed",
Self::Extreme => "extreme",
@@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
use super::MODEL3D_TS_EXPORT_DIR;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ts_rs::TS)]
#[ts(export, export_to = MODEL3D_TS_EXPORT_DIR)]
pub enum Model3dTextureVersion {
#[serde(rename = "v3.5-20260815")]
V35,
#[serde(rename = "v3.0-20250812")]
V30,
#[serde(rename = "v2.5-20250123")]
V25,
}
impl Model3dTextureVersion {
pub const fn as_str(self) -> &'static str {
match self {
Self::V35 => "v3.5-20260815",
Self::V30 => "v3.0-20250812",
Self::V25 => "v2.5-20250123",
}
}
}
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::model3d::common::{
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dInputOrientation,
Model3dModelVersion, Model3dStyle, Model3dTextureAlignment, Model3dTextureQuality,
Model3dModelVersion, Model3dTextureAlignment, Model3dTextureQuality, Model3dTextureVersion,
};
use super::MODEL3D_TS_EXPORT_DIR;
@@ -30,6 +30,10 @@ pub struct Model3dImageToModelRequest {
#[serde(default)]
pub texture_quality: Option<Model3dTextureQuality>,
#[serde(default)]
pub texture_version: Option<Model3dTextureVersion>,
#[serde(default)]
pub delight: Option<bool>,
#[serde(default)]
pub texture_alignment: Option<Model3dTextureAlignment>,
#[serde(default)]
pub geometry_quality: Option<Model3dGeometryQuality>,
@@ -52,6 +56,4 @@ pub struct Model3dImageToModelRequest {
pub export_uv: Option<bool>,
#[serde(default)]
pub export_orientation: Option<Model3dExportOrientation>,
#[serde(default)]
pub style: Option<Model3dStyle>,
}
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::model3d::common::{
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dInputOrientation,
Model3dModelVersion, Model3dTextureAlignment, Model3dTextureQuality,
Model3dModelVersion, Model3dTextureAlignment, Model3dTextureQuality, Model3dTextureVersion,
};
use super::MODEL3D_TS_EXPORT_DIR;
@@ -48,6 +48,10 @@ pub struct Model3dMultiviewToModelRequest {
#[serde(default)]
pub texture_quality: Option<Model3dTextureQuality>,
#[serde(default)]
pub texture_version: Option<Model3dTextureVersion>,
#[serde(default)]
pub delight: Option<bool>,
#[serde(default)]
pub geometry_quality: Option<Model3dGeometryQuality>,
#[serde(default)]
pub texture_alignment: Option<Model3dTextureAlignment>,
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use super::MODEL3D_TS_EXPORT_DIR;
use crate::model3d::common::{
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dModelVersion,
Model3dStyle, Model3dTextureQuality,
Model3dTextureQuality, Model3dTextureVersion,
};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
@@ -32,6 +32,10 @@ pub struct Model3dTextToModelRequest {
#[serde(default)]
pub texture_quality: Option<Model3dTextureQuality>,
#[serde(default)]
pub texture_version: Option<Model3dTextureVersion>,
#[serde(default)]
pub delight: Option<bool>,
#[serde(default)]
pub geometry_quality: Option<Model3dGeometryQuality>,
#[serde(default)]
#[ts(type = "number | null")]
@@ -50,6 +54,4 @@ pub struct Model3dTextToModelRequest {
pub export_uv: Option<bool>,
#[serde(default)]
pub export_orientation: Option<Model3dExportOrientation>,
#[serde(default)]
pub style: Option<Model3dStyle>,
}