Skip to main content

tripo_api/tasks/
retarget_animation.rs

1//! `retarget_animation` task variant. Wire `type`: `animate_retarget`.
2//!
3//! Uses `AnimationInput` to type-enforce the single-or-many wire-format
4//! invariant: a single animation serializes under key `animation`, a list
5//! under `animations`.
6
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::enums::{Animation, RigOutputFormat};
10
11/// One animation or many. Serializes to the correct wire key (`animation`
12/// or `animations`) — cannot be in an invalid "both set" state.
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
15pub enum AnimationInput {
16    /// One animation preset; serializes under key `animation`.
17    Single(Animation),
18    /// Multiple animation presets; serializes under key `animations`.
19    Many(Vec<Animation>),
20}
21
22/// Request body for `retarget_animation`. Wire `type`: `animate_retarget`.
23#[derive(Debug, Clone)]
24#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
25pub struct RetargetAnimationRequest {
26    /// Source rigged task id.
27    pub original_model_task_id: String,
28    /// Animation(s) to retarget; serializes as `animation` (single) or `animations` (list).
29    pub animation: AnimationInput,
30    /// Output file format.
31    pub out_format: Option<RigOutputFormat>,
32    /// Bake animation samples.
33    pub bake_animation: Option<bool>,
34    /// Export with skinned geometry.
35    pub export_with_geometry: Option<bool>,
36    /// Animate in-place.
37    pub animate_in_place: Option<bool>,
38}
39
40impl Serialize for RetargetAnimationRequest {
41    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
42        use serde::ser::SerializeMap;
43        let mut m = s.serialize_map(None)?;
44        m.serialize_entry("original_model_task_id", &self.original_model_task_id)?;
45        match &self.animation {
46            AnimationInput::Single(a) => m.serialize_entry("animation", a)?,
47            AnimationInput::Many(list) => m.serialize_entry("animations", list)?,
48        }
49        if let Some(v) = &self.out_format {
50            m.serialize_entry("out_format", v)?;
51        }
52        if let Some(v) = &self.bake_animation {
53            m.serialize_entry("bake_animation", v)?;
54        }
55        if let Some(v) = &self.export_with_geometry {
56            m.serialize_entry("export_with_geometry", v)?;
57        }
58        if let Some(v) = &self.animate_in_place {
59            m.serialize_entry("animate_in_place", v)?;
60        }
61        m.end()
62    }
63}
64
65impl<'de> Deserialize<'de> for RetargetAnimationRequest {
66    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
67        use serde::de::Error as _;
68
69        #[derive(Deserialize)]
70        #[serde(deny_unknown_fields)]
71        struct Wire {
72            original_model_task_id: String,
73            #[serde(default)]
74            animation: Option<Animation>,
75            #[serde(default)]
76            animations: Option<Vec<Animation>>,
77            #[serde(default)]
78            out_format: Option<RigOutputFormat>,
79            #[serde(default)]
80            bake_animation: Option<bool>,
81            #[serde(default)]
82            export_with_geometry: Option<bool>,
83            #[serde(default)]
84            animate_in_place: Option<bool>,
85        }
86
87        let w = Wire::deserialize(d)?;
88        let animation = match (w.animation, w.animations) {
89            (Some(a), None) => AnimationInput::Single(a),
90            (None, Some(list)) => AnimationInput::Many(list),
91            (Some(_), Some(_)) => {
92                return Err(D::Error::custom(
93                    "set either `animation` or `animations`, not both",
94                ));
95            }
96            (None, None) => {
97                return Err(D::Error::custom(
98                    "missing required field `animation` or `animations`",
99                ));
100            }
101        };
102        Ok(Self {
103            original_model_task_id: w.original_model_task_id,
104            animation,
105            out_format: w.out_format,
106            bake_animation: w.bake_animation,
107            export_with_geometry: w.export_with_geometry,
108            animate_in_place: w.animate_in_place,
109        })
110    }
111}
112
113impl RetargetAnimationRequest {
114    /// Build with a single animation preset.
115    #[must_use]
116    pub fn single(original_model_task_id: impl Into<String>, animation: Animation) -> Self {
117        Self {
118            original_model_task_id: original_model_task_id.into(),
119            animation: AnimationInput::Single(animation),
120            out_format: None,
121            bake_animation: None,
122            export_with_geometry: None,
123            animate_in_place: None,
124        }
125    }
126    /// Build with multiple animations (list).
127    #[must_use]
128    pub fn many(original_model_task_id: impl Into<String>, animations: Vec<Animation>) -> Self {
129        Self {
130            original_model_task_id: original_model_task_id.into(),
131            animation: AnimationInput::Many(animations),
132            out_format: None,
133            bake_animation: None,
134            export_with_geometry: None,
135            animate_in_place: None,
136        }
137    }
138}