use brepkit_math::vec::Vec3;
use brepkit_topology::Topology;
use brepkit_topology::face::{FaceId, FaceSurface};
use brepkit_topology::solid::SolidId;
use crate::extrude::extrude;
pub fn thicken(
topo: &mut Topology,
face: FaceId,
thickness: f64,
) -> Result<SolidId, crate::OperationsError> {
let tol = brepkit_math::tolerance::Tolerance::new();
if tol.approx_eq(thickness, 0.0) {
return Err(crate::OperationsError::InvalidInput {
reason: "thickness must be non-zero".into(),
});
}
let face_data = topo.face(face)?;
let normal = face_normal(face_data.surface())?;
let (direction, distance) = if thickness > 0.0 {
(normal, thickness)
} else {
(-normal, -thickness)
};
extrude(topo, face, direction, distance)
}
fn face_normal(surface: &FaceSurface) -> Result<Vec3, crate::OperationsError> {
match surface {
FaceSurface::Plane { normal, .. } => Ok(*normal),
FaceSurface::Nurbs(nurbs) => {
nurbs
.normal(0.5, 0.5)
.map_err(|e| crate::OperationsError::InvalidInput {
reason: format!("NURBS normal computation failed: {e}"),
})
}
FaceSurface::Cylinder(cyl) => Ok(cyl.normal(0.0, 0.0)),
FaceSurface::Cone(cone) => Ok(cone.normal(0.0, 0.0)),
FaceSurface::Sphere(sphere) => Ok(sphere.normal(0.0, 0.0)),
FaceSurface::Torus(torus) => Ok(torus.normal(0.0, 0.0)),
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use brepkit_math::tolerance::Tolerance;
use brepkit_topology::Topology;
use brepkit_topology::test_utils::make_unit_square_face;
use super::*;
#[test]
fn thicken_positive() {
let mut topo = Topology::new();
let face = make_unit_square_face(&mut topo);
let solid = thicken(&mut topo, face, 2.0).unwrap();
let vol = crate::measure::solid_volume(&topo, solid, 0.1).unwrap();
let tol = Tolerance::loose();
assert!(
tol.approx_eq(vol, 2.0),
"1×1 face thickened by 2 should have volume ~2.0, got {vol}"
);
}
#[test]
fn thicken_negative() {
let mut topo = Topology::new();
let face = make_unit_square_face(&mut topo);
let solid = thicken(&mut topo, face, -1.5).unwrap();
let vol = crate::measure::solid_volume(&topo, solid, 0.1).unwrap();
let tol = Tolerance::loose();
assert!(
tol.approx_eq(vol, 1.5),
"1×1 face thickened by -1.5 should have volume ~1.5, got {vol}"
);
}
#[test]
fn thicken_zero_error() {
let mut topo = Topology::new();
let face = make_unit_square_face(&mut topo);
assert!(thicken(&mut topo, face, 0.0).is_err());
}
#[test]
fn thicken_nurbs_face() {
use brepkit_math::nurbs::surface::NurbsSurface;
use brepkit_math::vec::Point3;
use brepkit_topology::edge::{Edge, EdgeCurve};
use brepkit_topology::face::{Face, FaceSurface};
use brepkit_topology::vertex::Vertex;
use brepkit_topology::wire::{OrientedEdge, Wire};
let mut topo = Topology::new();
let surface = NurbsSurface::new(
1,
1,
vec![0.0, 0.0, 1.0, 1.0],
vec![0.0, 0.0, 1.0, 1.0],
vec![
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0)],
vec![Point3::new(0.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0)],
],
vec![vec![1.0, 1.0], vec![1.0, 1.0]],
)
.unwrap();
let tol = 1e-7;
let v0 = topo.add_vertex(Vertex::new(Point3::new(0.0, 0.0, 0.0), tol));
let v1 = topo.add_vertex(Vertex::new(Point3::new(1.0, 0.0, 0.0), tol));
let v2 = topo.add_vertex(Vertex::new(Point3::new(1.0, 1.0, 0.0), tol));
let v3 = topo.add_vertex(Vertex::new(Point3::new(0.0, 1.0, 0.0), tol));
let e0 = topo.add_edge(Edge::new(v0, v1, EdgeCurve::Line));
let e1 = topo.add_edge(Edge::new(v1, v2, EdgeCurve::Line));
let e2 = topo.add_edge(Edge::new(v2, v3, EdgeCurve::Line));
let e3 = topo.add_edge(Edge::new(v3, v0, EdgeCurve::Line));
let wire = Wire::new(
vec![
OrientedEdge::new(e0, true),
OrientedEdge::new(e1, true),
OrientedEdge::new(e2, true),
OrientedEdge::new(e3, true),
],
true,
)
.unwrap();
let wid = topo.add_wire(wire);
let face = topo.add_face(Face::new(wid, vec![], FaceSurface::Nurbs(surface)));
let solid = thicken(&mut topo, face, 2.0).unwrap();
let vol = crate::measure::solid_volume(&topo, solid, 0.1).unwrap();
assert!(
vol > 0.0,
"thickened NURBS face should have positive volume, got {vol}"
);
}
}