#![forbid(unsafe_code)]
use std::collections::HashMap;
use std::fmt;
use axiolid_core::Interval;
use axiolid_curve::{Curve2, Curve3};
use axiolid_surface::Surface;
use axiolid_topology::{audit_brep, BRep, BRepHealth, EdgeId, FaceId, LoopId, ShellId};
pub mod name;
pub use name::{EdgeName, FaceName, Operand, SweptFace};
macro_rules! geometry_id {
($name:ident, $label:literal) => {
#[doc = concat!("Typed handle into the exact B-rep ", $label, " catalog.")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(u32);
impl $name {
fn from_index(index: usize) -> Self {
Self(u32::try_from(index).expect("exact B-rep catalog exceeds u32 capacity"))
}
pub const fn index(self) -> usize {
self.0 as usize
}
}
};
}
geometry_id!(Curve3Id, "3D curve");
geometry_id!(Curve2Id, "2D pcurve");
geometry_id!(SurfaceId, "surface");
pub type ExactTopology = BRep<Curve3Id, Curve2Id, SurfaceId>;
#[derive(Debug, Clone, PartialEq)]
pub struct ExactBRep {
topology: ExactTopology,
curves3: Vec<Curve3>,
curves2: Vec<Curve2>,
surfaces: Vec<Surface>,
edge_intervals: HashMap<EdgeId, Interval>,
pcurve_intervals: HashMap<(LoopId, usize), Interval>,
face_names: HashMap<FaceId, FaceName>,
edge_names: HashMap<EdgeId, EdgeName>,
}
impl ExactBRep {
pub fn face_name(&self, face: FaceId) -> Option<&FaceName> {
self.face_names.get(&face)
}
pub fn edge_name(&self, edge: EdgeId) -> Option<&EdgeName> {
self.edge_names.get(&edge)
}
pub fn face_by_name(&self, name: &FaceName) -> Option<FaceId> {
self.face_names
.iter()
.find(|(_, candidate)| *candidate == name)
.map(|(id, _)| *id)
}
pub fn edge_by_name(&self, name: &EdgeName) -> Option<EdgeId> {
self.edge_names
.iter()
.find(|(_, candidate)| *candidate == name)
.map(|(id, _)| *id)
}
pub fn name_caps(&mut self, start: Option<FaceName>, end: Option<FaceName>) {
let start_id = self.topology.face_id_at(0);
let end_id = self.topology.face_id_at(1);
if let (Some(name), Some(id)) = (start, start_id) {
self.face_names.insert(id, name);
}
if let (Some(name), Some(id)) = (end, end_id) {
self.face_names.insert(id, name);
}
}
pub fn topology(&self) -> &ExactTopology {
&self.topology
}
pub fn curves3(&self) -> &[Curve3] {
&self.curves3
}
pub fn curves2(&self) -> &[Curve2] {
&self.curves2
}
pub fn surfaces(&self) -> &[Surface] {
&self.surfaces
}
pub fn edge_interval(&self, edge: EdgeId) -> Option<Interval> {
self.edge_intervals.get(&edge).copied()
}
pub fn pcurve_interval(&self, loop_id: LoopId, use_index: usize) -> Option<Interval> {
self.pcurve_intervals.get(&(loop_id, use_index)).copied()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ExactBRepBuilder {
topology: ExactTopology,
curves3: Vec<Curve3>,
curves2: Vec<Curve2>,
surfaces: Vec<Surface>,
edge_intervals: HashMap<EdgeId, Interval>,
pcurve_intervals: HashMap<(LoopId, usize), Interval>,
face_names: HashMap<FaceId, FaceName>,
edge_names: HashMap<EdgeId, EdgeName>,
}
impl ExactBRepBuilder {
pub fn set_face_name(&mut self, face: FaceId, name: FaceName) {
self.face_names.insert(face, name);
}
pub fn set_edge_name(&mut self, edge: EdgeId, name: EdgeName) {
self.edge_names.insert(edge, name);
}
pub fn try_reserve(
&mut self,
curves3: usize,
curves2: usize,
surfaces: usize,
edge_intervals: usize,
pcurve_intervals: usize,
) -> Result<(), std::collections::TryReserveError> {
self.curves3.try_reserve_exact(curves3)?;
self.curves2.try_reserve_exact(curves2)?;
self.surfaces.try_reserve_exact(surfaces)?;
self.edge_intervals.try_reserve(edge_intervals)?;
self.pcurve_intervals.try_reserve(pcurve_intervals)?;
Ok(())
}
pub fn add_curve3(&mut self, curve: Curve3) -> Curve3Id {
let id = Curve3Id::from_index(self.curves3.len());
self.curves3.push(curve);
id
}
pub fn add_curve2(&mut self, curve: Curve2) -> Curve2Id {
let id = Curve2Id::from_index(self.curves2.len());
self.curves2.push(curve);
id
}
pub fn add_surface(&mut self, surface: Surface) -> SurfaceId {
let id = SurfaceId::from_index(self.surfaces.len());
self.surfaces.push(surface);
id
}
pub fn topology_mut(&mut self) -> &mut ExactTopology {
&mut self.topology
}
pub fn set_edge_interval(&mut self, edge: EdgeId, interval: Interval) {
self.edge_intervals.insert(edge, interval);
}
pub fn set_pcurve_interval(&mut self, loop_id: LoopId, use_index: usize, interval: Interval) {
self.pcurve_intervals.insert((loop_id, use_index), interval);
}
pub fn append(&mut self, other: &ExactBRep, reverse: bool) -> Vec<ShellId> {
use axiolid_topology::{Edge, EdgeUse, Face, Loop, Orientation, Shell, Vertex};
let curves3: Vec<Curve3Id> = other
.curves3
.iter()
.map(|curve| self.add_curve3(curve.clone()))
.collect();
let curves2: Vec<Curve2Id> = other
.curves2
.iter()
.map(|curve| self.add_curve2(curve.clone()))
.collect();
let surfaces: Vec<SurfaceId> = other
.surfaces
.iter()
.map(|surface| self.add_surface(surface.clone()))
.collect();
let source = &other.topology;
let vertices: Vec<_> = source
.vertices()
.iter()
.map(|vertex| {
self.topology.add_vertex(Vertex {
position: vertex.position,
})
})
.collect();
let mut edges = Vec::with_capacity(source.edges().len());
for (index, edge) in source.edges().iter().enumerate() {
let id = self.topology.add_edge(Edge {
start: vertices[edge.start.index()],
end: vertices[edge.end.index()],
curve: edge.curve.map(|curve| curves3[curve.index()]),
});
if let Some(old) = source.edge_id_at(index) {
if let Some(interval) = other.edge_intervals.get(&old) {
self.edge_intervals.insert(id, *interval);
}
if let Some(name) = other.edge_names.get(&old) {
self.edge_names.insert(id, name.clone());
}
}
edges.push(id);
}
let mut loops = Vec::with_capacity(source.loops().len());
for (index, wire) in source.loops().iter().enumerate() {
let id = self.topology.add_loop(Loop {
edges: wire
.edges
.iter()
.map(|use_| EdgeUse {
edge: edges[use_.edge.index()],
orientation: use_.orientation,
pcurve: use_.pcurve.map(|curve| curves2[curve.index()]),
})
.collect(),
});
if let Some(old) = source.loop_id_at(index) {
for use_index in 0..wire.edges.len() {
if let Some(interval) = other.pcurve_intervals.get(&(old, use_index)) {
self.pcurve_intervals.insert((id, use_index), *interval);
}
}
}
loops.push(id);
}
let mut faces = Vec::with_capacity(source.faces().len());
for (index, face) in source.faces().iter().enumerate() {
let id = self.topology.add_face(Face {
surface: face.surface.map(|surface| surfaces[surface.index()]),
bounds: face
.bounds
.iter()
.map(|bound| axiolid_topology::FaceBound {
loop_id: loops[bound.loop_id.index()],
..*bound
})
.collect(),
orientation: face.orientation,
});
if let Some(name) = source
.face_id_at(index)
.and_then(|old| other.face_names.get(&old))
{
self.face_names.insert(id, name.clone());
}
faces.push(id);
}
source
.shells()
.iter()
.map(|shell| {
self.topology.add_shell(Shell {
faces: shell
.faces
.iter()
.map(|&(face, sense)| {
let sense = match (sense, reverse) {
(sense, false) => sense,
(Orientation::Forward, true) => Orientation::Reversed,
(Orientation::Reversed, true) => Orientation::Forward,
};
(faces[face.index()], sense)
})
.collect(),
closed: shell.closed,
})
})
.collect()
}
pub fn finish(self) -> Result<ExactBRep, ExactBRepError> {
validate(&self)?;
Ok(ExactBRep {
topology: self.topology,
curves3: self.curves3,
curves2: self.curves2,
surfaces: self.surfaces,
edge_intervals: self.edge_intervals,
pcurve_intervals: self.pcurve_intervals,
face_names: self.face_names,
edge_names: self.edge_names,
})
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExactBRepError {
Empty,
Topology(BRepHealth),
MissingEdgeCurve { edge_index: usize },
UnknownCurve3 { edge_index: usize },
MissingEdgeInterval { edge_index: usize },
InvalidEdgeInterval { edge_index: usize },
MissingPcurve { loop_index: usize, use_index: usize },
UnknownCurve2 { loop_index: usize, use_index: usize },
MissingPcurveInterval { loop_index: usize, use_index: usize },
InvalidPcurveInterval { loop_index: usize, use_index: usize },
MissingFaceSurface { face_index: usize },
UnknownSurface { face_index: usize },
}
impl fmt::Display for ExactBRepError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => f.write_str("exact B-rep requires at least one face"),
Self::Topology(_) => f.write_str("exact B-rep topology is structurally invalid"),
Self::MissingEdgeCurve { edge_index } => {
write!(f, "edge {edge_index} lacks a 3D curve")
}
Self::UnknownCurve3 { edge_index } => {
write!(f, "edge {edge_index} has an unknown 3D curve")
}
Self::MissingEdgeInterval { edge_index } => {
write!(f, "edge {edge_index} lacks a curve interval")
}
Self::InvalidEdgeInterval { edge_index } => {
write!(f, "edge {edge_index} has an invalid curve interval")
}
Self::MissingPcurve {
loop_index,
use_index,
} => write!(f, "loop {loop_index} use {use_index} lacks a pcurve"),
Self::UnknownCurve2 {
loop_index,
use_index,
} => write!(f, "loop {loop_index} use {use_index} has an unknown pcurve"),
Self::MissingPcurveInterval {
loop_index,
use_index,
} => write!(
f,
"loop {loop_index} use {use_index} lacks a pcurve interval"
),
Self::InvalidPcurveInterval {
loop_index,
use_index,
} => write!(
f,
"loop {loop_index} use {use_index} has an invalid pcurve interval"
),
Self::MissingFaceSurface { face_index } => {
write!(f, "face {face_index} lacks a support surface")
}
Self::UnknownSurface { face_index } => {
write!(f, "face {face_index} has an unknown support surface")
}
}
}
}
impl std::error::Error for ExactBRepError {}
fn validate(value: &ExactBRepBuilder) -> Result<(), ExactBRepError> {
if value.topology.faces().is_empty() {
return Err(ExactBRepError::Empty);
}
let health = audit_brep(&value.topology);
if !health.is_tessellable() {
return Err(ExactBRepError::Topology(health));
}
for (edge_index, edge) in value.topology.edges().iter().enumerate() {
let Some(curve) = edge.curve else {
return Err(ExactBRepError::MissingEdgeCurve { edge_index });
};
if curve.index() >= value.curves3.len() {
return Err(ExactBRepError::UnknownCurve3 { edge_index });
}
let Some(edge_id) = value.topology.edge_id_at(edge_index) else {
return Err(ExactBRepError::MissingEdgeInterval { edge_index });
};
let Some(interval) = value.edge_intervals.get(&edge_id) else {
return Err(ExactBRepError::MissingEdgeInterval { edge_index });
};
if !valid_interval(*interval) {
return Err(ExactBRepError::InvalidEdgeInterval { edge_index });
}
}
for (loop_index, loop_) in value.topology.loops().iter().enumerate() {
let Some(loop_id) = value.topology.loop_id_at(loop_index) else {
return Err(ExactBRepError::Topology(health));
};
for (use_index, use_) in loop_.edges.iter().enumerate() {
let Some(curve) = use_.pcurve else {
return Err(ExactBRepError::MissingPcurve {
loop_index,
use_index,
});
};
if curve.index() >= value.curves2.len() {
return Err(ExactBRepError::UnknownCurve2 {
loop_index,
use_index,
});
}
let Some(interval) = value.pcurve_intervals.get(&(loop_id, use_index)) else {
return Err(ExactBRepError::MissingPcurveInterval {
loop_index,
use_index,
});
};
if !valid_interval(*interval) {
return Err(ExactBRepError::InvalidPcurveInterval {
loop_index,
use_index,
});
}
}
}
for (face_index, face) in value.topology.faces().iter().enumerate() {
let Some(surface) = face.surface else {
return Err(ExactBRepError::MissingFaceSurface { face_index });
};
if surface.index() >= value.surfaces.len() {
return Err(ExactBRepError::UnknownSurface { face_index });
}
}
Ok(())
}
fn valid_interval(interval: Interval) -> bool {
interval.start.is_finite() && interval.end.is_finite() && interval.length() > 0.0
}