BREP_kernel 0.3.1

A boundary representation (BREP) geometry kernel for building CAD applications.
Documentation
    mod assembly_structure;
    mod periodic_profiles;
    mod conic_endpoints;
    mod brep_with_voids;
    mod sphere_seams;
    mod nurbs_healing;
    mod round_trip;
    mod import_files;
    mod analytic_import;
    mod faceted_and_errors;
    mod presentation_colors;

    use super::*;
    use crate::{
        boolean_operation, export_step, make_box_brep, make_cone_brep, make_cylinder_brep,
        make_sphere_brep, make_torus_brep, solid_mass_properties, AnalyticSurface,
        BooleanOperation, BooleanOptions,
    };

    fn degree_five_bezier_with_start_offset(offset: f64, chord: f64) -> NurbsCurve {
        let points = (0..=5)
            .map(|index| {
                Vec4::from_point(
                    Vec3::new(offset + chord * index as f64 / 5.0, 0.0, 0.0),
                    1.0,
                )
            })
            .collect();
        NurbsCurve::new(
            5,
            vec![0.0; 6].into_iter().chain(vec![1.0; 6]).collect(),
            points,
        )
        .unwrap()
    }

    fn vec4_distance(a: Vec4, b: Vec4) -> f64 {
        ((a.x - b.x).powi(2) + (a.y - b.y).powi(2) + (a.z - b.z).powi(2) + (a.w - b.w).powi(2))
            .sqrt()
    }

    /// Generic Cox–de Boor point evaluation valid for ANY (also unclamped)
    /// knot vector — the independent reference the clamp is checked against.
    fn deboor_point(degree: usize, knots: &[f64], points: &[Vec4], t: f64) -> Vec4 {
        let p = degree;
        let mut k = p;
        for index in p..(points.len()) {
            if knots[index] <= t {
                k = index;
            }
        }
        let mut d: Vec<Vec4> = (0..=p).map(|j| points[j + k - p]).collect();
        for r in 1..=p {
            for j in (r..=p).rev() {
                let i = j + k - p;
                let denom = knots[i + p - r + 1] - knots[i];
                let alpha = if denom.abs() < 1e-15 {
                    0.0
                } else {
                    (t - knots[i]) / denom
                };
                d[j] = SplineElement::lerp(&d[j - 1], &d[j], alpha);
            }
        }
        d[p]
    }

    fn face_count(solid: &BrepSolid) -> usize {
        solid.shells.iter().map(|shell| shell.faces.len()).sum()
    }

    fn analytic_kinds(solid: &BrepSolid) -> Vec<&'static str> {
        solid
            .shells
            .iter()
            .flat_map(|shell| &shell.faces)
            .map(|face| match face.surface.analytic() {
                Some(AnalyticSurface::Plane { .. }) => "plane",
                Some(AnalyticSurface::RuledRevolution { .. }) => "ruled",
                Some(AnalyticSurface::Sphere { .. }) => "sphere",
                Some(AnalyticSurface::Torus { .. }) => "torus",
                Some(AnalyticSurface::Revolution { .. }) => "revolution",
                None => "nurbs",
            })
            .collect()
    }

    /// Export a solid then import it back and assert the reconstruction matches
    /// the original in validity, genus, topology counts, and volume.
    fn assert_round_trip(label: &str, original: &BrepSolid) {
        let step = export_step(std::slice::from_ref(original), label, "millimeter", "fixed")
            .expect("export");
        let imported = import_step(&step).expect("import");
        assert_eq!(imported.len(), 1, "{label}: expected one solid");
        let solid = &imported[0];

        assert!(
            solid.validate().is_empty(),
            "{label}: imported solid invalid: {:?}",
            solid.validate()
        );
        assert_eq!(solid.genus, original.genus, "{label}: genus mismatch");
        assert_eq!(
            face_count(solid),
            face_count(original),
            "{label}: face count mismatch"
        );
        assert_eq!(
            solid.edges.len(),
            original.edges.len(),
            "{label}: edge count mismatch"
        );
        assert_eq!(
            solid.vertices.len(),
            original.vertices.len(),
            "{label}: vertex count mismatch"
        );

        let original_volume = solid_mass_properties(original).expect("orig volume").volume;
        let imported_volume = solid_mass_properties(solid).expect("volume").volume;
        let relative = ((imported_volume - original_volume) / original_volume).abs();
        assert!(
            relative < 1e-6,
            "{label}: volume {imported_volume} vs {original_volume} (rel {relative:.3e})"
        );
    }

    // --- Analytic-entity fixtures (real-CAD conventions), verified against
    // --- closed-form volumes (the reliable oracle).

    /// Import a hand-authored analytic fixture and assert validity, the exact
    /// analytic surface types, and a closed-form volume within a tight tol.
    fn assert_analytic(
        text: &str,
        label: &str,
        expected_kinds: &[&str],
        genus: i64,
        expected_volume: f64,
    ) {
        let solids = import_step(text).unwrap_or_else(|error| panic!("{label}: {error}"));
        assert_eq!(solids.len(), 1, "{label}");
        let solid = &solids[0];
        assert!(
            solid.validate().is_empty(),
            "{label} invalid: {:?}",
            solid.validate()
        );
        assert_eq!(solid.genus, genus, "{label}: genus");
        let mut kinds = analytic_kinds(solid);
        kinds.sort_unstable();
        let mut expected = expected_kinds.to_vec();
        expected.sort_unstable();
        assert_eq!(kinds, expected, "{label}: analytic surface types");
        assert!(
            !kinds.contains(&"nurbs"),
            "{label}: analytic file must not fall back to NURBS faces"
        );
        let volume = solid_mass_properties(solid).unwrap().volume;
        let relative = ((volume - expected_volume) / expected_volume).abs();
        assert!(
            relative < 1e-6,
            "{label}: volume {volume} vs {expected_volume} (rel {relative:.3e})"
        );
    }