use ifc_model::{EntityId, Model, Transaction, Value};
use super::{invalid, optional_text, reals, refs, require_exists, require_type};
use crate::release::Release;
use crate::MaterialResult;
#[derive(Debug, Clone, Copy)]
pub struct ConstituentDraft<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub material: EntityId,
pub fraction: Option<f64>,
pub category: Option<&'a str>,
}
pub fn create_constituent(
tx: &mut Transaction,
model: &Model,
draft: ConstituentDraft<'_>,
) -> MaterialResult<EntityId> {
const ENTITY: &str = "IFCMATERIALCONSTITUENT";
let release = Release::of(model);
release.require_entity(ENTITY, None)?;
if let Some(fraction) = draft.fraction {
if !fraction.is_finite() || !(0.0..=1.0).contains(&fraction) {
return Err(invalid(
ENTITY,
"Fraction",
"expected a normalised ratio in 0..=1",
));
}
}
require_type(tx, model, release, draft.material, &["IFCMATERIAL"])?;
let record = release.record(
ENTITY,
vec![
("Name", optional_text(draft.name)),
("Description", optional_text(draft.description)),
("Material", Value::Ref(draft.material)),
("Fraction", draft.fraction.map_or(Value::Null, Value::Real)),
("Category", optional_text(draft.category)),
],
)?;
Ok(tx.create(record))
}
pub fn create_constituent_set(
tx: &mut Transaction,
model: &Model,
constituents: &[EntityId],
name: Option<&str>,
description: Option<&str>,
) -> MaterialResult<EntityId> {
const ENTITY: &str = "IFCMATERIALCONSTITUENTSET";
let release = Release::of(model);
release.require_entity(ENTITY, None)?;
if constituents.is_empty() {
return Err(invalid(
ENTITY,
"MaterialConstituents",
"expected at least one constituent",
));
}
for &constituent in constituents {
require_type(tx, model, release, constituent, &["IFCMATERIALCONSTITUENT"])?;
}
let record = release.record(
ENTITY,
vec![
("Name", optional_text(name)),
("Description", optional_text(description)),
("MaterialConstituents", refs(constituents)),
],
)?;
Ok(tx.create(record))
}
#[derive(Debug, Clone, Copy)]
pub struct ProfileDraft<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub material: Option<EntityId>,
pub profile: EntityId,
pub priority: Option<i64>,
pub category: Option<&'a str>,
}
fn profile_values(
tx: &Transaction,
model: &Model,
release: Release<'_>,
entity: &'static str,
draft: &ProfileDraft<'_>,
) -> MaterialResult<Vec<(&'static str, Value)>> {
release.require_entity(entity, None)?;
if let Some(priority) = draft.priority.filter(|value| !(0..=100).contains(value)) {
return Err(invalid(entity, "Priority", priority.to_string()));
}
if let Some(material) = draft.material {
require_type(tx, model, release, material, &["IFCMATERIAL"])?;
}
require_exists(tx, model, draft.profile)?;
Ok(vec![
("Name", optional_text(draft.name)),
("Description", optional_text(draft.description)),
("Material", draft.material.map_or(Value::Null, Value::Ref)),
("Profile", Value::Ref(draft.profile)),
(
"Priority",
draft.priority.map_or(Value::Null, Value::Integer),
),
("Category", optional_text(draft.category)),
])
}
pub fn create_profile(
tx: &mut Transaction,
model: &Model,
draft: ProfileDraft<'_>,
) -> MaterialResult<EntityId> {
const ENTITY: &str = "IFCMATERIALPROFILE";
let release = Release::of(model);
let values = profile_values(tx, model, release, ENTITY, &draft)?;
Ok(tx.create(release.record(ENTITY, values)?))
}
pub fn create_profile_set(
tx: &mut Transaction,
model: &Model,
profiles: &[EntityId],
name: Option<&str>,
description: Option<&str>,
composite_profile: Option<EntityId>,
) -> MaterialResult<EntityId> {
const ENTITY: &str = "IFCMATERIALPROFILESET";
let release = Release::of(model);
release.require_entity(ENTITY, None)?;
if profiles.is_empty() {
return Err(invalid(
ENTITY,
"MaterialProfiles",
"expected at least one profile",
));
}
for &profile in profiles {
require_type(tx, model, release, profile, &["IFCMATERIALPROFILE"])?;
}
if let Some(composite) = composite_profile {
require_exists(tx, model, composite)?;
}
let record = release.record(
ENTITY,
vec![
("Name", optional_text(name)),
("Description", optional_text(description)),
("MaterialProfiles", refs(profiles)),
(
"CompositeProfile",
composite_profile.map_or(Value::Null, Value::Ref),
),
],
)?;
Ok(tx.create(record))
}
pub fn create_profile_set_usage(
tx: &mut Transaction,
model: &Model,
for_profile_set: EntityId,
cardinal_point: Option<i64>,
reference_extent: Option<f64>,
) -> MaterialResult<EntityId> {
const ENTITY: &str = "IFCMATERIALPROFILESETUSAGE";
let release = Release::of(model);
release.require_entity(ENTITY, None)?;
require_type(
tx,
model,
release,
for_profile_set,
&["IFCMATERIALPROFILESET"],
)?;
if let Some(point) = cardinal_point.filter(|value| !(1..=9).contains(value)) {
return Err(invalid(ENTITY, "CardinalPoint", point.to_string()));
}
let record = release.record(
ENTITY,
vec![
("ForProfileSet", Value::Ref(for_profile_set)),
(
"CardinalPoint",
cardinal_point.map_or(Value::Null, Value::Integer),
),
(
"ReferenceExtent",
reference_extent.map_or(Value::Null, Value::Real),
),
],
)?;
Ok(tx.create(record))
}
pub fn create_profile_with_offsets(
tx: &mut Transaction,
model: &Model,
draft: ProfileDraft<'_>,
offset_values: [f64; 2],
) -> MaterialResult<EntityId> {
const ENTITY: &str = "IFCMATERIALPROFILEWITHOFFSETS";
let release = Release::of(model);
release.require_entity(ENTITY, None)?;
if offset_values.iter().any(|value| !value.is_finite()) {
return Err(invalid(ENTITY, "OffsetValues", "expected finite lengths"));
}
let mut values = profile_values(tx, model, release, ENTITY, &draft)?;
values.push(("OffsetValues", reals(&offset_values)));
Ok(tx.create(release.record(ENTITY, values)?))
}
pub fn create_profile_set_usage_tapering(
tx: &mut Transaction,
model: &Model,
for_profile_set: EntityId,
for_profile_end_set: EntityId,
cardinal_point: Option<i64>,
cardinal_end_point: Option<i64>,
reference_extent: Option<f64>,
) -> MaterialResult<EntityId> {
const ENTITY: &str = "IFCMATERIALPROFILESETUSAGETAPERING";
let release = Release::of(model);
release.require_entity(ENTITY, None)?;
for set in [for_profile_set, for_profile_end_set] {
require_type(tx, model, release, set, &["IFCMATERIALPROFILESET"])?;
}
for (point, attribute) in [
(cardinal_point, "CardinalPoint"),
(cardinal_end_point, "CardinalEndPoint"),
] {
if let Some(value) = point.filter(|value| !(1..=9).contains(value)) {
return Err(invalid(ENTITY, attribute, value.to_string()));
}
}
let record = release.record(
ENTITY,
vec![
("ForProfileSet", Value::Ref(for_profile_set)),
(
"CardinalPoint",
cardinal_point.map_or(Value::Null, Value::Integer),
),
(
"ReferenceExtent",
reference_extent.map_or(Value::Null, Value::Real),
),
("ForProfileEndSet", Value::Ref(for_profile_end_set)),
(
"CardinalEndPoint",
cardinal_end_point.map_or(Value::Null, Value::Integer),
),
],
)?;
Ok(tx.create(record))
}