geometry_quality 预检只拒绝 detailed,standard 按不传处理
- validate_generation_options 对非 v3.1 / v3.0 家族只在取值 detailed 时拒绝, 与 quad / smart_low_poly / generate_parts「只拒绝启用值」的处理对称 - 新增 wire_geometry_quality:不支持的家族把 standard 收敛成不发(等价于 provider 默认档), 三个 endpoint 的请求映射都过这一层,避免 provider 因「不支持该字段」整单拒绝 - 新增两组表驱动用例:非 v3 家族 standard 放行 / detailed 拒绝,以及映射层的收敛结果 - 同步技术方案:写明「预检只拒绝启用值」与映射层收敛 standard 的口径
This commit is contained in:
@@ -66,7 +66,8 @@ canvasCompletion? 画布占位框回填,只在项目资源落点下生效
|
||||
|
||||
**归一在预检之前完成并写回请求**:项目 ID 与素材夹 ID 都 trim,`project` / 旧 `folder-*` 走与图片画布同一个 `normalize_generated_asset_folder_id` 映射到当前 owner 的默认素材夹,素材名走同一个 `resolve_editor_generated_asset_label`(trim + 截断 + 缺省「3D 模型」)。入队的就是归一后的请求,因此预检值 = 队列载荷 = 落库值;模块侧落库原有的 `normalize_editor_generation_default_asset_folders` 继续兜底老队列载荷里的 `project`。
|
||||
|
||||
**定价相关参数在 API 层做组合校验**:`texture`、`quad`、`smartLowPoly`、`generateParts` 必须显式给出;`geometryQuality` 与 `textureQuality` 允许缺省,缺省按 `standard` 计价 —— provider 对 v2.5 不支持 `geometry_quality`、并拒绝任何 `texture_quality`,v3.x 的缺省档也是 `standard`,两边同价,因此只有显式 `detailed` / `extreme` 才命中 `hdGeometry` / `hdTexture` / `ultraTexture` 加价(把这两个字段写死必填只会让定价表里已有的 v2.5 / P1 / P2 永远不可达,或让 `texture=true` 的 v2.5 无论填什么都被 provider 预检拒绝)。`texture=false` 时禁止出现 `textureQuality`,且 `pbr` 必须显式 `false`。其余会改价的参数仍必须显式给出:provider 的隐式默认值会直接改变价格,一旦依赖默认值,报价与扣费会在“调用方少传字段”时分叉。
|
||||
**定价相关参数在 API 层做组合校验**:`texture`、`quad`、`smartLowPoly`、`generateParts` 必须显式给出;`geometryQuality` 与 `textureQuality` 允许缺省,缺省按 `standard` 计价 —— provider 对 v2.5 不支持 `geometry_quality`、并拒绝任何 `texture_quality`,v3.x 的缺省档也是 `standard`,两边同价,因此只有显式 `detailed` / `extreme` 才命中 `hdGeometry` / `hdTexture` / `ultraTexture` 加价(把这两个字段写死必填只会让定价表里已有的 v2.5 / P1 / P2 永远不可达,或让 `texture=true` 的 v2.5 无论填什么都被 provider 预检拒绝)。`texture=false` 时禁止出现 `textureQuality`,且 `pbr` 必须显式 `false`。**provider 预检只拒绝会启用能力的取值**:`geometry_quality` 对非 v3.1 / v3.0 家族只在取值 `detailed` 时拒绝,`standard` 放行;并且 `platform-tripo` 的请求映射会把「不支持的家族 + standard」收敛成「不发该字段」——不支持的家族传 `standard` 与不传等价(provider 默认档就是它,定价也同价),发出去只会换来「不支持该字段」的整单拒绝。
|
||||
其余会改价的参数仍必须显式给出:provider 的隐式默认值会直接改变价格,一旦依赖默认值,报价与扣费会在“调用方少传字段”时分叉。
|
||||
|
||||
两个 submit 都必须携带 `Idempotency-Key`,复用现有头部校验;缺失或格式非法直接 400,不静默生成键。
|
||||
|
||||
|
||||
@@ -19,5 +19,6 @@ pub use types::{
|
||||
};
|
||||
pub(crate) use validation::{
|
||||
TripoGenerationOptions, submitted_task_handle, validate_generation_options, validate_task_id,
|
||||
wire_geometry_quality,
|
||||
};
|
||||
pub(crate) use wire::{wire, wire_option};
|
||||
|
||||
@@ -108,13 +108,18 @@ pub(crate) fn validate_generation_options(
|
||||
));
|
||||
}
|
||||
|
||||
if options.geometry_quality.is_some() && !matches!(family, ModelFamily::H31 | ModelFamily::H30)
|
||||
// 只拒绝「启用加价」的取值,与 quad / smart_low_poly / generate_parts 的处理对称:
|
||||
// 非 v3 家族把 standard 传出去只会换来 provider 的「不支持该字段」,它等价于不传。
|
||||
if options
|
||||
.geometry_quality
|
||||
.is_some_and(|quality| matches!(quality, Model3dGeometryQuality::Detailed))
|
||||
&& !family.supports_geometry_quality()
|
||||
{
|
||||
return Err(invalid(
|
||||
Some(TripoField::GeometryQuality),
|
||||
TripoValidationReason::InvalidCombination,
|
||||
&format!(
|
||||
"geometry_quality is only supported by {}",
|
||||
"geometry_quality=detailed is only supported by {}",
|
||||
v3_model_labels()
|
||||
),
|
||||
));
|
||||
@@ -238,6 +243,32 @@ impl From<Model3dModelVersion> for ModelFamily {
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelFamily {
|
||||
/// 是否支持 `geometry_quality`:只有 v3.x 家族有这一档。
|
||||
const fn supports_geometry_quality(self) -> bool {
|
||||
matches!(self, Self::H31 | Self::H30)
|
||||
}
|
||||
}
|
||||
|
||||
/// 把「等于 provider 默认档」的几何质量收敛成「不发」。
|
||||
///
|
||||
/// 不支持的家族传 `standard` 与不传完全等价(两边的默认档都是 standard,定价也同价),
|
||||
/// 但传出去会让 provider 以「不支持该字段」拒绝整次提交,所以这里直接不发。
|
||||
/// 非默认档(`detailed`)在预检里已经被拒,不会走到这里。
|
||||
pub(crate) fn wire_geometry_quality(
|
||||
model: Model3dModelVersion,
|
||||
quality: Option<Model3dGeometryQuality>,
|
||||
) -> Option<Model3dGeometryQuality> {
|
||||
match quality {
|
||||
Some(Model3dGeometryQuality::Standard)
|
||||
if !ModelFamily::from(model).supports_geometry_quality() =>
|
||||
{
|
||||
None
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn face_limit_bounds(family: ModelFamily, options: &TripoGenerationOptions) -> (i64, i64) {
|
||||
if options.smart_low_poly == Some(true) {
|
||||
return if options.quad == Some(true) {
|
||||
@@ -538,6 +569,60 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// 几何质量只拒绝「会启用高清几何」的 detailed:非 v3 家族的 standard 必须放行,
|
||||
/// 否则定价表里已定价的 v2.5 / P1 / P2 又会被这条规则挡回去。
|
||||
#[test]
|
||||
fn geometry_quality_only_rejects_the_enabling_value_for_non_v3_families() {
|
||||
for model in Model3dModelVersion::ALL {
|
||||
let supported = matches!(model, Model3dModelVersion::H31 | Model3dModelVersion::H30);
|
||||
|
||||
let mut options = minimal_options(*model);
|
||||
options.geometry_quality = Some(Model3dGeometryQuality::Standard);
|
||||
assert!(
|
||||
validate_generation_options(&options).is_ok(),
|
||||
"{model:?} 的 geometry_quality=standard 必须放行"
|
||||
);
|
||||
|
||||
options.geometry_quality = Some(Model3dGeometryQuality::Detailed);
|
||||
let result = validate_generation_options(&options);
|
||||
assert_eq!(
|
||||
result.is_ok(),
|
||||
supported,
|
||||
"{model:?} detailed 的期望结果不符"
|
||||
);
|
||||
if let Err(error) = result {
|
||||
assert_eq!(
|
||||
rejection(error),
|
||||
Some((
|
||||
Some(TripoField::GeometryQuality),
|
||||
TripoValidationReason::InvalidCombination
|
||||
)),
|
||||
"{model:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 不支持该参数的家族把 standard 收敛成「不发」:传出去只会换来 provider 的
|
||||
/// 「不支持该字段」,而它与不传完全等价(两边默认档都是 standard,定价也同价)。
|
||||
#[test]
|
||||
fn wire_geometry_quality_drops_the_default_for_families_without_the_flag() {
|
||||
for model in Model3dModelVersion::ALL {
|
||||
let supported = matches!(model, Model3dModelVersion::H31 | Model3dModelVersion::H30);
|
||||
assert_eq!(
|
||||
wire_geometry_quality(*model, Some(Model3dGeometryQuality::Standard)),
|
||||
supported.then_some(Model3dGeometryQuality::Standard),
|
||||
"{model:?}"
|
||||
);
|
||||
assert_eq!(wire_geometry_quality(*model, None), None, "{model:?}");
|
||||
assert_eq!(
|
||||
wire_geometry_quality(*model, Some(Model3dGeometryQuality::Detailed)),
|
||||
Some(Model3dGeometryQuality::Detailed),
|
||||
"{model:?} 非默认档不在这里处理,交给预检拒绝"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 空白 task id 是 provider 违约,按 OutputSchema 失败而不是参数错误。
|
||||
#[test]
|
||||
fn blank_submitted_task_id_is_an_output_schema_error() {
|
||||
|
||||
@@ -3,7 +3,7 @@ use tripo3d_sdk::{models::FileInput, params::ImageToModelParams};
|
||||
|
||||
use crate::common::{
|
||||
TripoError, TripoField, TripoProviderClient, TripoTaskHandle, TripoValidationReason,
|
||||
extra_fields, submitted_task_handle, wire, wire_option,
|
||||
extra_fields, submitted_task_handle, wire, wire_geometry_quality, wire_option,
|
||||
};
|
||||
|
||||
use super::validation::validate_image_to_model_params;
|
||||
@@ -123,7 +123,9 @@ fn to_sdk_params(
|
||||
texture_quality: wire_option(params.texture_quality.as_ref())?,
|
||||
extra: extra_fields(params.texture_version, params.delight)?,
|
||||
texture_alignment: wire_option(params.texture_alignment.as_ref())?,
|
||||
geometry_quality: wire_option(params.geometry_quality.as_ref())?,
|
||||
geometry_quality: wire_option(
|
||||
wire_geometry_quality(params.model, params.geometry_quality).as_ref(),
|
||||
)?,
|
||||
face_limit: params.face_limit,
|
||||
auto_size: params.auto_size,
|
||||
orientation: wire_option(params.orientation.as_ref())?,
|
||||
|
||||
@@ -6,7 +6,7 @@ use tripo3d_sdk::{models::FileInput, params::MultiviewToModelParams};
|
||||
use crate::common::{
|
||||
TripoError, TripoField, TripoGenerationOptions, TripoProviderClient, TripoTaskHandle,
|
||||
TripoValidationReason, extra_fields, submitted_task_handle, validate_generation_options,
|
||||
validate_task_id, wire, wire_option,
|
||||
validate_task_id, wire, wire_geometry_quality, wire_option,
|
||||
};
|
||||
|
||||
impl TripoProviderClient {
|
||||
@@ -89,7 +89,8 @@ fn to_sdk_params(
|
||||
params.pbr = request.pbr;
|
||||
params.texture_quality = wire_option(request.texture_quality.as_ref())?;
|
||||
params.extra = extra_fields(request.texture_version, request.delight)?;
|
||||
params.geometry_quality = wire_option(request.geometry_quality.as_ref())?;
|
||||
params.geometry_quality =
|
||||
wire_option(wire_geometry_quality(request.model, request.geometry_quality).as_ref())?;
|
||||
params.texture_alignment = wire_option(request.texture_alignment.as_ref())?;
|
||||
params.face_limit = request.face_limit;
|
||||
params.auto_size = request.auto_size;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::common::{
|
||||
TripoError, TripoProviderClient, TripoTaskHandle, extra_fields, submitted_task_handle, wire,
|
||||
wire_option,
|
||||
wire_geometry_quality, wire_option,
|
||||
};
|
||||
use shared_contracts::model3d::text_to_model::Model3dTextToModelParams;
|
||||
|
||||
@@ -37,7 +37,9 @@ fn to_sdk_params(
|
||||
pbr: params.pbr,
|
||||
texture_quality: wire_option(params.texture_quality.as_ref())?,
|
||||
extra: extra_fields(params.texture_version, params.delight)?,
|
||||
geometry_quality: wire_option(params.geometry_quality.as_ref())?,
|
||||
geometry_quality: wire_option(
|
||||
wire_geometry_quality(params.model, params.geometry_quality).as_ref(),
|
||||
)?,
|
||||
face_limit: params.face_limit,
|
||||
auto_size: params.auto_size,
|
||||
quad: params.quad,
|
||||
|
||||
Reference in New Issue
Block a user