svg2gcode/
machine.rs

1use g_code::{
2    command,
3    emit::Token,
4    parse::{ast::Snippet, snippet_parser},
5};
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9/// Whether the tool is active (i.e. cutting)
10#[derive(Copy, Clone, PartialEq, Eq, Debug)]
11pub enum Tool {
12    Off,
13    On,
14}
15
16/// The distance mode for movement commands
17#[derive(Copy, Clone, PartialEq, Eq, Debug)]
18pub enum Distance {
19    Absolute,
20    Relative,
21}
22
23/// Generic machine state simulation, assuming nothing is known about the machine when initialized.
24/// This is used to reduce output G-Code verbosity and run repetitive actions.
25#[derive(Debug, Clone)]
26pub struct Machine<'input> {
27    supported_functionality: SupportedFunctionality,
28    tool_state: Option<Tool>,
29    distance_mode: Option<Distance>,
30    tool_on_sequence: Snippet<'input>,
31    tool_off_sequence: Snippet<'input>,
32    program_begin_sequence: Snippet<'input>,
33    program_end_sequence: Snippet<'input>,
34    /// Empty snippet used to provide the same iterator type when a sequence must be empty
35    empty_snippet: Snippet<'input>,
36}
37
38#[derive(Debug, Default, Clone, PartialEq)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40pub struct MachineConfig {
41    pub supported_functionality: SupportedFunctionality,
42    pub tool_on_sequence: Option<String>,
43    pub tool_off_sequence: Option<String>,
44    pub begin_sequence: Option<String>,
45    pub end_sequence: Option<String>,
46}
47
48#[derive(Debug, Default, Clone, PartialEq)]
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50pub struct SupportedFunctionality {
51    /// Indicates support for G2/G3 circular interpolation.
52    ///
53    /// Most modern machines support this. Old ones like early MakerBot 3D printers do not.
54    pub circular_interpolation: bool,
55}
56
57impl<'input> Machine<'input> {
58    pub fn new(
59        supported_functionality: SupportedFunctionality,
60        tool_on_sequence: Option<Snippet<'input>>,
61        tool_off_sequence: Option<Snippet<'input>>,
62        program_begin_sequence: Option<Snippet<'input>>,
63        program_end_sequence: Option<Snippet<'input>>,
64    ) -> Self {
65        let empty_snippet = snippet_parser("").expect("empty string is a valid snippet");
66        Self {
67            supported_functionality,
68            tool_on_sequence: tool_on_sequence.unwrap_or_else(|| empty_snippet.clone()),
69            tool_off_sequence: tool_off_sequence.unwrap_or_else(|| empty_snippet.clone()),
70            program_begin_sequence: program_begin_sequence.unwrap_or_else(|| empty_snippet.clone()),
71            program_end_sequence: program_end_sequence.unwrap_or_else(|| empty_snippet.clone()),
72            empty_snippet,
73            tool_state: Default::default(),
74            distance_mode: Default::default(),
75        }
76    }
77
78    pub fn supported_functionality(&self) -> &SupportedFunctionality {
79        &self.supported_functionality
80    }
81
82    /// Output gcode to turn the tool on.
83    pub fn tool_on(&mut self) -> impl Iterator<Item = Token<'input>> + '_ {
84        if self.tool_state == Some(Tool::Off) || self.tool_state.is_none() {
85            self.tool_state = Some(Tool::On);
86            self.tool_on_sequence.iter_emit_tokens()
87        } else {
88            self.empty_snippet.iter_emit_tokens()
89        }
90    }
91
92    /// Output gcode to turn the tool off.
93    pub fn tool_off(&mut self) -> impl Iterator<Item = Token<'input>> + '_ {
94        if self.tool_state == Some(Tool::On) || self.tool_state.is_none() {
95            self.tool_state = Some(Tool::Off);
96            self.tool_off_sequence.iter_emit_tokens()
97        } else {
98            self.empty_snippet.iter_emit_tokens()
99        }
100    }
101
102    /// Output user-defined setup gcode
103    pub fn program_begin(&self) -> impl Iterator<Item = Token<'input>> + '_ {
104        self.program_begin_sequence.iter_emit_tokens()
105    }
106
107    /// Output user-defined teardown gcode
108    pub fn program_end(&self) -> impl Iterator<Item = Token<'input>> + '_ {
109        self.program_end_sequence.iter_emit_tokens()
110    }
111
112    /// Output absolute distance field if mode was relative or unknown.
113    pub fn absolute(&mut self) -> Vec<Token<'input>> {
114        if self.distance_mode == Some(Distance::Relative) || self.distance_mode.is_none() {
115            self.distance_mode = Some(Distance::Absolute);
116            command!(AbsoluteDistanceMode {}).into_token_vec()
117        } else {
118            vec![]
119        }
120    }
121
122    /// Output relative distance field if mode was absolute or unknown.
123    pub fn relative(&mut self) -> Vec<Token<'input>> {
124        if self.distance_mode == Some(Distance::Absolute) || self.distance_mode.is_none() {
125            self.distance_mode = Some(Distance::Relative);
126            command!(RelativeDistanceMode {}).into_token_vec()
127        } else {
128            vec![]
129        }
130    }
131}