tripo-api 0.1.4

Unofficial async Rust client for the Tripo 3D Generation API
Documentation
//! `multiview_to_model` task variant.
//!
//! Wire-format note: the images array is sent as `files` (list); `None`
//! entries serialize as `{}` empty objects (positional "no image at this slot").

use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::compress::CompressionMode;
use crate::enums::{GeometryQuality, Orientation, TextureAlignment, TextureQuality};
use crate::error::Result;
use crate::image::ImageInput;

/// Request body for `multiview_to_model`. Wire `type`: `multiview_to_model`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct MultiviewToModelRequest {
    /// Ordered list of images. `None` entries become `{}` placeholders on the wire.
    #[serde(
        rename = "files",
        serialize_with = "serialize_files",
        deserialize_with = "deserialize_files"
    )]
    pub images: Vec<Option<ImageInput>>,
    /// Model version.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model_version: Option<String>,
    /// Target face count.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub face_limit: Option<i32>,
    /// Texture.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub texture: Option<bool>,
    /// PBR.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pbr: Option<bool>,
    /// Seed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model_seed: Option<i32>,
    /// Seed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub texture_seed: Option<i32>,
    /// Texture quality.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub texture_quality: Option<TextureQuality>,
    /// Geometry quality.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub geometry_quality: Option<GeometryQuality>,
    /// Texture alignment.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub texture_alignment: Option<TextureAlignment>,
    /// Auto-size.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub auto_size: Option<bool>,
    /// Orientation.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub orientation: Option<Orientation>,
    /// Quad mesh.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub quad: Option<bool>,
    /// Compression.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub compress: Option<CompressionMode>,
    /// Generate parts.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub generate_parts: Option<bool>,
    /// Smart lowpoly.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub smart_low_poly: Option<bool>,
}

impl MultiviewToModelRequest {
    pub(crate) fn validate(&self) -> Result<()> {
        super::validate_p1_params(
            self.model_version.as_deref(),
            self.quad,
            self.smart_low_poly,
            self.generate_parts,
            self.geometry_quality.as_ref(),
        )
    }
}

fn serialize_files<S: Serializer>(v: &[Option<ImageInput>], s: S) -> Result<S::Ok, S::Error> {
    use serde::ser::SerializeSeq;
    let mut seq = s.serialize_seq(Some(v.len()))?;
    for entry in v {
        match entry {
            Some(img) => seq.serialize_element(img)?,
            None => seq.serialize_element(&serde_json::Value::Object(serde_json::Map::new()))?,
        }
    }
    seq.end()
}

fn deserialize_files<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Option<ImageInput>>, D::Error> {
    let entries: Vec<serde_json::Value> = Vec::deserialize(d)?;
    let mut out = Vec::with_capacity(entries.len());
    for v in entries {
        match &v {
            serde_json::Value::Object(m) if m.is_empty() => out.push(None),
            serde_json::Value::Null => out.push(None),
            _ => {
                let img: ImageInput =
                    serde_json::from_value(v).map_err(serde::de::Error::custom)?;
                out.push(Some(img));
            }
        }
    }
    Ok(out)
}