Skip to main content

path_offset/offset/
flo_curves.rs

1//! Implements path offsetting using the `flo_curves` library.
2//!
3//! This module provides the `FloCurvesOffset` struct, which uses the `flo_curves`
4//! library to perform path offsetting.
5
6use flo_curves::{
7    BezierCurve, Coord2,
8    bezier::{
9        Curve, curve_is_tiny, fit_curve, offset,
10        path::{BezierPath, BezierPathFactory, SimpleBezierPath, path_remove_interior_points},
11        walk_curve_evenly,
12    },
13};
14
15use crate::{
16    error::{PathError, Result},
17    offset::Offset,
18    path::Path,
19};
20
21/// A path offsetter that uses the `flo_curves` library.
22///
23/// This struct encapsulates the logic for offsetting a path using the algorithms
24/// provided by the `flo_curves` library.
25pub struct FloCurvesOffset {
26    curves: Vec<Curve<Coord2>>,
27}
28
29impl FloCurvesOffset {
30    /// Creates a new `FloCurvesOffset` instance.
31    ///
32    /// # Arguments
33    ///
34    /// * `path` - A reference to the `Path` to be offset.
35    /// * `offset_distance` - The distance by which to offset the path.
36    pub fn new(path: &Path, offset_distance: f64) -> Self {
37        FloCurvesOffset {
38            curves: SimpleBezierPath::from(path)
39                .to_curves()
40                .into_iter()
41                .flat_map(|curve| offset(&curve, -offset_distance, -offset_distance))
42                .filter(|curve| !curve_is_tiny(curve))
43                .collect::<Vec<_>>(),
44        }
45    }
46
47    /// Returns a reference to the underlying `flo_curves` curves.
48    pub fn curves(&self) -> &Vec<Curve<Coord2>> {
49        &self.curves
50    }
51}
52
53impl Offset for FloCurvesOffset {
54    /// Offsets the path using the `flo_curves` library.
55    ///
56    /// This method takes the curves generated during the creation of the `FloCurvesOffset` instance,
57    /// samples them, fits a new curve to the sampled points, and then cleans the resulting path
58    /// to produce the final offset path.
59    ///
60    /// # Returns
61    ///
62    /// A `Result` containing the offset `Path` or an error if the offsetting process fails.
63    fn offset_path(&self) -> Result<Path> {
64        let offset_points = self
65            .curves
66            .iter()
67            .flat_map(|curve| sample_curve(curve))
68            .collect::<Vec<_>>();
69
70        let fitted_curve =
71            fit_curve::<Curve<Coord2>>(&offset_points, 1.0).ok_or(PathError::FitCurve)?;
72
73        let offset_toolpath = SimpleBezierPath::from_connected_curves(
74            fitted_curve
75                .into_iter()
76                .filter(|curve| !curve_is_tiny(curve)),
77        );
78
79        let clean_offset_toolpath: SimpleBezierPath =
80            path_remove_interior_points(&vec![offset_toolpath], 0.01)
81                .into_iter()
82                .next()
83                .ok_or(PathError::CleanPath)?;
84
85        Ok(Path::from(&clean_offset_toolpath))
86    }
87}
88
89/// Samples a Bezier curve and returns a set of representative points.
90///
91/// This function walks along the curve at a fixed distance and samples the midpoint
92/// of each segment to generate a set of points that approximate the curve.
93///
94/// # Arguments
95///
96/// * `curve` - The Bezier curve to sample.
97///
98/// # Returns
99///
100/// A `Vec<Coord2>` containing the sampled points.
101fn sample_curve(curve: &Curve<Coord2>) -> Vec<Coord2> {
102    let max_error = 0.01;
103    let distance = 0.1;
104
105    // Take the midpoint (t=0.5) of each sampled section as the final sample point.
106    walk_curve_evenly(curve, distance, max_error)
107        .map(|section| section.point_at_pos(0.5))
108        .collect::<Vec<_>>()
109}