Skip to main content

u_nesting_cutting/
pierce.rs

1//! Pierce point selection and optimization.
2//!
3//! Selects the optimal entry point on each contour where the cutting tool
4//! should pierce. The pierce point is chosen to minimize rapid travel
5//! distance from the previous contour's endpoint.
6
7use crate::config::{CutDirectionPreference, CuttingConfig};
8use crate::contour::{ContourType, CutContour};
9use crate::cost::{closest_point_on_polygon, point_distance};
10use crate::result::CutDirection;
11
12/// Selected pierce point for a contour.
13#[derive(Debug, Clone)]
14pub struct PierceSelection {
15    /// The pierce point coordinates.
16    pub point: (f64, f64),
17    /// The vertex index closest to (or at) the pierce point.
18    pub vertex_index: usize,
19    /// Cut direction for this contour.
20    pub direction: CutDirection,
21    /// The endpoint after cutting the full contour (returns to pierce point).
22    pub end_point: (f64, f64),
23}
24
25/// Selects the pierce point for a contour based on the approach point.
26///
27/// The pierce point is the closest point on the contour boundary to `from_point`.
28/// The cut direction is determined by the contour type and config preferences.
29pub fn select_pierce(
30    contour: &CutContour,
31    from_point: (f64, f64),
32    config: &CuttingConfig,
33) -> PierceSelection {
34    let (closest, vertex_idx, _t) = closest_point_on_polygon(&contour.vertices, from_point)
35        .unwrap_or((contour.vertices[0], 0, 0.0));
36
37    let direction = determine_cut_direction(contour.contour_type, config);
38
39    PierceSelection {
40        point: closest,
41        vertex_index: vertex_idx,
42        direction,
43        end_point: closest, // Full contour cut returns to pierce point
44    }
45}
46
47/// Determines the cutting direction based on contour type and config.
48fn determine_cut_direction(contour_type: ContourType, config: &CuttingConfig) -> CutDirection {
49    let pref = match contour_type {
50        ContourType::Exterior => config.exterior_direction,
51        ContourType::Interior => config.interior_direction,
52    };
53
54    match pref {
55        CutDirectionPreference::Ccw => CutDirection::Ccw,
56        CutDirectionPreference::Cw => CutDirection::Cw,
57        CutDirectionPreference::Auto => match contour_type {
58            ContourType::Exterior => CutDirection::Ccw,
59            ContourType::Interior => CutDirection::Cw,
60        },
61    }
62}
63
64/// Computes the rapid distance from a point to the best pierce point on a contour.
65pub fn rapid_distance_to_contour(from_point: (f64, f64), contour: &CutContour) -> f64 {
66    match closest_point_on_polygon(&contour.vertices, from_point) {
67        Some((closest, _, _)) => point_distance(from_point, closest),
68        None => f64::MAX,
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    fn make_square_contour(id: usize, cx: f64, cy: f64) -> CutContour {
77        CutContour {
78            id,
79            geometry_id: "test".to_string(),
80            instance: 0,
81            contour_type: ContourType::Exterior,
82            vertices: vec![
83                (cx - 5.0, cy - 5.0),
84                (cx + 5.0, cy - 5.0),
85                (cx + 5.0, cy + 5.0),
86                (cx - 5.0, cy + 5.0),
87            ],
88            perimeter: 40.0,
89            centroid: (cx, cy),
90        }
91    }
92
93    #[test]
94    fn test_select_pierce_closest_point() {
95        let contour = make_square_contour(0, 50.0, 50.0);
96        let config = CuttingConfig::default();
97        let sel = select_pierce(&contour, (50.0, 0.0), &config);
98
99        // Closest point on bottom edge to (50,0) should be (50, 45)
100        assert!((sel.point.0 - 50.0).abs() < 0.1);
101        assert!((sel.point.1 - 45.0).abs() < 0.1);
102    }
103
104    #[test]
105    fn test_auto_direction_exterior_ccw() {
106        let contour = CutContour {
107            id: 0,
108            geometry_id: "test".to_string(),
109            instance: 0,
110            contour_type: ContourType::Exterior,
111            vertices: vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)],
112            perimeter: 40.0,
113            centroid: (5.0, 5.0),
114        };
115        let config = CuttingConfig::default();
116        let sel = select_pierce(&contour, (0.0, 0.0), &config);
117        assert_eq!(sel.direction, CutDirection::Ccw);
118    }
119
120    #[test]
121    fn test_auto_direction_interior_cw() {
122        let contour = CutContour {
123            id: 0,
124            geometry_id: "test".to_string(),
125            instance: 0,
126            contour_type: ContourType::Interior,
127            vertices: vec![(2.0, 2.0), (8.0, 2.0), (8.0, 8.0), (2.0, 8.0)],
128            perimeter: 24.0,
129            centroid: (5.0, 5.0),
130        };
131        let config = CuttingConfig::default();
132        let sel = select_pierce(&contour, (0.0, 0.0), &config);
133        assert_eq!(sel.direction, CutDirection::Cw);
134    }
135
136    #[test]
137    fn test_rapid_distance() {
138        let contour = make_square_contour(0, 50.0, 50.0);
139        let dist = rapid_distance_to_contour((0.0, 0.0), &contour);
140        // Distance from origin to nearest point on square centered at (50,50) with side 10
141        // Nearest point is (45, 45), distance = sqrt(45^2 + 45^2) ~ 63.64
142        assert!(dist > 60.0 && dist < 70.0);
143    }
144
145    #[test]
146    fn test_pierce_end_point_equals_pierce_point() {
147        let contour = make_square_contour(0, 50.0, 50.0);
148        let config = CuttingConfig::default();
149        let sel = select_pierce(&contour, (0.0, 0.0), &config);
150        assert_eq!(sel.point, sel.end_point);
151    }
152
153    #[test]
154    fn test_forced_cw_exterior() {
155        let contour = CutContour {
156            id: 0,
157            geometry_id: "test".to_string(),
158            instance: 0,
159            contour_type: ContourType::Exterior,
160            vertices: vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)],
161            perimeter: 40.0,
162            centroid: (5.0, 5.0),
163        };
164        let config = CuttingConfig {
165            exterior_direction: CutDirectionPreference::Cw,
166            ..CuttingConfig::default()
167        };
168        let sel = select_pierce(&contour, (0.0, 0.0), &config);
169        assert_eq!(sel.direction, CutDirection::Cw);
170    }
171}