use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::error::{GeorefError, GeorefResult};
mod operation;
pub use operation::{
create_geographic_crs, create_map_conversion, create_map_conversion_scaled,
create_rigid_operation, create_well_known_text, GeographicCrsDraft, MapConversionDraft,
};
fn invalid(entity: &'static str, attribute: &'static str, value: impl Into<String>) -> GeorefError {
GeorefError::AuthoringInvalid {
entity,
attribute,
value: value.into(),
}
}
fn optional_text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |t| Value::Text(t.into()))
}
pub fn create_direction(tx: &mut Transaction, ratios: &[f64]) -> GeorefResult<EntityId> {
if !(2..=3).contains(&ratios.len()) {
return Err(invalid(
"IFCDIRECTION",
"DirectionRatios",
format!("{} ratios", ratios.len()),
));
}
if ratios.iter().any(|r| !r.is_finite()) {
return Err(invalid("IFCDIRECTION", "DirectionRatios", "not finite"));
}
if ratios.iter().all(|r| *r == 0.0) {
return Err(invalid("IFCDIRECTION", "DirectionRatios", "zero length"));
}
Ok(tx.create(Entity::new(
"IFCDIRECTION",
vec![Value::List(
ratios.iter().copied().map(Value::Real).collect(),
)],
)))
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ProjectedCrsDraft<'a> {
pub name: &'a str,
pub description: Option<&'a str>,
pub geodetic_datum: Option<&'a str>,
pub vertical_datum: Option<&'a str>,
pub map_projection: Option<&'a str>,
pub map_zone: Option<&'a str>,
pub map_unit: Option<EntityId>,
}
pub fn create_projected_crs(
tx: &mut Transaction,
draft: ProjectedCrsDraft<'_>,
) -> GeorefResult<EntityId> {
if draft.name.trim().is_empty() {
return Err(invalid("IFCPROJECTEDCRS", "Name", draft.name));
}
Ok(tx.create(Entity::new(
"IFCPROJECTEDCRS",
vec![
Value::Text(draft.name.into()),
optional_text(draft.description),
optional_text(draft.geodetic_datum),
optional_text(draft.vertical_datum),
optional_text(draft.map_projection),
optional_text(draft.map_zone),
draft.map_unit.map_or(Value::Null, Value::Ref),
],
)))
}
pub fn create_representation_context(
tx: &mut Transaction,
context_type: Option<&str>,
dimension: i64,
precision: Option<f64>,
world_coordinate_system: EntityId,
true_north: Option<EntityId>,
) -> GeorefResult<EntityId> {
if !(1..=3).contains(&dimension) {
return Err(invalid(
"IFCGEOMETRICREPRESENTATIONCONTEXT",
"CoordinateSpaceDimension",
dimension.to_string(),
));
}
if precision.is_some_and(|p| !p.is_finite()) {
return Err(invalid(
"IFCGEOMETRICREPRESENTATIONCONTEXT",
"Precision",
"not finite",
));
}
Ok(tx.create(Entity::new(
"IFCGEOMETRICREPRESENTATIONCONTEXT",
vec![
Value::Null,
optional_text(context_type),
Value::Integer(dimension),
precision.map_or(Value::Null, Value::Real),
Value::Ref(world_coordinate_system),
true_north.map_or(Value::Null, Value::Ref),
],
)))
}
pub fn create_representation_subcontext(
tx: &mut Transaction,
parent: EntityId,
parent_is_subcontext: bool,
context_identifier: Option<&str>,
target_view: &str,
user_defined_target_view: Option<&str>,
) -> GeorefResult<EntityId> {
if parent_is_subcontext {
return Err(invalid(
"IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
"ParentContext",
"a subcontext cannot parent a subcontext",
));
}
if target_view.trim().is_empty() {
return Err(invalid(
"IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
"TargetView",
target_view,
));
}
let named = user_defined_target_view.is_some_and(|v| !v.trim().is_empty());
if target_view == "USERDEFINED" && !named {
return Err(invalid(
"IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
"UserDefinedTargetView",
"required when TargetView is USERDEFINED",
));
}
Ok(tx.create(Entity::new(
"IFCGEOMETRICREPRESENTATIONSUBCONTEXT",
vec![
optional_text(context_identifier),
Value::Null,
Value::Derived,
Value::Derived,
Value::Derived,
Value::Derived,
Value::Ref(parent),
Value::Null,
Value::Enum(target_view.into()),
optional_text(user_defined_target_view),
],
)))
}