aoer_plotty_rs/gcode/
mod.rs

1//! Module which provides Line->GCode post-processing
2use geo_types::{CoordNum, MultiLineString};
3use tera::{Context, Tera};
4use std::error::Error;
5use num_traits::real::Real;
6
7/// List of all available machines as an Enum
8pub enum AoerPostMachines {
9    BAPv1,
10    CustomMachine(Tera),
11}
12
13#[derive(Debug)]
14pub enum PostTemplateError {
15    NoSuchTemplateError,
16    TemplateStructureError,
17}
18
19#[derive(Debug,Clone)]
20pub enum PostGeometrySource<T>
21    where T: CoordNum, T: Real{
22    MultiLineString(MultiLineString<T>),
23}
24
25
26/// # AoerPostMachines
27impl AoerPostMachines {
28    /// Getter for machine templates for the gcode processor. You'll need one of these
29    /// templates to generate gcode, and these are the lookups tables for the various
30    /// commands the post-processor needs.
31    pub fn get_machine(machine: AoerPostMachines) -> Result<Tera, PostTemplateError> {
32        let mut bap_post_template = Tera::default();
33        match machine {
34            AoerPostMachines::BAPv1 => {
35                bap_post_template.add_raw_templates(vec![
36                    ("prelude", "M280 S5\nG4 P150\nG28 X Y\nG90\n G92 X0 Y0 ; HOME"),
37                    ("epilog", "M280 S5\nG4 P150\nG0 X0 Y230\nM281 ; FINISHED"),
38                    ("penup", "M400\nM280 S9\nG4 P150\nM400\nM281 ; PENUP"),
39                    ("pendown", "M400\nM280 S12\nG4 P250\nM400 ; PENDOWN"),
40                    ("moveto", "G0 X{{xmm|round(precision=2)}} Y{{ymm|round(precision=2)}} ; NEW LINE START"),
41                    ("lineto", "G01 F1200 X{{xmm|round(precision=2)}} Y{{ymm|round(precision=2)}}"),
42                ]).unwrap();
43                Ok(bap_post_template)
44            }
45            _ => Err(PostTemplateError::NoSuchTemplateError)
46        }
47    }
48}
49
50
51/// Given a set of lines, gcode-process and generate GCode
52/// Returns either a list of gcode lines, or a box'd dyn error
53/// for what went wrong.
54pub fn post<T>(lines: &PostGeometrySource<T>, post_template: &Tera)
55               -> Result<Vec<String>, Box<dyn Error>>
56    where T: CoordNum, T: Real {
57    let mut program: Vec<String> = Vec::new();
58    // This currently only allows for a single enum subtype, but that's all we have
59    // for now. Eventually we'll expand this to include a variety of subtypes, like
60    // svg2polyline lines, and even multi-layers with separate tools.
61    let lines = match lines{
62        PostGeometrySource::MultiLineString(lines) => lines
63    };
64    program.extend(
65        post_template.render("prelude", &Context::new())?
66            .split("\n").map(|s| s.to_string()));
67    for line in lines.iter() {
68        program.extend(post_template.render("penup", &Context::new())?
69            .split("\n")
70            .map(|s| s.to_string()));
71        let mut context = Context::new();
72        context.insert("xmm", &line[0].x.to_f64().unwrap());
73        context.insert("ymm", &line[0].y.to_f64().unwrap());
74        program.extend(
75            post_template.render("moveto", &context)?
76                .split("\n")
77                .map(|s| s.to_string()));
78
79        program.extend(post_template.render("pendown", &Context::new())?
80            .split("\n")
81            .map(|s| s.to_string()));
82        for point in line.points().skip(1) {
83            let mut context = Context::new();
84            context.insert("xmm", &point.x().to_f64().unwrap());
85            context.insert("ymm", &point.y().to_f64().unwrap());
86            program.extend(
87                post_template.render("lineto", &context)?
88                    .split("\n").map(|s| s.to_string()));
89        }
90    }
91    program.extend(
92        post_template.render("epilog", &Context::new())?
93            .split("\n").map(|s| s.to_string()));
94    Ok(program)
95}
96
97
98#[cfg(test)]
99mod test {
100    use std::iter::zip;
101    use geo_types::{coord, LineString, MultiLineString};
102    use crate::gcode::{AoerPostMachines, post, PostGeometrySource};
103
104    #[test]
105    fn test_post() {
106        let post_template = AoerPostMachines::get_machine(AoerPostMachines::BAPv1)
107            .unwrap();
108        let lines = MultiLineString::new(vec![LineString::new(vec![
109            coord! {x: 0.0, y: 0.0},
110            coord! {x: 10.0, y: 0.0}])]);
111        let program = post(&PostGeometrySource::MultiLineString(lines), &post_template)
112            .unwrap();
113        let pairs: Vec<(String, String)> = zip(program, vec!["M280 S5", "G4 P150", "G28 X Y",
114                                                             "G90", " G92 X0 Y0 ; HOME", "M400",
115                                                             "M280 S9", "G4 P150", "M400",
116                                                             "M281 ; PENUP",
117                                                             "G0 X0 Y0 ; NEW LINE START", "M400",
118                                                             "M280 S12", "G4 P250",
119                                                             "M400 ; PENDOWN", "G01 F1200 X10 Y0",
120                                                             "M280 S5", "G4 P150", "G0 X0 Y230",
121                                                             "M281 ; FINISHED"].iter()
122            .map(|s| { s.to_string() })).collect();
123        for (left, right) in pairs {
124            assert!(left == right);
125        }
126    }
127}