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 r: f64 = f64::NAN;
119            // R form
120            for val in arc_values {
121                match val {
122                    ArcVal::X(val) => x = *val,
123                    ArcVal::Y(val) => y = *val,
124                    ArcVal::R(val) => r = *val,
125                    _ => {
126                        // Ignored params
127                    }
128                }
129            }
130            // r Must be specified from command.
131            debug_assert!(r.is_finite());
132            radius = r;
133            // Must solve this  par of simultaneous equations
134            // radius * radius = ( center.0 - current_x ) * ( center.0 - current_x ) + ( center.1 - current_y ) * ( center.1 - current_y )
135            // radians * radius = (current_x - center.0) * (current_x - center.0) + (current_y - center.1) * (current_y - center.1)
136            //
137            // which of the two solutions is correct can be determined by realizing that we are moving clockwise or counter clockwise
138            todo!();
139        }
140    }
141    ArcParams {
142        center,
143        radius,
144        theta_start,
145        theta_end,
146    }
147}
148
149// 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
150//
151// As show in this (image)[<../images/G3fog.png>]
152//
153// source <https://marlinfw.org/docs/gcode/G002-G003.html>
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    fn round_to_two_decimals(x: f64) -> f64 {
159        (x * 100.0).round() / 100.0
160    }
161
162    #[test]
163    fn compute_arc_ij() {
164        let arc = compute_arc(
165            9.0,
166            6.0,
167            &ArcForm::IJ(
168                [
169                    ArcVal::X(2.0),
170                    ArcVal::Y(7.0),
171                    ArcVal::I(-4.0),
172                    ArcVal::J(-3.0),
173                ]
174                .into(),
175            ),
176        );
177        assert_eq!(arc.center, (5.0, 3.0));
178        assert_eq!(arc.radius, 5.0);
179        assert_eq!(
180            round_to_two_decimals(arc.theta_start.to_degrees()),
181            36.87_f64
182        );
183        assert_eq!(
184            round_to_two_decimals(arc.theta_end.to_degrees()),
185            126.87_f64
186        );
187    }
188
189    #[test]
190    fn troublesome_arc_ij() {
191        let arc = compute_arc(
192            0.0,
193            5.0,
194            &ArcForm::IJ(
195                [
196                    ArcVal::X(5.0),
197                    ArcVal::Y(0.0),
198                    ArcVal::I(5.0),
199                    ArcVal::J(0.0),
200                ]
201                .into(),
202            ),
203        );
204        assert_eq!(arc.center, (5.0, 5.0));
205        assert_eq!(arc.radius, 5.0);
206        assert_eq!(round_to_two_decimals(arc.theta_start.to_degrees()), 180_f64);
207        assert_eq!(round_to_two_decimals(arc.theta_end.to_degrees()), 270_f64);
208    }
209
210    #[ignore]
211    #[test]
212    // ignored? - Complex algorithm to be implemented involving solving a par of simultaneous equations
213    fn compute_arc_r() {
214        let arc = compute_arc(
215            9.0,
216            6.0,
217            &ArcForm::R([ArcVal::X(2.0), ArcVal::Y(7.0), ArcVal::R(5.0)].into()),
218        );
219        assert_eq!(arc.center, (5.0, 3.0));
220        assert_eq!(arc.radius, 5.0);
221    }
222}