1use crate::boundary::Boundary3D;
14use crate::geometry::Geometry3D;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::Arc;
17use u_nesting_core::geometry::{Boundary, Geometry};
18use u_nesting_core::solver::Config;
19use u_nesting_core::Placement;
20
21#[derive(Debug, Clone)]
27pub struct InstanceInfo {
28 pub geometry_idx: usize,
30 pub instance_num: usize,
32 pub orientation_count: usize,
34}
35
36pub fn build_instances(geometries: &[Geometry3D]) -> Vec<InstanceInfo> {
41 let mut instances = Vec::new();
42 for (geom_idx, geom) in geometries.iter().enumerate() {
43 let orient_count = geom.allowed_orientations().len();
44 for instance_num in 0..geom.quantity() {
45 instances.push(InstanceInfo {
46 geometry_idx: geom_idx,
47 instance_num,
48 orientation_count: orient_count,
49 });
50 }
51 }
52 instances
53}
54
55pub struct PlacementItem {
57 pub instance_idx: usize,
59 pub orientation_idx: usize,
61}
62
63pub fn packing_fitness(placed_count: usize, total_count: usize, utilization: f64) -> f64 {
76 let placement_ratio = placed_count as f64 / total_count.max(1) as f64;
77 placement_ratio * 100.0 + utilization * 10.0
78}
79
80pub fn build_unplaced_list(
85 placements: &[Placement<f64>],
86 geometries: &[Geometry3D],
87) -> Vec<String> {
88 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
89 for p in placements {
90 placed_ids.insert(p.geometry_id.clone());
91 }
92 let mut unplaced = Vec::new();
93 for geom in geometries {
94 if !placed_ids.contains(geom.id()) {
95 unplaced.push(geom.id().clone());
96 }
97 }
98 unplaced
99}
100
101pub struct LayerPlacementResult {
103 pub placements: Vec<Placement<f64>>,
105 pub utilization: f64,
107 pub placed_count: usize,
109}
110
111pub fn layer_place_items(
136 items: &[PlacementItem],
137 instances: &[InstanceInfo],
138 geometries: &[Geometry3D],
139 boundary: &Boundary3D,
140 config: &Config,
141 cancelled: &Arc<AtomicBool>,
142) -> LayerPlacementResult {
143 let mut placements = Vec::new();
144
145 let margin = config.margin;
146 let spacing = config.spacing;
147
148 let bound_max_x = boundary.width() - margin;
149 let bound_max_y = boundary.depth() - margin;
150 let bound_max_z = boundary.height() - margin;
151
152 let mut current_x = margin;
153 let mut current_y = margin;
154 let mut current_z = margin;
155 let mut row_depth = 0.0_f64;
156 let mut layer_height = 0.0_f64;
157
158 let mut total_placed_volume = 0.0;
159 let mut total_placed_mass = 0.0;
160 let mut placed_count = 0;
161
162 for item in items {
163 if cancelled.load(Ordering::Relaxed) {
164 break;
165 }
166
167 if item.instance_idx >= instances.len() {
168 continue;
169 }
170
171 let info = &instances[item.instance_idx];
172 let geom = &geometries[info.geometry_idx];
173
174 let orientation_idx = item.orientation_idx % info.orientation_count.max(1);
175
176 let dims = geom.dimensions_for_orientation(orientation_idx);
178 let g_width = dims.x;
179 let g_depth = dims.y;
180 let g_height = dims.z;
181
182 if let (Some(max_mass), Some(item_mass)) = (boundary.max_mass(), geom.mass()) {
184 if total_placed_mass + item_mass > max_mass {
185 continue;
186 }
187 }
188
189 if current_x + g_width > bound_max_x {
191 current_x = margin;
192 current_y += row_depth + spacing;
193 row_depth = 0.0;
194 }
195
196 if current_y + g_depth > bound_max_y {
198 current_x = margin;
199 current_y = margin;
200 current_z += layer_height + spacing;
201 row_depth = 0.0;
202 layer_height = 0.0;
203 }
204
205 if current_z + g_height > bound_max_z {
207 continue;
208 }
209
210 let placement = Placement::new_3d(
212 geom.id().clone(),
213 info.instance_num,
214 current_x,
215 current_y,
216 current_z,
217 0.0,
218 0.0,
219 0.0,
220 );
221
222 placements.push(placement);
223 total_placed_volume += geom.measure();
224 if let Some(mass) = geom.mass() {
225 total_placed_mass += mass;
226 }
227 placed_count += 1;
228
229 current_x += g_width + spacing;
231 row_depth = row_depth.max(g_depth);
232 layer_height = layer_height.max(g_height);
233 }
234
235 let utilization = total_placed_volume / boundary.measure();
236 LayerPlacementResult {
237 placements,
238 utilization,
239 placed_count,
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_build_instances_single() {
249 let geometries = vec![Geometry3D::new("B1", 10.0, 10.0, 10.0).with_quantity(3)];
250 let instances = build_instances(&geometries);
251 assert_eq!(instances.len(), 3);
252 assert_eq!(instances[0].geometry_idx, 0);
253 assert_eq!(instances[0].instance_num, 0);
254 assert_eq!(instances[2].instance_num, 2);
255 }
256
257 #[test]
258 fn test_build_instances_multiple() {
259 let geometries = vec![
260 Geometry3D::new("A", 10.0, 10.0, 10.0).with_quantity(2),
261 Geometry3D::new("B", 20.0, 20.0, 20.0).with_quantity(3),
262 ];
263 let instances = build_instances(&geometries);
264 assert_eq!(instances.len(), 5);
265 assert_eq!(instances[0].geometry_idx, 0);
266 assert_eq!(instances[1].geometry_idx, 0);
267 assert_eq!(instances[2].geometry_idx, 1);
268 assert_eq!(instances[4].geometry_idx, 1);
269 }
270
271 #[test]
272 fn test_packing_fitness_all_placed() {
273 let fitness = packing_fitness(10, 10, 0.8);
274 assert!((fitness - 108.0).abs() < 1e-10);
276 }
277
278 #[test]
279 fn test_packing_fitness_partial() {
280 let fitness = packing_fitness(5, 10, 0.5);
281 assert!((fitness - 55.0).abs() < 1e-10);
283 }
284
285 #[test]
286 fn test_packing_fitness_none_placed() {
287 let fitness = packing_fitness(0, 10, 0.0);
288 assert!((fitness - 0.0).abs() < 1e-10);
289 }
290
291 #[test]
292 fn test_packing_fitness_zero_total() {
293 let fitness = packing_fitness(0, 0, 0.0);
294 assert!((fitness - 0.0).abs() < 1e-10);
295 }
296
297 #[test]
298 fn test_build_unplaced_list_all_placed() {
299 let geometries = vec![
300 Geometry3D::new("A", 10.0, 10.0, 10.0),
301 Geometry3D::new("B", 20.0, 20.0, 20.0),
302 ];
303 let placements = vec![
304 Placement::new_3d("A".to_string(), 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
305 Placement::new_3d("B".to_string(), 0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0),
306 ];
307 let unplaced = build_unplaced_list(&placements, &geometries);
308 assert!(unplaced.is_empty());
309 }
310
311 #[test]
312 fn test_build_unplaced_list_partial() {
313 let geometries = vec![
314 Geometry3D::new("A", 10.0, 10.0, 10.0),
315 Geometry3D::new("B", 20.0, 20.0, 20.0),
316 ];
317 let placements = vec![Placement::new_3d(
318 "A".to_string(),
319 0,
320 0.0,
321 0.0,
322 0.0,
323 0.0,
324 0.0,
325 0.0,
326 )];
327 let unplaced = build_unplaced_list(&placements, &geometries);
328 assert_eq!(unplaced, vec!["B"]);
329 }
330
331 #[test]
332 fn test_layer_place_items_basic() {
333 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2)];
334 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
335 let config = Config::default();
336 let instances = build_instances(&geometries);
337 let cancelled = Arc::new(AtomicBool::new(false));
338
339 let items: Vec<PlacementItem> = (0..instances.len())
340 .map(|i| PlacementItem {
341 instance_idx: i,
342 orientation_idx: 0,
343 })
344 .collect();
345
346 let result = layer_place_items(
347 &items,
348 &instances,
349 &geometries,
350 &boundary,
351 &config,
352 &cancelled,
353 );
354
355 assert_eq!(result.placed_count, 2);
356 assert_eq!(result.placements.len(), 2);
357 assert!(result.utilization > 0.0);
358 }
359
360 #[test]
361 fn test_layer_place_items_cancellation() {
362 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(5)];
363 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
364 let config = Config::default();
365 let instances = build_instances(&geometries);
366 let cancelled = Arc::new(AtomicBool::new(true)); let items: Vec<PlacementItem> = (0..instances.len())
369 .map(|i| PlacementItem {
370 instance_idx: i,
371 orientation_idx: 0,
372 })
373 .collect();
374
375 let result = layer_place_items(
376 &items,
377 &instances,
378 &geometries,
379 &boundary,
380 &config,
381 &cancelled,
382 );
383
384 assert_eq!(result.placed_count, 0);
385 }
386
387 #[test]
388 fn test_layer_place_items_mass_constraint() {
389 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0)
390 .with_quantity(10)
391 .with_mass(100.0)];
392 let boundary = Boundary3D::new(100.0, 100.0, 100.0).with_max_mass(250.0);
393 let config = Config::default();
394 let instances = build_instances(&geometries);
395 let cancelled = Arc::new(AtomicBool::new(false));
396
397 let items: Vec<PlacementItem> = (0..instances.len())
398 .map(|i| PlacementItem {
399 instance_idx: i,
400 orientation_idx: 0,
401 })
402 .collect();
403
404 let result = layer_place_items(
405 &items,
406 &instances,
407 &geometries,
408 &boundary,
409 &config,
410 &cancelled,
411 );
412
413 assert!(result.placed_count <= 2);
415 }
416}