完善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
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:
@@ -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';
|
||||
|
||||
+4
-1
@@ -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;
|
||||
};
|
||||
|
||||
+3
@@ -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;
|
||||
};
|
||||
|
||||
Generated
+1
@@ -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>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user