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. Implemented here
19//! are the ones a consumer can actually act on: rules about dimensionality,
20//! degeneracy and operand agreement. Rules that merely restate a type
21//! constraint the parser already enforces are noted as such and skipped.
22//!
23//! # Design
24//!
25//! A rule is a pure function from a resolved view to `Result<(), RuleViolation>`.
26//! Rules never mutate, never allocate on the success path, and are grouped by
27//! the entity they constrain. [`validate`] runs every rule that applies to an
28//! entity, so a caller checks a whole model without knowing the rule list.
29
30mod bspline;
31mod cardinality;
32mod curve;
33mod dimension;
34mod express;
35mod grid;
36pub mod placement;
37mod scalar;
38pub mod solid;
39mod surface;
40mod typing;
41pub mod violation;
42
43pub use violation::{RuleViolation, ViolationKind};
44
45use ifc_model::{EntityId, Model};
46
47/// Run every implemented where-rule that applies to this entity.
48///
49/// Returns all violations rather than the first, because a consumer fixing a
50/// file wants the whole list, and because one bad placement often implies
51/// several related failures.
52pub fn validate(model: &Model, id: EntityId) -> Vec<RuleViolation> {
53 let Some(entity) = model.get(id) else {
54 return Vec::new();
55 };
56 let mut found = Vec::new();
57 placement::check(model, id, entity, &mut found);
58 solid::check(model, id, entity, &mut found);
59 grid::check(model, id, entity, &mut found);
60 curve::check(model, id, entity, &mut found);
61 scalar::check(model, id, entity, &mut found);
62 cardinality::check(model, id, entity, &mut found);
63 typing::check(model, id, entity, &mut found);
64 surface::check(model, id, entity, &mut found);
65 bspline::check(model, id, entity, &mut found);
66 found
67}
68
69/// Validate every entity in a model.
70///
71/// Linear in model size and allocation-free unless a rule actually fails,
72/// so it is cheap enough to run as an import-time check.
73pub fn validate_model(model: &Model) -> Vec<RuleViolation> {
74 model
75 .iter()
76 .flat_map(|(id, _)| validate(model, id))
77 .collect()
78}