diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index c1dcd961cf..f11986d314 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -164,8 +164,6 @@ set(SLIC3R_SOURCES GCode/WipeTower.hpp GCode/GCodeProcessor.cpp GCode/GCodeProcessor.hpp - GCode/GCodeBinarizer.cpp - GCode/GCodeBinarizer.hpp GCode/AvoidCrossingPerimeters.cpp GCode/AvoidCrossingPerimeters.hpp GCode.cpp diff --git a/src/libslic3r/GCode/GCodeBinarizer.cpp b/src/libslic3r/GCode/GCodeBinarizer.cpp deleted file mode 100644 index d1e2368863..0000000000 --- a/src/libslic3r/GCode/GCodeBinarizer.cpp +++ /dev/null @@ -1,1976 +0,0 @@ -#include "GCodeBinarizer.hpp" - -#if ENABLE_BINARIZED_GCODE_DEBUG -#define NOMINMAX -#include -#include -#endif // ENABLE_BINARIZED_GCODE_DEBUG - -#if ENABLE_FILE_CONVERSION_INTERFACE -#include -#endif // ENABLE_FILE_CONVERSION_INTERFACE - -extern "C" { -#include -#include -} -#include - -#include -#include - -namespace bgcode { - -static size_t g_checksum_max_cache_size = 65536; -static constexpr const size_t MAX_GCODE_CACHE_SIZE = 65536; - -namespace MeatPack { -static constexpr const uint8_t Command_None{ 0 }; -//#Command_TogglePacking = 253 -- Currently unused, byte 253 can be reused later. -static constexpr const uint8_t Command_EnablePacking{ 251 }; -static constexpr const uint8_t Command_DisablePacking{ 250 }; -static constexpr const uint8_t Command_ResetAll{ 249 }; -static constexpr const uint8_t Command_QueryConfig{ 248 }; -static constexpr const uint8_t Command_EnableNoSpaces{ 247 }; -static constexpr const uint8_t Command_DisableNoSpaces{ 246 }; -static constexpr const uint8_t Command_SignalByte{ 0xFF }; - -static constexpr const uint8_t Flag_OmitWhitespaces{ 0x01 }; -static constexpr const uint8_t Flag_RemoveComments{ 0x02 }; - -static constexpr const uint8_t BothUnpackable{ 0b11111111 }; -static constexpr const char SpaceReplacedCharacter{ 'E' }; - -static const std::unordered_map ReverseLookupTbl = { - { '0', 0b00000000 }, - { '1', 0b00000001 }, - { '2', 0b00000010 }, - { '3', 0b00000011 }, - { '4', 0b00000100 }, - { '5', 0b00000101 }, - { '6', 0b00000110 }, - { '7', 0b00000111 }, - { '8', 0b00001000 }, - { '9', 0b00001001 }, - { '.', 0b00001010 }, - { ' ', 0b00001011 }, - { '\n', 0b00001100 }, - { 'G', 0b00001101 }, - { 'X', 0b00001110 }, - { '\0', 0b00001111 } // never used, 0b1111 is used to indicate the next 8-bits is a full character -}; - -class MPBinarizer -{ -public: - explicit MPBinarizer(uint8_t flags = 0) : m_flags(flags) {} - - void initialize(std::vector& dst) { - initialize_lookup_tables(); - append_command(Command_EnablePacking, dst); - m_binarizing = true; - } - - void finalize(std::vector& dst) { - if ((m_flags & Flag_RemoveComments) != 0) { - assert(m_binarizing); - append_command(Command_ResetAll, dst); - m_binarizing = false; - } - } - - void binarize_line(const std::string& line, std::vector& dst) { - auto unified_method = [this](const std::string& line) { - const std::string::size_type g_idx = line.find('G'); - if (g_idx != std::string::npos) { - if (g_idx + 1 < line.size() && line[g_idx + 1] >= '0' && line[g_idx + 1] <= '9') { - if ((m_flags & Flag_OmitWhitespaces) != 0) { - std::string result = line; - std::replace(result.begin(), result.end(), 'e', 'E'); - std::replace(result.begin(), result.end(), 'x', 'X'); - std::replace(result.begin(), result.end(), 'g', 'G'); - result.erase(std::remove(result.begin(), result.end(), ' '), result.end()); - if (result.find('*') != std::string::npos) { - size_t checksum = 0; - result.erase(std::remove(result.begin(), result.end(), '*'), result.end()); - for (size_t i = 0; i < result.size(); ++i) { - checksum ^= static_cast(result[i]); - } - result += "*" + std::to_string(checksum); - } - result += '\n'; - return result; - } - else { - std::string result = line; - std::replace(result.begin(), result.end(), 'x', 'X'); - std::replace(result.begin(), result.end(), 'g', 'G'); - result.erase(std::remove(result.begin(), result.end(), ' '), result.end()); - if (result.find('*') != std::string::npos) { - size_t checksum = 0; - result.erase(std::remove(result.begin(), result.end(), '*'), result.end()); - for (size_t i = 0; i < result.size(); ++i) { - checksum ^= static_cast(result[i]); - } - result += "*" + std::to_string(checksum); - } - result += '\n'; - return result; - } - } - } - return line; - }; - auto is_packable = [](char c) { - return (s_lookup_tables.packable[static_cast(c)] != 0); - }; - auto pack_chars = [](char low, char high) { - return (((s_lookup_tables.value[static_cast(high)] & 0xF) << 4) | - (s_lookup_tables.value[static_cast(low)] & 0xF)); - }; - - if (!line.empty()) { - if ((m_flags & Flag_RemoveComments) == 0) { - if (line[0] == ';') { - if (m_binarizing) { - append_command(Command_DisablePacking, dst); - m_binarizing = false; - } - - dst.insert(dst.end(), line.begin(), line.end()); - return; - } - } - - if (line[0] == ';' || - line[0] == '\n' || - line[0] == '\r' || - line.size() < 2) - return; - - std::string modifiedLine = line.substr(0, line.find(';')); - if (modifiedLine.empty()) - return; - auto trim_right = [](const std::string& str) { - if (str.back() != ' ') - return str; - auto bit = str.rbegin(); - while (bit != str.rend() && *bit == ' ') { - ++bit; - } - return str.substr(0, std::distance(str.begin(), bit.base())); - }; - modifiedLine = trim_right(modifiedLine); - modifiedLine = unified_method(modifiedLine); - if (modifiedLine.back() != '\n') - modifiedLine.push_back('\n'); - const size_t line_len = modifiedLine.size(); - std::vector temp_buffer; - temp_buffer.reserve(line_len); - - for (size_t line_idx = 0; line_idx < line_len; line_idx += 2) { - const bool skip_last = line_idx == (line_len - 1); - const char char_1 = modifiedLine[line_idx]; - const char char_2 = (skip_last ? '\n' : modifiedLine[line_idx + 1]); - const bool c1_p = is_packable(char_1); - const bool c2_p = is_packable(char_2); - - if (c1_p) { - if (c2_p) - temp_buffer.emplace_back(static_cast(pack_chars(char_1, char_2))); - else { - temp_buffer.emplace_back(static_cast(pack_chars(char_1, '\0'))); - temp_buffer.emplace_back(static_cast(char_2)); - } - } - else { - if (c2_p) { - temp_buffer.emplace_back(static_cast(pack_chars('\0', char_2))); - temp_buffer.emplace_back(static_cast(char_1)); - } - else { - temp_buffer.emplace_back(static_cast(BothUnpackable)); - temp_buffer.emplace_back(static_cast(char_1)); - temp_buffer.emplace_back(static_cast(char_2)); - } - } - } - - if (!m_binarizing && !temp_buffer.empty()) { - append_command(Command_EnablePacking, dst); - m_binarizing = true; - } - - dst.insert(dst.end(), temp_buffer.begin(), temp_buffer.end()); - } - } - -private: - unsigned char m_flags{ 0 }; - bool m_binarizing{ false }; - - struct LookupTables - { - std::array packable; - std::array value; - bool initialized; - unsigned char flags; - }; - - static LookupTables s_lookup_tables; - - void append_command(unsigned char cmd, std::vector& dst) { - dst.emplace_back(Command_SignalByte); - dst.emplace_back(Command_SignalByte); - dst.emplace_back(cmd); - } - - void initialize_lookup_tables() { - if (s_lookup_tables.initialized && m_flags == s_lookup_tables.flags) - return; - - for (const auto& [c, value] : ReverseLookupTbl) { - const int index = static_cast(c); - s_lookup_tables.packable[index] = 1; - s_lookup_tables.value[index] = value; - } - - if ((m_flags & Flag_OmitWhitespaces) != 0) { - s_lookup_tables.value[static_cast(SpaceReplacedCharacter)] = ReverseLookupTbl.at(' '); - s_lookup_tables.packable[static_cast(SpaceReplacedCharacter)] = 1; - s_lookup_tables.packable[static_cast(' ')] = 0; - } - else { - s_lookup_tables.packable[static_cast(SpaceReplacedCharacter)] = 0; - s_lookup_tables.packable[static_cast(' ')] = 1; - } - - s_lookup_tables.initialized = true; - s_lookup_tables.flags = m_flags; - } -}; - -MPBinarizer::LookupTables MPBinarizer::s_lookup_tables = { { 0 }, { 0 }, false, 0 }; - -static constexpr const unsigned char SecondNotPacked{ 0b11110000 }; -static constexpr const unsigned char FirstNotPacked{ 0b00001111 }; -static constexpr const unsigned char NextPackedFirst{ 0b00000001 }; -static constexpr const unsigned char NextPackedSecond{ 0b00000010 }; - -// See for reference: https://github.com/scottmudge/Prusa-Firmware-MeatPack/blob/MK3_sm_MeatPack/Firmware/meatpack.cpp -static void unbinarize(const std::vector& src, std::string& dst) { - bool unbinarizing = false; - bool cmd_active = false; // Is a command pending - uint8_t char_buf = 0; // Buffers a character if dealing with out-of-sequence pairs - size_t cmd_count = 0; // Counts how many command bytes are received (need 2) - size_t full_char_queue = 0; // Counts how many full-width characters are to be received - std::array char_out_buf; // Output buffer for caching up to 2 characters - size_t char_out_count = 0; // Stores number of characters to be read out - - auto handle_command = [&](uint8_t c) { - switch (c) - { - case Command_EnablePacking: { unbinarizing = true; break; } - case Command_DisablePacking: { unbinarizing = false; break; } - case Command_ResetAll: { unbinarizing = false; break; } - default: - case Command_QueryConfig: { break; } - } - }; - - auto handle_output_char = [&](uint8_t c) { - char_out_buf[char_out_count++] = c; - }; - - auto get_char = [](uint8_t c) { - switch (c) - { - case 0b0000: { return '0'; } - case 0b0001: { return '1'; } - case 0b0010: { return '2'; } - case 0b0011: { return '3'; } - case 0b0100: { return '4'; } - case 0b0101: { return '5'; } - case 0b0110: { return '6'; } - case 0b0111: { return '7'; } - case 0b1000: { return '8'; } - case 0b1001: { return '9'; } - case 0b1010: { return '.'; } - case 0b1011: { return 'E'; } - case 0b1100: { return '\n'; } - case 0b1101: { return 'G'; } - case 0b1110: { return 'X'; } - } - return '\0'; - }; - - auto unpack_chars = [&](uint8_t pk, std::array& chars_out) { - uint8_t out = 0; - - // If lower 4 bytes is 0b1111, the higher 4 are unused, and next char is full. - if ((pk & FirstNotPacked) == FirstNotPacked) - out |= NextPackedFirst; - else - chars_out[0] = get_char(pk & 0xF); // Assign lower char - - // Check if upper 4 bytes is 0b1111... if so, we don't need the second char. - if ((pk & SecondNotPacked) == SecondNotPacked) - out |= NextPackedSecond; - else - chars_out[1] = get_char((pk >> 4) & 0xF); // Assign upper char - - return out; - }; - - auto handle_rx_char = [&](uint8_t c) { - if (unbinarizing) { - if (full_char_queue > 0) { - handle_output_char(c); - if (char_buf > 0) { - handle_output_char(char_buf); - char_buf = 0; - } - --full_char_queue; - } - else { - std::array buf = { 0, 0 }; - const uint8_t res = unpack_chars(c, buf); - - if ((res & NextPackedFirst) != 0) { - ++full_char_queue; - if ((res & NextPackedSecond) != 0) - ++full_char_queue; - else - char_buf = buf[1]; - } - else { - handle_output_char(buf[0]); - if (buf[0] != '\n') { - if ((res & NextPackedSecond) != 0) - ++full_char_queue; - else - handle_output_char(buf[1]); - } - } - } - } - else // Packing not enabled, just copy character to output - handle_output_char(c); - }; - - auto get_result_char = [&](std::array& chars_out) { - if (char_out_count > 0) { - const size_t res = char_out_count; - for (uint8_t i = 0; i < char_out_count; ++i) { - chars_out[i] = (char)char_out_buf[i]; - } - char_out_count = 0; - return res; - } - return (size_t)0; - }; - - std::vector unbin_buffer(2 * src.size(), 0); - auto it_unbin_end = unbin_buffer.begin(); - -#if ENABLE_BINARIZED_GCODE_DEBUG - size_t line_start = 0; -#endif // ENABLE_BINARIZED_GCODE_DEBUG - bool add_space = false; - - auto begin = src.begin(); - auto end = src.end(); - - auto it_bin = begin; - while (it_bin != end) { - uint8_t c_bin = *it_bin; - if (c_bin == Command_SignalByte) { - if (cmd_count > 0) { - cmd_active = true; - cmd_count = 0; - } - else - ++cmd_count; - } - else { - if (cmd_active) { - handle_command(c_bin); - cmd_active = false; - } - else { - if (cmd_count > 0) { - handle_rx_char(Command_SignalByte); - cmd_count = 0; - } - - handle_rx_char(c_bin); - } - } - - auto is_gline_parameter = [](const char c) { - static const std::vector parameters = { - // G0, G1 - 'X', 'Y', 'Z', 'E', 'F', - // G2, G3 - 'I', 'J', 'R', - // G29 - 'P', 'W', 'H', 'C', 'A' - }; - return std::find(parameters.begin(), parameters.end(), c) != parameters.end(); - }; - - std::array c_unbin{ 0, 0 }; - const size_t char_count = get_result_char(c_unbin); - for (size_t i = 0; i < char_count; ++i) { - // GCodeReader::parse_line_internal() is unable to parse a G line where the data are not separated by spaces - // so we add them where needed - const size_t curr_unbin_buffer_length = std::distance(unbin_buffer.begin(), it_unbin_end); - if (c_unbin[i] == 'G' && (curr_unbin_buffer_length == 0 || *std::prev(it_unbin_end, 1) == '\n')) - add_space = true; - else if (c_unbin[i] == '\n') - add_space = false; - - if (add_space && (curr_unbin_buffer_length == 0 || *std::prev(it_unbin_end, 1) != ' ') && - is_gline_parameter(c_unbin[i])) { - *it_unbin_end = ' '; - ++it_unbin_end; - } - - if (c_unbin[i] != '\n' || std::distance(unbin_buffer.begin(), it_unbin_end) == 0 || *std::prev(it_unbin_end, 1) != '\n') { - *it_unbin_end = c_unbin[i]; - ++it_unbin_end; - } - -#if ENABLE_BINARIZED_GCODE_DEBUG - if (c_unbin[i] == '\n') { - const std::string out(unbin_buffer.begin() + line_start, it_unbin_end); - if (!out.empty()) { - OutputDebugStringA(out.c_str()); - line_start = std::distance(unbin_buffer.begin(), it_unbin_end); - } - } -#endif // ENABLE_BINARIZED_GCODE_DEBUG - } - - ++it_bin; - } - - dst.insert(dst.end(), unbin_buffer.begin(), it_unbin_end); -} -} // namespace MeatPack - -std::string translate_result(EResult result) -{ - switch (result) - { - case EResult::Success: { return "Success"; } - case EResult::ReadError: { return "Read error"; } - case EResult::WriteError: { return "Write error"; } - case EResult::InvalidMagicNumber: { return "Invalid magic number"; } - case EResult::InvalidVersionNumber: { return "Invalid version number"; } - case EResult::InvalidChecksumType: { return "Invalid checksum type"; } - case EResult::InvalidBlockType: { return "Invalid block type"; } - case EResult::InvalidCompressionType: { return "Invalid compression type"; } - case EResult::InvalidMetadataEncodingType: { return "Invalid metadata encoding type"; } - case EResult::InvalidGCodeEncodingType: { return "Invalid gcode encoding type"; } - case EResult::DataCompressionError: { return "Data compression error"; } - case EResult::DataUncompressionError: { return "Data uncompression error"; } - case EResult::MetadataEncodingError: { return "Data encoding error"; } - case EResult::MetadataDecodingError: { return "Data decoding error"; } - case EResult::GCodeEncodingError: { return "GCode encoding error"; } - case EResult::GCodeDecodingError: { return "GCode decoding error"; } - case EResult::BlockNotFound: { return "Block not found"; } - case EResult::InvalidChecksum: { return "Invalid checksum"; } - case EResult::InvalidThumbnailFormat: { return "Invalid thumbnail format"; } - case EResult::InvalidThumbnailWidth: { return "Invalid thumbnail width"; } - case EResult::InvalidThumbnailHeight: { return "Invalid thumbnail height"; } - case EResult::InvalidThumbnailDataSize: { return "Invalid thumbnail data size"; } - case EResult::InvalidBinaryGCodeFile: { return "Invalid binary GCode file"; } - case EResult::InvalidSequenceOfBlocks: { return "Invalid sequence of blocks"; } - } - return std::string(); -} - -size_t get_checksum_max_cache_size() { return g_checksum_max_cache_size; } -void set_checksum_max_cache_size(size_t size) { g_checksum_max_cache_size = size; } - -static uint16_t checksum_types_count() { return 1 + (uint16_t)EChecksumType::CRC32; } -static uint16_t block_types_count() { return 1 + (uint16_t)EBlockType::Thumbnail; } -static uint16_t compression_types_count() { return 1 + (uint16_t)ECompressionType::Heatshrink_12_4; } -static uint16_t thumbnail_formats_count() { return 1 + (uint16_t)EThumbnailFormat::QOI; } -static uint16_t metadata_encoding_types_count() { return 1 + (uint16_t)EMetadataEncodingType::INI; } -static uint16_t gcode_encoding_types_count() { return 1 + (uint16_t)EGCodeEncodingType::MeatPackComments; } - -static bool write_to_file(FILE& file, const void* data, size_t data_size) -{ - fwrite(data, 1, data_size, &file); - return !ferror(&file); -} - -static bool read_from_file(FILE& file, void* data, size_t data_size) -{ - fread(data, 1, data_size, &file); - return !ferror(&file); -} - -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: - { - dst.insert(dst.end(), key.begin(), key.end()); - dst.emplace_back('='); - dst.insert(dst.end(), value.begin(), value.end()); - dst.emplace_back('\n'); - break; - } - } - } - return true; -} - -static bool decode_metadata(const std::vector& src, std::vector>& dst, - EMetadataEncodingType encoding_type) -{ - switch (encoding_type) - { - case EMetadataEncodingType::INI: - { - auto begin_it = src.begin(); - auto end_it = src.begin(); - while (end_it != src.end()) { - while (end_it != src.end() && *end_it != '\n') { - ++end_it; - } - const std::string item(begin_it, end_it); - const size_t pos = item.find_first_of('='); - if (pos != std::string::npos) { - dst.emplace_back(std::make_pair(item.substr(0, pos), item.substr(pos + 1))); - begin_it = ++end_it; - } - } - break; - } - } - - return true; -} - -static bool encode_gcode(const std::string& src, std::vector& dst, EGCodeEncodingType encoding_type) -{ - switch (encoding_type) - { - case EGCodeEncodingType::None: - { - dst.insert(dst.end(), src.begin(), src.end()); - break; - } - case EGCodeEncodingType::MeatPack: - case EGCodeEncodingType::MeatPackComments: - { - uint8_t binarizer_flags = (encoding_type == EGCodeEncodingType::MeatPack) ? MeatPack::Flag_RemoveComments : 0; - binarizer_flags |= MeatPack::Flag_OmitWhitespaces; - MeatPack::MPBinarizer binarizer(binarizer_flags); - binarizer.initialize(dst); - auto begin_it = src.begin(); - auto end_it = src.begin(); - while (end_it != src.end()) { - while (end_it != src.end() && *end_it != '\n') { - ++end_it; - } - const std::string line(begin_it, ++end_it); - binarizer.binarize_line(line, dst); - begin_it = end_it; - } - binarizer.finalize(dst); - break; - } - } - return true; -} - -static bool decode_gcode(const std::vector& src, std::string& dst, EGCodeEncodingType encoding_type) -{ - switch (encoding_type) - { - case EGCodeEncodingType::None: - { - dst.insert(dst.end(), src.begin(), src.end()); - break; - } - case EGCodeEncodingType::MeatPack: - case EGCodeEncodingType::MeatPackComments: - { - MeatPack::unbinarize(src, dst); - break; - } - } - return true; -} - -static bool compress(const std::vector& src, std::vector& dst, ECompressionType compression_type) -{ - switch (compression_type) - { - case ECompressionType::Deflate: - { - dst.clear(); - - const size_t BUFSIZE = 2048; - std::vector temp_buffer(BUFSIZE); - - z_stream strm{}; - strm.next_in = const_cast(src.data()); - strm.avail_in = (uInt)src.size(); - strm.next_out = temp_buffer.data(); - strm.avail_out = BUFSIZE; - - const int level = Z_DEFAULT_COMPRESSION; - int res = deflateInit(&strm, level); - if (res != Z_OK) - return false; - - while (strm.avail_in > 0) { - res = deflate(&strm, Z_NO_FLUSH); - if (res != Z_OK) { - deflateEnd(&strm); - return false; - } - if (strm.avail_out == 0) { - dst.insert(dst.end(), temp_buffer.data(), temp_buffer.data() + BUFSIZE); - strm.next_out = temp_buffer.data(); - strm.avail_out = BUFSIZE; - } - } - - int deflate_res = Z_OK; - while (deflate_res == Z_OK) { - if (strm.avail_out == 0) { - dst.insert(dst.end(), temp_buffer.data(), temp_buffer.data() + BUFSIZE); - strm.next_out = temp_buffer.data(); - strm.avail_out = BUFSIZE; - } - deflate_res = deflate(&strm, Z_FINISH); - } - - if (deflate_res != Z_STREAM_END) { - deflateEnd(&strm); - return false; - } - - dst.insert(dst.end(), temp_buffer.data(), temp_buffer.data() + BUFSIZE - strm.avail_out); - deflateEnd(&strm); - break; - } - case ECompressionType::Heatshrink_11_4: - case ECompressionType::Heatshrink_12_4: - { - const uint8_t window_sz = (compression_type == ECompressionType::Heatshrink_11_4) ? 11 : 12; - const uint8_t lookahead_sz = 4; - heatshrink_encoder* encoder = heatshrink_encoder_alloc(window_sz, lookahead_sz); - if (encoder == nullptr) - return false; - - // calculate the maximum compressed size (assuming a conservative estimate) - const size_t src_size = src.size(); - const size_t max_compressed_size = src_size + (src_size >> 2); - dst.resize(max_compressed_size); - - uint8_t* buf = const_cast(src.data()); - uint8_t* outbuf = dst.data(); - - // compress data - size_t tosink = src_size; - size_t output_size = 0; - while (tosink > 0) { - size_t sunk = 0; - const HSE_sink_res sink_res = heatshrink_encoder_sink(encoder, buf, tosink, &sunk); - if (sink_res != HSER_SINK_OK) { - heatshrink_encoder_free(encoder); - return false; - } - if (sunk == 0) - // all input data processed - break; - - tosink -= sunk; - buf += sunk; - - size_t polled = 0; - const HSE_poll_res poll_res = heatshrink_encoder_poll(encoder, outbuf + output_size, max_compressed_size - output_size, &polled); - if (poll_res < 0) { - heatshrink_encoder_free(encoder); - return false; - } - output_size += polled; - } - - // input data finished - const HSE_finish_res finish_res = heatshrink_encoder_finish(encoder); - if (finish_res < 0) { - heatshrink_encoder_free(encoder); - return false; - } - - // poll for final output - size_t polled = 0; - const HSE_poll_res poll_res = heatshrink_encoder_poll(encoder, outbuf + output_size, max_compressed_size - output_size, &polled); - if (poll_res < 0) { - heatshrink_encoder_free(encoder); - return false; - } - dst.resize(output_size + polled); - heatshrink_encoder_free(encoder); - break; - } - case ECompressionType::None: - default: - { - break; - } - } - - return true; -} - -static bool uncompress(const std::vector& src, std::vector& dst, ECompressionType compression_type, size_t uncompressed_size) -{ - switch (compression_type) - { - case ECompressionType::Deflate: - { - dst.clear(); - dst.reserve(uncompressed_size); - - const size_t BUFSIZE = 2048; - std::vector temp_buffer(BUFSIZE); - - z_stream strm{}; - strm.next_in = const_cast(src.data()); - strm.avail_in = (uInt)src.size(); - strm.next_out = temp_buffer.data(); - strm.avail_out = BUFSIZE; - int res = inflateInit(&strm); - if (res != Z_OK) - return false; - - while (strm.avail_in > 0) { - res = inflate(&strm, Z_NO_FLUSH); - if (res != Z_OK && res != Z_STREAM_END) { - inflateEnd(&strm); - return false; - } - if (strm.avail_out == 0) { - dst.insert(dst.end(), temp_buffer.data(), temp_buffer.data() + BUFSIZE); - strm.next_out = temp_buffer.data(); - strm.avail_out = BUFSIZE; - } - } - - int inflate_res = Z_OK; - while (inflate_res == Z_OK) { - if (strm.avail_out == 0) { - dst.insert(dst.end(), temp_buffer.data(), temp_buffer.data() + BUFSIZE); - strm.next_out = temp_buffer.data(); - strm.avail_out = BUFSIZE; - } - inflate_res = inflate(&strm, Z_FINISH); - } - - if (inflate_res != Z_STREAM_END) { - inflateEnd(&strm); - return false; - } - - dst.insert(dst.end(), temp_buffer.data(), temp_buffer.data() + BUFSIZE - strm.avail_out); - inflateEnd(&strm); - break; - } - case ECompressionType::Heatshrink_11_4: - case ECompressionType::Heatshrink_12_4: - { - const uint8_t window_sz = (compression_type == ECompressionType::Heatshrink_11_4) ? 11 : 12; - const uint8_t lookahead_sz = 4; - const uint16_t input_buffer_size = 2048; - heatshrink_decoder* decoder = heatshrink_decoder_alloc(input_buffer_size, window_sz, lookahead_sz); - if (decoder == nullptr) - return false; - - dst.resize(uncompressed_size); - - uint8_t* buf = const_cast(src.data()); - uint8_t* outbuf = dst.data(); - - uint32_t sunk = 0; - uint32_t polled = 0; - - const size_t compressed_size = src.size(); - while (sunk < compressed_size) { - size_t count = 0; - const HSD_sink_res sink_res = heatshrink_decoder_sink(decoder, &buf[sunk], compressed_size - sunk, &count); - if (sink_res < 0) { - heatshrink_decoder_free(decoder); - return false; - } - - sunk += (uint32_t)count; - - HSD_poll_res poll_res; - do { - poll_res = heatshrink_decoder_poll(decoder, &outbuf[polled], uncompressed_size - polled, &count); - if (poll_res < 0) { - heatshrink_decoder_free(decoder); - return false; - } - polled += (uint32_t)count; - } while (polled < uncompressed_size && poll_res == HSDR_POLL_MORE); - } - - const HSD_finish_res finish_res = heatshrink_decoder_finish(decoder); - if (finish_res < 0) { - heatshrink_decoder_free(decoder); - return false; - } - - heatshrink_decoder_free(decoder); - break; - } - case ECompressionType::None: - default: - { - break; - } - } - - return true; -} - -static uint32_t crc32_sw(const uint8_t* buffer, uint32_t length, uint32_t crc) -{ - uint32_t value = crc ^ 0xFFFFFFFF; - while (length--) { - value ^= (uint32_t)*buffer++; - for (int bit = 0; bit < 8; bit++) { - if (value & 1) - value = (value >> 1) ^ 0xEDB88320; - else - value >>= 1; - } - } - value ^= 0xFFFFFFFF; - return value; -} - -std::vector encode(const void* data, size_t data_size) -{ - std::vector ret(data_size); - memcpy(ret.data(), data, data_size); - return ret; -} - -Checksum::Checksum(EChecksumType type) -: m_type(type) -{ - if (m_type != EChecksumType::None) - m_checksum = std::vector(checksum_size(m_type), '\0'); -} - -EChecksumType Checksum::get_type() const -{ - return m_type; -} - -void Checksum::append(const std::vector& data) -{ - size_t remaining_data_size = std::distance(data.begin(), data.end()); - auto it_begin = data.begin(); - while (remaining_data_size + m_cache.size() > g_checksum_max_cache_size) { - update(); - if (remaining_data_size > g_checksum_max_cache_size) { - m_cache.insert(m_cache.end(), it_begin, it_begin + g_checksum_max_cache_size); - it_begin += g_checksum_max_cache_size; - remaining_data_size -= g_checksum_max_cache_size; - } - } - - m_cache.insert(m_cache.end(), it_begin, data.end()); -} - -bool Checksum::matches(Checksum& other) -{ - update(); - other.update(); - return m_checksum == other.m_checksum; -} - -EResult Checksum::write(FILE& file) -{ - if (m_type != EChecksumType::None) { - update(); - if (!write_to_file(file, (const void*)m_checksum.data(), m_checksum.size())) - return EResult::WriteError; - } - return EResult::Success; -} - -EResult Checksum::read(FILE& file) -{ - if (m_type != EChecksumType::None) { - if (!read_from_file(file, (void*)m_checksum.data(), m_checksum.size())) - return EResult::ReadError; - } - return EResult::Success; -} - -void Checksum::update() -{ - if (m_cache.empty()) - return; - - switch (m_type) - { - case EChecksumType::None: - { - break; - } - case EChecksumType::CRC32: - { - const uint32_t old_crc = *(uint32_t*)m_checksum.data(); - const uint32_t new_crc = crc32_sw(m_cache.data(), (uint32_t)m_cache.size(), old_crc); - *(uint32_t*)m_checksum.data() = new_crc; - break; - } - } - - m_cache.clear(); -} - -EResult FileHeader::write(FILE& file) const -{ - if (magic != *(uint32_t*)(MAGIC.data())) - return EResult::InvalidMagicNumber; - if (checksum_type >= checksum_types_count()) - return EResult::InvalidChecksumType; - - if (!write_to_file(file, (const void*)&magic, sizeof(magic))) - return EResult::WriteError; - if (!write_to_file(file, (const void*)&version, sizeof(version))) - return EResult::WriteError; - if (!write_to_file(file, (const void*)&checksum_type, sizeof(checksum_type))) - return EResult::WriteError; - - return EResult::Success; -} - -EResult FileHeader::read(FILE& file, const uint32_t* const max_version) -{ - if (!read_from_file(file, (void*)&magic, sizeof(magic))) - return EResult::ReadError; - if (magic != *(uint32_t*)(MAGIC.data())) - return EResult::InvalidMagicNumber; - - if (!read_from_file(file, (void*)&version, sizeof(version))) - return EResult::ReadError; - if (max_version != nullptr && version > *max_version) - return EResult::InvalidVersionNumber; - - if (!read_from_file(file, (void*)&checksum_type, sizeof(checksum_type))) - return EResult::ReadError; - if (checksum_type >= checksum_types_count()) - return EResult::InvalidChecksumType; - - return EResult::Success; -} - -void BlockHeader::update_checksum(Checksum& checksum) const -{ - checksum.append(encode((const void*)&type, sizeof(type))); - checksum.append(encode((const void*)&compression, sizeof(compression))); - checksum.append(encode((const void*)&uncompressed_size, sizeof(uncompressed_size))); - if (compression != (uint16_t)ECompressionType::None) - checksum.append(encode((const void*)&compressed_size, sizeof(compressed_size))); -} - -EResult BlockHeader::write(FILE& file) const -{ - if (!write_to_file(file, (const void*)&type, sizeof(type))) - return EResult::WriteError; - if (!write_to_file(file, (const void*)&compression, sizeof(compression))) - return EResult::WriteError; - if (!write_to_file(file, (const void*)&uncompressed_size, sizeof(uncompressed_size))) - return EResult::WriteError; - if (compression != (uint16_t)ECompressionType::None) { - if (!write_to_file(file, (const void*)&compressed_size, sizeof(compressed_size))) - return EResult::WriteError; - } - return EResult::Success; -} - -EResult BlockHeader::read(FILE& file) -{ - if (!read_from_file(file, (void*)&type, sizeof(type))) - return EResult::ReadError; - if (type >= block_types_count()) - return EResult::InvalidBlockType; - - if (!read_from_file(file, (void*)&compression, sizeof(compression))) - return EResult::ReadError; - if (compression >= compression_types_count()) - return EResult::InvalidCompressionType; - - if (!read_from_file(file, (void*)&uncompressed_size, sizeof(uncompressed_size))) - return EResult::ReadError; - if (compression != (uint16_t)ECompressionType::None) { - if (!read_from_file(file, (void*)&compressed_size, sizeof(compressed_size))) - return EResult::ReadError; - } - - return EResult::Success; -} - -EResult BaseMetadataBlock::write(FILE& file, EBlockType block_type, ECompressionType compression_type, Checksum& checksum) const -{ - if (encoding_type > metadata_encoding_types_count()) - return EResult::InvalidMetadataEncodingType; - - BlockHeader block_header = { (uint16_t)block_type, (uint16_t)compression_type, (uint32_t)0 }; - std::vector out_data; - if (!raw_data.empty()) { - // process payload encoding - std::vector uncompressed_data; - if (!encode_metadata(raw_data, uncompressed_data, (EMetadataEncodingType)encoding_type)) - return EResult::MetadataEncodingError; - // process payload compression - block_header.uncompressed_size = (uint32_t)uncompressed_data.size(); - std::vector compressed_data; - if (compression_type != ECompressionType::None) { - if (!compress(uncompressed_data, compressed_data, compression_type)) - return EResult::DataCompressionError; - block_header.compressed_size = (uint32_t)compressed_data.size(); - } - out_data.swap((compression_type == ECompressionType::None) ? uncompressed_data : compressed_data); - } - - // write block header - EResult res = block_header.write(file); - if (res != EResult::Success) - // propagate error - return res; - - // write block payload - if (!write_to_file(file, (const void*)&encoding_type, sizeof(encoding_type))) - return EResult::WriteError; - if (!out_data.empty()) { - if (!write_to_file(file, (const void*)out_data.data(), out_data.size())) - return EResult::WriteError; - } - - if (checksum.get_type() != EChecksumType::None) { - // update checksum with block header - block_header.update_checksum(checksum); - // update checksum with block payload - checksum.append(encode((const void*)&encoding_type, sizeof(encoding_type))); - if (!out_data.empty()) - checksum.append(out_data); - } - - return EResult::Success; -} - -EResult BaseMetadataBlock::read_data(FILE& file, const BlockHeader& block_header) -{ - const ECompressionType compression_type = (ECompressionType)block_header.compression; - - if (!read_from_file(file, (void*)&encoding_type, sizeof(encoding_type))) - return EResult::ReadError; - if (encoding_type > metadata_encoding_types_count()) - return EResult::InvalidMetadataEncodingType; - - std::vector data; - const size_t data_size = (compression_type == ECompressionType::None) ? block_header.uncompressed_size : block_header.compressed_size; - if (data_size > 0) { - data.resize(data_size); - if (!read_from_file(file, (void*)data.data(), data_size)) - return EResult::ReadError; - } - - std::vector uncompressed_data; - if (compression_type != ECompressionType::None) { - if (!uncompress(data, uncompressed_data, compression_type, block_header.uncompressed_size)) - return EResult::DataUncompressionError; - } - - if (!decode_metadata((compression_type == ECompressionType::None) ? data : uncompressed_data, raw_data, (EMetadataEncodingType)encoding_type)) - return EResult::MetadataDecodingError; - - return EResult::Success; -} - -EResult FileMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const -{ - Checksum cs(checksum_type); - - // write block header, payload - EResult res = BaseMetadataBlock::write(file, EBlockType::FileMetadata, compression_type, cs); - if (res != EResult::Success) - // propagate error - return res; - - // write block checksum - if (checksum_type != EChecksumType::None) - return cs.write(file); - - return EResult::Success; -} - -EResult FileMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - // read block payload - EResult res = BaseMetadataBlock::read_data(file, block_header); - if (res != EResult::Success) - // propagate error - return res; - - const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type; - if (checksum_type != EChecksumType::None) { - // read block checksum - Checksum cs(checksum_type); - res = cs.read(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -EResult ThumbnailBlock::write(FILE& file, EChecksumType checksum_type) const -{ - if (format >= thumbnail_formats_count()) - return EResult::InvalidThumbnailFormat; - if (width == 0) - return EResult::InvalidThumbnailWidth; - if (height == 0) - return EResult::InvalidThumbnailHeight; - if (data.size() == 0) - return EResult::InvalidThumbnailDataSize; - - // write block header - const BlockHeader block_header = { (uint16_t)EBlockType::Thumbnail, (uint16_t)ECompressionType::None, (uint32_t)data.size() }; - EResult res = block_header.write(file); - if (res != EResult::Success) - // propagate error - return res; - - // write block payload - if (!write_to_file(file, (const void*)&format, sizeof(format))) - return EResult::WriteError; - if (!write_to_file(file, (const void*)&width, sizeof(width))) - return EResult::WriteError; - if (!write_to_file(file, (const void*)&height, sizeof(height))) - return EResult::WriteError; - if (!write_to_file(file, (const void*)data.data(), data.size())) - return EResult::WriteError; - - if (checksum_type != EChecksumType::None) { - Checksum cs(checksum_type); - // update checksum with block header - block_header.update_checksum(cs); - // update checksum with block payload - update_checksum(cs); - // write block checksum - res = cs.write(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -EResult ThumbnailBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - // read block payload - if (!read_from_file(file, (void*)&format, sizeof(format))) - return EResult::ReadError; - if (format >= thumbnail_formats_count()) - return EResult::InvalidThumbnailFormat; - if (!read_from_file(file, (void*)&width, sizeof(width))) - return EResult::ReadError; - if (width == 0) - return EResult::InvalidThumbnailWidth; - if (!read_from_file(file, (void*)&height, sizeof(height))) - return EResult::ReadError; - if (height == 0) - return EResult::InvalidThumbnailHeight; - if (block_header.uncompressed_size == 0) - return EResult::InvalidThumbnailDataSize; - - data.resize(block_header.uncompressed_size); - if (!read_from_file(file, (void*)data.data(), block_header.uncompressed_size)) - return EResult::ReadError; - - const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type; - if (checksum_type != EChecksumType::None) { - // read block checksum - Checksum cs(checksum_type); - const EResult res = cs.read(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -void ThumbnailBlock::update_checksum(Checksum& checksum) const -{ - checksum.append(encode((const void*)&format, sizeof(format))); - checksum.append(encode((const void*)&width, sizeof(width))); - checksum.append(encode((const void*)&height, sizeof(height))); - checksum.append(data); -} - -EResult PrinterMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const -{ - Checksum cs(checksum_type); - - // write block header, payload - EResult res = BaseMetadataBlock::write(file, EBlockType::PrinterMetadata, compression_type, cs); - if (res != EResult::Success) - // propagate error - return res; - - // write block checksum - if (checksum_type != EChecksumType::None) - return cs.write(file); - - return EResult::Success; -} - -EResult PrinterMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - // read block payload - EResult res = BaseMetadataBlock::read_data(file, block_header); - if (res != EResult::Success) - // propagate error - return res; - - const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type; - if (checksum_type != EChecksumType::None) { - // read block checksum - Checksum cs(checksum_type); - res = cs.read(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -EResult PrintMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const -{ - Checksum cs(checksum_type); - - // write block header, payload - EResult res = BaseMetadataBlock::write(file, EBlockType::PrintMetadata, compression_type, cs); - if (res != EResult::Success) - // propagate error - return res; - - // write block checksum - if (checksum_type != EChecksumType::None) - return cs.write(file); - - return EResult::Success; -} - -EResult PrintMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - // read block payload - EResult res = BaseMetadataBlock::read_data(file, block_header); - if (res != EResult::Success) - // propagate error - return res; - - const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type; - if (checksum_type != EChecksumType::None) { - // read block checksum - Checksum cs(checksum_type); - res = cs.read(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -EResult SlicerMetadataBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const -{ - Checksum cs(checksum_type); - - // write block header, payload - EResult res = BaseMetadataBlock::write(file, EBlockType::SlicerMetadata, compression_type, cs); - if (res != EResult::Success) - // propagate error - return res; - - // write block checksum - if (checksum_type != EChecksumType::None) - return cs.write(file); - - return EResult::Success; -} - -EResult SlicerMetadataBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - // read block payload - EResult res = BaseMetadataBlock::read_data(file, block_header); - if (res != EResult::Success) - // propagate error - return res; - - const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type; - if (checksum_type != EChecksumType::None) { - // read block checksum - Checksum cs(checksum_type); - res = cs.read(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -EResult GCodeBlock::write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const -{ - if (encoding_type > gcode_encoding_types_count()) - return EResult::InvalidGCodeEncodingType; - - BlockHeader block_header = { (uint16_t)EBlockType::GCode, (uint16_t)compression_type, (uint32_t)0 }; - std::vector out_data; - if (!raw_data.empty()) { - // process payload encoding - std::vector uncompressed_data; - if (!encode_gcode(raw_data, uncompressed_data, (EGCodeEncodingType)encoding_type)) - return EResult::GCodeEncodingError; - // process payload compression - block_header.uncompressed_size = (uint32_t)uncompressed_data.size(); - std::vector compressed_data; - if (compression_type != ECompressionType::None) { - if (!compress(uncompressed_data, compressed_data, compression_type)) - return EResult::DataCompressionError; - block_header.compressed_size = (uint32_t)compressed_data.size(); - } - out_data.swap((compression_type == ECompressionType::None) ? uncompressed_data : compressed_data); - } - - // write block header - EResult res = block_header.write(file); - if (res != EResult::Success) - // propagate error - return res; - - // write block payload - if (!write_to_file(file, (const void*)&encoding_type, sizeof(encoding_type))) - return EResult::WriteError; - if (!out_data.empty()) { -#if ENABLE_BINARIZED_GCODE_DEBUG - const std::string out = "GCodeBlock data size:" + std::to_string(out_data.size()) + "\n"; - OutputDebugStringA(out.c_str()); -#endif // ENABLE_BINARIZED_GCODE_DEBUG - if (!write_to_file(file, (const void*)out_data.data(), out_data.size())) - return EResult::WriteError; - } - - // write checksum - if (checksum_type != EChecksumType::None) { - Checksum cs(checksum_type); - // update checksum with block header - block_header.update_checksum(cs); - // update checksum with block payload - cs.append(encode((const void*)&encoding_type, sizeof(encoding_type))); - if (!out_data.empty()) - cs.append(out_data); - res = cs.write(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -EResult GCodeBlock::read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - const ECompressionType compression_type = (ECompressionType)block_header.compression; - - if (!read_from_file(file, (void*)&encoding_type, sizeof(encoding_type))) - return EResult::ReadError; - if (encoding_type > gcode_encoding_types_count()) - return EResult::InvalidGCodeEncodingType; - - std::vector data; - const size_t data_size = (compression_type == ECompressionType::None) ? block_header.uncompressed_size : block_header.compressed_size; - if (data_size > 0) { - data.resize(data_size); - if (!read_from_file(file, (void*)data.data(), data_size)) - return EResult::ReadError; - } - - std::vector uncompressed_data; - if (compression_type != ECompressionType::None) { - if (!uncompress(data, uncompressed_data, compression_type, block_header.uncompressed_size)) - return EResult::DataUncompressionError; - } - - if (!decode_gcode((compression_type == ECompressionType::None) ? data : uncompressed_data, raw_data, (EGCodeEncodingType)encoding_type)) - return EResult::GCodeDecodingError; - - const EChecksumType checksum_type = (EChecksumType)file_header.checksum_type; - if (checksum_type != EChecksumType::None) { - // read block checksum - Checksum cs(checksum_type); - const EResult res = cs.read(file); - if (res != EResult::Success) - // propagate error - return res; - } - - return EResult::Success; -} - -#if ENABLE_CHECKSUM_BLOCK -EResult ChecksumBlock::write(FILE& file) const -{ - if (!data.empty()) { - const BlockHeader block_header = { (uint16_t)EBlockType::Checksum, (uint16_t)ECompressionType::None, (uint32_t)data.size() }; - // write block header - const EResult res = block_header.write(file); - if (res != EResult::Success) - // propagate error - return res; - // write block payload - if (!write_to_file(file, (const void*)data.data(), data.size())) - return EResult::WriteError; - } - - return EResult::Success; -} - -EResult ChecksumBlock::read_data(FILE& file, const BlockHeader& block_header) -{ - if (block_header.uncompressed_size > 0) { - data.resize(block_header.uncompressed_size); - if (!read_from_file(file, (void*)data.data(), block_header.uncompressed_size)) - return EResult::ReadError; - } - else - data.clear(); - - return EResult::Success; -} -#endif // ENABLE_CHECKSUM_BLOCK - -EResult Binarizer::initialize(FILE& file, const BinarizerConfig& config) -{ - if (!m_enabled) - return EResult::Success; - - m_file = &file; - - m_config = config; -#if ENABLE_CHECKSUM_BLOCK - // initialize checksum - m_checksum = ChecksumBlock(); -#endif // ENABLE_CHECKSUM_BLOCK - - // save header - FileHeader file_header; - file_header.checksum_type = (uint16_t)m_config.checksum; - EResult res = file_header.write(*m_file); - if (res != EResult::Success) - return res; - - // save file metadata block - res = m_binary_data.file_metadata.write(*m_file, m_config.compression.file_metadata, m_config.checksum); - if (res != EResult::Success) - return res; - - // save printer metadata block - res = m_binary_data.printer_metadata.write(*m_file, m_config.compression.printer_metadata, m_config.checksum); - if (res != EResult::Success) - return res; - - // save thumbnail blocks - for (const ThumbnailBlock& block : m_binary_data.thumbnails) { - res = block.write(*m_file, m_config.checksum); - if (res != EResult::Success) - return res; - } - - // save print metadata block - res = m_binary_data.print_metadata.write(*m_file, m_config.compression.print_metadata, m_config.checksum); - if (res != EResult::Success) - return res; - - // save slicer metadata block - res = m_binary_data.slicer_metadata.write(*m_file, m_config.compression.slicer_metadata, m_config.checksum); - if (res != EResult::Success) - return res; - - return EResult::Success; -} - -static EResult write_gcode_block(FILE& file, const std::string& raw_data, const BinarizerConfig& config) -{ - GCodeBlock block; - block.encoding_type = (uint16_t)config.gcode_encoding; - block.raw_data = raw_data; - return block.write(file, config.compression.gcode, config.checksum); -} - -EResult Binarizer::append_gcode(const std::string& gcode) -{ - if (gcode.empty()) - return EResult::Success; - - assert(m_file != nullptr); - if (m_file == nullptr) - return EResult::WriteError; - - auto it_begin = gcode.begin(); - do { - const size_t begin_pos = std::distance(gcode.begin(), it_begin); - const size_t end_line_pos = gcode.find_first_of('\n', begin_pos); - if (end_line_pos == std::string::npos) - return EResult::WriteError; - - const size_t line_size = 1 + end_line_pos - begin_pos; - if (line_size + m_gcode_cache.length() > MAX_GCODE_CACHE_SIZE) { - if (!m_gcode_cache.empty()) { - const EResult res = write_gcode_block(*m_file, m_gcode_cache, m_config); - if (res != EResult::Success) - // propagate error - return res; - m_gcode_cache.clear(); - } - } - - if (line_size > MAX_GCODE_CACHE_SIZE) - return EResult::WriteError; - - m_gcode_cache.insert(m_gcode_cache.end(), it_begin, it_begin + line_size); - it_begin += line_size; - } - while (it_begin != gcode.end()); - - return EResult::Success; -} - -EResult Binarizer::finalize() -{ - if (!m_enabled) - return EResult::Success; - - // save gcode cache, if not empty - if (!m_gcode_cache.empty()) { - const EResult res = write_gcode_block(*m_file, m_gcode_cache, m_config); - if (res != EResult::Success) - // propagate error - return res; - } - -#if ENABLE_CHECKSUM_BLOCK - if (m_checksum_type != EChecksumType::None) { - // save checksum - // dummy checksum until it is not properly implemented - switch (m_checksum_type) - { - case EChecksumType::CRC32: - case EChecksumType::MD5: - { - m_checksum.data.clear(); - break; - } - } - - res = m_checksum.write(file); - if (res != EResult::Success) - return res; - } -#endif // ENABLE_CHECKSUM_BLOCK - - return EResult::Success; -} - -bool is_valid_binary_gcode(FILE& file) -{ - // cache file position - const long curr_pos = ftell(&file); - rewind(&file); - - std::array magic; - fread((void*)magic.data(), 1, magic.size(), &file); - if (ferror(&file)) - return false; - else { - // restore file position - fseek(&file, curr_pos, SEEK_SET); - return magic == MAGIC; - } -} - -EResult read_header(FILE& file, FileHeader& header, const uint32_t* const max_version) -{ - rewind(&file); - return header.read(file, max_version); -} - -static EResult checksums_match(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - // cache file position - const long curr_pos = ftell(&file); - - Checksum curr_cs((EChecksumType)file_header.checksum_type); - // update block checksum block header - block_header.update_checksum(curr_cs); - - // read block payload - size_t remaining_payload_size = block_payload_size(block_header); - while (remaining_payload_size > 0) { - const size_t size_to_read = std::min(remaining_payload_size, g_checksum_max_cache_size); - std::vector payload(size_to_read); - if (!read_from_file(file, payload.data(), payload.size())) - return EResult::ReadError; - curr_cs.append(payload); - remaining_payload_size -= size_to_read; - } - - // read checksum - Checksum read_cs((EChecksumType)file_header.checksum_type); - EResult res = read_cs.read(file); - if (res != EResult::Success) - // propagate error - return res; - - // Verify checksum - if (!curr_cs.matches(read_cs)) - return EResult::InvalidChecksum; - - // restore file position - fseek(&file, curr_pos, SEEK_SET); - - return EResult::Success; -} - -EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, bool verify_checksum) -{ - if (verify_checksum && (EChecksumType)file_header.checksum_type != EChecksumType::None) { - const EResult res = block_header.read(file); - if (res != EResult::Success) - // propagate error - return res; - - return checksums_match(file, file_header, block_header); - } - else - return block_header.read(file); -} - -EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, EBlockType type, bool verify_checksum) -{ - // cache file position - const long curr_pos = ftell(&file); - - do { - EResult res = read_next_block_header(file, file_header, block_header, false); - if (res != EResult::Success) - // propagate error - return res; - else if (feof(&file)) { - // block not found - // restore file position - fseek(&file, curr_pos, SEEK_SET); - return EResult::BlockNotFound; - } - else if ((EBlockType)block_header.type == type) { - // block found - if (verify_checksum) { - res = checksums_match(file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - else - break; - } - } - - if (!feof(&file)) { - res = skip_block_content(file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - } - } while (true); - - return EResult::Success; -} - -EResult skip_block_payload(FILE& file, const BlockHeader& block_header) -{ - fseek(&file, (long)block_payload_size(block_header), SEEK_CUR); - return ferror(&file) ? EResult::ReadError : EResult::Success; -} - -EResult skip_block_content(FILE& file, const FileHeader& file_header, const BlockHeader& block_header) -{ - fseek(&file, (long)block_content_size(file_header, block_header), SEEK_CUR); - return ferror(&file) ? EResult::ReadError : EResult::Success; -} - -size_t block_parameters_size(EBlockType type) -{ - switch (type) - { - case EBlockType::FileMetadata: { return FileMetadataBlock::get_parameters_size(); } - case EBlockType::GCode: { return GCodeBlock::get_parameters_size(); } - case EBlockType::SlicerMetadata: { return SlicerMetadataBlock::get_parameters_size(); } - case EBlockType::PrinterMetadata: { return PrinterMetadataBlock::get_parameters_size(); } - case EBlockType::PrintMetadata: { return PrintMetadataBlock::get_parameters_size(); } - case EBlockType::Thumbnail: { return ThumbnailBlock::get_parameters_size(); } - } - return 0; -} - -size_t block_payload_size(const BlockHeader& block_header) -{ - size_t ret = block_parameters_size((EBlockType)block_header.type); - ret += ((ECompressionType)block_header.compression == ECompressionType::None) ? - block_header.uncompressed_size : block_header.compressed_size; - return ret; -} - -size_t checksum_size(EChecksumType type) -{ - switch (type) - { - case EChecksumType::None: { return 0; } - case EChecksumType::CRC32: { return 4; } - } - return 0; -} - -extern size_t block_content_size(const FileHeader& file_header, const BlockHeader& block_header) -{ -#if ENABLE_CHECKSUM_BLOCK - return ((EBlockType)block_header.type == EBlockType::Checksum) ? - block_payload_size(block_header) : block_payload_size(block_header) + checksum_size((EChecksumType)file_header.checksum_type); -#else - return block_payload_size(block_header) + checksum_size((EChecksumType)file_header.checksum_type); -#endif // ENABLE_CHECKSUM_BLOCK -} - -#if ENABLE_FILE_CONVERSION_INTERFACE -EResult from_ascii_to_binary(FILE& src_file, FILE& dst_file) -{ - return EResult::WriteError; -} - -EResult from_binary_to_ascii(FILE& src_file, FILE& dst_file, bool verify_checksum) -{ - auto write_line = [&](const std::string& line) { - fwrite(line.data(), 1, line.length(), &dst_file); - return !ferror(&dst_file); - }; - - auto write_metadata = [&](const std::vector>& data) { - for (const auto& [key, value] : data) { - if (!write_line("; " + key + " = " + value + "\n")) - return false; - } - return !ferror(&dst_file); - }; - - if (!is_valid_binary_gcode(src_file)) - return EResult::InvalidBinaryGCodeFile; - - fseek(&src_file, 0, SEEK_END); - const long file_size = ftell(&src_file); - rewind(&src_file); - - // - // read file header - // - FileHeader file_header; - EResult res = read_header(src_file, file_header, nullptr); - if (res != EResult::Success) - // propagate error - return res; - - // - // convert file metadata block - // - BlockHeader block_header; - res = read_next_block_header(src_file, file_header, block_header, verify_checksum); - if (res != EResult::Success) - // propagate error - return res; - if ((EBlockType)block_header.type != EBlockType::FileMetadata) - return EResult::InvalidSequenceOfBlocks; - FileMetadataBlock file_metadata_block; - res = file_metadata_block.read_data(src_file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - auto producer_it = std::find_if(file_metadata_block.raw_data.begin(), file_metadata_block.raw_data.end(), - [](const std::pair& item) { return item.first == "Producer"; }); - const std::string producer_str = (producer_it != file_metadata_block.raw_data.end()) ? producer_it->second : "Unknown"; - if (!write_line("; generated by " + producer_str + "\n\n\n")) - return EResult::WriteError; - - // - // convert printer metadata block - // - res = read_next_block_header(src_file, file_header, block_header, verify_checksum); - if (res != EResult::Success) - // propagate error - return res; - if ((EBlockType)block_header.type != EBlockType::PrinterMetadata) - return EResult::InvalidSequenceOfBlocks; - PrinterMetadataBlock printer_metadata_block; - res = printer_metadata_block.read_data(src_file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - if (!write_metadata(printer_metadata_block.raw_data)) - return EResult::WriteError; - - // - // convert thumbnail blocks - // - long restore_position = ftell(&src_file); - res = read_next_block_header(src_file, file_header, block_header, verify_checksum); - if (res != EResult::Success) - // propagate error - return res; - while ((EBlockType)block_header.type == EBlockType::Thumbnail) { - ThumbnailBlock thumbnail_block; - res = thumbnail_block.read_data(src_file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - static constexpr const size_t max_row_length = 78; - std::string encoded; - encoded.resize(boost::beast::detail::base64::encoded_size(thumbnail_block.data.size())); - encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)thumbnail_block.data.data(), thumbnail_block.data.size())); - std::string format; - switch ((EThumbnailFormat)thumbnail_block.format) - { - default: - case EThumbnailFormat::PNG: { format = "thumbnail"; break; } - case EThumbnailFormat::JPG: { format = "thumbnail_JPG"; break; } - case EThumbnailFormat::QOI: { format = "thumbnail_QOI"; break; } - } - if (!write_line(";\n; " + format + " begin " + std::to_string(thumbnail_block.width) + "x" + std::to_string(thumbnail_block.height) + - " " + std::to_string(encoded.length()) + "\n")) - return EResult::WriteError; - while (encoded.size() > max_row_length) { - if (!write_line("; " + encoded.substr(0, max_row_length) + "\n")) - return EResult::WriteError; - encoded = encoded.substr(max_row_length); - } - if (encoded.size() > 0) { - if (!write_line("; " + encoded + "\n")) - return EResult::WriteError; - } - if (!write_line("; " + format + " end\n;\n\n")) - return EResult::WriteError; - - restore_position = ftell(&src_file); - res = read_next_block_header(src_file, file_header, block_header, verify_checksum); - if (res != EResult::Success) - // propagate error - return res; - } - - // - // convert gcode blocks - // - res = skip_block_content(src_file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - res = read_next_block_header(src_file, file_header, block_header, EBlockType::GCode, verify_checksum); - if (res != EResult::Success) - // propagate error - return res; - while ((EBlockType)block_header.type == EBlockType::GCode) { - GCodeBlock block; - res = block.read_data(src_file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - if (!write_line(block.raw_data)) - return EResult::WriteError; - if (ftell(&src_file) == file_size) - break; - res = read_next_block_header(src_file, file_header, block_header, verify_checksum); - if (res != EResult::Success) - // propagate error - return res; - } - - // - // convert print metadata block - // - fseek(&src_file, restore_position, SEEK_SET); - res = read_next_block_header(src_file, file_header, block_header, verify_checksum); - if (res != EResult::Success) - // propagate error - return res; - if ((EBlockType)block_header.type != EBlockType::PrintMetadata) - return EResult::InvalidSequenceOfBlocks; - PrintMetadataBlock print_metadata_block; - res = print_metadata_block.read_data(src_file, file_header, block_header); - if (res != EResult::Success) - // propagate error - return res; - if (!write_line("\n")) - return EResult::WriteError; - if (!write_metadata(print_metadata_block.raw_data)) - return EResult::WriteError; - - // - // convert slicer metadata block - // - res = read_next_block_header(src_file, file_header, block_header, verify_checksum); - 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; - - return EResult::Success; -} -#endif // ENABLE_FILE_CONVERSION_INTERFACE - -} // namespace bgcode - diff --git a/src/libslic3r/GCode/GCodeBinarizer.hpp b/src/libslic3r/GCode/GCodeBinarizer.hpp deleted file mode 100644 index 2961fe548f..0000000000 --- a/src/libslic3r/GCode/GCodeBinarizer.hpp +++ /dev/null @@ -1,382 +0,0 @@ -#ifndef slic3r_GCode_GCodeBinarizer_hpp_ -#define slic3r_GCode_GCodeBinarizer_hpp_ - -#ifdef _WIN32 -#define ENABLE_BINARIZED_GCODE_DEBUG 1 -#endif // _WIN32 - -#define ENABLE_CHECKSUM_BLOCK 0 -#define ENABLE_FILE_CONVERSION_INTERFACE 1 - -#include -#include -#include -#include -#include - -namespace bgcode { - -static const std::array MAGIC{ 'G', 'C', 'D', 'E' }; -static const uint32_t VERSION = 1; - -enum class EResult : uint16_t -{ - Success, - ReadError, - WriteError, - InvalidMagicNumber, - InvalidVersionNumber, - InvalidChecksumType, - InvalidBlockType, - InvalidCompressionType, - InvalidMetadataEncodingType, - InvalidGCodeEncodingType, - DataCompressionError, - DataUncompressionError, - MetadataEncodingError, - MetadataDecodingError, - GCodeEncodingError, - GCodeDecodingError, - BlockNotFound, - InvalidChecksum, - InvalidThumbnailFormat, - InvalidThumbnailWidth, - InvalidThumbnailHeight, - InvalidThumbnailDataSize, - InvalidBinaryGCodeFile, - InvalidSequenceOfBlocks -}; - -// Returns a string description of the given result -extern std::string translate_result(EResult result); - -enum class EChecksumType : uint16_t -{ - None, - CRC32 -}; - -class Checksum -{ -public: - // Constructs a checksum of the given type. - // The checksum data are sized accordingly. - explicit Checksum(EChecksumType type); - - EChecksumType get_type() const; - - // Appends the given data to the cache and performs a checksum update if - // the size of the cache exceeds the max checksum cache size. - void append(const std::vector& data); - // Returns true if the given checksum is equal to this one - bool matches(Checksum& other); - - EResult write(FILE& file); - EResult read(FILE& file); - -private: - EChecksumType m_type; - std::vector m_cache; - std::vector m_checksum; - - void update(); -}; - -struct FileHeader -{ - uint32_t magic{ *(uint32_t*)(MAGIC.data()) }; - uint32_t version{ VERSION }; - uint16_t checksum_type{ (uint16_t)EChecksumType::None }; - - EResult write(FILE& file) const; - EResult read(FILE& file, const uint32_t* const max_version); -}; - -enum class EBlockType : uint16_t -{ -#if ENABLE_CHECKSUM_BLOCK - Checksum, -#endif // ENABLE_CHECKSUM_BLOCK - FileMetadata, - GCode, - SlicerMetadata, - PrinterMetadata, - PrintMetadata, - Thumbnail -}; - -enum class ECompressionType : uint16_t -{ - None, - Deflate, - Heatshrink_11_4, - Heatshrink_12_4, -}; - -struct BlockHeader -{ - uint16_t type{ 0 }; - uint16_t compression{ 0 }; - uint32_t uncompressed_size{ 0 }; - uint32_t compressed_size{ 0 }; - - // Updates the given checksum with the data of this BlockHeader - void update_checksum(Checksum& checksum) const; - - EResult write(FILE& file) const; - EResult read(FILE& file); -}; - -enum class EMetadataEncodingType : uint16_t -{ - INI, -}; - -struct BaseMetadataBlock -{ - // type of data encoding - uint16_t encoding_type{ 0 }; - // data in key/value form - std::vector> raw_data; - - // write block header and data in encoded format - EResult write(FILE& file, EBlockType block_type, ECompressionType compression_type, Checksum& checksum) const; - // read block data in encoded format - EResult read_data(FILE& file, const BlockHeader& block_header); - - static size_t get_parameters_size() { return sizeof(encoding_type); } -}; - -struct FileMetadataBlock : public BaseMetadataBlock -{ - // write block header and data - EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const; - // read block data - EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header); -}; - -enum class EThumbnailFormat : uint16_t -{ - PNG, - JPG, - QOI -}; - -struct ThumbnailBlock -{ - uint16_t format{ 0 }; - uint16_t width{ 0 }; - uint16_t height{ 0 }; - std::vector data; - - // write block header and data - EResult write(FILE& file, EChecksumType checksum_type) const; - // read block data - EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header); - - static size_t get_parameters_size() { return sizeof(format) + sizeof(width) + sizeof(height); } - -private: - void update_checksum(Checksum& checksum) const; -}; - -struct PrinterMetadataBlock : public BaseMetadataBlock -{ - // write block header and data - EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const; - // read block data - EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header); -}; - -struct PrintMetadataBlock : public BaseMetadataBlock -{ - // write block header and data - EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const; - // read block data - EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header); -}; - -struct SlicerMetadataBlock : public BaseMetadataBlock -{ - // write block header and data - EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const; - // read block data - EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header); -}; - -enum class EGCodeEncodingType : uint16_t -{ - None, - MeatPack, - MeatPackComments -}; - -struct GCodeBlock -{ - uint16_t encoding_type{ 0 }; - std::string raw_data; - - // write block header and data - EResult write(FILE& file, ECompressionType compression_type, EChecksumType checksum_type) const; - // read block data - EResult read_data(FILE& file, const FileHeader& file_header, const BlockHeader& block_header); - - static size_t get_parameters_size() { return sizeof(encoding_type); } -}; - -#if ENABLE_CHECKSUM_BLOCK -struct ChecksumBlock -{ - std::vector data; - - // write block header and data - EResult write(FILE& file) const; - // read block data - EResult read_data(FILE& file, const BlockHeader& block_header); -}; -#endif // ENABLE_CHECKSUM_BLOCK - -//===================================================================================================================================== -// -// PRUSASLICER INTERFACE -// -//===================================================================================================================================== - -struct BinaryData -{ - FileMetadataBlock file_metadata; - PrinterMetadataBlock printer_metadata; - std::vector thumbnails; - SlicerMetadataBlock slicer_metadata; - PrintMetadataBlock print_metadata; - - void reset() { - file_metadata.raw_data.clear(); - printer_metadata.raw_data.clear(); - thumbnails.clear(); - slicer_metadata.raw_data.clear(); - print_metadata.raw_data.clear(); - } -}; - -struct BinarizerConfig -{ - struct Compression - { - ECompressionType file_metadata{ ECompressionType::None }; - ECompressionType printer_metadata{ ECompressionType::None }; - ECompressionType print_metadata{ ECompressionType::None }; - ECompressionType slicer_metadata{ ECompressionType::None }; - ECompressionType gcode{ ECompressionType::None }; - }; - Compression compression; - EGCodeEncodingType gcode_encoding{ EGCodeEncodingType::None }; - EMetadataEncodingType metadata_encoding{ EMetadataEncodingType::INI }; - EChecksumType checksum{ EChecksumType::CRC32 }; -}; - -class Binarizer -{ -public: - bool is_enabled() const { return m_enabled; } - void set_enabled(bool enable) { m_enabled = enable; } - - BinaryData& get_binary_data() { return m_binary_data; } - const BinaryData& get_binary_data() const { return m_binary_data; } - - EResult initialize(FILE& file, const BinarizerConfig& config); - EResult append_gcode(const std::string& gcode); - EResult finalize(); - -private: - bool m_enabled{ false }; - - BinarizerConfig m_config; - FILE* m_file{ nullptr }; - BinaryData m_binary_data; - std::string m_gcode_cache; -#if ENABLE_CHECKSUM_BLOCK - ChecksumBlock m_checksum; -#endif // ENABLE_CHECKSUM_BLOCK -}; - -//===================================================================================================================================== -// -// GCODEVIEWER INTERFACE -// FIRMWARE INTERFACE -// -//===================================================================================================================================== - -// Get the max size of the cache used to calculate checksums, in bytes -size_t get_checksum_max_cache_size(); -// Set the max size of the cache used to calculate checksums, in bytes -void set_checksum_max_cache_size(size_t size); - -// Returns true if the given file is a valid binary gcode -// Does not modify the file position -extern bool is_valid_binary_gcode(FILE& file); - -// Reads the file header. -// If max_version is not null, version is checked against the passed value -// If return == EResult::Success: -// - header will contain the file header -// - file position will be set at the start of the 1st block header -extern EResult read_header(FILE& file, FileHeader& header, const uint32_t* const max_version); - -// Reads next block header from the current file position. -// File position must be at the start of a block header. -// If return == EResult::Success: -// - block_header will contain the header of the block -// - file position will be set at the start of the block parameters data -extern EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, bool verify_checksum); - -// Searches and reads next block header with the given type from the current file position. -// File position must be at the start of a block header. -// If return == EResult::Success: -// - block_header will contain the header of the block with the required type -// - file position will be set at the start of the block parameters data -// otherwise: -// - file position will keep the current value -extern EResult read_next_block_header(FILE& file, const FileHeader& file_header, BlockHeader& block_header, EBlockType type, bool verify_checksum); - -// Skips the payload (parameters + data) of the block with the given block header. -// File position must be at the start of the block parameters. -// If return == EResult::Success: -// - file position will be set at the start of the block checksum, if present, or of next block header -extern EResult skip_block_payload(FILE& file, const BlockHeader& block_header); - -// Skips the content (parameters + data + checksum) of the block with the given block header. -// File position must be at the start of the block parameters. -// If return == EResult::Success: -// - file position will be set at the start of the next block header -extern EResult skip_block_content(FILE& file, const FileHeader& file_header, const BlockHeader& block_header); - -// Returns the size of the parameters of the given block type, in bytes. -extern size_t block_parameters_size(EBlockType type); - -// Returns the size of the payload (parameters + data) of the block with the given header, in bytes. -extern size_t block_payload_size(const BlockHeader& block_header); - -// Returns the size of the checksum of the given type, in bytes. -extern size_t checksum_size(EChecksumType type); - -// Returns the size of the content (parameters + data + checksum) of the block with the given header, in bytes. -extern size_t block_content_size(const FileHeader& file_header, const BlockHeader& block_header); - -#if ENABLE_FILE_CONVERSION_INTERFACE -//===================================================================================================================================== -// -// FILE CONVERSION INTERFACE -// -//===================================================================================================================================== - -// Converts the gcode file contained into src_file from ascii to binary format and save the results into dst_file -extern EResult from_ascii_to_binary(FILE& src_file, FILE& dst_file); - -// Converts the gcode file contained into src_file from binary to ascii format and save the results into dst_file -extern EResult from_binary_to_ascii(FILE& src_file, FILE& dst_file, bool verify_checksum); -#endif // ENABLE_FILE_CONVERSION_INTERFACE - -} // namespace bgcode - -#endif // slic3r_GCode_GCodeBinarizer_hpp_