Skip to main content

ifc_style/colour/
select.rs

1//! `IfcColourOrFactor` select projection.
2
3use ifc_model::{EntityId, Value};
4
5use crate::error::{StyleError, StyleResult};
6use crate::view::Record;
7
8/// One resolved member of the `IfcColourOrFactor` select.
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum ColourOrFactor {
11    /// A reference to an `IfcColourSpecification` or `IfcPreDefinedColour`.
12    Colour(EntityId),
13    /// A bare normalized scalar in `[0, 1]`, used directly as a factor
14    /// rather than looked up as a colour entity.
15    Factor(f64),
16}
17
18/// Reads an optional `IfcColourOrFactor` attribute, distinguishing a colour
19/// reference from a scalar factor. Returns `Ok(None)` when the attribute is
20/// absent (this select is always optional in its IFC usages).
21pub(crate) fn optional_colour_or_factor(
22    record: &Record<'_, '_>,
23    attribute: &'static str,
24) -> StyleResult<Option<ColourOrFactor>> {
25    let Some(value) = record.optional_raw(attribute)? else {
26        return Ok(None);
27    };
28    match value.unwrap_typed() {
29        Value::Ref(target) => {
30            record.check_reference_select(
31                *target,
32                "IfcColour",
33                &["IfcColourSpecification", "IfcPreDefinedColour"],
34            )?;
35            Ok(Some(ColourOrFactor::Colour(*target)))
36        }
37        Value::Real(factor) if factor.is_finite() && (0.0..=1.0).contains(factor) => {
38            Ok(Some(ColourOrFactor::Factor(*factor)))
39        }
40        Value::Integer(factor) if (0..=1).contains(factor) => {
41            Ok(Some(ColourOrFactor::Factor(*factor as f64)))
42        }
43        value => Err(StyleError::InvalidValue {
44            entity: record.entity.type_name.to_string(),
45            id: record.id,
46            attribute,
47            value: format!("{value:?}"),
48        }),
49    }
50}