use ifc_model::{Entity, EntityId, Model, Value};
use super::violation::{RuleViolation, ViolationKind};
pub fn check(model: &Model, id: EntityId, entity: &Entity, out: &mut Vec<RuleViolation>) {
let name = entity.type_name.to_ascii_uppercase();
if name == "IFCCOMPOSITECURVESEGMENT" || name == "IFCREPARAMETRISEDCOMPOSITECURVESEGMENT" {
require_kind(
model,
id,
entity,
&name,
2,
"IFCBOUNDEDCURVE",
true,
"ParentIsBoundedCurve",
"ParentCurve",
out,
);
}
if name == "IFCTRIMMEDCURVE" {
require_kind(
model,
id,
entity,
&name,
0,
"IFCBOUNDEDCURVE",
false,
"NoTrimOfBoundedCurves",
"BasisCurve",
out,
);
}
if crate::select::is_a(&name, "IFCSURFACECURVE") {
require_kind(
model,
id,
entity,
&name,
0,
"IFCPCURVE",
false,
"CurveIsNotPcurve",
"Curve3D",
out,
);
}
if name == "IFCBOXEDHALFSPACE" {
require_kind(
model,
id,
entity,
&name,
0,
"IFCCURVEBOUNDEDPLANE",
false,
"UnboundedSurface",
"BaseSurface",
out,
);
}
if name == "IFCGEOMETRICCURVESET" {
if let Some(Value::List(items)) = entity.attribute(0).map(|v| v.unwrap_typed()) {
for item in items {
let Value::Ref(target) = item.unwrap_typed() else {
continue;
};
let Some(e) = model.get(*target) else {
continue;
};
if crate::select::is_a(&e.type_name.to_ascii_uppercase(), "IFCSURFACE") {
out.push(RuleViolation::new(
id,
name.clone(),
"NoSurfaces",
ViolationKind::WrongType,
format!("Elements holds surface {target}, which a curve set excludes"),
));
}
}
}
}
if name == "IFCSWEPTDISKSOLIDPOLYGONAL" {
if let Some(Value::Ref(target)) = entity.attribute(0).map(|v| v.unwrap_typed()) {
if let Some(e) = model.get(*target) {
let n = e.type_name.to_ascii_uppercase();
let plain_indexed = n == "IFCINDEXEDPOLYCURVE"
&& !matches!(e.attribute(1).map(|v| v.unwrap_typed()), Some(Value::List(s)) if !s.is_empty());
if n != "IFCPOLYLINE" && !plain_indexed {
out.push(RuleViolation::new(
id,
name.clone(),
"DirectrixIsPolyline",
ViolationKind::WrongType,
format!("Directrix {target} is {n}, must be a polyline"),
));
}
}
}
}
if name == "IFCINDEXEDPOLYCURVE" {
if let Some(Value::List(items)) = entity.attribute(1).map(|v| v.unwrap_typed()) {
let segments: Vec<Vec<i64>> = items
.iter()
.map(|s| match s.unwrap_typed() {
Value::List(idx) => idx
.iter()
.filter_map(|v| match v.unwrap_typed() {
Value::Integer(n) => Some(*n),
Value::Real(n) => Some(*n as i64),
_ => None,
})
.collect(),
_ => Vec::new(),
})
.collect();
if !segments.is_empty() && !super::express::consecutive_segments(&segments) {
out.push(RuleViolation::new(
id,
name.clone(),
"Consecutive",
ViolationKind::Disagreement,
"Segments do not join end-to-start".to_string(),
));
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn require_kind(
model: &Model,
id: EntityId,
entity: &Entity,
type_name: &str,
slot: usize,
kind: &str,
want: bool,
rule: &'static str,
label: &str,
out: &mut Vec<RuleViolation>,
) {
let Some(Value::Ref(target)) = entity.attribute(slot).map(|v| v.unwrap_typed()) else {
return;
};
let Some(e) = model.get(*target) else { return };
let actual = e.type_name.to_ascii_uppercase();
if crate::select::is_a(&actual, kind) == want {
return;
}
let detail = if want {
format!("{label} {target} is {actual}, must be a {kind}")
} else {
format!("{label} {target} is {actual}, which a {kind} excludes")
};
out.push(RuleViolation::new(
id,
type_name.to_string(),
rule,
ViolationKind::WrongType,
detail,
));
}