openconfiguration 1.5.0

OpenConfiguration (OC) is a modular, efficient and flexible approach for the uni-directional exchange of visual e-commerce configurations.
Documentation
use openconfiguration_derive::Visitable;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::{
    package::Package, script::Script, status::Status, support, Camera, CatalogEntry, Commercial,
    Geometry, Material, Product, Representation,
};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Visitable)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(deny_unknown_fields))]
#[serde(rename_all = "camelCase")]
/// The Scene is the central part of an OC data set. Each OC data set
/// contains exactly one.
///
/// A Scene may contain initial product representations and/or updates.
///
/// A Scene may contain products that should be inserted instantly, as well
/// as add-ons to be inserted later on and shown as placeholders before.
pub struct Scene {
    /// Reference to the JSON schema.
    #[serde(rename = "$schema")]
    #[cfg_attr(feature = "schema", schemars(default = "Scene::schema"))]
    pub schema: Option<String>,
    /// The mandatory attribute format must be structured as follows:
    ///
    /// "OpenConfiguration_<Major/>.<Minor/> [PRE]"
    ///
    /// Legal combinations of <Major/>.<Minor/> are: 1.0, 1.1 and 1.2
    ///
    /// The optional postfix " PRE" marks a preliminary format.
    pub format: String,
    /// This optional attribute contains informal information about the
    /// creator of this OC data set.
    pub creator: Option<String>,
    /// This optional attribute contains informal information about the
    /// projects used in this OC data set and its corresponding informal
    /// versions.
    pub packages: Option<Vec<Package>>,
    /// This attribute contains general status information about the Scene contents.
    pub status: Status,
    /// The representations of the scene. Legal values are:
    ///
    /// "Standard" - Real-time mesh data.
    ///
    /// "CAD_BIM" - CAD/BIM volume bodies.
    ///
    /// "Photo" - High-resolution mesh data.
    ///
    /// This just lists possible usages and can be used for fast filtering,
    /// it does not influence the actual data. A "Photo" renderer would still use
    /// meshes attached as geometry.mesh
    pub representations: Option<Vec<Representation>>,
    /// This optional attribute may contain the id of a server-side session.
    pub configuration_id: Option<String>,
    /// This optional attribute may contain the link of the original configuration.
    pub configuration_link: Option<String>,
    /// Optional, embedded catalog.
    pub catalog: Option<Vec<CatalogEntry>>,
    /// The optional attribute provides a partial URI to be added to all
    /// relative asset uris. A valid base path should normally start with
    /// https:// or file://, and end with either a slash or the path separator character of the operating system.
    /// "Relative" basePath is always relative to the scene.json path,
    /// not to an application/deployment specific one!
    /// example.configurator.com/assets/oc/scene.json containing "tex.jpg"
    /// basepathes "gfx", "./gfx", "/gfx" are all resolved to the same
    /// example.configurator.com/assets/oc/gfx/tex.jpg
    pub base_path: Option<String>,
    /// The mandatory attribute provides unique content hashes for assets
    /// that are directly referenced in the OC data set. The specific hash
    /// algorithm is unspecified. It may be an MD5 hash of the binary
    /// content for instance. But low-res assets may use the same hash as
    /// the originals, they are derived from. So, the only operation that is
    /// legal for hash, is to compare them with an optionally existing one.
    ///
    /// All assets should have an entry here as downloaders may iterate through
    /// this record rather than exploring the other data entities.
    #[serde(deserialize_with = "crate::utils::deserialize_map_without_null_values")]
    pub hashes: HashMap<String, String>,
    /// Redirections maybe needed to convert absolute asset urls into file
    /// names, especially when compiling a self-contained OC zip.
    /// The attribute is optional.
    ///
    /// Version: OC 1.3
    #[serde(
        deserialize_with = "crate::utils::deserialize_optional_map_without_null_values",
        default
    )]
    pub redirections: Option<HashMap<String, String>>,
    /// The optional attribute contains all client-side JavaScript packages.
    pub scripts: Option<Vec<Script>>,
    /// The optional attribute provides an embedded geometry index.
    ///
    /// IGXC Compatibility: now it's embedded, self-contained geometries
    /// rather than just geometry names. Thus, separate assignments of
    /// geometry normal maps, deformations, etc. are removed. Also, the
    /// embedded geometry definition is optional. There can be external
    /// geometry definitions, too.
    #[serde(
        deserialize_with = "crate::utils::deserialize_optional_map_without_null_values",
        default
    )]
    #[visitable(visit_with = "visit_geometries")]
    pub geometries: Option<HashMap<String, Geometry>>,
    /// The optional attribute provides links to external geometry indices.
    /// The key should be a two-level technical namespace to speedup the
    /// geometry lookup.
    /// The value must be an absolute or relative URI.
    /// The content of the value, must be de-serialized as GeometryIndex.
    ///
    /// IGXC Compatibility: In IGXC, this concept did not exist.
    #[serde(
        deserialize_with = "crate::utils::deserialize_optional_map_without_null_values",
        default
    )]
    #[visitable(visit_with = "visit_geometry_indexes")]
    pub geometry_indexes: Option<HashMap<String, String>>,
    /// The optional attribute provides an embedded geometry index.
    ///
    /// IGXC Compatibility: The embedded material definition is optional.
    /// There can be external material definitions, too.
    #[serde(
        deserialize_with = "crate::utils::deserialize_optional_map_without_null_values",
        default
    )]
    #[visitable(visit_with = "visit_materials")]
    pub materials: Option<HashMap<String, Material>>,
    /// The optional attribute provides links to external geometry indices.
    /// The key should be a two-level technical namespace to speedup the
    /// geometry lookup.
    /// The value must be an absolute or relative URI.
    /// The content of the value, must be de-serialized as MaterialIndex.
    ///
    /// IGXC Compatibility: In IGXC, this concept did not exist.
    #[serde(
        deserialize_with = "crate::utils::deserialize_optional_map_without_null_values",
        default
    )]
    #[visitable(visit_with = "visit_material_indexes")]
    pub material_indexes: Option<HashMap<String, String>>,
    /// An optional camera setup to restore the camera in another viewer.
    pub camera: Option<Camera>,
    /// The mandatory attribute contains the products to be inserted into/
    /// updated in the client world.
    ///
    /// IGXC Compatibility: in IGXC, there is only one product.
    pub products: Vec<Product>,
    /// Commercial products without an own visual representation.
    ///
    /// Version: OC 1.3
    pub com_products: Option<Vec<Commercial>>,
}

