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 == "IFCBSPLINECURVEWITHKNOTS" || name == "IFCRATIONALBSPLINECURVEWITHKNOTS" {
same_len(
id,
&name,
entity,
(5, 6),
"CorrespondingKnotLists",
("KnotMultiplicities", "Knots"),
out,
);
}
if name == "IFCRATIONALBSPLINECURVEWITHKNOTS" {
same_len(
id,
&name,
entity,
(8, 1),
"SameNumOfWeightsAndPoints",
("WeightsData", "ControlPointsList"),
out,
);
}
if name == "IFCSECTIONEDSPINE" {
same_len(
id,
&name,
entity,
(1, 2),
"CorrespondingSectionPositions",
("CrossSections", "CrossSectionPositions"),
out,
);
}
basis_surface_rules(model, id, entity, &name, out);
if name == "IFCINTERSECTIONCURVE" || name == "IFCSEAMCURVE" {
if let Some(Value::List(items)) = entity.attribute(1).map(|v| v.unwrap_typed()) {
if items.len() != 2 {
out.push(RuleViolation::new(
id,
name.clone(),
"TwoPCurves",
ViolationKind::Disagreement,
format!(
"AssociatedGeometry holds {} p-curves, must hold 2",
items.len()
),
));
}
}
}
}
fn same_len(
id: EntityId,
type_name: &str,
entity: &Entity,
slots: (usize, usize),
rule: &'static str,
labels: (&str, &str),
out: &mut Vec<RuleViolation>,
) {
let ((slot_a, slot_b), (label_a, label_b)) = (slots, labels);
let (Some(a), Some(b)) = (list_len(entity, slot_a), list_len(entity, slot_b)) else {
return;
};
if a != b {
out.push(RuleViolation::new(
id,
type_name.to_string(),
rule,
ViolationKind::Disagreement,
format!("{label_a} holds {a} but {label_b} holds {b}"),
));
}
}
fn list_len(entity: &Entity, slot: usize) -> Option<usize> {
match entity.attribute(slot).map(|v| v.unwrap_typed()) {
Some(Value::List(items)) => Some(items.len()),
_ => None,
}
}
fn basis_surface_rules(
model: &Model,
id: EntityId,
entity: &Entity,
name: &str,
out: &mut Vec<RuleViolation>,
) {
if crate::select::is_a(name, "IFCCOMPOSITECURVEONSURFACE") {
if crate::surface::basis::basis_surfaces(model, id).is_empty() {
out.push(RuleViolation::new(
id,
name.to_string(),
"SameSurface",
ViolationKind::Disagreement,
"the segments of this composite curve share no basis surface".to_string(),
));
}
return;
}
let associated = super::dimension::list_refs(entity, 1);
if associated.len() != 2 {
return;
}
let first = crate::surface::basis::basis_surfaces(model, associated[0]);
let second = crate::surface::basis::basis_surfaces(model, associated[1]);
if first.is_empty() || second.is_empty() {
return;
}
let same = first == second;
let (rule, want_same, detail) = if crate::select::is_a(name, "IFCSEAMCURVE") {
(
"SameSurface",
true,
"a seam curve must run twice over one surface, but its two \
p-curves name different surfaces",
)
} else if crate::select::is_a(name, "IFCINTERSECTIONCURVE") {
(
"DistinctSurfaces",
false,
"an intersection curve must cross two different surfaces, but \
both its p-curves name the same one",
)
} else {
return;
};
if same != want_same {
out.push(RuleViolation::new(
id,
name.to_string(),
rule,
ViolationKind::Disagreement,
detail.to_string(),
));
}
}