BREP_kernel 0.3.1

A boundary representation (BREP) geometry kernel for building CAD applications.
Documentation
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticSeverity {
    Info,
    Warning,
    Degraded,
    Error,
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelStage {
    Collect,
    Classify,
    Intersect,
    Refine,
    Fragment,
    Select,
    Sew,
    Validate,
    Export,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DiagnosticEvent {
    pub severity: DiagnosticSeverity,
    pub stage: KernelStage,
    /// Stable machine-readable identifier suitable for tests and UI routing.
    pub code: String,
    pub message: String,
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct KernelDiagnostics {
    pub events: Vec<DiagnosticEvent>,
    pub counters: BTreeMap<String, u64>,
    pub measurements: BTreeMap<String, f64>,
}

impl KernelDiagnostics {
    pub fn event(
        &mut self,
        severity: DiagnosticSeverity,
        stage: KernelStage,
        code: impl Into<String>,
        message: impl Into<String>,
    ) {
        self.events.push(DiagnosticEvent {
            severity,
            stage,
            code: code.into(),
            message: message.into(),
        });
    }

    pub fn count(&mut self, code: impl Into<String>) {
        *self.counters.entry(code.into()).or_default() += 1;
    }

    pub fn count_n(&mut self, code: impl Into<String>, amount: u64) {
        *self.counters.entry(code.into()).or_default() += amount;
    }

    pub fn measure_max(&mut self, code: impl Into<String>, value: f64) {
        let entry = self.measurements.entry(code.into()).or_insert(value);
        *entry = entry.max(value);
    }

    pub fn worst(&self) -> Option<DiagnosticSeverity> {
        self.events.iter().map(|event| event.severity).max()
    }

    pub fn shippable(&self) -> bool {
        self.events
            .iter()
            .all(|event| event.severity < DiagnosticSeverity::Error)
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct KernelOutcome<T> {
    pub value: T,
    pub diagnostics: KernelDiagnostics,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn diagnostics_are_stable_and_queryable() {
        let mut diagnostics = KernelDiagnostics::default();
        diagnostics.count("intersect.pairs");
        diagnostics.count_n("intersect.pairs", 2);
        diagnostics.measure_max("validate.edge_error", 0.1);
        diagnostics.measure_max("validate.edge_error", 0.05);
        diagnostics.event(
            DiagnosticSeverity::Warning,
            KernelStage::Classify,
            "pair.near_tangent",
            "dedicated path selected",
        );
        assert_eq!(diagnostics.counters["intersect.pairs"], 3);
        assert_eq!(diagnostics.measurements["validate.edge_error"], 0.1);
        assert!(diagnostics.shippable());
    }
}