ifc-style 0.3.0

Presentation: styles, colours, textures, layers, annotation.
Documentation
//! Surface presentation styles.

use ifc_model::EntityId;
use ifc_schema::Schema;

use crate::error::{StyleError, StyleResult};
use crate::view::Record;

mod lighting;
mod refraction;
mod rendering;
mod shading;

pub use lighting::SurfaceStyleLighting;
pub use refraction::SurfaceStyleRefraction;
pub use rendering::SurfaceStyleRendering;
pub use shading::SurfaceStyleShading;

pub(crate) const SURFACE_STYLE_ELEMENT_MEMBERS: &[&str] = &[
    "IfcSurfaceStyleShading",
    "IfcSurfaceStyleLighting",
    "IfcSurfaceStyleRefraction",
    "IfcSurfaceStyleWithTextures",
    "IfcExternallyDefinedSurfaceStyle",
];

pub(crate) fn duplicate_surface_element_category<'a>(
    schema: &Schema,
    actual_types: impl IntoIterator<Item = &'a str>,
) -> Option<&'static str> {
    let mut seen = [false; SURFACE_STYLE_ELEMENT_MEMBERS.len()];
    for actual in actual_types {
        let category = SURFACE_STYLE_ELEMENT_MEMBERS
            .iter()
            .position(|member| schema.is_a(actual, member))?;
        if seen[category] {
            return Some(SURFACE_STYLE_ELEMENT_MEMBERS[category]);
        }
        seen[category] = true;
    }
    None
}

/// The `IfcSurfaceSide` enumeration: which face(s) of a surface a style applies to.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SurfaceSide {
    /// `POSITIVE`: the surface's positive normal side only.
    Positive,
    /// `NEGATIVE`: the surface's negative normal side only.
    Negative,
    /// `BOTH`: both sides of the surface.
    Both,
}

impl SurfaceSide {
    pub(crate) fn as_ifc(self) -> &'static str {
        match self {
            Self::Positive => "POSITIVE",
            Self::Negative => "NEGATIVE",
            Self::Both => "BOTH",
        }
    }
}

/// Borrowed projection of `IfcSurfaceStyle`.
#[derive(Debug, Clone, Copy)]
pub struct SurfaceStyle<'m, 's> {
    record: Record<'m, 's>,
}

impl<'m, 's> SurfaceStyle<'m, 's> {
    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
        Self { record }
    }

    /// The `Name` attribute, when authored.
    pub fn name(&self) -> StyleResult<Option<&'m str>> {
        self.record.optional_text("Name")
    }

    /// The `Side` attribute: which face(s) the contained styles apply to. Mandatory.
    pub fn side(&self) -> StyleResult<SurfaceSide> {
        match self.record.required_enum("Side")? {
            value if value.eq_ignore_ascii_case("POSITIVE") => Ok(SurfaceSide::Positive),
            value if value.eq_ignore_ascii_case("NEGATIVE") => Ok(SurfaceSide::Negative),
            value if value.eq_ignore_ascii_case("BOTH") => Ok(SurfaceSide::Both),
            value => Err(StyleError::InvalidValue {
                entity: "IFCSURFACESTYLE".to_owned(),
                id: self.record.id,
                attribute: "Side",
                value: value.to_owned(),
            }),
        }
    }

    /// The `Styles` select: the surface-style elements attached to this
    /// style (shading, lighting, refraction, textures, or an externally
    /// defined style), one to five members. Rejects a duplicate element
    /// category (e.g. two `IfcSurfaceStyleShading` members).
    pub fn elements(&self) -> StyleResult<Vec<EntityId>> {
        let elements = self.record.required_refs_select(
            "Styles",
            "IfcSurfaceStyleElementSelect",
            SURFACE_STYLE_ELEMENT_MEMBERS,
            1,
            Some(5),
        )?;
        let actual_types = elements.iter().filter_map(|id| {
            self.record
                .model
                .get(*id)
                .map(|entity| entity.type_name.as_ref())
        });
        if let Some(category) = duplicate_surface_element_category(self.record.schema, actual_types)
        {
            return Err(StyleError::InvalidValue {
                entity: "IFCSURFACESTYLE".to_owned(),
                id: self.record.id,
                attribute: "Styles",
                value: format!("duplicate {category} category"),
            });
        }
        Ok(elements)
    }
}

/// Borrowed projection of `IfcSurfaceStyleWithTextures`.
#[derive(Debug, Clone, Copy)]
pub struct SurfaceStyleWithTextures<'m, 's> {
    record: Record<'m, 's>,
}

impl<'m, 's> SurfaceStyleWithTextures<'m, 's> {
    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
        Self { record }
    }

    /// The `Textures` list: one or more `IfcSurfaceTexture` references.
    pub fn textures(&self) -> StyleResult<Vec<EntityId>> {
        self.record
            .required_refs("Textures", "IfcSurfaceTexture", 1, None)
    }
}