ifc-geometry 0.4.4

IFC semantic views lowered into the format-neutral geometry DAG.
Documentation
//! EXPRESS `WHERE` rules: the schema's own correctness conditions.
//!
//! # Why these are worth implementing
//!
//! A file can parse and still describe impossible geometry: a 2D direction
//! on a 3D placement, a boolean between operands of different dimensionality,
//! an extrusion parallel to the plane it extrudes. The schema states these
//! conditions as `WHERE` rules, and they are the difference between "the
//! parser accepted it" and "a kernel can build it".
//!
//! Checking them **here** rather than in the kernel matters: the kernel would
//! discover the problem as a numerical failure deep in an algorithm, where the
//! diagnostic is a degenerate matrix rather than "RefDirection is parallel to
//! Axis in #4711".
//!
//! # Scope
//!
//! IFC4 declares 95 where-rules across 56 geometry entities, and all 95 are
//! implemented (`data/ifc4-where-rules.tsv`, asserted by
//! `tests/where_rule_inventory.rs`). A rule whose check cannot fail on a
//! parsed model would be kept as `inventoried`, with the reason beside the
//! code, rather than implemented as a check that always passes.
//!
//! Rules do not re-check what the parser and typed views already enforce
//! (declared types, attribute arity, SELECT membership); a rule states only
//! the schema's own `WHERE` condition. Numerical validation of a built shape
//! belongs to the kernel, not here.
//!
//! # Design
//!
//! A rule is a pure function from a resolved view to `Result<(), RuleViolation>`.
//! Rules never mutate, never allocate on the success path, and are grouped by
//! the entity they constrain. [`validate`] runs every rule that applies to an
//! entity, so a caller checks a whole model without knowing the rule list.

mod bspline;
mod cardinality;
mod curve;
mod dimension;
mod express;
mod grid;
pub mod placement;
mod scalar;
pub mod solid;
mod surface;
mod typing;
pub mod violation;

pub use violation::{RuleViolation, ViolationKind};

use ifc_model::{EntityId, Model};

/// Run every implemented where-rule that applies to this entity.
///
/// Returns all violations rather than the first, because a consumer fixing a
/// file wants the whole list, and because one bad placement often implies
/// several related failures.
pub fn validate(model: &Model, id: EntityId) -> Vec<RuleViolation> {
    let Some(entity) = model.get(id) else {
        return Vec::new();
    };
    let mut found = Vec::new();
    placement::check(model, id, entity, &mut found);
    solid::check(model, id, entity, &mut found);
    grid::check(model, id, entity, &mut found);
    curve::check(model, id, entity, &mut found);
    scalar::check(model, id, entity, &mut found);
    cardinality::check(model, id, entity, &mut found);
    typing::check(model, id, entity, &mut found);
    surface::check(model, id, entity, &mut found);
    bspline::check(model, id, entity, &mut found);
    found
}

/// Validate every entity in a model.
///
/// Linear in model size and allocation-free unless a rule actually fails,
/// so it is cheap enough to run as an import-time check.
pub fn validate_model(model: &Model) -> Vec<RuleViolation> {
    model
        .iter()
        .flat_map(|(id, _)| validate(model, id))
        .collect()
}