Skip to main content

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//! Rules do not re-check what the parser and typed views already enforce
25//! (declared types, attribute arity, SELECT membership); a rule states only
26//! the schema's own `WHERE` condition. Numerical validation of a built shape
27//! belongs to the kernel, not here.
28//!
29//! # Design
30//!
31//! A rule is a pure function from a resolved view to `Result<(), RuleViolation>`.
32//! Rules never mutate, never allocate on the success path, and are grouped by
33//! the entity they constrain. [`validate`] runs every rule that applies to an
34//! entity, so a caller checks a whole model without knowing the rule list.
35
36mod bspline;
37mod cardinality;
38mod curve;
39mod dimension;
40mod express;
41mod grid;
42pub mod placement;
43mod scalar;
44pub mod solid;
45mod surface;
46mod typing;
47pub mod violation;
48
49pub use violation::{RuleViolation, ViolationKind};
50
51use ifc_model::{EntityId, Model};
52
53/// Run every implemented where-rule that applies to this entity.
54///
55/// Returns all violations rather than the first, because a consumer fixing a
56/// file wants the whole list, and because one bad placement often implies
57/// several related failures.
58pub fn validate(model: &Model, id: EntityId) -> Vec<RuleViolation> {
59    let Some(entity) = model.get(id) else {
60        return Vec::new();
61    };
62    let mut found = Vec::new();
63    placement::check(model, id, entity, &mut found);
64    solid::check(model, id, entity, &mut found);
65    grid::check(model, id, entity, &mut found);
66    curve::check(model, id, entity, &mut found);
67    scalar::check(model, id, entity, &mut found);
68    cardinality::check(model, id, entity, &mut found);
69    typing::check(model, id, entity, &mut found);
70    surface::check(model, id, entity, &mut found);
71    bspline::check(model, id, entity, &mut found);
72    found
73}
74
75/// Validate every entity in a model.
76///
77/// Linear in model size and allocation-free unless a rule actually fails,
78/// so it is cheap enough to run as an import-time check.
79pub fn validate_model(model: &Model) -> Vec<RuleViolation> {
80    model
81        .iter()
82        .flat_map(|(id, _)| validate(model, id))
83        .collect()
84}