3D 生成请求允许缺省 geometryQuality 并按 standard 计价

- PricingParamView::validate 去掉 geometryQuality 必填,缺省按 standard 计入定价
- 只有显式 detailed 才命中 hdGeometry 加价,缺省不会改变报价
- 新增缺省几何质量的定价用例(v3.1 与 v2.5 两条路径),并把它从必填字段用例里移除
- 同步技术方案:说明该字段允许缺省的原因,v2.5 / P1 / P2 因 provider 不支持该参数而必须允许缺省
This commit is contained in:
2026-09-23 16:24:18 +08:00
parent ffee739774
commit 427b9f19a1
2 changed files with 38 additions and 6 deletions
@@ -66,7 +66,7 @@ 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``textureQuality``geometryQuality``quad``smartLowPoly``generateParts` 必须显式给出。`texture=false` 时禁止出现 `textureQuality`,且 `pbr` 必须显式 `false``texture=true``textureQuality` 必填。理由是 provider 的隐式默认值会直接改变价格,一旦依赖默认值,报价与扣费会在“调用方少传字段”时分叉。
**定价相关参数在 API 层做组合校验**`texture``textureQuality``quad``smartLowPoly``generateParts` 必须显式给出`geometryQuality` 允许缺省,缺省按 `standard` 计价 —— provider 对 v2.5 / P1 / P2 不支持该参数,v3.x 的缺省也是 `standard`,两边同价,因此只有显式 `detailed` 才命中 `hdGeometry` 加价(写死必填只会让定价表里已有的 v2.5 / P1 / P2 永远不可达)`texture=false` 时禁止出现 `textureQuality`,且 `pbr` 必须显式 `false``texture=true``textureQuality` 必填。其余会改价的参数仍必须显式给出:provider 的隐式默认值会直接改变价格,一旦依赖默认值,报价与扣费会在“调用方少传字段”时分叉。
两个 submit 都必须携带 `Idempotency-Key`,复用现有头部校验;缺失或格式非法直接 400,不静默生成键。
@@ -1,7 +1,8 @@
//! Tripo 生成请求的平台侧组合校验。
//!
//! 这里只判两类事:
//! 1. 平台自己新增的口径——价格相关参数必须显式给出,贴图、贴图档位与 pbr 不能互相矛盾;
//! 1. 平台自己新增的口径——会改价的参数必须显式给出`geometryQuality` 例外:缺省等价于
//! provider 的 standard,不产生加价),贴图、贴图档位与 pbr 不能互相矛盾;
//! 2. 模型档位与参数的能力组合——直接复用 `platform-tripo` 的请求预检,不维护第二份规则。
//!
//! 校验必须在扣费与任何 provider 调用之前完成;拿不到合法参数与价格就不提交、不入队。
@@ -106,9 +107,12 @@ impl PricingParamView {
let texture = self.texture.ok_or_else(|| {
Model3dRequestError::invalid("generation.texture", "必须显式给出是否生成贴图")
})?;
let geometry_quality = self.geometry_quality.ok_or_else(|| {
Model3dRequestError::invalid("generation.geometryQuality", "必须显式给出几何质量")
})?;
// 几何质量允许缺省:provider 对 v2.5 / P1 / P2 不支持这个参数,而 v3.x 的缺省就是
// standard,两边的「缺省」与「standard」同价。缺省按 standard 计价,因此只有显式
// detailed 会命中 hdGeometry 加价;写死必填只会让这些已定价的模型永远不可达。
let geometry_quality = self
.geometry_quality
.unwrap_or(Model3dGeometryQuality::Standard);
let quad = self.quad.ok_or_else(|| {
Model3dRequestError::invalid("generation.quad", "必须显式给出是否quad网格")
})?;
@@ -253,7 +257,6 @@ mod tests {
fn pricing_params_must_be_explicit() {
for field in [
"generation.texture",
"generation.geometryQuality",
"generation.quad",
"generation.smartLowPoly",
"generation.generateParts",
@@ -265,6 +268,35 @@ mod tests {
}
}
/// 缺省几何质量按 standard 计价:不报错,也不产生 hdGeometry 加价。
///
/// v2.5 这类不支持 `geometry_quality` 的模型因此可达 —— 它们已经在定价表里,
/// 却曾因为「API 层必填 + provider 预检拒绝」的组合永远拿不到价格。
#[test]
fn geometry_quality_is_optional_and_defaults_to_standard() {
for (label, overrides) in [
("v3.1 缺省几何质量", json!({})),
(
"v2.5 缺省几何质量",
json!({
"model": "v2.5-20250123",
"texture": false,
"textureQuality": null,
"geometryQuality": null,
"pbr": false
}),
),
] {
let request = text_request(generation_with(overrides));
let query = validate_text_to_model_request(&request)
.unwrap_or_else(|error| panic!("{label} 应通过校验,实际被拒:{error}"));
assert!(
!query.add_ons.contains(Model3dAddOn::HdGeometry),
"{label} 缺省几何质量不产生高清几何加价"
);
}
}
#[test]
fn texture_off_rejects_texture_quality_and_requires_explicit_non_pbr() {
let request = text_request(generation_with(json!({ "texture": false })));