diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8972208..cb452cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -194,7 +194,7 @@ jobs: path: ${{github.workspace}}/build/dist/lib/wasm/ build-msvc: - runs-on: windows-2019 + runs-on: windows-2022 strategy: matrix: diff --git a/example/bginfo.py b/example/bginfo.py new file mode 100755 index 0000000..f4fb564 --- /dev/null +++ b/example/bginfo.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +import pybgcode as bg + +def dump_info(fn): + fp = bg.open(fn, 'r') + file_header = bg.FileHeader() + file_header.read(fp) + + block_header = bg.BlockHeader() + + + res = bg.read_next_block_header(fp, file_header, block_header) + TYPES = dict(enumerate([ + 'FileMetadata', + 'GCode', + 'SlicerMetadata', + 'PrinterMetadata', + 'PrintMetadata', + 'Thumbnail' + ])) + while res == bg.EResult.Success: + print(f'Block type: {TYPES.get(block_header.type, block_header.type)} size {block_header.compressed_size} / {block_header.uncompressed_size}') + cls = None + if block_header.type == bg.EBlockType.FileMetadata.value: + cls = bg.FileMetadataBlock + elif block_header.type == bg.EBlockType.PrinterMetadata.value: + cls = bg.PrinterMetadataBlock + elif block_header.type == bg.EBlockType.PrintMetadata.value: + cls = bg.PrintMetadataBlock + # elif block_header.type == bg.EBlockType.Thumbnail.value: + # cls = bg.ThumbnailBlock + elif block_header.type == bg.EBlockType.SlicerMetadata.value: + tp = bg.peek_slicer_metadata_block(fp, block_header) + if tp == bg.EPeekSlicerMetadataResult.SlicerMetadataFound: + cls = bg.SlicerMetadataBlock + elif tp == bg.EPeekSlicerMetadataResult.Slicer3MetadataFound: + cls = bg.Slicer3MetadataBlock + if cls is not None: + metadata = cls() + metadata.read_data(fp, file_header, block_header) + for k, v in metadata.raw_data: + w = v if len(v) < 120 else f'... ({len(v)} bytes)' + print(f' {k}: {w}') + else: + bg.skip_block_content(fp, file_header, block_header) + res = bg.read_next_block_header(fp, file_header, block_header) + + + +if __name__ == '__main__': + import sys + for fn in sys.argv[1:]: + dump_info(fn) diff --git a/pybgcode/pybgcode.cpp b/pybgcode/pybgcode.cpp index ac8f2d7..f44a1bf 100644 --- a/pybgcode/pybgcode.cpp +++ b/pybgcode/pybgcode.cpp @@ -466,6 +466,26 @@ PYBIND11_MODULE(MODULE_NAME, m) { return self.read_data(*file.fptr, file_header, block_header); }, R"pbdoc(read block data)pbdoc", py::arg("file"), py::arg("file_header"), py::arg("block_header")); + py::class_(m, "Slicer3MetadataBlock") + .def(py::init<>()) + .def("write", [](binarize::Slicer3MetadataBlock &self, FILEWrapper &file, core::ECompressionType compression_type, core::EChecksumType checksum_type){ + return self.write(*file.fptr, compression_type, checksum_type); + }, R"pbdoc(write block header and data)pbdoc", py::arg("file"), py::arg("compression_type"), py::arg("checksum_type")) + .def("read_data", [](binarize::Slicer3MetadataBlock &self, FILEWrapper &file, const core::FileHeader &file_header, const core::BlockHeader& block_header) { + return self.read_data(*file.fptr, file_header, block_header); + }, R"pbdoc(read block data)pbdoc", py::arg("file"), py::arg("file_header"), py::arg("block_header")); + + py::enum_(m, "EPeekSlicerMetadataResult") + .value("Slicer3MetadataFound", binarize::EPeekSlicerMetadataResult::Slicer3MetadataFound) + .value("SlicerMetadataFound", binarize::EPeekSlicerMetadataResult::SlicerMetadataFound) + .value("OtherBlockFound", binarize::EPeekSlicerMetadataResult::OtherBlockFound) + .value("ReadError", binarize::EPeekSlicerMetadataResult::ReadError) + ; + + m.def("peek_slicer_metadata_block", [](FILEWrapper& file, const core::BlockHeader& block_header) { + return binarize::peek_slicer_metadata_block(*file.fptr, block_header); + }); + py::class_(m, "BinarizerCompression") .def(py::init<>()) .def_readwrite("file_metadata", &binarize::BinarizerConfig::Compression::file_metadata) @@ -487,6 +507,7 @@ PYBIND11_MODULE(MODULE_NAME, m) { .def_readwrite("printer_metadata", &binarize::BinaryData::printer_metadata) .def_readwrite("thumbnails", &binarize::BinaryData::thumbnails) .def_readwrite("slicer_metadata", &binarize::BinaryData::slicer_metadata) + .def_readwrite("slicer3_metadata", &binarize::BinaryData::slicer3_metadata) .def_readwrite("printer_metadata", &binarize::BinaryData::printer_metadata); py::class_(m, "Binarizer") diff --git a/pybgcode/pybgcode/__init__.py b/pybgcode/pybgcode/__init__.py index 3e7120d..7822745 100644 --- a/pybgcode/pybgcode/__init__.py +++ b/pybgcode/pybgcode/__init__.py @@ -7,11 +7,13 @@ EBlockType, EResult, EThumbnailFormat, + EPeekSlicerMetadataResult, FileHeader, FILEWrapper, PrintMetadataBlock, PrinterMetadataBlock, SlicerMetadataBlock, + Slicer3MetadataBlock, FileMetadataBlock, ThumbnailBlock, close, @@ -24,7 +26,10 @@ read_header, read_next_block_header, rewind, + skip_block, + skip_block_content, translate_result, + peek_slicer_metadata_block, version, ) @@ -36,6 +41,7 @@ "EBlockType", "EResult", "EThumbnailFormat", + "EPeekSlicerMetadataResult", "FileHeader", "FileMetadataBlock", "PrintMetadataBlock", @@ -47,9 +53,12 @@ "get_config", "is_open", "open", + "peek_slicer_metadata_block", "read_header", "read_next_block_header", "rewind", + "skip_block", + "skip_block_content", "translate_result"] diff --git a/pyproject.toml b/pyproject.toml index 1a93b3b..0487800 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pybgcode" -version = "0.2.0" +version = "0.3.0" description = "Prusa Block & Binary G-code reader / writer / converter" authors = [ { name = "Enrico Turri" }, { name = "Tomas Meszaros" } diff --git a/src/LibBGCode/binarize/binarize.cpp b/src/LibBGCode/binarize/binarize.cpp index 4781ed6..cb1b2ba 100644 --- a/src/LibBGCode/binarize/binarize.cpp +++ b/src/LibBGCode/binarize/binarize.cpp @@ -49,25 +49,34 @@ static std::vector encode(const std::byte* data, size_t data_size) return ret; } -static uint16_t metadata_encoding_types_count() { return 1 + (uint16_t)EMetadataEncodingType::INI; } +static uint16_t metadata_encoding_types_count() { return 1 + (uint16_t)EMetadataEncodingType::JSON; } static uint16_t thumbnail_formats_count() { return 1 + (uint16_t)EThumbnailFormat::QOI; } static uint16_t gcode_encoding_types_count() { return 1 + (uint16_t)EGCodeEncodingType::MeatPackComments; } static bool encode_metadata(const std::vector>& src, std::vector& dst, EMetadataEncodingType encoding_type) { - for (const auto& [key, value] : src) { - switch (encoding_type) - { - case EMetadataEncodingType::INI: - { + + switch (encoding_type) + { + case EMetadataEncodingType::INI: + { + for (const auto& [key, value] : src) { dst.insert(dst.end(), key.begin(), key.end()); dst.emplace_back('='); dst.insert(dst.end(), value.begin(), value.end()); dst.emplace_back('\n'); - break; - } } + break; + } + case EMetadataEncodingType::JSON: + { + if (src.size() != 1) + return false; + const auto& [k, v] = src.at(0); + dst.insert(dst.end(), v.begin(), v.end()); + break; + } } return true; } @@ -88,12 +97,19 @@ static bool decode_metadata(const std::vector& src, std::vector>( + core::EMetadataEncodingType::JSON); + } + + void set_json(std::string_view); + const std::string& json() const; + + // write block header and data + core::EResult write(FILE& file, core::ECompressionType compression_type, core::EChecksumType checksum_type) const; + // read block data + core::EResult read_data(FILE& file, const core::FileHeader& file_header, const core::BlockHeader& block_header); +}; + +enum class EPeekSlicerMetadataResult { + Slicer3MetadataFound, + SlicerMetadataFound, + OtherBlockFound, + ReadError +}; + +// Peek the block content (just metadata extra "header") and decide what kind of block follows +extern BGCODE_BINARIZE_EXPORT EPeekSlicerMetadataResult peek_slicer_metadata_block(FILE& file, const core::BlockHeader& block_header); + + struct BinarizerConfig { struct Compression @@ -80,6 +107,7 @@ struct BinarizerConfig core::ECompressionType print_metadata{ core::ECompressionType::None }; core::ECompressionType slicer_metadata{ core::ECompressionType::None }; core::ECompressionType gcode{ core::ECompressionType::None }; + core::ECompressionType slicer3_metadata{ core::ECompressionType::None }; }; Compression compression; core::EGCodeEncodingType gcode_encoding{ core::EGCodeEncodingType::None }; @@ -93,6 +121,7 @@ struct BGCODE_BINARIZE_EXPORT BinaryData PrinterMetadataBlock printer_metadata; std::vector thumbnails; SlicerMetadataBlock slicer_metadata; + Slicer3MetadataBlock slicer3_metadata; PrintMetadataBlock print_metadata; }; diff --git a/src/LibBGCode/convert/convert.cpp b/src/LibBGCode/convert/convert.cpp index 0d552b3..ca4b104 100644 --- a/src/LibBGCode/convert/convert.cpp +++ b/src/LibBGCode/convert/convert.cpp @@ -221,6 +221,7 @@ BGCODE_CONVERT_EXPORT EResult from_ascii_to_binary(FILE& src_file, FILE& dst_fil static constexpr const std::string_view ThumbnailQOIEnd = "thumbnail_QOI end"sv; static constexpr const std::string_view PrusaSlicerConfig = "prusaslicer_config"sv; + static constexpr const std::string_view PrusaSlicerConfigJson = "prusaslicer_json_config"sv; auto search_metadata_value = [&](const std::string_view& str, const std::string_view& key) { std::string ret; @@ -281,6 +282,8 @@ BGCODE_CONVERT_EXPORT EResult from_ascii_to_binary(FILE& src_file, FILE& dst_fil size_t curr_thumbnail_data_size = 0; size_t curr_thumbnail_data_loaded = 0; + std::optional slicer_json; + bool producer_found = false; bool reading_config = false; @@ -507,6 +510,26 @@ BGCODE_CONVERT_EXPORT EResult from_ascii_to_binary(FILE& src_file, FILE& dst_fil } } + if (!slicer_json.has_value()) { + if (search_metadata_value(sv_line, PrusaSlicerConfigJson) == "begin") { + slicer_json = ""; + processed_lines.emplace_back(lines_counter++); + return; + } + } + else { + if (search_metadata_value(sv_line, PrusaSlicerConfigJson) == "end") { + binary_data.slicer3_metadata.set_json(slicer_json.value()); + processed_lines.emplace_back(lines_counter++); + return; + } + else { + slicer_json.value() += sv_line; + processed_lines.emplace_back(lines_counter++); + return; + } + } + ++lines_counter; })) return EResult::ReadError; @@ -811,25 +834,67 @@ BGCODE_CONVERT_EXPORT EResult from_binary_to_ascii(FILE& src_file, FILE& dst_fil return EResult::WriteError; // - // convert slicer metadata block + // convert slicer metadata blocks // - res = read_next_block_header(src_file, file_header, block_header, checksum_buffer.data(), checksum_buffer.size()); - if (res != EResult::Success) - // propagate error - return res; - if ((EBlockType)block_header.type != EBlockType::SlicerMetadata) - return EResult::InvalidSequenceOfBlocks; - SlicerMetadataBlock slicer_metadata_block; - res = slicer_metadata_block.read_data(src_file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - if (!write_line("\n; prusaslicer_config = begin\n")) - return EResult::WriteError; - if (!write_metadata(slicer_metadata_block.raw_data)) - return EResult::WriteError; - if (!write_line("; prusaslicer_config = end\n\n")) - return EResult::WriteError; + std::optional slicer_legacy_metadata_block; + std::optional slicer_metadata_block; + + for (size_t i = 0; i < 2; i++) { + res = read_next_block_header(src_file, file_header, block_header, checksum_buffer.data(), checksum_buffer.size()); + if (res != EResult::Success) + // propagate error + return res; + // at least one slicer metadata block is required + if ((EBlockType)block_header.type != EBlockType::SlicerMetadata && i == 0) { + return EResult::InvalidSequenceOfBlocks; + } + auto peek_result = peek_slicer_metadata_block(src_file, block_header); + switch (peek_result) { + case EPeekSlicerMetadataResult::ReadError: + return EResult::ReadError; + + case EPeekSlicerMetadataResult::Slicer3MetadataFound: + { + Slicer3MetadataBlock block; + res = block.read_data(src_file, file_header, block_header); + slicer_metadata_block = std::move(block); + + break; + } + + case EPeekSlicerMetadataResult::SlicerMetadataFound: + { + SlicerMetadataBlock block; + res = block.read_data(src_file, file_header, block_header); + slicer_legacy_metadata_block = std::move(block); + break; + } + + case EPeekSlicerMetadataResult::OtherBlockFound: + break;; + + } + + if (res != EResult::Success) + // propagate error + return res; + } + if (slicer_metadata_block.has_value()) { + if (!write_line("\n; prusaslicer_json_config = begin\n")) + return EResult::WriteError; + if (!write_line("; " + slicer_metadata_block->json() + "\n")) + return EResult::WriteError; + if (!write_line("; prusaslicer_json_config = end\n")) + return EResult::WriteError; + } + if (slicer_legacy_metadata_block.has_value()) { + if (!write_line("\n; prusaslicer_config = begin\n")) + return EResult::WriteError; + if (!write_metadata(slicer_legacy_metadata_block->raw_data)) + return EResult::WriteError; + if (!write_line("; prusaslicer_config = end\n\n")) + return EResult::WriteError; + } return EResult::Success; } diff --git a/src/LibBGCode/core/core.cpp b/src/LibBGCode/core/core.cpp index 1080cfb..12d78b6 100644 --- a/src/LibBGCode/core/core.cpp +++ b/src/LibBGCode/core/core.cpp @@ -367,25 +367,27 @@ BGCODE_CORE_EXPORT EResult is_valid_binary_gcode(FILE& file, bool check_contents return EResult::InvalidBlockType; } - // read slicer metadata block header - res = skip_block(file, file_header, block_header); - if (res != EResult::Success) { - // restore file position - fseek(&file, curr_pos, SEEK_SET); - // propagate error - return res; - } - res = read_next_block_header(file, file_header, block_header, cs_buffer, cs_buffer_size); - if (res != EResult::Success) { - // restore file position - fseek(&file, curr_pos, SEEK_SET); - // propagate error - return res; - } - if ((EBlockType)block_header.type != EBlockType::SlicerMetadata) { - // restore file position - fseek(&file, curr_pos, SEEK_SET); - return EResult::InvalidBlockType; + for (size_t i = 0; i < 2; i++) { + // read slicer metadata block header + res = skip_block(file, file_header, block_header); + if (res != EResult::Success) { + // restore file position + fseek(&file, curr_pos, SEEK_SET); + // propagate error + return res; + } + res = read_next_block_header(file, file_header, block_header, cs_buffer, cs_buffer_size); + if (res != EResult::Success) { + // restore file position + fseek(&file, curr_pos, SEEK_SET); + // propagate error + return res; + } + if ((EBlockType)block_header.type != EBlockType::SlicerMetadata && i == 0) { + // restore file position + fseek(&file, curr_pos, SEEK_SET); + return EResult::InvalidBlockType; + } } // read gcode block headers diff --git a/src/LibBGCode/core/core.hpp b/src/LibBGCode/core/core.hpp index a942e41..de14045 100644 --- a/src/LibBGCode/core/core.hpp +++ b/src/LibBGCode/core/core.hpp @@ -74,7 +74,8 @@ enum class ECompressionType : uint16_t enum class EMetadataEncodingType : uint16_t { - INI + INI, + JSON }; enum class EGCodeEncodingType : uint16_t diff --git a/tests/core/core_tests.cpp b/tests/core/core_tests.cpp index d105863..91cbf66 100644 --- a/tests/core/core_tests.cpp +++ b/tests/core/core_tests.cpp @@ -58,6 +58,7 @@ static std::string metadata_encoding_as_string(EMetadataEncodingType type) switch (type) { case EMetadataEncodingType::INI: { return "INI"; } + case EMetadataEncodingType::JSON: { return "JSON"; } } return ""; };