use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::error::GeometryError;
use crate::resource::operator::slot;
use super::{invalid, refs, require_finite};
#[derive(Debug, Default, Clone, Copy)]
pub struct Transform {
pub axis1: Option<EntityId>,
pub axis2: Option<EntityId>,
pub scale: Option<f64>,
}
fn check_scale(
type_name: &'static str,
attribute: &'static str,
scale: Option<f64>,
) -> Result<(), GeometryError> {
let Some(scale) = scale else {
return Ok(());
};
require_finite(type_name, attribute, &[scale])?;
if scale <= 0.0 {
return Err(invalid(
type_name,
attribute,
format!("expected a scale above zero, got {scale}"),
));
}
Ok(())
}
fn base(
type_name: &'static str,
width: usize,
local_origin: EntityId,
transform: Transform,
) -> Result<Vec<Value>, GeometryError> {
check_scale(type_name, "Scale", transform.scale)?;
let mut attrs = vec![Value::Null; width];
attrs[slot::AXIS1] = transform.axis1.map_or(Value::Null, Value::Ref);
attrs[slot::AXIS2] = transform.axis2.map_or(Value::Null, Value::Ref);
attrs[slot::LOCAL_ORIGIN] = Value::Ref(local_origin);
attrs[slot::SCALE] = transform.scale.map_or(Value::Null, Value::Real);
Ok(attrs)
}
pub fn transformation_operator_2d(
tx: &mut Transaction,
local_origin: EntityId,
transform: Transform,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR2D";
let attrs = base(T, 4, local_origin, transform)?;
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn transformation_operator_2d_non_uniform(
tx: &mut Transaction,
local_origin: EntityId,
transform: Transform,
scale2: Option<f64>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR2DNONUNIFORM";
check_scale(T, "Scale2", scale2)?;
let mut attrs = base(T, 5, local_origin, transform)?;
attrs[slot::SCALE2_2D] = scale2.map_or(Value::Null, Value::Real);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn transformation_operator_3d(
tx: &mut Transaction,
local_origin: EntityId,
transform: Transform,
axis3: Option<EntityId>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR3D";
let mut attrs = base(T, 5, local_origin, transform)?;
attrs[slot::AXIS3] = axis3.map_or(Value::Null, Value::Ref);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn transformation_operator_3d_non_uniform(
tx: &mut Transaction,
local_origin: EntityId,
transform: Transform,
axis3: Option<EntityId>,
scale2: Option<f64>,
scale3: Option<f64>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCCARTESIANTRANSFORMATIONOPERATOR3DNONUNIFORM";
check_scale(T, "Scale2", scale2)?;
check_scale(T, "Scale3", scale3)?;
let mut attrs = base(T, 7, local_origin, transform)?;
attrs[slot::AXIS3] = axis3.map_or(Value::Null, Value::Ref);
attrs[slot::SCALE2_3D] = scale2.map_or(Value::Null, Value::Real);
attrs[slot::SCALE3_3D] = scale3.map_or(Value::Null, Value::Real);
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn representation_map(
tx: &mut Transaction,
mapping_origin: EntityId,
mapped_representation: EntityId,
) -> EntityId {
let attrs = vec![
Value::Ref(mapping_origin),
Value::Ref(mapped_representation),
];
tx.create(Entity::new("IFCREPRESENTATIONMAP", attrs))
}
pub fn mapped_item(
tx: &mut Transaction,
mapping_source: EntityId,
mapping_target: EntityId,
) -> EntityId {
let attrs = vec![Value::Ref(mapping_source), Value::Ref(mapping_target)];
tx.create(Entity::new("IFCMAPPEDITEM", attrs))
}
pub fn topology_representation(
tx: &mut Transaction,
context: EntityId,
identifier: Option<&str>,
representation_type: Option<&str>,
items: &[EntityId],
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCTOPOLOGYREPRESENTATION";
if items.is_empty() {
return Err(invalid(T, "Items", "expected at least one item"));
}
let attrs = vec![
Value::Ref(context),
identifier.map_or(Value::Null, |v| Value::Text(v.into())),
representation_type.map_or(Value::Null, |v| Value::Text(v.into())),
refs(items),
];
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn shape_aspect(
tx: &mut Transaction,
shape_representations: &[EntityId],
name: Option<&str>,
description: Option<&str>,
product_definitional: Option<bool>,
part_of_product_definition_shape: Option<EntityId>,
) -> Result<EntityId, GeometryError> {
const ENTITY: &str = "IFCSHAPEASPECT";
if shape_representations.is_empty() {
return Err(invalid(
ENTITY,
"ShapeRepresentations",
"expected at least one representation, per LIST [1:?]",
));
}
let attrs = vec![
refs(shape_representations),
name.map_or(Value::Null, |v| Value::Text(v.into())),
description.map_or(Value::Null, |v| Value::Text(v.into())),
product_definitional.map_or(Value::LogicalUnknown, Value::Bool),
part_of_product_definition_shape.map_or(Value::Null, Value::Ref),
];
Ok(tx.create(Entity::new(ENTITY, attrs)))
}