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);