use ifc_model::{Edit, EntityId, Model, Transaction, Value};
use ifc_schema::Schema;
use super::{
build_named, enumeration, invalid_authoring, optional_reference, optional_text, text,
validate_optional_ref, validate_ratio, validate_ref,
};
use crate::colour::ColourOrFactor;
use crate::error::{StyleError, StyleResult};
use crate::surface_style::{duplicate_surface_element_category, SURFACE_STYLE_ELEMENT_MEMBERS};
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ColourRgbDraft<'a> {
pub name: Option<&'a str>,
pub red: f64,
pub green: f64,
pub blue: f64,
}
impl<'a> ColourRgbDraft<'a> {
#[must_use]
pub fn new(red: f64, green: f64, blue: f64) -> Self {
Self {
name: None,
red,
green,
blue,
}
}
#[must_use]
pub fn name(mut self, value: &'a str) -> Self {
self.name = Some(value);
self
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct SurfaceStyleShadingDraft {
pub surface_colour: EntityId,
pub transparency: Option<f64>,
}
impl SurfaceStyleShadingDraft {
#[must_use]
pub fn new(surface_colour: EntityId) -> Self {
Self {
surface_colour,
transparency: None,
}
}
#[must_use]
pub fn transparency(mut self, value: f64) -> Self {
self.transparency = Some(value);
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SurfaceStyleDraft<'a> {
pub name: Option<&'a str>,
pub side: crate::SurfaceSide,
pub elements: Vec<EntityId>,
}
impl<'a> SurfaceStyleDraft<'a> {
#[must_use]
pub fn new(side: crate::SurfaceSide, elements: Vec<EntityId>) -> Self {
Self {
name: None,
side,
elements,
}
}
#[must_use]
pub fn name(mut self, value: &'a str) -> Self {
self.name = Some(value);
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct StyledItemDraft<'a> {
pub item: Option<EntityId>,
pub styles: Vec<EntityId>,
pub name: Option<&'a str>,
}
impl<'a> StyledItemDraft<'a> {
#[must_use]
pub fn new(styles: Vec<EntityId>) -> Self {
Self {
item: None,
styles,
name: None,
}
}
#[must_use]
pub fn item(mut self, value: EntityId) -> Self {
self.item = Some(value);
self
}
#[must_use]
pub fn name(mut self, value: &'a str) -> Self {
self.name = Some(value);
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PresentationLayerDraft<'a> {
pub name: &'a str,
pub description: Option<&'a str>,
pub assigned_items: Vec<EntityId>,
pub identifier: Option<&'a str>,
pub layer_on: Option<bool>,
pub layer_frozen: Option<bool>,
pub layer_blocked: Option<bool>,
pub layer_styles: Vec<EntityId>,
}
impl<'a> PresentationLayerDraft<'a> {
#[must_use]
pub fn new(name: &'a str, assigned_items: Vec<EntityId>) -> Self {
Self {
name,
description: None,
assigned_items,
identifier: None,
layer_on: None,
layer_frozen: None,
layer_blocked: None,
layer_styles: Vec::new(),
}
}
#[must_use]
pub fn description(mut self, value: &'a str) -> Self {
self.description = Some(value);
self
}
#[must_use]
pub fn identifier(mut self, value: &'a str) -> Self {
self.identifier = Some(value);
self
}
#[must_use]
pub fn layer_on(mut self, value: bool) -> Self {
self.layer_on = Some(value);
self
}
#[must_use]
pub fn layer_frozen(mut self, value: bool) -> Self {
self.layer_frozen = Some(value);
self
}
#[must_use]
pub fn layer_blocked(mut self, value: bool) -> Self {
self.layer_blocked = Some(value);
self
}
#[must_use]
pub fn layer_styles(mut self, value: Vec<EntityId>) -> Self {
self.layer_styles = value;
self
}
}
pub fn create_colour_rgb(
tx: &mut Transaction,
schema: &Schema,
draft: ColourRgbDraft<'_>,
) -> StyleResult<EntityId> {
validate_ratio("IfcColourRgb", "Red", draft.red)?;
validate_ratio("IfcColourRgb", "Green", draft.green)?;
validate_ratio("IfcColourRgb", "Blue", draft.blue)?;
let mut values = Vec::new();
optional_text(&mut values, "Name", draft.name);
values.extend([
("Red", Value::Real(draft.red)),
("Green", Value::Real(draft.green)),
("Blue", Value::Real(draft.blue)),
]);
Ok(tx.create(build_named(schema, "IfcColourRgb", values)?))
}
pub fn create_surface_style_shading(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: SurfaceStyleShadingDraft,
) -> StyleResult<EntityId> {
validate_ref(tx, model, schema, draft.surface_colour, "IfcColourRgb")?;
if let Some(value) = draft.transparency {
validate_ratio("IfcSurfaceStyleShading", "Transparency", value)?;
}
let mut values = vec![("SurfaceColour", Value::Ref(draft.surface_colour))];
if let Some(value) = draft.transparency {
values.push(("Transparency", Value::Real(value)));
}
Ok(tx.create(build_named(schema, "IfcSurfaceStyleShading", values)?))
}
pub fn create_surface_style(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: SurfaceStyleDraft<'_>,
) -> StyleResult<EntityId> {
if draft.elements.is_empty() || draft.elements.len() > 5 {
return Err(invalid_authoring(
"IfcSurfaceStyle",
"Styles",
format!("expected 1..=5 elements, found {}", draft.elements.len()),
));
}
let mut element_types = Vec::with_capacity(draft.elements.len());
for element in &draft.elements {
element_types.push(validate_surface_element(tx, model, schema, *element)?);
}
if let Some(category) =
duplicate_surface_element_category(schema, element_types.iter().map(String::as_str))
{
return Err(invalid_authoring(
"IfcSurfaceStyle",
"Styles",
format!("duplicate {category} category"),
));
}
let mut values = Vec::new();
optional_text(&mut values, "Name", draft.name);
values.push(("Side", enumeration(draft.side.as_ifc())));
values.push((
"Styles",
Value::List(draft.elements.into_iter().map(Value::Ref).collect()),
));
Ok(tx.create(build_named(schema, "IfcSurfaceStyle", values)?))
}
pub fn create_styled_item(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: StyledItemDraft<'_>,
) -> StyleResult<EntityId> {
validate_optional_ref(tx, model, schema, draft.item, "IfcRepresentationItem")?;
if draft.styles.is_empty() {
return Err(invalid_authoring(
"IfcStyledItem",
"Styles",
"at least one style is required",
));
}
for style in &draft.styles {
validate_ref(tx, model, schema, *style, "IfcPresentationStyle")?;
}
let mut staged = tx.clone();
let style_values = if schema.version() == Some(ifc_schema::SchemaVersion::Ifc2x3) {
let wrapper = build_named(
schema,
"IfcPresentationStyleAssignment",
vec![(
"Styles",
Value::List(draft.styles.into_iter().map(Value::Ref).collect()),
)],
)?;
vec![Value::Ref(staged.create(wrapper))]
} else {
draft.styles.into_iter().map(Value::Ref).collect()
};
let mut values = vec![("Styles", Value::List(style_values))];
optional_reference(&mut values, "Item", draft.item);
optional_text(&mut values, "Name", draft.name);
let id = staged.create(build_named(schema, "IfcStyledItem", values)?);
*tx = staged;
Ok(id)
}
pub fn create_presentation_layer_with_style(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: PresentationLayerDraft<'_>,
) -> StyleResult<EntityId> {
if draft.name.is_empty() {
return Err(invalid_authoring(
"IfcPresentationLayerWithStyle",
"Name",
"must not be empty",
));
}
if draft.assigned_items.is_empty() {
return Err(invalid_authoring(
"IfcPresentationLayerWithStyle",
"AssignedItems",
"at least one item is required",
));
}
for item in &draft.assigned_items {
validate_layered_item(tx, model, schema, *item)?;
}
for style in &draft.layer_styles {
validate_ref(tx, model, schema, *style, "IfcPresentationStyle")?;
}
let mut values = vec![
("Name", text(draft.name)),
(
"AssignedItems",
Value::List(draft.assigned_items.into_iter().map(Value::Ref).collect()),
),
(
"LayerStyles",
Value::List(draft.layer_styles.into_iter().map(Value::Ref).collect()),
),
];
optional_text(&mut values, "Description", draft.description);
optional_text(&mut values, "Identifier", draft.identifier);
if let Some(value) = draft.layer_on {
values.push(("LayerOn", Value::Bool(value)));
}
if let Some(value) = draft.layer_frozen {
values.push(("LayerFrozen", Value::Bool(value)));
}
if let Some(value) = draft.layer_blocked {
values.push(("LayerBlocked", Value::Bool(value)));
}
Ok(tx.create(build_named(
schema,
"IfcPresentationLayerWithStyle",
values,
)?))
}
fn staged_type(tx: &Transaction, model: &Model, target: EntityId) -> Option<String> {
tx.edits()
.iter()
.rev()
.find_map(|edit| match edit {
Edit::Create { id, entity } if *id == target => Some(entity.type_name.to_string()),
_ => None,
})
.or_else(|| model.get(target).map(|entity| entity.type_name.to_string()))
}
fn validate_surface_element(
tx: &Transaction,
model: &Model,
schema: &Schema,
target: EntityId,
) -> StyleResult<String> {
let actual = staged_type(tx, model, target).ok_or(StyleError::DanglingReference {
source_id: EntityId(0),
target,
})?;
if SURFACE_STYLE_ELEMENT_MEMBERS
.iter()
.any(|member| schema.is_a(&actual, member))
{
Ok(actual)
} else {
Err(StyleError::ReferenceType {
target,
expected: "IfcSurfaceStyleElementSelect",
actual,
})
}
}
fn validate_layered_item(
tx: &Transaction,
model: &Model,
schema: &Schema,
target: EntityId,
) -> StyleResult<()> {
let actual = staged_type(tx, model, target).ok_or(StyleError::DanglingReference {
source_id: EntityId(0),
target,
})?;
if schema.is_a(&actual, "IfcRepresentationItem") || schema.is_a(&actual, "IfcRepresentation") {
Ok(())
} else {
Err(StyleError::ReferenceType {
target,
expected: "IfcLayeredItem",
actual,
})
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct SurfaceStyleRenderingDraft {
pub surface_colour: EntityId,
pub transparency: Option<f64>,
pub diffuse: Option<ColourOrFactor>,
pub transmission: Option<ColourOrFactor>,
pub diffuse_transmission: Option<ColourOrFactor>,
pub reflection: Option<ColourOrFactor>,
pub specular: Option<ColourOrFactor>,
pub reflectance_method: &'static str,
}
impl SurfaceStyleRenderingDraft {
#[must_use]
pub fn new(surface_colour: EntityId, reflectance_method: &'static str) -> Self {
Self {
surface_colour,
transparency: None,
diffuse: None,
transmission: None,
diffuse_transmission: None,
reflection: None,
specular: None,
reflectance_method,
}
}
#[must_use]
pub fn transparency(mut self, value: f64) -> Self {
self.transparency = Some(value);
self
}
#[must_use]
pub fn diffuse(mut self, value: ColourOrFactor) -> Self {
self.diffuse = Some(value);
self
}
#[must_use]
pub fn transmission(mut self, value: ColourOrFactor) -> Self {
self.transmission = Some(value);
self
}
#[must_use]
pub fn diffuse_transmission(mut self, value: ColourOrFactor) -> Self {
self.diffuse_transmission = Some(value);
self
}
#[must_use]
pub fn reflection(mut self, value: ColourOrFactor) -> Self {
self.reflection = Some(value);
self
}
#[must_use]
pub fn specular(mut self, value: ColourOrFactor) -> Self {
self.specular = Some(value);
self
}
}
pub fn create_surface_style_rendering(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: SurfaceStyleRenderingDraft,
) -> StyleResult<EntityId> {
const ENTITY: &str = "IfcSurfaceStyleRendering";
validate_ref(tx, model, schema, draft.surface_colour, "IfcColourRgb")?;
if let Some(value) = draft.transparency {
validate_ratio(ENTITY, "Transparency", value)?;
}
let mut values = vec![("SurfaceColour", Value::Ref(draft.surface_colour))];
if let Some(value) = draft.transparency {
values.push(("Transparency", Value::Real(value)));
}
for (attribute, member) in [
("DiffuseColour", draft.diffuse),
("TransmissionColour", draft.transmission),
("DiffuseTransmissionColour", draft.diffuse_transmission),
("ReflectionColour", draft.reflection),
("SpecularColour", draft.specular),
] {
let Some(member) = member else { continue };
let value = match member {
ColourOrFactor::Colour(id) => {
validate_ref(tx, model, schema, id, "IfcColourRgb")?;
Value::Ref(id)
}
ColourOrFactor::Factor(factor) => {
validate_ratio(ENTITY, attribute, factor)?;
Value::Real(factor)
}
};
values.push((attribute, value));
}
let declared = schema
.attributes(ENTITY)
.iter()
.find(|a| a.name.eq_ignore_ascii_case("ReflectanceMethod"))
.and_then(|a| schema.type_def(&a.type_name))
.is_some_and(|def| match &def.kind {
ifc_schema::TypeKind::Enumeration(values) => values
.iter()
.any(|v| v.eq_ignore_ascii_case(draft.reflectance_method)),
_ => false,
});
if !declared {
return Err(invalid_authoring(
ENTITY,
"ReflectanceMethod",
draft.reflectance_method,
));
}
values.push((
"ReflectanceMethod",
Value::Enum(draft.reflectance_method.into()),
));
Ok(tx.create(build_named(schema, ENTITY, values)?))
}