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;
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()
}
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()
}
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})"
);
}
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})"
);
}