mod bounds;
use axiolid_contracts::ExecutionOptions;
use axiolid_core::{Aabb, Tolerance};
use axiolid_mesh::TriMesh;
use axiolid_mesh_compile_contract::{MeshClosure, MeshCompiler};
use ifc_model::{EntityId, Model};
use crate::error::{GeometryError, GeometryResult};
use crate::lower::{
lower_product_net, lower_product_representation, LoweringSession, NetLowering,
RepresentationPurpose,
};
use crate::units;
#[cfg(feature = "compile-reference-backend")]
use axiolid_mesh_boolean_boolmesh::BoolmeshBoolean;
#[cfg(feature = "compile-reference-backend")]
use axiolid_mesh_compile::ReferenceMeshCompiler;
#[cfg(feature = "compile-reference-backend")]
#[must_use]
pub fn default_backend() -> ReferenceMeshCompiler<BoolmeshBoolean> {
ReferenceMeshCompiler::new(BoolmeshBoolean)
}
#[cfg(feature = "compile-reference-backend")]
pub fn compile_product_mesh(
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<TriMesh>> {
compile_product_mesh_with(&default_backend(), model, product, tolerance)
}
pub fn compile_product_mesh_with<B: MeshCompiler>(
backend: &B,
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<TriMesh>> {
let scale = units::resolve(model);
let mut session = LoweringSession::new(model, &scale);
let Some(root) =
lower_product_representation(&mut session, product, RepresentationPurpose::Body)?
else {
return Ok(None);
};
let lowered = session.finish(root)?;
let options = ExecutionOptions::new(tolerance);
backend
.compile_mesh(&lowered.graph, lowered.root, &options)
.map(Some)
.map_err(|error| refused(product, error))
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct CompiledMesh {
pub mesh: TriMesh,
pub closure: MeshClosure,
}
impl CompiledMesh {
pub fn solid_mesh(&self, product: EntityId) -> GeometryResult<&TriMesh> {
match self.closure {
MeshClosure::Solid => Ok(&self.mesh),
closure => Err(GeometryError::NotASolid {
entity: product,
closure,
}),
}
}
}
#[cfg(feature = "compile-reference-backend")]
pub fn compile_product_mesh_reported(
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<CompiledMesh>> {
compile_product_mesh_reported_with(&default_backend(), model, product, tolerance)
}
pub fn compile_product_mesh_reported_with<B: MeshCompiler>(
backend: &B,
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<CompiledMesh>> {
let scale = units::resolve(model);
let mut session = LoweringSession::new(model, &scale);
let Some(root) =
lower_product_representation(&mut session, product, RepresentationPurpose::Body)?
else {
return Ok(None);
};
let lowered = session.finish(root)?;
let options = ExecutionOptions::new(tolerance);
backend
.compile_mesh_reported(&lowered.graph, lowered.root, &options)
.map(|outcome| {
Some(CompiledMesh {
closure: outcome.closure,
mesh: outcome.mesh,
})
})
.map_err(|error| refused(product, error))
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NetMesh {
pub mesh: TriMesh,
pub openings: Vec<EntityId>,
pub closure: MeshClosure,
}
#[cfg(feature = "compile-reference-backend")]
pub fn compile_product_mesh_net(
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<NetMesh>> {
compile_product_mesh_net_with(&default_backend(), model, product, tolerance)
}
pub fn compile_product_mesh_net_with<B: MeshCompiler>(
backend: &B,
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<NetMesh>> {
let scale = units::resolve(model);
let mut session = LoweringSession::new(model, &scale);
let Some(net) = lower_product_net(&mut session, product)? else {
return Ok(None);
};
let openings = net.openings();
let lowered = session.finish(net.root)?;
let options = ExecutionOptions::new(tolerance);
match backend.compile_mesh_reported(&lowered.graph, lowered.root, &options) {
Ok(outcome) => Ok(Some(NetMesh {
closure: outcome.closure,
mesh: outcome.mesh,
openings,
})),
Err(error) => Err(attribute_net_refusal(
backend,
&lowered.graph,
&net,
&options,
product,
error,
)),
}
}
fn attribute_net_refusal<B: MeshCompiler>(
backend: &B,
graph: &axiolid_model::GeometryGraph,
net: &NetLowering,
options: &ExecutionOptions,
product: EntityId,
original: axiolid_contracts::GeomError,
) -> GeometryError {
if let Err(error) = backend.compile_mesh(graph, net.gross, options) {
return refused(product, error);
}
let blame = |opening: EntityId, error| GeometryError::OpeningNotSubtracted {
host: product,
opening,
cause: Box::new(refused(opening, error)),
};
for step in &net.subtractions {
if let Err(error) = backend.compile_mesh(graph, step.result, options) {
return blame(step.opening, error);
}
}
refused(product, original)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BoundsSource {
Exact,
Tessellated,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct ProductBounds {
pub aabb: Aabb,
pub source: BoundsSource,
}
#[cfg(feature = "compile-reference-backend")]
pub fn product_bounds(
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<ProductBounds>> {
product_bounds_with(&default_backend(), model, product, tolerance)
}
pub fn product_bounds_with<B: MeshCompiler>(
backend: &B,
model: &Model,
product: EntityId,
tolerance: Tolerance,
) -> GeometryResult<Option<ProductBounds>> {
let scale = units::resolve(model);
let mut session = LoweringSession::new(model, &scale);
let Some(root) =
lower_product_representation(&mut session, product, RepresentationPurpose::Body)?
else {
return Ok(None);
};
let lowered = session.finish(root)?;
let (aabb, source) = match bounds::exact(&lowered.graph, lowered.root) {
Some(aabb) => (aabb, BoundsSource::Exact),
None => {
let options = ExecutionOptions::new(tolerance);
let mesh = backend
.compile_mesh(&lowered.graph, lowered.root, &options)
.map_err(|error| refused(product, error))?;
(mesh.bounds(), BoundsSource::Tessellated)
}
};
if aabb.is_empty() || !aabb.is_finite() {
return Err(GeometryError::Degenerate {
entity: product,
type_name: model
.get(product)
.map_or_else(String::new, |entity| entity.type_name.to_string()),
detail: "body has no finite extent to bound".into(),
});
}
Ok(Some(ProductBounds { aabb, source }))
}
fn refused(entity: EntityId, error: axiolid_contracts::GeomError) -> GeometryError {
GeometryError::CompilationRefused {
entity,
reason: format!("{error:?}"),
}
}