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