use super::slice_summary::{SliceSummary, SourceFormat};
use super::{ParseError, SliceParser, gcode_metadata};
pub struct Gcode;
impl SliceParser for Gcode {
fn detect(bytes: &[u8], filename: &str) -> bool {
if filename.to_ascii_lowercase().ends_with(".gcode") {
return true;
}
looks_like_plain_gcode(bytes)
}
fn parse(bytes: &[u8]) -> Result<SliceSummary, ParseError> {
let text = std::str::from_utf8(bytes).map_err(|_utf8_err| ParseError::NotThisFormat)?;
let comment_lines = text
.lines()
.filter_map(|line| line.trim_start().strip_prefix(';'));
let metadata = gcode_metadata::parse_lines(comment_lines);
gcode_metadata::build_slice_summary(&metadata, SourceFormat::Gcode)
}
}
fn looks_like_plain_gcode(bytes: &[u8]) -> bool {
!bytes.starts_with(b"PK\x03\x04")
&& !bytes.starts_with(b"GCDE")
&& std::str::from_utf8(bytes).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
fn read_test_file(name: &str) -> Vec<u8> {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../Testfiles")
.join(name);
std::fs::read(path).expect("test fixture missing")
}
#[test]
fn detects_by_extension() {
assert!(Gcode::detect(b"", "widget.gcode"));
assert!(!Gcode::detect(
b"PK\x03\x04not actually gcode",
"widget.sl1"
));
assert!(!Gcode::detect(
b"GCDE not actually gcode either",
"widget.bgcode"
));
}
#[test]
fn parses_real_fixture() {
let bytes = read_test_file("Cube_0.2mm_PLA_Prusa MINI_20m7s.gcode");
assert!(Gcode::detect(
&bytes,
"Cube_0.2mm_PLA_Prusa MINI_20m7s.gcode"
));
let summary = Gcode::parse(&bytes).unwrap();
assert_eq!(summary.source_format, SourceFormat::Gcode);
assert_eq!(summary.plates.len(), 1);
let plate = &summary.plates[0];
assert_eq!(plate.plate_index, 1);
assert!((plate.print_time_seconds - 1207.0).abs() < 1e-9);
assert_eq!(plate.printer_model.as_deref(), Some("Prusa MINI"));
assert_eq!(plate.layer_height, Some(0.2));
assert_eq!(plate.filament_usage.len(), 1);
let usage = &plate.filament_usage[0];
assert_eq!(usage.slot_id, "1");
assert!((usage.grams.unwrap() - 3.10).abs() < 1e-9);
assert_eq!(usage.ml, None);
assert_eq!(usage.material_type.as_deref(), Some("PLA"));
}
#[test]
fn reads_profile_names_and_prices_from_the_config_block() {
let bytes = read_test_file("Cube_0.2mm_PLA_Prusa MINI_20m7s.gcode");
let summary = Gcode::parse(&bytes).unwrap();
assert_eq!(
summary.meta.printer_profile.as_deref(),
Some("Prusa MINI 0.4 nozzle")
);
assert_eq!(
summary.meta.print_profile.as_deref(),
Some("0.20mm Standard @MINI")
);
assert_eq!(summary.meta.nozzle_diameter, Some(0.4));
assert_eq!(summary.meta.printer_variant.as_deref(), Some("0.4"));
assert_eq!(summary.meta.slicer.as_deref(), Some("OrcaSlicer 2.4.2"));
let usage = &summary.plates[0].filament_usage[0];
assert_eq!(usage.profile_name.as_deref(), Some("Generic PLA @System"));
assert_eq!(usage.vendor.as_deref(), Some("Generic"));
assert_eq!(usage.price, Some(20.0));
assert_eq!(usage.density, Some(1.24));
assert_eq!(usage.color.as_deref(), Some("#26A69A"));
assert!((usage.meters.unwrap() - 1.03974).abs() < 1e-9);
}
#[test]
fn parses_core_one_idex_single_extruder_fixture() {
let bytes = read_test_file("Shape-Box_0.4n_0.2mm_PLA_COREONEINDX_19m.gcode");
let summary = Gcode::parse(&bytes).unwrap();
let plate = &summary.plates[0];
assert_eq!(plate.printer_model.as_deref(), Some("COREONE_INDX8T"));
assert_eq!(plate.filament_usage.len(), 1);
let usage = &plate.filament_usage[0];
assert_eq!(usage.slot_id, "0");
assert!((usage.grams.unwrap() - 2.82).abs() < 1e-9);
assert_eq!(usage.material_type.as_deref(), Some("PLA"));
assert_eq!(usage.color.as_deref(), Some("#FF8000"));
}
#[test]
fn parses_core_one_idex_dual_extruder_fixture() {
let bytes = read_test_file("Shape-Box_0.4n_0.2mm_PLA,PLA_COREONEINDX_19m.gcode");
let summary = Gcode::parse(&bytes).unwrap();
let plate = &summary.plates[0];
assert_eq!(plate.filament_usage.len(), 2);
assert_eq!(plate.filament_usage[0].slot_id, "0");
assert!((plate.filament_usage[0].grams.unwrap() - 1.38).abs() < 1e-9);
assert_eq!(plate.filament_usage[1].slot_id, "1");
assert!((plate.filament_usage[1].grams.unwrap() - 1.47).abs() < 1e-9);
for u in &plate.filament_usage {
assert_eq!(
u.profile_name.as_deref(),
Some("Generic PLA Silk @COREONEINDX HF0.4")
);
assert_eq!(u.price, Some(25.4));
}
}
}