g_win/
lib.rs

1// include readme in docs
2#![doc = include_str!("../README.md")]
3
4pub mod emit;
5mod file;
6mod parsers;
7mod tests;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use microns::Microns;
13use std::{io::Write, path::Path};
14/// Default basic annotations for G1 moves, generated automatically
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
17pub enum Tag {
18    Retraction,
19    DeRetraction,
20    Travel,
21    RaiseZ,
22    LowerZ,
23    Wipe,
24    Extrusion,
25    Feedrate,
26    #[default]
27    Uninitialized,
28}
29
30/// Struct to store G1 params as optional strings
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
33pub struct G1 {
34    pub x: Option<Microns>,
35    pub y: Option<Microns>,
36    pub z: Option<Microns>,
37    pub e: Option<Microns>,
38    pub f: Option<Microns>,
39    pub tag: Tag,
40}
41
42/// Enum to represent all possible gcode commands that we would
43/// like to handle, leaving any unknown commands as raw strings.
44/// Specific structs to store information for each command can
45/// be added as needed.
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[derive(Clone, Debug, PartialEq, Eq, Hash)]
48pub enum Command {
49    G1(G1),
50    G90,
51    G91,
52    M82,
53    M83,
54    Raw(String),
55}
56
57impl Command {
58    pub fn tag(&self) -> Tag {
59        match self {
60            Command::G1(g1) => g1.tag,
61            _ => Tag::Uninitialized,
62        }
63    }
64}
65
66/// Struct to store a single line of gcode, with an id, command,
67/// and comments
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69#[derive(Clone, Debug, PartialEq, Eq, Hash)]
70pub struct GCodeLine {
71    pub id: Id,
72    pub command: Command,
73    pub comments: String,
74}
75
76/// Struct to store all information for a .gcode file,
77/// specifically calling out relative vs absolute positioning
78/// and extrusion and with a counter to generate line ids
79///
80//~ NOTE: this struct is generated through the FromStr trait
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82#[derive(Clone, Debug, Default, PartialEq, Eq)]
83pub struct GCodeModel {
84    pub lines: Vec<GCodeLine>, // keep track of line order
85    pub rel_xyz: bool,
86    pub rel_e: bool,
87    pub id_counter: Counter,
88}
89
90impl std::str::FromStr for GCodeModel {
91    type Err = parsers::GCodeParseError;
92    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
93        let gcode = parsers::gcode_parser(&mut s);
94        match gcode {
95            Ok(gcode) => Ok(gcode),
96            Err(e) => Err(e),
97        }
98    }
99}
100
101impl GCodeModel {
102    pub fn from_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
103        Ok(file::open_gcode_file(path)?.parse()?)
104    }
105    pub fn write_to_file(&self, path: &Path) -> Result<(), std::io::Error> {
106        use emit::Emit;
107        use std::fs::File;
108        let out = self.emit(false);
109        let mut f = File::create(path)?;
110        f.write_all(out.as_bytes())?;
111        println!("save successful");
112        Ok(())
113    }
114    pub fn tag_g1(&mut self) {
115        let mut prev = [
116            Microns::ZERO,
117            Microns::ZERO,
118            Microns::ZERO,
119        ];
120        for line in self.lines.iter_mut() {
121            if let Command::G1(G1 { x, y, z, e, f, tag }) = &mut line.command {
122                let curr = [
123                    prev[0] + x.unwrap_or(Microns::ZERO),
124                    prev[1] + y.unwrap_or(Microns::ZERO),
125                    prev[2] + z.unwrap_or(Microns::ZERO),
126                ];
127
128                let dx = curr[0] - prev[0];
129                let dy = curr[1] - prev[1];
130                let dz = curr[2] - prev[2];
131                let de = e.unwrap_or(Microns::ZERO);
132                let f = f.unwrap_or(Microns::ZERO);
133
134                *tag = {
135                    if de > Microns::ZERO {
136                        if dx.abs() > Microns::ZERO || dy.abs() > Microns::ZERO {
137                            Tag::Extrusion
138                        } else { Tag::DeRetraction }
139                    } else if de == Microns::ZERO {
140                        if dx.abs() > Microns::ZERO || dy.abs() > Microns::ZERO {
141                            Tag::Travel
142                        } else if dz > Microns::ZERO {
143                            Tag::RaiseZ
144                        } else if dz < Microns::ZERO {
145                            Tag::LowerZ
146                        } else if f > Microns::ZERO {
147                            Tag::Feedrate
148                        } else { Tag::Uninitialized }
149                    } else if dx.abs() > Microns::ZERO || dy.abs() > Microns::ZERO {
150                            Tag::Wipe
151                    } else {
152                        Tag::Retraction
153                    }
154                };
155                prev = curr;
156            }
157        }
158    }
159}
160
161#[test]
162fn tag_test() {
163    let mut gcode = GCodeModel::default();
164    gcode.lines.push(GCodeLine {
165        id: gcode.id_counter.get(),
166        command: Command::G1(G1 {
167            x: Some(Microns::from(10.0)),
168            y: Some(Microns::from(10.0)),
169            z: Some(Microns::from(10.0)),
170            e: Some(Microns::from(10.0)),
171            f: Some(Microns::from(10.0)),
172            tag: Tag::Uninitialized,
173        }),
174        comments: String::new(),
175    });
176    gcode.tag_g1();
177    assert_eq!(gcode.lines[0].command.tag(), Tag::Extrusion);
178    gcode.lines.push(GCodeLine {
179        id: gcode.id_counter.get(),
180        command: Command::G1(G1::default()),
181        comments: String::new(),
182    });
183    gcode.tag_g1();
184    assert_eq!(gcode.lines[1].command.tag(), Tag::Uninitialized);
185    gcode.lines.push(GCodeLine {
186        id: gcode.id_counter.get(),
187        command: Command::G1(G1 {
188            e: Some(Microns::from(-10.0)),
189            ..Default::default()
190        }),
191        comments: String::new(),
192    });
193    gcode.tag_g1();
194    assert_eq!(gcode.lines[2].command.tag(), Tag::Retraction);
195    gcode.lines.push(GCodeLine {
196        id: gcode.id_counter.get(),
197        command: Command::G1(G1 {
198            e: Some(Microns::from(-10.0)),
199            x: Some(Microns::from(10.0)),
200            y: Some(Microns::from(10.0)),
201            ..Default::default()
202        }),
203        comments: String::new(),
204    });
205    gcode.tag_g1();
206    assert_eq!(gcode.lines[3].command.tag(), Tag::Wipe);
207    gcode.lines.push(GCodeLine {
208        id: gcode.id_counter.get(),
209        command: Command::G1(G1 {
210            e: Some(Microns::from(-10.0)),
211            z: Some(Microns::from(10.0)),
212            ..Default::default()
213        }),
214        comments: String::new(),
215    });
216    gcode.tag_g1();
217    assert_eq!(gcode.lines[4].command.tag(), Tag::Retraction);
218    gcode.lines.push(GCodeLine {
219        id: gcode.id_counter.get(),
220        command: Command::G1(G1 {
221            e: Some(Microns::from(-10.0)),
222            z: Some(Microns::from(-10.0)),
223            ..Default::default()
224        }),
225        comments: String::new(),
226    });
227    gcode.tag_g1();
228    assert_eq!(gcode.lines[5].command.tag(), Tag::Retraction);
229    gcode.lines.push(GCodeLine {
230        id: gcode.id_counter.get(),
231        command: Command::G1(G1 {
232            f: Some(Microns::from(10.0)),
233            ..Default::default()
234        }),
235        comments: String::new(),
236    });
237    gcode.tag_g1();
238    assert_eq!(gcode.lines[6].command.tag(), Tag::Feedrate);
239    gcode.lines.push(GCodeLine {
240        id: gcode.id_counter.get(),
241        command: Command::G1(G1 {
242            e: Some(Microns::from(10.0)),
243            ..Default::default()
244        }),
245        comments: String::new(),
246    });
247    gcode.tag_g1();
248    assert_eq!(gcode.lines[7].command.tag(), Tag::DeRetraction);
249}
250#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
251#[derive(Clone, Debug, Default, PartialEq, Eq)]
252pub struct Counter {
253    count: u32,
254}
255
256impl Counter {
257    fn get(&mut self) -> Id {
258        let out = self.count;
259        self.count += 1;
260        Id(out)
261    }
262}
263#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
264#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
265pub struct Id(u32);
266
267impl Id {
268    pub fn get(&self) -> u32 {
269        self.0
270    }
271}