xgcode/
lib.rs

1use std::io::{Read, Write};
2use byteorder::{ReadBytesExt, LittleEndian, WriteBytesExt};
3use thiserror::Error;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6/// XGCode header
7pub struct Header {
8    /// Print time, in seconds
9    pub print_time: u32,
10    /// Filament usage, extruder 0 (right), in mm
11    pub filament_0_usage: u32,
12    /// Filament usage, extruder 1 (left), in mm
13    pub filament_1_usage: u32,
14
15    /// Type of multi-extruder
16    pub multi_extruder_type: u16,
17
18    /// Layer height, microns
19    pub layer_height: u16,
20
21    /// Function unknown
22    pub reserved0: u16,
23
24    /// Perimeter shells, number
25    pub perimeter_shells: u16,
26
27    /// Print speed, mm/s
28    pub print_speed: u16,
29
30    /// Hotbed temperature, °C
31    pub hotbed_temp: u16,
32
33    /// Extruder 0 (right) temperature, °C
34    pub extruder_0_temp: u16,
35
36    /// Extruder 1 (left) temperature, °C
37    pub extruder_1_temp: u16,
38
39    /// Function unknown
40    pub reserved1: u16,
41
42}
43
44#[derive(Clone,Debug,Eq,PartialEq)]
45pub struct XGCode {
46    pub header: Header,
47    pub thumbnail: Vec<u8>,
48    pub gcode: Vec<u8>
49}
50
51#[derive(Copy,Clone,Debug,Eq,PartialEq)]
52pub struct XGCodeRef<'a> {
53    pub header: Header,
54    pub thumbnail: &'a [u8],
55    pub gcode: &'a [u8],
56}
57
58
59
60#[derive(Debug,Error)]
61pub enum Error {
62    #[error("Bad magic header")]    BadMagic(Box<[u8; 16]>),
63    #[error("Bad header size")]     BadHeaderSize(u32),
64    #[error("Thumb size negative")] ThumbSizeNegative(i32),
65    #[error("GCode too big")]       ThumbnailTooLarge(usize),
66    #[error("Second goffset not found")]  SecondGOffsetNotFound,
67    #[error("Data in reserved field")]  DataInReservedField {offset: u16, value: u16},
68    #[error("IO error")]            IO(#[from] std::io::Error),
69}
70
71const XGCODE_MAGIC: &'static [u8; 16] = b"xgcode 1.0\n\0\0\0\0\0";
72const THUMB_OFFSET: u32 = 0x3A;
73
74impl XGCode {
75
76    pub fn read<R: Read>(mut source: R) -> Result<Self, Error> {
77        let mut magic = [0; 16];
78        source.read_exact(&mut magic)?;
79        
80        if &magic != XGCODE_MAGIC { return Err(Error::BadMagic(Box::new(magic))) }
81
82        let thumb_offset = source.read_u32::<LittleEndian>()?;
83        if thumb_offset != THUMB_OFFSET { return Err(Error::BadHeaderSize(thumb_offset)) }
84
85        let gcode_offset = source.read_u32::<LittleEndian>()?;
86        let thumb_size = (gcode_offset as usize).checked_sub(THUMB_OFFSET as usize)
87            .ok_or(Error::ThumbSizeNegative(gcode_offset as i32 - THUMB_OFFSET as i32))?;
88
89
90        let gcode_offset2 = source.read_u32::<LittleEndian>()?;
91        if gcode_offset != gcode_offset2 { return Err(Error::SecondGOffsetNotFound)}
92
93        let print_time = source.read_u32::<LittleEndian>()?;
94        let filament_0_usage = source.read_u32::<LittleEndian>()?;
95        let filament_1_usage = source.read_u32::<LittleEndian>()?;
96        let multi_extruder_type = source.read_u16::<LittleEndian>()?;
97        let layer_height = source.read_u16::<LittleEndian>()?;
98        let reserved0 = source.read_u16::<LittleEndian>()?;
99        let perimeter_shells = source.read_u16::<LittleEndian>()?;
100        let print_speed = source.read_u16::<LittleEndian>()?;
101        let hotbed_temp = source.read_u16::<LittleEndian>()?;
102        let extruder_0_temp = source.read_u16::<LittleEndian>()?;
103        let extruder_1_temp = source.read_u16::<LittleEndian>()?;
104        let reserved1 = source.read_u16::<LittleEndian>()?;
105
106        let header = Header { print_time, filament_0_usage, filament_1_usage, multi_extruder_type, layer_height, perimeter_shells, print_speed, hotbed_temp, extruder_0_temp, extruder_1_temp, reserved0, reserved1 };
107
108        let mut thumbnail = vec![0; thumb_size];
109        source.read_exact(&mut thumbnail)?;
110
111        let mut gcode = vec![];
112        source.read_to_end(&mut gcode)?;
113
114        Ok(XGCode{ header, thumbnail, gcode })
115
116    }
117
118    pub fn as_ref(&self) -> XGCodeRef {
119        XGCodeRef { header: self.header, thumbnail: &self.thumbnail[..], gcode: &self.gcode[..] }
120    }
121
122    pub fn write<W: Write>(&self, writer: W) -> Result<(), Error> {
123        self.as_ref().write(writer)
124
125
126    }
127
128}
129
130impl<'a> XGCodeRef<'a> {
131    fn write<W: Write>(&self, mut writer: W) -> Result<(), Error> {
132
133        let gcode_offset = THUMB_OFFSET as usize + self.thumbnail.len();
134        if gcode_offset > (u32::MAX as usize) { return Err(Error::ThumbnailTooLarge(self.thumbnail.len()))}
135
136        writer.write_all(XGCODE_MAGIC)?;
137        writer.write_u32::<LittleEndian>(THUMB_OFFSET)?;
138        writer.write_u32::<LittleEndian>(gcode_offset as u32)?;
139        writer.write_u32::<LittleEndian>(gcode_offset as u32)?;  // Yes, there is a second field
140
141        writer.write_u32::<LittleEndian>(self.header.print_time)?;
142        writer.write_u32::<LittleEndian>(self.header.filament_0_usage)?;
143        writer.write_u32::<LittleEndian>(self.header.filament_1_usage)?;
144        writer.write_u16::<LittleEndian>(self.header.multi_extruder_type)?;
145        writer.write_u16::<LittleEndian>(self.header.layer_height)?;
146        writer.write_u16::<LittleEndian>(self.header.reserved0)?;
147        writer.write_u16::<LittleEndian>(self.header.perimeter_shells)?;
148        writer.write_u16::<LittleEndian>(self.header.print_speed)?;
149        writer.write_u16::<LittleEndian>(self.header.hotbed_temp)?;
150        writer.write_u16::<LittleEndian>(self.header.extruder_0_temp)?;
151        writer.write_u16::<LittleEndian>(self.header.extruder_1_temp)?;
152        writer.write_u16::<LittleEndian>(self.header.reserved1)?;
153
154        writer.write_all(self.thumbnail)?;
155        writer.write_all(self.gcode)?;
156
157        Ok(())
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    
164
165    use crate::XGCode;
166
167    #[test]
168    fn test_sample_file() {
169        let file = include_bytes!("../test/20mm_Box.gx");
170        let parsed = XGCode::read(&mut &file[..]).unwrap();
171
172        // Check .bmp magic in thumbnail
173        assert_eq!(&parsed.thumbnail[..2], b"BM");
174
175        let mut file2 = vec![];
176        parsed.write(&mut file2).unwrap();
177
178
179        assert_eq!(file, &file2[..]);
180
181    }
182
183}