Skip to main content

geop_core_topology/
test_fixtures.rs

1//! Test fixture — this crate's tests, and others' with the `testing`
2//! feature: a plain axis-aligned unit-cube-shaped solid, described whole
3//! and built at once (see [`crate::build`]).
4//!
5//! This crate sits *below* `geop-lib-sweep` in the workspace's
6//! dependency order (that crate is what actually builds shapes like a real
7//! `cube_solid` via `extrude`), so validation/edit tests here that
8//! just need "some realistic populated `Model`" to run structural or
9//! numerical checks against can't reach for it — a dev-dependency back on
10//! `geop-lib-sweep` would make Cargo compile this crate twice
11//! (once under `#[cfg(test)]`, once as that crate's plain library
12//! dependency), and the two copies' `Model` types don't unify, which
13//! surfaces as `expected Model, found Model` at every call site. This is a
14//! small, self-contained stand-in scoped to exactly what these tests need:
15//! six planar faces, twelve shared edges, eight shared vertices — no
16//! `extrude` generality (arbitrary footprints, holes) required.
17
18use geop_core_geometry::{
19    nurb_curve::{NurbCurve2D, NurbCurve3D},
20    nurb_surface::NurbSurface3D,
21};
22use geop_core_math::{
23    geop_error::GeopError,
24    scalars::Scalar,
25    vector::{Vector2, Vector3},
26};
27
28use crate::{
29    Model, Sense, SolidId,
30    build::{BodySpec, CoedgeOn, EdgeSpec},
31    validation::ValidationParameters,
32};
33
34/// Build `model`'s unit cube (corners at `0`/`1` on every axis) and return
35/// its `SolidId`. Every face is a flat, degree-`1` bilinear patch; every
36/// edge a straight degree-`1` line — plenty for the structural/numerical
37/// checks these tests actually exercise.
38pub fn test_cube_solid(model: &mut Model) -> SolidId {
39    let mut spec = BodySpec::new(true);
40    // 0..3 the bottom (z=0) ring, 4..7 the matching top (z=1) ring.
41    for [x, y, z] in [
42        [0.0, 0.0, 0.0],
43        [1.0, 0.0, 0.0],
44        [1.0, 1.0, 0.0],
45        [0.0, 1.0, 0.0],
46        [0.0, 0.0, 1.0],
47        [1.0, 0.0, 1.0],
48        [1.0, 1.0, 1.0],
49        [0.0, 1.0, 1.0],
50    ] {
51        spec.vertices
52            .push(Vector3::from_array([x, y, z].map(Scalar::from_f64)));
53    }
54    // Each ring, and the edges rising from the bottom one to the top.
55    let pairs = (0..4)
56        .flat_map(|k| [(k, (k + 1) % 4), (4 + k, 4 + (k + 1) % 4), (k, 4 + k)])
57        .collect::<Vec<_>>();
58    for &(start, end) in &pairs {
59        spec.edges.push(EdgeSpec {
60            curve: NurbCurve3D::line(spec.vertices[start], spec.vertices[end]).unwrap(),
61            start,
62            end,
63        });
64    }
65    let edge = |a: usize, b: usize| {
66        let e = pairs
67            .iter()
68            .position(|&p| p == (a, b) || p == (b, a))
69            .expect("an edge of the cube");
70        CoedgeOn::Edge(e, Sense::Forward)
71    };
72    // Each face's corners in `(0,0) -> (1,0) -> (1,1) -> (0,1)` order, so
73    // that `Su x Sv` points away from the solid's interior.
74    for corners in [
75        [0, 3, 2, 1], // bottom
76        [4, 5, 6, 7], // top
77        [0, 1, 5, 4], // front (y=0)
78        [3, 7, 6, 2], // back (y=1)
79        [0, 4, 7, 3], // left (x=0)
80        [1, 2, 6, 5], // right (x=1)
81    ] {
82        let at = |k: usize| spec.vertices[corners[k]];
83        let surface = NurbSurface3D::bilinear(at(0), at(1), at(2), at(3)).unwrap();
84        let sides = std::array::from_fn(|k| edge(corners[k], corners[(k + 1) % 4]));
85        let face = spec.square_face(surface, corners, sides).unwrap();
86        spec.faces.push(face);
87    }
88    spec.shells = vec![(0..6).collect()];
89    model
90        .build_body(spec)
91        .unwrap()
92        .solid
93        .expect("built as a solid")
94}
95
96/// A new model holding only [`test_cube_solid`].
97pub fn test_cube_model() -> Model {
98    let mut model = Model::new();
99    test_cube_solid(&mut model);
100    model
101}
102
103/// One check of [`crate::validation`]: it adds what it finds wrong with a
104/// model to the errors.
105pub type Check = fn(&ValidationParameters, &mut Vec<GeopError>, &Model);
106
107/// The errors `check` finds in `model`, under the default parameters.
108pub fn check_errors(check: Check, model: &Model) -> Vec<GeopError> {
109    let mut errors = Vec::new();
110    check(&ValidationParameters::default(), &mut errors, model);
111    errors
112}
113
114/// The straight line from `a` to `b`, over `[0, 1]`.
115pub fn line3(a: (f64, f64, f64), b: (f64, f64, f64)) -> NurbCurve3D {
116    let p = |(x, y, z): (f64, f64, f64)| Vector3::from_array([x, y, z].map(Scalar::from_f64));
117    NurbCurve3D::line(p(a), p(b)).unwrap()
118}
119
120/// The straight pcurve from `(u, v)` `a` to `b`, over `[0, 1]`.
121pub fn line2(a: (f64, f64), b: (f64, f64)) -> NurbCurve2D {
122    let p = |(u, v): (f64, f64)| Vector2::from_array([u, v].map(Scalar::from_f64));
123    NurbCurve2D::line(p(a), p(b)).unwrap()
124}