diff --git a/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md b/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md index ac724e190..1230bd374 100644 --- a/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md +++ b/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md @@ -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,不静默生成键。 diff --git a/server-rs/crates/api-server/src/tripo3d/validation.rs b/server-rs/crates/api-server/src/tripo3d/validation.rs index 1563d3d7d..0353aff2b 100644 --- a/server-rs/crates/api-server/src/tripo3d/validation.rs +++ b/server-rs/crates/api-server/src/tripo3d/validation.rs @@ -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 })));