use ifc_model::{Entity, EntityId, Model};
use super::violation::{RuleViolation, ViolationKind};
use crate::constraint::grid::GridAxis;
use crate::resource::point::CartesianPoint;
pub fn check(model: &Model, id: EntityId, entity: &Entity, out: &mut Vec<RuleViolation>) {
if entity.type_name.eq_ignore_ascii_case("IFCGRIDAXIS") {
grid_axis(model, id, entity, out);
}
}
fn grid_axis(model: &Model, id: EntityId, entity: &Entity, out: &mut Vec<RuleViolation>) {
const TYPE: &str = "IFCGRIDAXIS";
let view = GridAxis::new(id, entity);
if let Ok(curve) = view.curve() {
if let Some(dim) = curve_dim(model, curve) {
if dim != 2 {
out.push(RuleViolation::new(
id,
TYPE,
"WR1",
ViolationKind::Dimensionality,
format!("AxisCurve {curve} is {dim}D, must be 2D"),
));
}
}
}
grid_axis_membership(model, id, out);
}
fn grid_axis_membership(model: &Model, id: EntityId, out: &mut Vec<RuleViolation>) {
const TYPE: &str = "IFCGRIDAXIS";
let mut memberships = 0usize;
for (grid_id, grid) in model.iter() {
if !grid.type_name.eq_ignore_ascii_case("IFCGRID") {
continue;
}
let _ = grid_id;
for slot in [grid_slot::U_AXES, grid_slot::V_AXES, grid_slot::W_AXES] {
let Some(list) = grid.attributes.get(slot).and_then(|value| value.as_list()) else {
continue;
};
if list
.iter()
.filter_map(ifc_model::Value::as_ref_id)
.any(|a| a == id)
{
memberships += 1;
}
}
}
if memberships != 1 {
out.push(RuleViolation::new(
id,
TYPE,
"WR2",
ViolationKind::Disagreement,
format!("axis belongs to {memberships} grid axis lists, must belong to exactly 1"),
));
}
}
pub(crate) mod grid_slot {
pub const U_AXES: usize = 7;
pub const V_AXES: usize = 8;
pub const W_AXES: usize = 9;
}
fn curve_dim(model: &Model, id: EntityId) -> Option<usize> {
let entity = model.get(id)?;
match entity.type_name.to_ascii_uppercase().as_str() {
"IFCPOLYLINE" => {
let first = entity.attributes.first()?.as_list()?.first()?.as_ref_id()?;
point_dim(model, first)
}
"IFCLINE" => {
let pnt = entity.attributes.first()?.as_ref_id()?;
point_dim(model, pnt)
}
"IFCCIRCLE" | "IFCELLIPSE" => {
let position = entity.attributes.first()?.as_ref_id()?;
let placement = model.get(position)?;
match placement.type_name.to_ascii_uppercase().as_str() {
"IFCAXIS2PLACEMENT2D" => Some(2),
"IFCAXIS2PLACEMENT3D" => Some(3),
_ => None,
}
}
_ => None,
}
}
fn point_dim(model: &Model, id: EntityId) -> Option<usize> {
let entity = model.get(id)?;
CartesianPoint::new(id, entity)
.coordinates()
.ok()
.map(|c| c.len())
}
#[cfg(test)]
mod tests {
use super::*;
use ifc_model::Value;
fn point(model: &mut Model, id: u64, coords: &[f64]) {
model.insert(
EntityId(id),
Entity::new(
"IFCCARTESIANPOINT",
vec![Value::List(
coords.iter().map(|c| Value::Real(*c)).collect(),
)],
),
);
}
fn polyline(model: &mut Model, id: u64, pts: &[u64]) {
model.insert(
EntityId(id),
Entity::new(
"IFCPOLYLINE",
vec![Value::List(
pts.iter().map(|p| Value::Ref(EntityId(*p))).collect(),
)],
),
);
}
fn axis(model: &mut Model, id: u64, curve: u64) {
model.insert(
EntityId(id),
Entity::new(
"IFCGRIDAXIS",
vec![
Value::Text("A".into()),
Value::Ref(EntityId(curve)),
Value::Bool(true),
],
),
);
}
fn grid(model: &mut Model, id: u64, u: &[u64], v: &[u64], w: &[u64]) {
let list =
|ids: &[u64]| Value::List(ids.iter().map(|a| Value::Ref(EntityId(*a))).collect());
let mut attrs = vec![Value::Null; 7];
attrs.push(list(u));
attrs.push(list(v));
attrs.push(list(w));
model.insert(EntityId(id), Entity::new("IFCGRID", attrs));
}
#[test]
fn a_three_dimensional_axis_curve_violates_wr1() {
let mut model = Model::new();
point(&mut model, 1, &[0.0, 0.0, 0.0]);
polyline(&mut model, 2, &[1]);
axis(&mut model, 3, 2);
grid(&mut model, 4, &[3], &[], &[]);
let found = crate::rules::validate(&model, EntityId(3));
let wr1: Vec<_> = found.iter().filter(|v| v.rule == "WR1").collect();
assert_eq!(wr1.len(), 1, "a 3D axis curve must be reported");
assert!(wr1[0].detail.contains("3D"));
}
#[test]
fn a_two_dimensional_axis_in_one_list_is_silent() {
let mut model = Model::new();
point(&mut model, 1, &[0.0, 0.0]);
polyline(&mut model, 2, &[1]);
axis(&mut model, 3, 2);
grid(&mut model, 4, &[3], &[], &[]);
assert!(crate::rules::validate(&model, EntityId(3)).is_empty());
}
#[test]
fn an_axis_in_two_lists_violates_wr2() {
let mut model = Model::new();
point(&mut model, 1, &[0.0, 0.0]);
polyline(&mut model, 2, &[1]);
axis(&mut model, 3, 2);
grid(&mut model, 4, &[3], &[3], &[]);
let found = crate::rules::validate(&model, EntityId(3));
let wr2: Vec<_> = found.iter().filter(|v| v.rule == "WR2").collect();
assert_eq!(wr2.len(), 1);
assert!(wr2[0].detail.contains("2 grid axis lists"));
}
#[test]
fn an_axis_in_no_list_violates_wr2() {
let mut model = Model::new();
point(&mut model, 1, &[0.0, 0.0]);
polyline(&mut model, 2, &[1]);
axis(&mut model, 3, 2);
grid(&mut model, 4, &[], &[], &[]);
let found = crate::rules::validate(&model, EntityId(3));
assert!(found.iter().any(|v| v.rule == "WR2"));
}
}