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