impl Scene {
    pub const fn schema() -> &'static str {
        "https://archive.intelligentgraphics.biz/schemas/openconfiguration/scene.json"
    }

    pub fn current_format() -> String {
        format!("OpenConfiguration_{}", env!("CARGO_PKG_VERSION"))
    }

    pub fn new() -> Self {
        Self::new_with_format(Self::current_format())
    }

    pub fn new_with_format(format: String) -> Self {
        Self {
            schema: Some(Scene::schema().to_owned()),
            format,
            creator: Default::default(),
            packages: Default::default(),
            status: Default::default(),
            representations: Default::default(),
            configuration_id: Default::default(),
            configuration_link: Default::default(),
            catalog: Default::default(),
            base_path: Default::default(),
            hashes: Default::default(),
            redirections: Default::default(),
            scripts: Default::default(),
            geometries: Default::default(),
            geometry_indexes: Default::default(),
            materials: Default::default(),
            material_indexes: Default::default(),
            camera: Default::default(),
            products: Default::default(),
            com_products: Default::default(),
        }
    }
}

fn visit_geometries(
    map: &mut Option<HashMap<String, Geometry>>,
    visitor: &mut dyn support::Visitor,
) {
    if let Some(map) = map {
        for (key, value) in map.iter_mut() {
            if let Some(ig) = &mut value.ig {
                visitor.visit_geometry(&key, ig);
            }
            support::Visitable::visit_with(value, visitor);
        }
    }
}

fn visit_geometry_indexes(
    map: &mut Option<HashMap<String, String>>,
    visitor: &mut dyn support::Visitor,
) {
    if let Some(map) = map {
        for (key, value) in map.iter_mut() {
            visitor.visit_geometry_index(&key, value);
            visitor.visit_path(value);
        }
    }
}

fn visit_materials(
    map: &mut Option<HashMap<String, Material>>,
    visitor: &mut dyn support::Visitor,
) {
    if let Some(map) = map {
        for (key, value) in map.iter_mut() {
            if let Some(ig) = &mut value.ig {
                visitor.visit_material(&key, ig);
            }
            support::Visitable::visit_with(value, visitor);
        }
    }
}

fn visit_material_indexes(
    map: &mut Option<HashMap<String, String>>,
    visitor: &mut dyn support::Visitor,
) {
    if let Some(map) = map {
        for (key, value) in map.iter_mut() {
            visitor.visit_material_index(&key, value);
            visitor.visit_path(value);
        }
    }
}