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