Skip to main content

u_nesting_cutting/
result.rs

1//! Result types for cutting path optimization.
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use crate::contour::{ContourId, ContourType};
7
8/// Result of cutting path optimization.
9#[derive(Debug, Clone)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub struct CuttingPathResult {
12    /// Ordered sequence of cutting steps.
13    pub sequence: Vec<CutStep>,
14
15    /// Total cutting distance (sum of all contour perimeters).
16    pub total_cut_distance: f64,
17
18    /// Total non-cutting (rapid traverse) distance.
19    pub total_rapid_distance: f64,
20
21    /// Total number of piercing operations.
22    pub total_pierces: usize,
23
24    /// Computation time in milliseconds.
25    pub computation_time_ms: u64,
26
27    /// Estimated total cutting time in seconds (if speeds are configured).
28    pub estimated_time_seconds: Option<f64>,
29}
30
31impl CuttingPathResult {
32    /// Creates a new empty result.
33    pub fn new() -> Self {
34        Self {
35            sequence: Vec::new(),
36            total_cut_distance: 0.0,
37            total_rapid_distance: 0.0,
38            total_pierces: 0,
39            computation_time_ms: 0,
40            estimated_time_seconds: None,
41        }
42    }
43
44    /// Returns the total distance (cutting + rapid).
45    pub fn total_distance(&self) -> f64 {
46        self.total_cut_distance + self.total_rapid_distance
47    }
48
49    /// Returns the cutting efficiency (cut distance / total distance).
50    pub fn efficiency(&self) -> f64 {
51        let total = self.total_distance();
52        if total > 0.0 {
53            self.total_cut_distance / total
54        } else {
55            0.0
56        }
57    }
58}
59
60impl Default for CuttingPathResult {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66/// A single step in the cutting sequence.
67#[derive(Debug, Clone)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69pub struct CutStep {
70    /// ID of the contour being cut.
71    pub contour_id: ContourId,
72
73    /// ID of the source geometry this contour belongs to.
74    pub geometry_id: String,
75
76    /// Instance index of the placed geometry (0-based).
77    pub instance: usize,
78
79    /// Whether this is an exterior or interior contour.
80    pub contour_type: ContourType,
81
82    /// Piercing point (entry point on the contour).
83    pub pierce_point: (f64, f64),
84
85    /// Cutting direction around the contour.
86    pub cut_direction: CutDirection,
87
88    /// Starting point of the rapid move to reach this contour.
89    /// None for the first step (starts from home position).
90    pub rapid_from: Option<(f64, f64)>,
91
92    /// Distance of the rapid move to reach the pierce point.
93    pub rapid_distance: f64,
94
95    /// Perimeter (cutting distance) of this contour.
96    pub cut_distance: f64,
97}
98
99/// Cutting direction around a contour.
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
102pub enum CutDirection {
103    /// Counter-clockwise (conventional for exterior contours).
104    Ccw,
105    /// Clockwise (conventional for interior/hole contours).
106    Cw,
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_empty_result() {
115        let result = CuttingPathResult::new();
116        assert!(result.sequence.is_empty());
117        assert_eq!(result.total_pierces, 0);
118        assert_eq!(result.total_distance(), 0.0);
119        assert_eq!(result.efficiency(), 0.0);
120    }
121
122    #[test]
123    fn test_efficiency() {
124        let mut result = CuttingPathResult::new();
125        result.total_cut_distance = 800.0;
126        result.total_rapid_distance = 200.0;
127        assert!((result.efficiency() - 0.8).abs() < 1e-10);
128    }
129}