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}