gcode_nom/
lib.rs

1//! gcode-nom
2//!
3#![deny(clippy::all)]
4#![warn(clippy::cargo)]
5#![warn(clippy::complexity)]
6#![warn(clippy::pedantic)]
7#![warn(clippy::nursery)]
8#![warn(clippy::perf)]
9#![warn(missing_debug_implementations)]
10#![warn(missing_docs)]
11#![allow(clippy::many_single_char_names)]
12
13use core::f64;
14
15use crate::arc::ArcVal;
16use crate::arc::Form as ArcForm;
17
18/// G2/G3 Arc commands.
19/// Used in step size calculations
20pub static MM_PER_ARC_SEGMENT: f64 = 1_f64;
21
22/// Streaming for binary gcode files
23pub mod binary;
24/// Parsing rules for gcode commands
25pub mod command;
26mod double;
27/// Parsing rules for G0/G1 commands
28pub mod params;
29
30/// Parsing rules for G2/G3 arc commands
31pub mod arc;
32
33/// Absolute or Relative positioning
34#[derive(Default, Debug, Eq, PartialEq)]
35pub enum PositionMode {
36    /// As per spec `Positionmode::Absolute` is the default
37    /// <https://marlinfw.org/docs/gcode/G090.html>
38    #[default]
39    Absolute,
40    /// Relative positioning.
41    Relative,
42}
43
44/// Returns values used to render an ARC
45///
46/// input state is current position and the raw param values
47/// extracted from command.
48///
49#[derive(Debug)]
50pub struct ArcParams {
51    /// The center of the arc
52    pub center: (f64, f64),
53    /// The radius of the arc
54    pub radius: f64,
55    /// The start angle of the arc in radians
56    pub theta_start: f64,
57    /// The end angle of the arc in radians
58    pub theta_end: f64,
59}
60
61#[must_use] // // pub fn compute_arc(&payload) ->  ( origin, radius, theta_start, theta_end)
62/// Computes the parameters of an arc given the current position and the arc form.
63///
64/// `ArcParams` contains the values in a form which can be rendered to a OBJ/SVG file.
65pub fn compute_arc(current_x: f64, current_y: f64, form: &ArcForm) -> ArcParams {
66    // Unspecified relative offsets default to zero.
67    let mut i: f64 = 0_f64;
68    let mut j: f64 = 0_f64;
69
70    let mut x: f64 = f64::NAN;
71    let mut y: f64 = f64::NAN;
72
73    let radius: f64;
74    let center: (f64, f64);
75    let mut theta_start: f64;
76    let mut theta_end: f64;
77
78    match form {
79        ArcForm::IJ(arc_values) => {
80            // I and J form
81            for val in arc_values {
82                match val {
83                    ArcVal::X(val) => x = *val,
84                    ArcVal::Y(val) => y = *val,
85                    ArcVal::I(val) => i = *val,
86                    ArcVal::J(val) => j = *val,
87                    _ => {}
88                }
89            }
90
91            // x and y MUST be extracted from command
92            debug_assert!(x.is_finite());
93            debug_assert!(y.is_finite());
94
95            radius = i.hypot(j);
96            center = (current_x + i, current_y + j);
97
98            let delta_start_x = current_x - center.0;
99            let delta_start_y = current_y - center.1;
100
101            theta_start = (delta_start_y).atan2(delta_start_x);
102            // atan2 returns a value in the range [ -PI, PI].
103            // Want a range to be [0,2PI]
104            if theta_start < 0_f64 {
105                theta_start += 2_f64 * f64::consts::PI;
106            }
107
108            let delta_end_x = x - center.0;
109            let delta_end_y = y - center.1;
110            theta_end = (delta_end_y).atan2(delta_end_x);
111            // atan2 returns a value in the range [ -PI, PI].
112            // Want a range to be [0,2PI]
113            if theta_end < 0_f64 {
114                theta_end += 2_f64 * f64::consts::PI;
115            }
116        }
117        ArcForm::R(arc_values) => {
118            let mut radius: f64 = f64::NAN;
119            // R form
120
121            for val in arc_values {
122                match val {
123                    ArcVal::X(val) => x = *val,
124                    ArcVal::Y(val) => y = *val,
125                    ArcVal::R(val) => radius = *val,
126                    _ => {
127                        // Ignored params
128                    }
129                }
130            }
131            debug_assert!(x.is_finite());
132            debug_assert!(y.is_finite());
133            // r Must be specified from command.
134            debug_assert!(radius.is_finite());
135            // radius = r;
136            // Must solve this  par of simultaneous equations
137            // radius * radius = ( center.0 - current_x ) * ( center.0 - current_x ) + ( center.1 - current_y ) * ( center.1 - current_y )
138            // radians * radius = (current_x - center.0) * (current_x - center.0) + (current_y - center.1) * (current_y - center.1)
139            //
140            // which of the two solutions is correct can be determined by realizing that we are moving clockwise or counter clockwise
141            todo!();
142        }
143    }
144    ArcParams {
145        center,
146        radius,
147        theta_start,
148        theta_end,
149    }
150}
151
152// This illustrates a counter clockwise arc, starting at [9, 6]. It can be generated either by G3 X2 Y7 I-4 J-3 or G3 X2 Y7 R5
153//
154// As show in this (image)[<../images/G3fog.png>]
155//
156// source <https://marlinfw.org/docs/gcode/G002-G003.html>
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    fn round_to_two_decimals(x: f64) -> f64 {
162        (x * 100.0).round() / 100.0
163    }
164
165    #[test]
166    fn compute_arc_ij() {
167        let arc = compute_arc(
168            9.0,
169            6.0,
170            &ArcForm::IJ(
171                [
172                    ArcVal::X(2.0),
173                    ArcVal::Y(7.0),
174                    ArcVal::I(-4.0),
175                    ArcVal::J(-3.0),
176                ]
177                .into(),
178            ),
179        );
180        assert_eq!(arc.center, (5.0, 3.0));
181        assert_eq!(arc.radius, 5.0);
182        assert_eq!(
183            round_to_two_decimals(arc.theta_start.to_degrees()),
184            36.87_f64
185        );
186        assert_eq!(
187            round_to_two_decimals(arc.theta_end.to_degrees()),
188            126.87_f64
189        );
190    }
191
192    #[test]
193    fn troublesome_arc_ij() {
194        let arc = compute_arc(
195            0.0,
196            5.0,
197            &ArcForm::IJ(
198                [
199                    ArcVal::X(5.0),
200                    ArcVal::Y(0.0),
201                    ArcVal::I(5.0),
202                    ArcVal::J(0.0),
203                ]
204                .into(),
205            ),
206        );
207        assert_eq!(arc.center, (5.0, 5.0));
208        assert_eq!(arc.radius, 5.0);
209        assert_eq!(round_to_two_decimals(arc.theta_start.to_degrees()), 180_f64);
210        assert_eq!(round_to_two_decimals(arc.theta_end.to_degrees()), 270_f64);
211    }
212
213    #[ignore]
214    #[test]
215    // ignored? - Complex algorithm to be implemented involving solving a par of simultaneous equations
216    fn compute_arc_r() {
217        let arc = compute_arc(
218            9.0,
219            6.0,
220            &ArcForm::R([ArcVal::X(2.0), ArcVal::Y(7.0), ArcVal::R(5.0)].into()),
221        );
222        assert_eq!(arc.center, (5.0, 3.0));
223        assert_eq!(arc.radius, 5.0);
224    }
225}