Skip to main content

gcode_nom/binary/
mod.rs

1//! The new binary G-code file consists of a file header followed by an ordered succession of blocks, in the following sequence:
2//!
3//! File Header
4//! File Metadata Block (optional)
5//! Printer Metadata Block
6//! Thumbnails Blocks (optional)
7//! Print Metadata Block
8//! Slicer Metadata Block
9//! G-code Blocks
10//!
11//! All of the multi-byte integers are encoded in little-endian byte ordering.
12//!
13//! <https://github.com/prusa3d/libbgcode/blob/main/doc/specifications.md>
14//!
15//! using this file as a example of good binary parsing
16//!
17//! <https://github.com/rust-av/flavors/blob/master/src/parser.rs>
18//!
19
20/// Public: Contains the SVG converter.
21pub mod gcode_block;
22/// Decompression helpers.
23pub mod inflate;
24
25mod block_header;
26mod compression_type;
27mod default_params;
28mod file_handler;
29mod file_metadata_block;
30
31mod print_metadata_block;
32mod printer_metadata_block;
33mod slicer_block;
34mod thumbnail_block;
35
36use core::fmt::Display;
37
38use file_handler::FileHeader;
39use file_handler::file_header_parser;
40use file_metadata_block::FileMetadataBlock;
41use file_metadata_block::file_metadata_parser;
42use file_metadata_block::file_metadata_parser_with_checksum;
43use nom::IResult;
44use nom::Parser;
45use nom::combinator::eof;
46use nom::combinator::map;
47use nom::combinator::opt;
48use nom::error::ErrorKind;
49use nom::error::ParseError;
50use nom::multi::many_till;
51use nom::multi::many0;
52
53use compression_type::CompressionType;
54use gcode_block::GCodeBlock;
55use gcode_block::gcode_parser;
56use gcode_block::gcode_parser_with_checksum;
57use print_metadata_block::PrintMetadataBlock;
58use print_metadata_block::print_metadata_parser;
59use print_metadata_block::print_metadata_parser_with_checksum;
60use printer_metadata_block::PrinterMetadataBlock;
61use printer_metadata_block::printer_metadata_parser;
62use printer_metadata_block::printer_metadata_parser_with_checksum;
63use slicer_block::SlicerBlock;
64use slicer_block::slicer_parser;
65use slicer_block::slicer_parser_with_checksum;
66use thumbnail_block::ThumbnailBlock;
67use thumbnail_block::thumbnail_parser;
68use thumbnail_block::thumbnail_parser_with_checksum;
69
70/// A trait for markdown formatting.
71pub trait Markdown {
72    /// Write to formatter a markdown block.
73    ///
74    /// # Errors
75    ///   When a call to write fails.
76    fn markdown<W>(&self, f: &mut W) -> core::fmt::Result
77    where
78        W: core::fmt::Write;
79}
80
81/// Error while parsing text into a `Bgcode` structure.
82#[derive(Debug)]
83pub enum BlockError {
84    /// Error decoding the file header block.
85    FileHeader,
86    /// Error decoding the file metadata block.
87    FileMetaData,
88    /// Error decoding a Gcode block
89    Gcode,
90    /// Error decoding the printer metadata block.
91    PrinterMetaData,
92    /// Error decoding the print metadata block.
93    PrintMetaData,
94    /// Error decoding the slicer block.
95    Slicer,
96    /// Error decoding the thumbnails block.
97    Thumbnail,
98    /// Unexpected end of file.
99    EOF,
100    /// `ParseError` return type.
101    ParseError,
102}
103
104impl<I> ParseError<I> for BlockError
105where
106    I: std::fmt::Debug,
107{
108    fn from_error_kind(_input: I, _kind: ErrorKind) -> Self {
109        // This is trapping an EOF error
110        Self::EOF
111    }
112
113    // if combining multiple errors, we show them one after the other
114    fn append(_input: I, _kind: ErrorKind, _other: Self) -> Self {
115        Self::ParseError
116    }
117
118    fn from_char(input: I, c: char) -> Self {
119        let message = format!("'{c}':\t{input:?}\n",);
120        println!("{message}");
121        // big match statement append message to existing message
122        Self::ParseError
123    }
124
125    fn or(self, _other: Self) -> Self {
126        Self::ParseError
127    }
128}
129
130/// Structure of the binary file.
131///
132/// extension .bgcode
133#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct Bgcode<'a> {
135    fh: FileHeader,
136    /// A file block.
137    pub file_metadata: Option<FileMetadataBlock<'a>>,
138    /// A file block.
139    pub printer_metadata: PrinterMetadataBlock<'a>,
140    /// A collection of image blocks.
141    pub thumbnails: Vec<ThumbnailBlock<'a>>,
142    /// A file block.
143    pub print_metadata: PrintMetadataBlock<'a>,
144    /// A file block.
145    pub slicer: SlicerBlock<'a>,
146    /// A collection of gcode blocks.
147    pub gcode: Vec<GCodeBlock<'a>>,
148}
149
150impl Display for Bgcode<'_> {
151    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152        writeln!(f, "{}", self.fh)?;
153
154        if let Some(file_metadata) = &self.file_metadata {
155            writeln!(f, "{file_metadata}")?;
156        } else {
157            writeln!(f, "No optional file metadata block")?;
158        }
159
160        writeln!(f, "{}", &self.printer_metadata)?;
161
162        if self.thumbnails.is_empty() {
163            writeln!(f, "No optional thumbnail block")?;
164        } else {
165            for thumb in &self.thumbnails {
166                writeln!(f, "{thumb}")?;
167            }
168        }
169
170        writeln!(f, "{}", self.print_metadata)?;
171
172        writeln!(f, "{}", self.slicer)?;
173
174        if self.gcode.is_empty() {
175            writeln!(f, "No optional thumbnail block")?;
176        } else {
177            for g in &self.gcode {
178                writeln!(f, "{g}")?;
179            }
180        }
181        Ok(())
182    }
183}
184
185impl Markdown for Bgcode<'_> {
186    /// Write to formatter a markdown block.
187    ///
188    /// # Errors
189    ///   When match fails.
190    fn markdown<W>(&self, f: &mut W) -> core::fmt::Result
191    where
192        W: std::fmt::Write,
193    {
194        self.fh.markdown(&mut *f)?;
195
196        if let Some(file_metadata) = &self.file_metadata {
197            file_metadata.markdown(&mut *f)?;
198        } else {
199            writeln!(f, "No optional file metadata block")?;
200        }
201
202        self.printer_metadata.markdown(&mut *f)?;
203
204        self.thumbnails.markdown(&mut *f)?;
205
206        self.print_metadata.markdown(&mut *f)?;
207
208        self.slicer.markdown(&mut *f)?;
209
210        self.gcode.markdown(f)?;
211
212        Ok(())
213    }
214}
215
216/// Parses a binary gcode
217///
218/// Fast version checksum is logged but not validated.
219///
220/// # Errors
221///   When the bytes stream is not a valid file.
222pub fn bgcode_parser(input: &[u8]) -> IResult<&[u8], Bgcode<'_>, BlockError> {
223    map(
224        (
225            file_header_parser,
226            opt(file_metadata_parser),
227            printer_metadata_parser,
228            many0(thumbnail_parser),
229            print_metadata_parser,
230            slicer_parser,
231            // eof here asserts than what remains is_empty()
232            many_till(gcode_parser, eof),
233        ),
234        |(
235            fh,
236            file_metadata,
237            printer_metadata,
238            thumbnail,
239            print_metadata,
240            slicer,
241            (gcode, _remain),
242        )| {
243            log::info!("File has been validated");
244            Bgcode {
245                fh,
246                file_metadata,
247                printer_metadata,
248                thumbnails: thumbnail,
249                print_metadata,
250                slicer,
251                gcode,
252            }
253        },
254    )
255    .parse(input)
256}
257
258/// Parses a binary gcode
259///
260/// Slower more exacting version where each block is rejected
261/// if checksum fails.
262///
263/// # Errors
264///   When the bytes stream is not a valid file.
265pub fn bgcode_parser_with_checksum(input: &[u8]) -> IResult<&[u8], Bgcode<'_>, BlockError> {
266    map(
267        (
268            file_header_parser,
269            opt(file_metadata_parser_with_checksum),
270            printer_metadata_parser_with_checksum,
271            many0(thumbnail_parser_with_checksum),
272            print_metadata_parser_with_checksum,
273            slicer_parser_with_checksum,
274            // eof here asserts than what remains is_empty()
275            many_till(gcode_parser_with_checksum, eof),
276        ),
277        |(
278            fh,
279            file_metadata,
280            printer_metadata,
281            thumbnail,
282            print_metadata,
283            slicer,
284            (gcode, _remain),
285        )| {
286            log::info!("File has been validated");
287            Bgcode {
288                fh,
289                file_metadata,
290                printer_metadata,
291                thumbnails: thumbnail,
292                print_metadata,
293                slicer,
294                gcode,
295            }
296        },
297    )
298    .parse(input)
299}