2e9aed3750
- 新增 submitted_task_handle:空白 id 在提交调用附近以 OutputSchema 失败 - text / image / multiview 提交路径共用它,任务不再以孤儿状态等到下游查询才报错
281 lines
9.3 KiB
Rust
281 lines
9.3 KiB
Rust
use shared_contracts::model3d::common::{
|
|
Model3dCompression, Model3dGeometryQuality, Model3dModelVersion, Model3dTextureQuality,
|
|
Model3dTextureVersion,
|
|
};
|
|
use shared_contracts::model3d::image_to_model::Model3dImageToModelParams;
|
|
use shared_contracts::model3d::text_to_model::Model3dTextToModelParams;
|
|
|
|
use super::{TripoError, TripoField, TripoTaskHandle, TripoValidationReason};
|
|
|
|
pub(crate) struct TripoGenerationOptions {
|
|
pub model: 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>,
|
|
}
|
|
|
|
impl From<&Model3dTextToModelParams> for TripoGenerationOptions {
|
|
fn from(params: &Model3dTextToModelParams) -> Self {
|
|
Self {
|
|
model: params.model,
|
|
texture: params.texture,
|
|
pbr: params.pbr,
|
|
texture_quality: params.texture_quality,
|
|
texture_version: params.texture_version,
|
|
geometry_quality: params.geometry_quality,
|
|
face_limit: params.face_limit,
|
|
auto_size: params.auto_size,
|
|
quad: params.quad,
|
|
smart_low_poly: params.smart_low_poly,
|
|
generate_parts: params.generate_parts,
|
|
compress: params.compress,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&Model3dImageToModelParams> for TripoGenerationOptions {
|
|
fn from(params: &Model3dImageToModelParams) -> Self {
|
|
Self {
|
|
model: params.model,
|
|
texture: params.texture,
|
|
pbr: params.pbr,
|
|
texture_quality: params.texture_quality,
|
|
texture_version: params.texture_version,
|
|
geometry_quality: params.geometry_quality,
|
|
face_limit: params.face_limit,
|
|
auto_size: params.auto_size,
|
|
quad: params.quad,
|
|
smart_low_poly: params.smart_low_poly,
|
|
generate_parts: params.generate_parts,
|
|
compress: params.compress,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn validate_generation_options(
|
|
options: &TripoGenerationOptions,
|
|
) -> Result<(), TripoError> {
|
|
let family = ModelFamily::from(options.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 == Some(true) && 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::OutOfRange,
|
|
&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 {
|
|
field: Some(TripoField::TaskId),
|
|
reason: TripoValidationReason::Required,
|
|
message: "task_id must not be blank".into(),
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// 提交成功后立刻收口 provider 返回的 task id。
|
|
///
|
|
/// 空白 id 是 provider 违约,不是调用方参数问题:必须在提交调用附近以 `OutputSchema`
|
|
/// 失败,而不是拖到下游 `get_task` 才报错——那时任务已经提交出去,成为无法追踪的孤儿。
|
|
pub(crate) fn submitted_task_handle(task_id: String) -> Result<TripoTaskHandle, TripoError> {
|
|
let task_id = task_id.trim().to_string();
|
|
if task_id.is_empty() {
|
|
return Err(TripoError::OutputSchema {
|
|
task_id: "submit".into(),
|
|
message: "submit response has no task_id".into(),
|
|
});
|
|
}
|
|
Ok(TripoTaskHandle { task_id })
|
|
}
|