use ifc_model::{EntityId, Value};
use crate::error::{OccurrenceError, OccurrenceResult};
use crate::release::Layout;
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct OccurrenceDraft<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub placement: Option<EntityId>,
pub representation: Option<EntityId>,
pub tag: Option<&'a str>,
pub shape_type: Option<&'a str>,
pub nominal_diameter: Option<f64>,
pub cross_section_area: Option<f64>,
pub bar_role: Option<&'a str>,
pub longitudinal_bars: Option<MeshBars>,
pub transverse_bars: Option<MeshBars>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct MeshBars {
pub nominal_diameter: f64,
pub cross_section_area: f64,
pub spacing: f64,
}
impl MeshBars {
#[must_use]
pub const fn new(nominal_diameter: f64, cross_section_area: f64, spacing: f64) -> Self {
Self {
nominal_diameter,
cross_section_area,
spacing,
}
}
}
impl<'a> OccurrenceDraft<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn name(mut self, value: &'a str) -> Self {
self.name = Some(value);
self
}
#[must_use]
pub fn description(mut self, value: &'a str) -> Self {
self.description = Some(value);
self
}
#[must_use]
pub fn object_type(mut self, value: &'a str) -> Self {
self.object_type = Some(value);
self
}
#[must_use]
pub fn placement(mut self, value: EntityId) -> Self {
self.placement = Some(value);
self
}
#[must_use]
pub fn representation(mut self, value: EntityId) -> Self {
self.representation = Some(value);
self
}
#[must_use]
pub fn tag(mut self, value: &'a str) -> Self {
self.tag = Some(value);
self
}
#[must_use]
pub fn shape_type(mut self, value: &'a str) -> Self {
self.shape_type = Some(value);
self
}
#[must_use]
pub fn nominal_diameter(mut self, value: f64) -> Self {
self.nominal_diameter = Some(value);
self
}
#[must_use]
pub fn cross_section_area(mut self, value: f64) -> Self {
self.cross_section_area = Some(value);
self
}
#[must_use]
pub fn bar_role(mut self, value: &'a str) -> Self {
self.bar_role = Some(value);
self
}
#[must_use]
pub fn longitudinal_bars(mut self, value: MeshBars) -> Self {
self.longitudinal_bars = Some(value);
self
}
#[must_use]
pub fn transverse_bars(mut self, value: MeshBars) -> Self {
self.transverse_bars = Some(value);
self
}
}
#[derive(Clone, Copy)]
enum Measure {
PositiveLength,
Area,
}
pub(crate) fn specific_values(
layout: Layout,
entity: &'static str,
draft: &OccurrenceDraft<'_>,
) -> OccurrenceResult<Vec<(&'static str, Value)>> {
let mut values = Vec::new();
for (attribute, token) in [("ShapeType", draft.shape_type), ("BarRole", draft.bar_role)] {
let Some(token) = token else {
continue;
};
let Some(members) = layout.members(entity, attribute) else {
return Err(OccurrenceError::AuthoringNotInSchema {
entity,
attribute,
schema: layout.version(),
});
};
if !members.contains(&token) {
return Err(OccurrenceError::UnknownToken {
entity,
attribute,
token: token.into(),
});
}
values.push((attribute, Value::Enum(token.into())));
}
let mut measures = vec![
(
"NominalDiameter",
draft.nominal_diameter,
Measure::PositiveLength,
),
("CrossSectionArea", draft.cross_section_area, Measure::Area),
];
for (bars, [diameter, area, spacing]) in [
(
draft.longitudinal_bars,
[
"LongitudinalBarNominalDiameter",
"LongitudinalBarCrossSectionArea",
"LongitudinalBarSpacing",
],
),
(
draft.transverse_bars,
[
"TransverseBarNominalDiameter",
"TransverseBarCrossSectionArea",
"TransverseBarSpacing",
],
),
] {
measures.extend([
(
diameter,
bars.map(|b| b.nominal_diameter),
Measure::PositiveLength,
),
(area, bars.map(|b| b.cross_section_area), Measure::Area),
(spacing, bars.map(|b| b.spacing), Measure::PositiveLength),
]);
}
for (attribute, value, measure) in measures {
let Some(value) = value else {
continue;
};
let admitted = match measure {
Measure::PositiveLength => value.is_finite() && value > 0.0,
Measure::Area => value.is_finite(),
};
if !admitted {
return Err(OccurrenceError::InvalidMeasure {
entity,
attribute,
value: format!("{value:?}"),
});
}
values.push((attribute, Value::Real(value)));
}
Ok(values)
}