ifc_geometry/rules/mod.rs
1//! EXPRESS `WHERE` rules: the schema's own correctness conditions.
2//!
3//! # Why these are worth implementing
4//!
5//! A file can parse and still describe impossible geometry: a 2D direction
6//! on a 3D placement, a boolean between operands of different dimensionality,
7//! an extrusion parallel to the plane it extrudes. The schema states these
8//! conditions as `WHERE` rules, and they are the difference between "the
9//! parser accepted it" and "a kernel can build it".
10//!
11//! Checking them **here** rather than in the kernel matters: the kernel would
12//! discover the problem as a numerical failure deep in an algorithm, where the
13//! diagnostic is a degenerate matrix rather than "RefDirection is parallel to
14//! Axis in #4711".
15//!
16//! # Scope
17//!
18//! IFC4 declares 95 where-rules across 56 geometry entities, and all 95 are
19//! implemented (`data/ifc4-where-rules.tsv`, asserted by
20//! `tests/where_rule_inventory.rs`). A rule whose check cannot fail on a
21//! parsed model would be kept as `inventoried`, with the reason beside the
22//! code, rather than implemented as a check that always passes.
23//!
24//! # Design
25//!
26//! A rule is a pure function from a resolved view to `Result<(), RuleViolation>`.
27//! Rules never mutate, never allocate on the success path, and are grouped by
28//! the entity they constrain. [`validate`] runs every rule that applies to an
29//! entity, so a caller checks a whole model without knowing the rule list.
30
31mod bspline;
32mod cardinality;
33mod curve;
34mod dimension;
35mod express;
36mod grid;
37pub mod placement;
38mod scalar;
39pub mod solid;
40mod surface;
41mod typing;
42pub mod violation;
43
44pub use violation::{RuleViolation, ViolationKind};
45
46use ifc_model::{EntityId, Model};
47
48/// Run every implemented where-rule that applies to this entity.
49///
50/// Returns all violations rather than the first, because a consumer fixing a
51/// file wants the whole list, and because one bad placement often implies
52/// several related failures.
53pub fn validate(model: &Model, id: EntityId) -> Vec<RuleViolation> {
54 let Some(entity) = model.get(id) else {
55 return Vec::new();
56 };
57 let mut found = Vec::new();
58 placement::check(model, id, entity, &mut found);
59 solid::check(model, id, entity, &mut found);
60 grid::check(model, id, entity, &mut found);
61 curve::check(model, id, entity, &mut found);
62 scalar::check(model, id, entity, &mut found);
63 cardinality::check(model, id, entity, &mut found);
64 typing::check(model, id, entity, &mut found);
65 surface::check(model, id, entity, &mut found);
66 bspline::check(model, id, entity, &mut found);
67 found
68}
69
70/// Validate every entity in a model.
71///
72/// Linear in model size and allocation-free unless a rule actually fails,
73/// so it is cheap enough to run as an import-time check.
74pub fn validate_model(model: &Model) -> Vec<RuleViolation> {
75 model
76 .iter()
77 .flat_map(|(id, _)| validate(model, id))
78 .collect()
79}