Skip to main content

tripo_api/tasks/
rig_model.rs

1//! `rig_model` task variant. Wire `type`: `animate_rig`.
2
3use serde::{Deserialize, Serialize};
4
5use crate::enums::{RigOutputFormat, RigSpec, RigType};
6use crate::error::{Error, Result};
7use crate::versions;
8
9/// Request body for `rig_model`. Wire `type`: `animate_rig`.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
12#[serde(deny_unknown_fields)]
13pub struct RigModelRequest {
14    /// Source task id.
15    pub original_model_task_id: String,
16    /// Model version.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub model_version: Option<String>,
19    /// Output file format.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub out_format: Option<RigOutputFormat>,
22    /// Rig classification.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub rig_type: Option<RigType>,
25    /// Skeleton spec.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub spec: Option<RigSpec>,
28}
29
30impl RigModelRequest {
31    /// Reject non-biped `rig_type` with a rigger version that only supports
32    /// biped. The server's default rigger (`v1.0-20240301`) is biped-only, so
33    /// any non-biped `rig_type` requires an explicit `--model-version v2.5-20260210`
34    /// (v2.0-20250506 still works but is deprecated as of API 1.9.7). Without this
35    /// check, the server returns `error_code: 1004` ("one or more of your parameter
36    /// is invalid") with no `error_msg`.
37    pub(crate) fn validate(&self) -> Result<()> {
38        let non_biped = matches!(
39            self.rig_type,
40            Some(
41                RigType::Avian
42                    | RigType::Quadruped
43                    | RigType::Hexapod
44                    | RigType::Octopod
45                    | RigType::Serpentine
46                    | RigType::Aquatic
47                    | RigType::Others
48            )
49        );
50        let biped_only_version = match self.model_version.as_deref() {
51            None | Some(versions::rig::V1_0) => true,
52            Some(_) => false,
53        };
54        if non_biped && biped_only_version {
55            return Err(Error::InvalidRequest(format!(
56                "rig_type {:?} requires model_version {} — the default/v1.0 rigger only supports biped",
57                self.rig_type.as_ref().unwrap(),
58                versions::rig::V2_5,
59            )));
60        }
61        Ok(())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    fn req(rig_type: Option<RigType>, model_version: Option<&str>) -> RigModelRequest {
70        RigModelRequest {
71            original_model_task_id: "task_id".into(),
72            model_version: model_version.map(str::to_owned),
73            out_format: None,
74            rig_type,
75            spec: None,
76        }
77    }
78
79    #[test]
80    fn biped_with_default_version_ok() {
81        req(Some(RigType::Biped), None).validate().unwrap();
82    }
83
84    #[test]
85    fn no_rig_type_any_version_ok() {
86        req(None, None).validate().unwrap();
87        req(None, Some(versions::rig::V1_0)).validate().unwrap();
88        req(None, Some(versions::rig::V2_5)).validate().unwrap();
89    }
90
91    #[test]
92    fn avian_with_v1_is_rejected() {
93        let err = req(Some(RigType::Avian), None).validate().unwrap_err();
94        assert!(
95            matches!(err, Error::InvalidRequest(ref msg) if msg.contains("v2.5") && msg.contains("biped")),
96            "{err}"
97        );
98        let err = req(Some(RigType::Avian), Some(versions::rig::V1_0))
99            .validate()
100            .unwrap_err();
101        assert!(matches!(err, Error::InvalidRequest(_)));
102    }
103
104    #[test]
105    fn avian_with_v2_is_accepted() {
106        // v2.0 is deprecated but still accepted server-side, so validation must
107        // not reject it.
108        #[allow(deprecated)]
109        req(Some(RigType::Avian), Some(versions::rig::V2_0))
110            .validate()
111            .unwrap();
112        req(Some(RigType::Avian), Some(versions::rig::V2_5))
113            .validate()
114            .unwrap();
115    }
116
117    #[test]
118    fn non_biped_rig_types_all_rejected_on_v1() {
119        for t in [
120            RigType::Avian,
121            RigType::Quadruped,
122            RigType::Hexapod,
123            RigType::Octopod,
124            RigType::Serpentine,
125            RigType::Aquatic,
126            RigType::Others,
127        ] {
128            req(Some(t.clone()), None).validate().unwrap_err();
129        }
130    }
131}