use ifc_model::{Entity, EntityId, Transaction, Value};
use super::profile::ProfileType;
use crate::error::GeometryError;
use crate::slots::profile_slot as slot;
use super::std_profile::positive;
use super::{invalid, reals, refs, require_finite};
fn header(attrs: &mut [Value], profile_type: ProfileType, name: Option<&str>) {
attrs[slot::PROFILE_TYPE] = Value::Enum(profile_type.token().into());
attrs[slot::PROFILE_NAME] = name.map_or(Value::Null, |n| Value::Text(n.into()));
}
pub fn arbitrary_open_profile(
tx: &mut Transaction,
name: Option<&str>,
curve: EntityId,
) -> EntityId {
let mut attrs = vec![Value::Null; 3];
header(&mut attrs, ProfileType::Curve, name);
attrs[slot::OUTER_CURVE] = Value::Ref(curve);
tx.create(Entity::new("IFCARBITRARYOPENPROFILEDEF", attrs))
}
pub fn center_line_profile(
tx: &mut Transaction,
name: Option<&str>,
curve: EntityId,
thickness: f64,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCENTERLINEPROFILEDEF";
positive(T, "Thickness", thickness)?;
let mut attrs = vec![Value::Null; 4];
header(&mut attrs, ProfileType::Area, name);
attrs[slot::OUTER_CURVE] = Value::Ref(curve);
attrs[3] = Value::Real(thickness);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn arbitrary_profile_with_voids(
tx: &mut Transaction,
name: Option<&str>,
outer_curve: EntityId,
inner_curves: &[EntityId],
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCARBITRARYPROFILEDEFWITHVOIDS";
if inner_curves.is_empty() {
return Err(invalid(
T,
"InnerCurves",
"expected at least one void; without one this is an IfcArbitraryClosedProfileDef",
));
}
let mut attrs = vec![Value::Null; 4];
header(&mut attrs, ProfileType::Area, name);
attrs[slot::OUTER_CURVE] = Value::Ref(outer_curve);
attrs[slot::INNER_CURVES] = refs(inner_curves);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn composite_profile(
tx: &mut Transaction,
profile_type: ProfileType,
name: Option<&str>,
profiles: &[EntityId],
label: Option<&str>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCOMPOSITEPROFILEDEF";
if profiles.len() < 2 {
return Err(invalid(
T,
"Profiles",
format!("expected at least 2 profiles, got {}", profiles.len()),
));
}
let mut attrs = vec![Value::Null; 4];
header(&mut attrs, profile_type, name);
attrs[2] = refs(profiles);
attrs[3] = label.map_or(Value::Null, |l| Value::Text(l.into()));
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn derived_profile(
tx: &mut Transaction,
profile_type: ProfileType,
name: Option<&str>,
parent: EntityId,
operator: EntityId,
label: Option<&str>,
) -> EntityId {
let mut attrs = vec![Value::Null; 5];
header(&mut attrs, profile_type, name);
attrs[2] = Value::Ref(parent);
attrs[3] = Value::Ref(operator);
attrs[4] = label.map_or(Value::Null, |l| Value::Text(l.into()));
tx.create(Entity::new("IFCDERIVEDPROFILEDEF", attrs))
}
pub fn mirrored_profile(
tx: &mut Transaction,
profile_type: ProfileType,
name: Option<&str>,
parent: EntityId,
label: Option<&str>,
) -> EntityId {
let mut attrs = vec![Value::Null; 5];
header(&mut attrs, profile_type, name);
attrs[2] = Value::Ref(parent);
attrs[3] = Value::Derived;
attrs[4] = label.map_or(Value::Null, |l| Value::Text(l.into()));
tx.create(Entity::new("IFCMIRROREDPROFILEDEF", attrs))
}
pub fn rounded_rectangle_profile(
tx: &mut Transaction,
name: Option<&str>,
position: Option<EntityId>,
x_dim: f64,
y_dim: f64,
rounding_radius: f64,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCROUNDEDRECTANGLEPROFILEDEF";
positive(T, "XDim", x_dim)?;
positive(T, "YDim", y_dim)?;
positive(T, "RoundingRadius", rounding_radius)?;
if rounding_radius > x_dim / 2.0 || rounding_radius > y_dim / 2.0 {
return Err(invalid(
T,
"RoundingRadius",
format!("{rounding_radius} exceeds half of the {x_dim} by {y_dim} rectangle"),
));
}
let mut attrs = vec![Value::Null; 6];
header(&mut attrs, ProfileType::Area, name);
attrs[slot::POSITION] = position.map_or(Value::Null, Value::Ref);
attrs[slot::X_DIM] = Value::Real(x_dim);
attrs[slot::Y_DIM] = Value::Real(y_dim);
attrs[5] = Value::Real(rounding_radius);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn profile_def(
tx: &mut Transaction,
profile_type: ProfileType,
name: Option<&str>,
) -> EntityId {
let attrs = vec![
Value::Enum(profile_type.token().into()),
name.map_or(Value::Null, |n| Value::Text(n.into())),
];
tx.create(Entity::new("IFCPROFILEDEF", attrs))
}
pub fn open_cross_profile(
tx: &mut Transaction,
name: Option<&str>,
horizontal_widths: bool,
widths: &[f64],
slopes: &[f64],
tags: Option<&[&str]>,
offset_point: Option<EntityId>,
) -> Result<EntityId, GeometryError> {
const ENTITY: &str = "IFCOPENCROSSPROFILEDEF";
if widths.is_empty() {
return Err(invalid(ENTITY, "Widths", "expected LIST [1:?]"));
}
if widths.len() != slopes.len() {
return Err(invalid(
ENTITY,
"Slopes",
format!(
"expected one slope per width: {} widths, {} slopes",
widths.len(),
slopes.len()
),
));
}
require_finite(ENTITY, "Widths", widths)?;
require_finite(ENTITY, "Slopes", slopes)?;
if let Some(bad) = widths.iter().position(|w| *w < 0.0) {
return Err(invalid(
ENTITY,
"Widths",
format!("width at index {bad} is negative"),
));
}
if let Some(tags) = tags {
if tags.len() != widths.len() + 1 {
return Err(invalid(
ENTITY,
"Tags",
format!("expected {} tags, got {}", widths.len() + 1, tags.len()),
));
}
}
let attrs = vec![
Value::Enum(ProfileType::Curve.token().into()),
name.map_or(Value::Null, |n| Value::Text(n.into())),
Value::Bool(horizontal_widths),
reals(widths),
reals(slopes),
tags.map_or(Value::Null, |t| {
Value::List(t.iter().map(|v| Value::Text((*v).into())).collect())
}),
offset_point.map_or(Value::Null, Value::Ref),
];
Ok(tx.create(Entity::new(ENTITY, attrs)))
}