diff --git a/resources/icons/convert_file.svg b/resources/icons/convert_file.svg new file mode 100644 index 0000000000..2de2b707f0 --- /dev/null +++ b/resources/icons/convert_file.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libslic3r/GCode/GCodeBinarizer.cpp b/src/libslic3r/GCode/GCodeBinarizer.cpp index 8afda44169..cd46324174 100644 --- a/src/libslic3r/GCode/GCodeBinarizer.cpp +++ b/src/libslic3r/GCode/GCodeBinarizer.cpp @@ -6,6 +6,10 @@ #include #endif // ENABLE_BINARIZED_GCODE_DEBUG +#if ENABLE_FILE_CONVERSION_INTERFACE +#include +#endif // ENABLE_FILE_CONVERSION_INTERFACE + extern "C" { #include #include @@ -441,32 +445,34 @@ static void unbinarize(const std::vector& src, std::string& dst) { } } // namespace MeatPack -std::string translate_result(BinaryGCode::EResult result) +std::string translate_result(EResult result) { switch (result) { - case BinaryGCode::EResult::Success: { return "Success"; } - case BinaryGCode::EResult::ReadError: { return "Read error"; } - case BinaryGCode::EResult::WriteError: { return "Write error"; } - case BinaryGCode::EResult::InvalidMagicNumber: { return "Invalid magic number"; } - case BinaryGCode::EResult::InvalidVersionNumber: { return "Invalid version number"; } - case BinaryGCode::EResult::InvalidChecksumType: { return "Invalid checksum type"; } - case BinaryGCode::EResult::InvalidBlockType: { return "Invalid block type"; } - case BinaryGCode::EResult::InvalidCompressionType: { return "Invalid compression type"; } - case BinaryGCode::EResult::InvalidMetadataEncodingType: { return "Invalid metadata encoding type"; } - case BinaryGCode::EResult::InvalidGCodeEncodingType: { return "Invalid gcode encoding type"; } - case BinaryGCode::EResult::DataCompressionError: { return "Data compression error"; } - case BinaryGCode::EResult::DataUncompressionError: { return "Data uncompression error"; } - case BinaryGCode::EResult::MetadataEncodingError: { return "Data encoding error"; } - case BinaryGCode::EResult::MetadataDecodingError: { return "Data decoding error"; } - case BinaryGCode::EResult::GCodeEncodingError: { return "GCode encoding error"; } - case BinaryGCode::EResult::GCodeDecodingError: { return "GCode decoding error"; } - case BinaryGCode::EResult::BlockNotFound: { return "Block not found"; } - case BinaryGCode::EResult::InvalidChecksum: { return "Invalid checksum"; } - case BinaryGCode::EResult::InvalidThumbnailFormat: { return "Invalid thumbnail format"; } - case BinaryGCode::EResult::InvalidThumbnailWidth: { return "Invalid thumbnail width"; } - case BinaryGCode::EResult::InvalidThumbnailHeight: { return "Invalid thumbnail height"; } - case BinaryGCode::EResult::InvalidThumbnailDataSize: { return "Invalid thumbnail data size"; } + 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(); } @@ -1658,5 +1664,200 @@ extern size_t block_content_size(const FileHeader& file_header, const BlockHeade #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 BinaryGCode diff --git a/src/libslic3r/GCode/GCodeBinarizer.hpp b/src/libslic3r/GCode/GCodeBinarizer.hpp index ab03f32563..236aee1c5f 100644 --- a/src/libslic3r/GCode/GCodeBinarizer.hpp +++ b/src/libslic3r/GCode/GCodeBinarizer.hpp @@ -6,6 +6,7 @@ #endif // _WIN32 #define ENABLE_CHECKSUM_BLOCK 0 +#define ENABLE_FILE_CONVERSION_INTERFACE 1 #include #include @@ -41,7 +42,9 @@ enum class EResult : uint16_t InvalidThumbnailFormat, InvalidThumbnailWidth, InvalidThumbnailHeight, - InvalidThumbnailDataSize + InvalidThumbnailDataSize, + InvalidBinaryGCodeFile, + InvalidSequenceOfBlocks }; // Returns a string description of the given result @@ -359,6 +362,20 @@ 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 BinaryGCode #endif // slic3r_GCode_GCodeBinarizer_hpp_ diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 2a4d8ae180..23ae8a1a24 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1349,6 +1349,17 @@ void MainFrame::init_menubar_as_editor() []() {return true; }, this); append_submenu(fileMenu, export_menu, wxID_ANY, _L("&Export"), ""); +#if ENABLE_BINARIZED_GCODE + wxMenu* convert_menu = new wxMenu(); + append_menu_item(convert_menu, wxID_ANY, _L("Convert ascii G-code to &binary") + dots, _L("Convert a G-code file from ascii to binary format"), + [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->convert_gcode_to_binary(); }, "convert_file", nullptr, + [this]() { return true; }, this); + append_menu_item(convert_menu, wxID_ANY, _L("Convert binary G-code to &ascii") + dots, _L("Convert a G-code file from binary to ascii format"), + [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->convert_gcode_to_ascii(); }, "convert_file", nullptr, + [this]() { return true; }, this); + append_submenu(fileMenu, convert_menu, wxID_ANY, _L("&Convert"), ""); +#endif // ENABLE_BINARIZED_GCODE + append_menu_item(fileMenu, wxID_ANY, _L("Ejec&t SD Card / Flash Drive") + dots + "\tCtrl+T", _L("Eject SD card / Flash drive after the G-code was exported to it."), [this](wxCommandEvent&) { if (m_plater) m_plater->eject_drive(); }, "eject_sd", nullptr, [this]() {return can_eject(); }, this); @@ -1621,13 +1632,22 @@ void MainFrame::init_menubar_as_gcodeviewer() _L("Reload the plater from disk"), [this](wxCommandEvent&) { m_plater->reload_gcode_from_disk(); }, "", nullptr, [this]() { return !m_plater->get_last_loaded_gcode().empty(); }, this); #endif // __APPLE__ +#if ENABLE_BINARIZED_GCODE + fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_ANY, _L("Convert ascii G-code to &binary") + dots, _L("Convert a G-code file from ascii to binary format"), + [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->convert_gcode_to_binary(); }, "convert_file", nullptr, + [this]() { return true; }, this); + append_menu_item(fileMenu, wxID_ANY, _L("Convert binary G-code to &ascii") + dots, _L("Convert a G-code file from binary to ascii format"), + [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->convert_gcode_to_ascii(); }, "convert_file", nullptr, + [this]() { return true; }, this); +#endif // ENABLE_BINARIZED_GCODE fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("Export &Toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"), [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, [this]() {return can_export_toolpaths(); }, this); append_menu_item(fileMenu, wxID_ANY, _L("Open &PrusaSlicer") + dots, _L("Open PrusaSlicer"), [](wxCommandEvent&) { start_new_slicer(); }, "", nullptr, - []() {return true; }, this); + []() { return true; }, this); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fcd97cdc15..2878e28a5a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5430,6 +5430,77 @@ void Plater::reload_gcode_from_disk() load_gcode(filename); } +#if ENABLE_BINARIZED_GCODE +static bool is_valid_binary_gcode(const wxString& filename) +{ + FILE* file = boost::nowide::fopen(into_u8(filename).c_str(), "rb"); + if (file == nullptr) + return false; + + const bool ret = BinaryGCode::is_valid_binary_gcode(*file); + fclose(file); + return ret; +} + +void Plater::convert_gcode_to_ascii() +{ + // Ask user for a gcode file name. + wxString input_file; + wxGetApp().load_gcode(this, input_file); + + class ScopedFile + { + public: + explicit ScopedFile(FILE* file) : m_file(file) {} + ~ScopedFile() { if (m_file != nullptr) fclose(m_file); } + private: + FILE* m_file{ nullptr }; + }; + + // Open source file + FILE* in_file = boost::nowide::fopen(into_u8(input_file).c_str(), "rb"); + if (in_file == nullptr) { + MessageDialog msg_dlg(this, _L("Unable to open the selected file."), _L("Error"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); + return; + } + ScopedFile scoped_in_file(in_file); + + // Set out filename + boost::filesystem::path path(into_u8(input_file)); + const std::string output_file = path.parent_path().string() + "/" + path.stem().string() + "_ascii" + path.extension().string(); + + // Open destination file + FILE* out_file = boost::nowide::fopen(output_file.c_str(), "wb"); + if (out_file == nullptr) { + MessageDialog msg_dlg(this, _L("Unable to open output file."), _L("Error"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); + return; + } + ScopedFile scoped_out_file(out_file); + + // Perform conversion + { + wxBusyCursor busy; + BinaryGCode::EResult res = BinaryGCode::from_binary_to_ascii(*in_file, *out_file, true); + if (res != BinaryGCode::EResult::Success) { + MessageDialog msg_dlg(this, _L(BinaryGCode::translate_result(res)), _L("Error converting gcode file"), wxICON_INFORMATION | wxOK); + msg_dlg.ShowModal(); + return; + } + } + + MessageDialog msg_dlg(this, _L("Succesfully created gcode ascii file:\n") + output_file, _L("Convert gcode file to ascii format"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); +} + +void Plater::convert_gcode_to_binary() +{ + MessageDialog msg_dlg(this, _L("Not implemented yet."), _L("Error"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); +} +#endif // ENABLE_BINARIZED_GCODE + void Plater::refresh_print() { p->preview->refresh_print(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 8eeabae428..342555040b 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -172,6 +172,10 @@ public: void load_gcode(); void load_gcode(const wxString& filename); void reload_gcode_from_disk(); +#if ENABLE_BINARIZED_GCODE + void convert_gcode_to_ascii(); + void convert_gcode_to_binary(); +#endif // ENABLE_BINARIZED_GCODE void refresh_print(); std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);