gcode_nom/
arc.rs

1use core::hash::Hash;
2use core::hash::Hasher;
3use std::collections::HashSet;
4
5use nom::bytes::complete::tag;
6use nom::character::complete::space0;
7use nom::combinator::map;
8use nom::sequence::preceded;
9use nom::IResult;
10use nom::Parser;
11
12/// G2/G3 Arc Command
13///
14/// Payload has two forms,
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum Form {
17    /// Arc with center offset in I and J
18    IJ(HashSet<ArcVal>),
19    /// Arc with radius R
20    R(HashSet<ArcVal>),
21}
22
23/// Parameters for `Command::G2` and `Command::G3`
24///
25/// Similar to `PosVal` but with additional parameters
26/// I,J, P, R.
27#[derive(Clone, Debug)]
28pub enum ArcVal {
29    /// Axis A
30    A(f64),
31    /// Axis B
32    B(f64),
33    /// Axis C
34    C(f64),
35
36    /// Extruder
37    E(f64),
38    /// sets the federate for all subsequent moved.
39    F(f64),
40
41    /// Sets the laser power for the move
42    S(f64),
43
44    /// Arc center offset in the I direction
45    I(f64),
46    /// Arc center offset in the J direction
47    J(f64),
48
49    /// `P<Count>` number of complete circles.
50    P(f64),
51    /// Radius of the arc
52    R(f64),
53
54    /// Axis U
55    U(f64),
56    /// Axis V
57    V(f64),
58
59    /// Axis X
60    X(f64),
61    /// Axis Y
62    Y(f64),
63    /// Axis Z
64    Z(f64),
65    /// Axis W
66    W(f64),
67}
68
69impl Eq for ArcVal {}
70
71/// Bit wise comparison cant' compare directly [NAN and inf]
72///
73/// N.B. Equality is not used in production code -  assertion testing only.
74impl PartialEq for ArcVal {
75    fn eq(&self, other: &Self) -> bool {
76        match (self, other) {
77            (Self::A(x), Self::A(y))
78            | (Self::B(x), Self::B(y))
79            | (Self::C(x), Self::C(y))
80            | (Self::E(x), Self::E(y))
81            | (Self::F(x), Self::F(y))
82            | (Self::I(x), Self::I(y))
83            | (Self::J(x), Self::J(y))
84            | (Self::P(x), Self::P(y))
85            | (Self::R(x), Self::R(y))
86            | (Self::S(x), Self::S(y))
87            | (Self::U(x), Self::U(y))
88            | (Self::V(x), Self::V(y))
89            | (Self::W(x), Self::W(y))
90            | (Self::X(x), Self::X(y))
91            | (Self::Y(x), Self::Y(y))
92            | (Self::Z(x), Self::Z(y)) => x.to_bits() == y.to_bits(),
93            _ => false,
94        }
95    }
96}
97
98/// Hash is used to determine if an entry should be added to the Sets
99///
100/// malformed commands with duplicate parameters will be rejected here.
101///
102/// G1 X95.110 X96.233 E2.07708
103///
104/// By ignoring the f64 in hashing the parsed Command will only have one
105/// X value.
106impl Hash for ArcVal {
107    fn hash<H: Hasher>(&self, state: &mut H) {
108        match self {
109            Self::A(_) => "A".hash(state),
110            Self::B(_) => "B".hash(state),
111            Self::C(_) => "C".hash(state),
112            Self::E(_) => "E".hash(state),
113            Self::F(_) => "F".hash(state),
114            Self::I(_) => "I".hash(state),
115            Self::J(_) => "J".hash(state),
116            Self::P(_) => "P".hash(state),
117            Self::R(_) => "R".hash(state),
118            Self::S(_) => "S".hash(state),
119            Self::U(_) => "U".hash(state),
120            Self::V(_) => "V".hash(state),
121            Self::W(_) => "W".hash(state),
122            Self::X(_) => "X".hash(state),
123            Self::Y(_) => "Y".hash(state),
124            Self::Z(_) => "Z".hash(state),
125        }
126    }
127}
128
129// A macro to make parse_a generic
130//
131// BUGFIX: using double_no_exponent from double.rs
132// as parsing a float with an exponent conflicts
133// with the E parameter in GCode.
134// use nom::number::complete::double;
135macro_rules! parse_arc_val {
136    ($name:ident, $tag:literal, $variant:ident) => {
137        #[doc = "Extracts"]
138        #[doc = stringify!($tag)]
139        #[doc = " parameter"]
140        #[doc = ""]
141        #[doc = "# Errors"]
142        #[doc = "  When match fails."]
143        pub fn $name(i: &str) -> IResult<&str, ArcVal> {
144            map(
145                preceded((space0, tag($tag)), crate::double::double_no_exponent),
146                ArcVal::$variant,
147            )
148            .parse(i)
149        }
150    };
151}
152
153parse_arc_val!(parse_arc_a, "A", A);
154parse_arc_val!(parse_arc_b, "B", B);
155parse_arc_val!(parse_arc_c, "C", C);
156parse_arc_val!(parse_arc_e, "E", E);
157
158parse_arc_val!(parse_arc_i, "I", I);
159parse_arc_val!(parse_arc_j, "J", J);
160parse_arc_val!(parse_arc_p, "P", P);
161parse_arc_val!(parse_arc_r, "R", R);
162
163parse_arc_val!(parse_arc_f, "F", F);
164parse_arc_val!(parse_arc_s, "S", S);
165parse_arc_val!(parse_arc_u, "U", U);
166parse_arc_val!(parse_arc_v, "V", V);
167
168parse_arc_val!(parse_arc_w, "W", W);
169parse_arc_val!(parse_arc_x, "X", X);
170parse_arc_val!(parse_arc_y, "Y", Y);
171parse_arc_val!(parse_arc_z, "Z", Z);
172
173#[cfg(test)]
174mod test {
175    use super::*;
176
177    // Test the macro
178    #[test]
179    fn parse_a_macro() {
180        assert_eq!(parse_arc_a("A95.110"), Ok(("", ArcVal::A(95.110))));
181    }
182
183    // Offsets and extrusions can be negative
184    #[test]
185    fn parse_negative_value() {
186        assert_eq!(parse_arc_e("E-1.1"), Ok(("", ArcVal::E(-1.1))));
187        assert_eq!(parse_arc_i("I-10.10"), Ok(("", ArcVal::I(-10.10))));
188        assert_eq!(parse_arc_j("J-100.100"), Ok(("", ArcVal::J(-100.100))));
189    }
190
191    #[test]
192    fn pos_value_equality() {
193        // Pass: - parameter wrapper and inner value match.
194        assert!(ArcVal::A(95.0) == ArcVal::A(95.0));
195
196        // Fail: -  A = A but inner value is different.
197        assert!(ArcVal::A(95.0) != ArcVal::B(9.0));
198
199        // FAIL: - A != B but with identical inner value.
200        assert!(ArcVal::A(95.0) != ArcVal::B(95.0));
201    }
202}