use crate::mesh::{MeshBuilder, MeshError, MeshSnapshot};
use crate::model::{Bounds3, Point3, Setup};
use crate::numeric::Tolerance;
use brep_kernel::{tessellate_brep_watertight, BrepSolid};
#[derive(Clone, Debug, PartialEq)]
pub enum AdapterError {
Tessellation { solid: String, message: String },
Mesh { solid: String, error: MeshError },
ChordTolerance(f64),
}
impl std::fmt::Display for AdapterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AdapterError::Tessellation { solid, message } => {
write!(f, "tessellating body {solid:?}: {message}")
}
AdapterError::Mesh { solid, error } => write!(f, "mesh of body {solid:?}: {error}"),
AdapterError::ChordTolerance(v) => write!(f, "chord tolerance {v} must be finite and positive"),
}
}
}
impl std::error::Error for AdapterError {}
pub fn face_names(solid: &BrepSolid) -> Vec<String> {
solid
.shells
.iter()
.flat_map(|shell| shell.faces.iter())
.enumerate()
.map(|(i, face)| match &face.name {
Some(name) if !name.is_empty() => name.clone(),
_ => format!("#{i}"),
})
.collect()
}
pub struct BodyInput<'a> {
pub name: &'a str,
pub solid: &'a BrepSolid,
pub protected_only: bool,
}
pub fn snapshot_from_bodies(
bodies: &[BodyInput<'_>],
chord_tolerance: f64,
tolerance: Tolerance,
) -> Result<MeshSnapshot, AdapterError> {
if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
return Err(AdapterError::ChordTolerance(chord_tolerance));
}
let mut builder = MeshBuilder::new(tolerance);
for body in bodies {
add_body(&mut builder, body, chord_tolerance)?;
}
Ok(builder.finish())
}
pub fn add_body(builder: &mut MeshBuilder, body: &BodyInput<'_>, chord_tolerance: f64) -> Result<(), AdapterError> {
let mesh = tessellate_brep_watertight(body.solid, chord_tolerance)
.map_err(|message| AdapterError::Tessellation { solid: body.name.to_string(), message })?;
let names = face_names(body.solid);
builder
.add_body_buffers(body.name, body.protected_only, &mesh.positions, &mesh.indices, &mesh.face_ids, &names)
.map_err(|error| AdapterError::Mesh { solid: body.name.to_string(), error })
}
pub fn machine_snapshot<'a, F>(
setup: &'a Setup,
chord_tolerance: f64,
tolerance: Tolerance,
mut resolve: F,
) -> Result<(MeshSnapshot, Vec<String>), AdapterError>
where
F: FnMut(&str) -> Option<&'a BrepSolid>,
{
let mut bodies: Vec<BodyInput<'a>> = Vec::new();
let mut unresolved = Vec::new();
let mut seen: Vec<&str> = Vec::new();
for (list, protected_only) in [(&setup.targets, false), (&setup.protected, true)] {
for target in list {
let name = target.solid.as_str();
if seen.contains(&name) {
continue;
}
seen.push(name);
match resolve(name) {
Some(solid) => bodies.push(BodyInput { name, solid, protected_only }),
None => unresolved.push(target.solid.clone()),
}
}
}
let model = snapshot_from_bodies(&bodies, chord_tolerance, tolerance)?;
Ok((crate::mesh::to_machine(setup, &model), unresolved))
}
pub fn body_bounds(solid: &BrepSolid, chord_tolerance: f64) -> Result<Bounds3, AdapterError> {
let mesh = tessellate_brep_watertight(solid, chord_tolerance)
.map_err(|message| AdapterError::Tessellation { solid: String::new(), message })?;
let mut bounds = Bounds3::empty();
for p in mesh.positions.chunks_exact(3) {
bounds.include(Point3::new(p[0], p[1], p[2]));
}
Ok(bounds)
}
pub mod planar;
pub mod features;
pub fn revolved_faces(body: &BodyInput<'_>, setup: &Setup) -> Vec<crate::holes::RevolvedFace> {
let names = face_names(body.solid);
let point = |v: brep_kernel::Vec3| Point3::new(v.x, v.y, v.z);
body.solid.shells.iter().flat_map(|s| s.faces.iter()).enumerate()
.filter_map(|(i, face)| {
if let Some(brep_kernel::AnalyticSurface::RuledRevolution { frame, rho0, rho1, height }) = face.surface.analytic() {
Some(crate::holes::RevolvedFace {
solid: body.name.to_string(), face: names[i].clone(),
origin: setup.model_to_machine.apply_point(point(frame.origin)),
axis: setup.model_to_machine.apply_vector(point(frame.axis)),
radius0: *rho0, radius1: *rho1, height: *height,
})
} else { None }
}).collect()
}
pub fn turning_faces(body: &BodyInput<'_>, setup: &Setup, tolerance: Tolerance) -> Result<Vec<crate::turning::profile::AxialFace>, crate::CamError> {
use crate::turning::profile::{AxialFace, AxialCarrier};
use brep_kernel::AnalyticSurface;
setup.model_to_machine.validate()?;
tolerance.validate()?;
let names = face_names(body.solid);
let point = |v: brep_kernel::Vec3| Point3::new(v.x, v.y, v.z);
let mut result = Vec::new();
for (i, face) in body.solid.shells.iter().flat_map(|s| s.faces.iter()).enumerate() {
let refuse = || crate::CamError::InvalidInput(format!("non-axisymmetric or unsupported turning face {}/{}", body.name, names[i]));
let coaxial = |origin: Point3, axis: Point3| origin.x.hypot(origin.y) <= tolerance.distance_eps && (axis.z.abs()-1.).abs() <= tolerance.angle_eps;
let carrier = match face.surface.analytic().ok_or_else(refuse)? {
AnalyticSurface::Plane { origin, u_dir, v_dir, .. } => {
let normal = setup.model_to_machine.apply_vector(point(u_dir.cross(*v_dir).normalized().map_err(|_| refuse())?));
if (normal.z.abs()-1.).abs() > tolerance.angle_eps { return Err(refuse()); }
AxialCarrier::Plane { z: setup.model_to_machine.apply_point(point(*origin)).z }
}
AnalyticSurface::RuledRevolution { frame, rho0, rho1, height } => {
let origin = setup.model_to_machine.apply_point(point(frame.origin));
let axis = setup.model_to_machine.apply_vector(point(frame.axis));
if !coaxial(origin, axis) { return Err(refuse()); }
AxialCarrier::Line { a: Point3::new(*rho0, 0., origin.z), b: Point3::new(*rho1, 0., origin.z+axis.z*height) }
}
AnalyticSurface::Sphere { frame, radius } => {
let origin = setup.model_to_machine.apply_point(point(frame.origin));
if origin.x.hypot(origin.y) > tolerance.distance_eps { return Err(refuse()); }
AxialCarrier::Circle { centre: Point3::new(0., 0., origin.z), radius: *radius }
}
AnalyticSurface::Torus { frame, major_radius, minor_radius } => {
let origin = setup.model_to_machine.apply_point(point(frame.origin));
let axis = setup.model_to_machine.apply_vector(point(frame.axis));
if !coaxial(origin, axis) || major_radius <= minor_radius { return Err(refuse()); }
AxialCarrier::Circle { centre: Point3::new(*major_radius, 0., origin.z), radius: *minor_radius }
}
_ => return Err(refuse()),
};
result.push(AxialFace { solid: body.name.into(), face: names[i].clone(), carrier });
}
Ok(result)
}
pub struct FlowFace<'a> {
face: &'a brep_kernel::FaceRecord,
target: crate::TargetRef,
transform: crate::RigidTransform,
domain: crate::flow::UvDomain,
}
impl<'a> FlowFace<'a> {
pub fn new(face: &'a brep_kernel::FaceRecord, target: crate::TargetRef, setup: &Setup) -> Result<Self, crate::CamError> {
let fail=crate::CamError::InvalidInput;
setup.model_to_machine.validate().map_err(|e| fail(e.to_string()))?;
let u=face.surface.domain_u().map_err(fail)?;
let v=face.surface.domain_v().map_err(fail)?;
let trims=brep_kernel::trim_polygons(face).map_err(fail)?.into_iter()
.map(|l|l.into_iter().map(|p|crate::Point2::new(p[0],p[1])).collect()).collect();
Ok(Self {face,target,transform:setup.model_to_machine,domain:crate::flow::UvDomain {u,v,trims}})
}
}
impl crate::flow::ParametricFace for FlowFace<'_> {
fn target(&self)->crate::TargetRef {self.target.clone()}
fn domain(&self)->crate::flow::UvDomain {self.domain.clone()}
fn sample(&self,u:f64,v:f64)->Result<crate::flow::SurfaceSample,crate::CamError> {
let p=self.face.surface.evaluate(u,v).map_err(crate::CamError::InvalidInput)?;
let n=self.face.surface.normal(u,v).map_err(crate::CamError::InvalidInput)?;
let sign=if self.face.same_sense {1.}else{-1.};
Ok(crate::flow::SurfaceSample {
point:self.transform.apply_point(Point3::new(p.x,p.y,p.z)),
normal:self.transform.apply_vector(Point3::new(sign*n.x,sign*n.y,sign*n.z)),
})
}
}