Skip to main content

g_code/emit/
command.rs

1//! Higher-level constructs for g-code emission
2
3use paste::paste;
4
5use super::{Field, Token, Value};
6
7/// A macro for quickly instantiating a float-valued command
8///
9/// For instance:
10///
11/// ```
12/// use g_code::command;
13/// assert_eq!(
14///     command!(RapidPositioning { X: 0., Y: 1. })
15///         .iter()
16///         .fold(String::default(), |s, f| s + &f.to_string()),
17///     "G0X0Y1"
18/// );
19/// ```
20#[macro_export]
21macro_rules! command {
22    ($commandName: ident {
23        $($arg: ident : $value: expr),* $(,)?
24    }) => {
25        {
26            paste::expr!{
27                g_code::emit::command::[<$commandName:snake:lower>](
28                    vec![$(
29                        g_code::emit::Field {
30                            letters: std::borrow::Cow::Borrowed(stringify!([<$arg:upper>])),
31                            value: g_code::emit::Value::Float($value),
32                        }
33                    ,)*].into_iter()
34                )
35            }
36        }
37    };
38}
39
40macro_rules! impl_commands {
41    ($($(#[$outer:meta])* $commandName: ident {$letters: expr, $value: literal, {$($(#[$inner:meta])* $arg: ident), *} } )*) => {
42
43        paste! {
44            $(
45                $(#[$outer])*
46                ///
47                /// To instantiate the command, call this function
48                /// or use the [crate::command] macro.
49                pub fn [<$commandName:snake:lower>]<'a, I: Iterator<Item = Field<'a>>>(args: I) -> Command<'a> {
50                    Command {
51                        name: [<$commandName:snake:upper _FIELD>].clone(),
52                        args: args.filter(|arg| {
53                            match arg.letters.to_ascii_uppercase().as_str() {
54                                $(stringify!($arg) => true,)*
55                                _ => false
56                            }
57                        }).collect(),
58                    }
59                }
60
61                /// Constant for this command's name used to reduce allocations.
62                pub const [<$commandName:snake:upper _FIELD>]: Field<'static> = Field {
63                    letters: std::borrow::Cow::Borrowed($letters),
64                    value: crate::emit::Value::Integer($value),
65                };
66            )*
67        }
68
69        /// Commands are the operational unit of g-code
70        ///
71        /// They consist of a G, M, or other top-level field followed by field arguments
72        #[derive(Clone, PartialEq, Debug)]
73        pub struct Command<'a> {
74            name: Field<'a>,
75            args: Vec<Field<'a>>,
76        }
77
78        impl<'a> Command<'a> {
79            /// Add a field to the command.
80            ///
81            /// Returns an error if the Field's letters aren't recognized.
82            pub fn push(&mut self, arg: Field<'a>) -> Result<(), &'static str> {
83                paste!{
84                    match &self.name {
85                        $(x if *x == [<$commandName:snake:upper _FIELD>] => {
86                            if match arg.letters.as_ref() {
87                                $(stringify!([<$arg:upper>]) => {true},)*
88                                $(stringify!([<$arg:lower>]) => {true},)*
89                                _ => false,
90                            } {
91                                self.args.push(arg);
92                                Ok(())
93                            } else {
94                                Err(concat!($(stringify!([<$arg:lower>]), " ", stringify!([<$arg:upper>]), " ", )*))
95                            }
96                        },)*
97                        _ => {
98                            unreachable!("a command's name cannot change");
99                        }
100                    }
101                }
102            }
103
104            /// Iterate over all fields including the command's name (i.e. G0 for rapid positioning)
105            pub fn iter(&self) -> impl Iterator<Item = &Field<'_>> {
106                std::iter::once(&self.name).chain(self.args.iter())
107            }
108
109            /// Consumes the command to produce tokens suitable for output
110            pub fn into_token_vec(mut self) -> Vec<Token<'a>> {
111                std::iter::once(self.name).chain(self.args.drain(..)).map(|f| f.into()).collect()
112            }
113
114            /// Iterate over the fields after the command's name
115            pub fn iter_args(&self) -> impl Iterator<Item = &Field<'_>> {
116                self.iter().skip(1)
117            }
118
119            pub fn iter_mut_args(&mut self) -> impl Iterator<Item = &mut Field<'a>> {
120                self.args.iter_mut()
121            }
122
123            pub fn get(&'_ self, letters: &str) -> Option<&'_ Field<'_>> {
124                let letters = letters.to_ascii_uppercase();
125                self.iter_args().find(|arg| arg.letters == letters)
126            }
127
128            pub fn set(&mut self, letters: &str, value: Value<'a>) {
129                let letters = letters.to_ascii_uppercase();
130                for i in 0..self.args.len() {
131                    if self.args[i].letters == letters {
132                        self.args[i].value = value;
133                        break;
134                    }
135                }
136            }
137        }
138    };
139}
140
141impl_commands!(
142    /// Moves the head to the desired position
143    /// at the fastest possible speed.
144    ///
145    /// *NEVER* enter a cut with rapid positioning.
146    /// Some older machines may "dog leg" rapid positioning, moving one axis at a time
147    RapidPositioning {
148        "G", 0, {
149            X,
150            Y,
151            Z,
152            E,
153            F,
154            H,
155            R,
156            S,
157            A,
158            B,
159            C
160        }
161    }
162    /// Interpolate along a line to the desired position
163    ///
164    /// Typically used for "cutting" motion
165    LinearInterpolation {
166        "G", 1, {
167            X,
168            Y,
169            Z,
170            E,
171            F,
172            H,
173            R,
174            S,
175            A,
176            B,
177            C
178        }
179    }
180    /// Interpolate along an arc to the desired position
181    ///
182    /// The machine will maintain either a constant distance
183    /// from the arc's center `(I, J, K)` or a constant radius `R`.
184    ///
185    /// Not all machines support this command. Those that do typically
186    /// recommend short arcs. Some may have a maximum supported radius.
187    ClockwiseCircularInterpolation {
188        "G", 2, {
189            X,
190            Y,
191            Z,
192            I,
193            J,
194            K,
195            E,
196            F,
197            R
198        }
199    }
200    /// See guidance on [clockwise_circular_interpolation]
201    CounterclockwiseCircularInterpolation {
202        "G", 3, {
203            X,
204            Y,
205            Z,
206            I,
207            J,
208            K,
209            E,
210            F,
211            R
212        }
213    }
214    /// This will keep the axes unmoving for the period of time in seconds specified by the P number
215    Dwell {
216        "G", 4, {
217            /// Time in seconds
218            P
219        }
220    }
221    /// Use inches for length units
222    UnitsInches {
223        "G", 20, {}
224    }
225    /// Use millimeters for length units
226    UnitsMillimeters {
227        "G", 21, {}
228    }
229    /// In absolute distance mode, axis numbers usually represent positions in terms of the currently active coordinate system.
230    AbsoluteDistanceMode {
231        "G", 90, {}
232    }
233    /// In relative distance mode, axis numbers usually represent increments from the current values of the numbers
234    RelativeDistanceMode {
235        "G", 91, {}
236    }
237    FeedRateUnitsPerMinute {
238        "G", 94, {}
239    }
240    /// Start spinning the spindle clockwise with speed `p`
241    StartSpindleClockwise {
242        "M", 3, {
243            /// Speed
244            P
245        }
246    }
247    /// Start spinning the spindle counterclockwise with speed `p`
248    StartSpindleCounterclockwise {
249        "M", 4, {
250            /// Speed
251            P
252        }
253    }
254    /// Stop spinning the spindle
255    StopSpindle {
256        "M", 5, {}
257    }
258    /// Signals the end of a program
259    ProgramEnd {
260        "M", 2, {}
261    }
262);