pub mod alns_nesting;
pub mod boundary;
pub mod brkga_nesting;
pub mod ga_nesting;
pub mod gdrr_nesting;
pub mod geometry;
#[cfg(feature = "milp")]
pub mod milp_solver;
pub mod nester;
pub mod nfp;
#[cfg(feature = "milp")]
pub mod nfp_cm_solver;
pub mod nfp_sliding;
pub mod placement_utils;
pub mod sa_nesting;
pub mod spatial_index;
pub fn clamp_placement_to_boundary(
x: f64,
y: f64,
geom_aabb: ([f64; 2], [f64; 2]),
boundary_aabb: ([f64; 2], [f64; 2]),
) -> Option<(f64, f64)> {
let (g_min, g_max) = geom_aabb;
let (b_min, b_max) = boundary_aabb;
let min_valid_x = b_min[0] - g_min[0];
let max_valid_x = b_max[0] - g_max[0];
let min_valid_y = b_min[1] - g_min[1];
let max_valid_y = b_max[1] - g_max[1];
if max_valid_x < min_valid_x || max_valid_y < min_valid_y {
return None;
}
let clamped_x = x.clamp(min_valid_x, max_valid_x);
let clamped_y = y.clamp(min_valid_y, max_valid_y);
Some((clamped_x, clamped_y))
}
pub fn clamp_placement_to_boundary_with_margin(
x: f64,
y: f64,
geom_aabb: ([f64; 2], [f64; 2]),
boundary_aabb: ([f64; 2], [f64; 2]),
margin: f64,
) -> Option<(f64, f64)> {
let (g_min, g_max) = geom_aabb;
let (b_min, b_max) = boundary_aabb;
let min_valid_x = b_min[0] + margin - g_min[0];
let max_valid_x = b_max[0] - margin - g_max[0];
let min_valid_y = b_min[1] + margin - g_min[1];
let max_valid_y = b_max[1] - margin - g_max[1];
if max_valid_x < min_valid_x || max_valid_y < min_valid_y {
return None;
}
let clamped_x = x.clamp(min_valid_x, max_valid_x);
let clamped_y = y.clamp(min_valid_y, max_valid_y);
Some((clamped_x, clamped_y))
}
pub fn is_placement_within_bounds(
placement: &Placement<f64>,
geometry: &Geometry2D,
boundary: &Boundary2D,
tolerance: f64,
) -> bool {
use u_nesting_core::geometry::Boundary;
let x = placement.position.first().copied().unwrap_or(0.0);
let y = placement.position.get(1).copied().unwrap_or(0.0);
let rotation = placement.rotation.first().copied().unwrap_or(0.0);
let (g_min, g_max) = geometry.aabb_at_rotation(rotation);
let (b_min, b_max) = boundary.aabb();
let placed_min_x = x + g_min[0];
let placed_max_x = x + g_max[0];
let placed_min_y = y + g_min[1];
let placed_max_y = y + g_max[1];
placed_min_x >= b_min[0] - tolerance
&& placed_max_x <= b_max[0] + tolerance
&& placed_min_y >= b_min[1] - tolerance
&& placed_max_y <= b_max[1] + tolerance
}
pub fn validate_and_filter_placements(
mut result: SolveResult<f64>,
geometries: &[Geometry2D],
boundary: &Boundary2D,
) -> SolveResult<f64> {
use std::collections::HashMap;
use u_nesting_core::geometry::{Boundary, Geometry};
const TOLERANCE: f64 = 1e-6;
let geom_map: HashMap<_, _> = geometries.iter().map(|g| (g.id().clone(), g)).collect();
let (b_min, b_max) = boundary.aabb();
log::debug!(
"Validating placements against boundary: ({:.2}, {:.2}) to ({:.2}, {:.2})",
b_min[0],
b_min[1],
b_max[0],
b_max[1]
);
let mut valid_placements = Vec::new();
let mut total_valid_area = 0.0;
let mut filtered_count = 0;
for placement in result.placements {
if let Some(geom) = geom_map.get(&placement.geometry_id) {
let px = placement.position.first().copied().unwrap_or(0.0);
let py = placement.position.get(1).copied().unwrap_or(0.0);
let rot = placement.rotation.first().copied().unwrap_or(0.0);
if is_placement_within_bounds(&placement, geom, boundary, TOLERANCE) {
total_valid_area += geom.measure();
valid_placements.push(placement);
} else {
let (g_min, g_max) = geom.aabb_at_rotation(rot);
let placed_min_x = px + g_min[0];
let placed_max_x = px + g_max[0];
let placed_min_y = py + g_min[1];
let placed_max_y = py + g_max[1];
log::warn!(
"FILTERED: {} at ({:.2}, {:.2}) rot={:.2}° - bounds ({:.2}, {:.2}) to ({:.2}, {:.2}) outside boundary",
placement.geometry_id,
px, py,
rot.to_degrees(),
placed_min_x, placed_min_y, placed_max_x, placed_max_y
);
filtered_count += 1;
result.unplaced.push(placement.geometry_id.clone());
}
} else {
log::warn!("Geometry {} not found in lookup map", placement.geometry_id);
result.unplaced.push(placement.geometry_id.clone());
}
}
if filtered_count > 0 {
log::warn!(
"Validation filtered out {} placements as out-of-bounds",
filtered_count
);
}
result.placements = valid_placements;
result.utilization = total_valid_area / boundary.measure();
result
}
pub use boundary::Boundary2D;
pub use geometry::Geometry2D;
pub use nester::Nester2D;
pub use nfp::{NfpConfig, NfpMethod};
pub use spatial_index::{SpatialEntry2D, SpatialIndex2D};
pub use u_nesting_core::{
Boundary, Boundary2DExt, Config, Error, Geometry, Geometry2DExt, Placement, Result,
RotationConstraint, SolveResult, Solver, Strategy, Transform2D, AABB2D,
};