diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index b00f85ba70..d5edcb5719 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -20,8 +20,8 @@ # therefore, unfortunatelly, the installation cannot be copied/moved elsewhere without re-installing wxWidgets. # +cmake_minimum_required(VERSION 3.10) project(PrusaSlicer-deps) -cmake_minimum_required(VERSION 3.2) include(ExternalProject) include(ProcessorCount) @@ -62,6 +62,10 @@ if (NOT _is_multi AND NOT CMAKE_BUILD_TYPE) message(STATUS "Forcing CMAKE_BUILD_TYPE to Release as it was not specified.") endif () +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + cmake_policy(SET CMP0135 NEW) +endif () + function(prusaslicer_add_cmake_project projectname) cmake_parse_arguments(P_ARGS "" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN}) @@ -195,6 +199,8 @@ include(NanoSVG/NanoSVG.cmake) include(wxWidgets/wxWidgets.cmake) include(OCCT/OCCT.cmake) +include(LibBGCode/LibBGCode.cmake) + set(_dep_list dep_Boost dep_TBB @@ -210,6 +216,7 @@ set(_dep_list ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} + dep_LibBGCode ) # if (NOT MSVC) diff --git a/deps/LibBGCode/LibBGCode.cmake b/deps/LibBGCode/LibBGCode.cmake new file mode 100644 index 0000000000..076fe290a2 --- /dev/null +++ b/deps/LibBGCode/LibBGCode.cmake @@ -0,0 +1,34 @@ +set(LibBGCode_SOURCE_DIR "" CACHE PATH "Optionally specify local LibBGCode source directory") + +set(_source_dir_line + URL https://github.com/prusa3d/libbgcode/archive/49e7b47b7a9a7e9365613d3f90ad49d71a0e93d6.zip + URL_HASH SHA256=b9513ab2bbaf06a79c35c4d69226d211f1226ca625ce07c04f54b89b93e9bc75 +) + +if (LibBGCode_SOURCE_DIR) + set(_source_dir_line "SOURCE_DIR;${LibBGCode_SOURCE_DIR};BUILD_ALWAYS;ON") +endif () + +prusaslicer_add_cmake_project(LibBGCode_deps + ${_source_dir_line} + SOURCE_SUBDIR deps + DEPENDS dep_Boost ${ZLIB_PKG} + CMAKE_ARGS + -DDEP_DOWNLOAD_DIR:PATH=${DEP_DOWNLOAD_DIR} + -DDEP_CMAKE_OPTS:STRING=-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON + -DLibBGCode_Deps_SELECT_ALL:BOOL=OFF + -DLibBGCode_Deps_SELECT_heatshrink:BOOL=ON + -DDESTDIR=${DESTDIR} +) + +prusaslicer_add_cmake_project(LibBGCode + ${_source_dir_line} + DEPENDS dep_LibBGCode_deps + CMAKE_ARGS + -DLibBGCode_BUILD_TESTS:BOOL=OFF + -DLibBGCode_BUILD_CMD_TOOL:BOOL=OFF +) + +if (MSVC) + add_debug_dep(dep_LibBGCode) +endif () \ No newline at end of file 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/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 41ef6482aa..95728d63a7 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -14,6 +14,11 @@ if (TARGET OpenVDB::openvdb) set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp OpenVDBUtilsLegacy.hpp) endif() +find_package(LibBGCode REQUIRED COMPONENTS Convert) +slic3r_remap_configs(LibBGCode::bgcode_core RelWithDebInfo Release) +slic3r_remap_configs(LibBGCode::bgcode_binarize RelWithDebInfo Release) +slic3r_remap_configs(LibBGCode::bgcode_convert RelWithDebInfo Release) + set(SLIC3R_SOURCES pchheader.cpp pchheader.hpp @@ -571,6 +576,7 @@ target_link_libraries(libslic3r ZLIB::ZLIB JPEG::JPEG qoi + LibBGCode::bgcode_convert ) if (APPLE) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 28224d9baf..b6b967dba6 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -23,6 +23,10 @@ #include #include +#if ENABLE_BINARIZED_GCODE +#include +#endif // ENABLE_BINARIZED_GCODE + //FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion) // This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy(). #include "PrintConfig.hpp" @@ -720,11 +724,43 @@ void ConfigBase::setenv_() const } } -ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) +ConfigSubstitutions ConfigBase::load(const std::string& filename, ForwardCompatibilitySubstitutionRule compatibility_rule) { - return is_gcode_file(file) ? - this->load_from_gcode_file(file, compatibility_rule) : - this->load_from_ini(file, compatibility_rule); +#if ENABLE_BINARIZED_GCODE + enum class EFileType + { + Ini, + AsciiGCode, + BinaryGCode + }; + + EFileType file_type; + + if (is_gcode_file(filename)) { + FILE* file = boost::nowide::fopen(filename.c_str(), "rb"); + if (file == nullptr) + throw Slic3r::RuntimeError("Error opening the file: " + filename + "\n"); + + using namespace bgcode::core; + std::vector cs_buffer(65536); + file_type = (is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == EResult::Success) ? EFileType::BinaryGCode : EFileType::AsciiGCode; + fclose(file); + } + else + file_type = EFileType::Ini; + + switch (file_type) + { + case EFileType::Ini: { return this->load_from_ini(filename, compatibility_rule); } + case EFileType::AsciiGCode: { return this->load_from_gcode_file(filename, compatibility_rule);} + case EFileType::BinaryGCode: { return this->load_from_binary_gcode_file(filename, compatibility_rule);} + default: { throw Slic3r::RuntimeError("Invalid file: " + filename + "\n"); } + } +#else + return is_gcode_file(filename) ? + this->load_from_gcode_file(filename, compatibility_rule) : + this->load_from_ini(filename, compatibility_rule); +#endif // ENABLE_BINARIZED_GCODE } ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) @@ -928,15 +964,15 @@ private: }; // Load the config keys from the tail of a G-code file. -ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) +ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &filename, ForwardCompatibilitySubstitutionRule compatibility_rule) { // Read a 64k block from the end of the G-code. - boost::nowide::ifstream ifs(file, std::ifstream::binary); + boost::nowide::ifstream ifs(filename, std::ifstream::binary); // Look for Slic3r or PrusaSlicer header. // Look for the header across the whole file as the G-code may have been extended at the start by a post-processing script or the user. bool has_delimiters = false; { - static constexpr const char slic3r_gcode_header[] = "; generated by Slic3r "; + static constexpr const char slic3r_gcode_header[] = "; generated by Slic3r "; static constexpr const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer "; std::string header; bool header_found = false; @@ -986,7 +1022,7 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo break; } if (! end_found) - throw Slic3r::RuntimeError(format("Configuration block closing tag \"; prusaslicer_config = end\" not found when reading %1%", file)); + throw Slic3r::RuntimeError(format("Configuration block closing tag \"; prusaslicer_config = end\" not found when reading %1%", filename)); std::string key, value; while (reader.getline(line)) { if (line == "; prusaslicer_config = begin") { @@ -1009,7 +1045,7 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo } } if (! begin_found) - throw Slic3r::RuntimeError(format("Configuration block opening tag \"; prusaslicer_config = begin\" not found when reading %1%", file)); + throw Slic3r::RuntimeError(format("Configuration block opening tag \"; prusaslicer_config = begin\" not found when reading %1%", filename)); } else { @@ -1017,8 +1053,8 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo // Try a heuristics reading the G-code from back. ifs.seekg(0, ifs.end); auto file_length = ifs.tellg(); - auto data_length = std::min(65535, file_length - header_end_pos); - ifs.seekg(file_length - data_length, ifs.beg); + auto data_length = std::min(65535, file_length - header_end_pos); + ifs.seekg(file_length - data_length, ifs.beg); std::vector data(size_t(data_length) + 1, 0); ifs.read(data.data(), data_length); ifs.close(); @@ -1026,10 +1062,52 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo } if (key_value_pairs < 80) - throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", filename, key_value_pairs)); return std::move(substitutions_ctxt.substitutions); } +#if ENABLE_BINARIZED_GCODE +ConfigSubstitutions ConfigBase::load_from_binary_gcode_file(const std::string& filename, ForwardCompatibilitySubstitutionRule compatibility_rule) +{ + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + + FilePtr file{ boost::nowide::fopen(filename.c_str(), "rb") }; + if (file.f == nullptr) + throw Slic3r::RuntimeError(format("Error opening the file: %1%", filename)); + + using namespace bgcode::core; + using namespace bgcode::binarize; + std::vector cs_buffer(65536); + EResult res = is_valid_binary_gcode(*file.f, true, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError(format("The selected file is not a valid binary gcode.\nError: %1%", std::string(translate_result(res)))); + + FileHeader file_header; + res = read_header(*file.f, file_header, nullptr); + if (res != EResult::Success) + throw Slic3r::RuntimeError(format("Error while reading file '%1%': %2%", filename, std::string(translate_result(res)))); + + // searches for config block + BlockHeader block_header; + res = read_next_block_header(*file.f, file_header, block_header, EBlockType::SlicerMetadata, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError(format("Error while reading file '%1%': %2%", filename, std::string(translate_result(res)))); + if ((EBlockType)block_header.type != EBlockType::SlicerMetadata) + throw Slic3r::RuntimeError(format("Unable to find slicer metadata block in file: '%1%'", filename)); + SlicerMetadataBlock slicer_metadata_block; + res = slicer_metadata_block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + throw Slic3r::RuntimeError(format("Error while reading file '%1%': %2%", filename, std::string(translate_result(res)))); + + // extracts data from block + for (const auto& [key, value] : slicer_metadata_block.raw_data) { + this->set_deserialize(key, value, substitutions_ctxt); + } + + return std::move(substitutions_ctxt.substitutions); +} +#endif // ENABLE_BINARIZED_GCODE + void ConfigBase::save(const std::string &file) const { boost::nowide::ofstream c; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index c0ab5266f7..16469f6947 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2307,7 +2307,10 @@ public: // Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF. // Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment). ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule); - ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); + ConfigSubstitutions load_from_gcode_file(const std::string &filename, ForwardCompatibilitySubstitutionRule compatibility_rule); +#if ENABLE_BINARIZED_GCODE + ConfigSubstitutions load_from_binary_gcode_file(const std::string& filename, ForwardCompatibilitySubstitutionRule compatibility_rule); +#endif // ENABLE_BINARIZED_GCODE ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule); void save(const std::string &file) const; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 971ee8df6a..116077ba4c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -20,7 +20,10 @@ #include "ClipperUtils.hpp" #include "libslic3r.h" #include "LocalesUtils.hpp" -#include "libslic3r/format.hpp" +#include "format.hpp" +#if ENABLE_BINARIZED_GCODE +#include "libslic3r_version.h" +#endif // ENABLE_BINARIZED_GCODE #include #include @@ -556,6 +559,9 @@ void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorRes m_processor.initialize(path_tmp); m_processor.set_print(print); +#if ENABLE_BINARIZED_GCODE + m_processor.get_binary_data() = bgcode::binarize::BinaryData(); +#endif // ENABLE_BINARIZED_GCODE GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); if (! file.is_open()) throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); @@ -688,68 +694,105 @@ namespace DoExport { } // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. +#if ENABLE_BINARIZED_GCODE static std::string update_print_stats_and_format_filament_stats( const bool has_wipe_tower, - const WipeTowerData &wipe_tower_data, + const WipeTowerData &wipe_tower_data, + const FullPrintConfig &config, + const std::vector &extruders, + unsigned int initial_extruder_id, + PrintStatistics &print_statistics, + bool export_binary_data, + bgcode::binarize::BinaryData &binary_data) +#else + static std::string update_print_stats_and_format_filament_stats( + const bool has_wipe_tower, + const WipeTowerData &wipe_tower_data, const FullPrintConfig &config, - const std::vector &extruders, + const std::vector &extruders, unsigned int initial_extruder_id, PrintStatistics &print_statistics) +#endif // ENABLE_BINARIZED_GCODE { - std::string filament_stats_string_out; + std::string filament_stats_string_out; - print_statistics.clear(); + print_statistics.clear(); print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); print_statistics.initial_extruder_id = initial_extruder_id; std::vector filament_types; - if (! extruders.empty()) { - std::pair out_filament_used_mm ("; filament used [mm] = ", 0); - std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); - std::pair out_filament_used_g ("; filament used [g] = ", 0); - std::pair out_filament_cost ("; filament cost = ", 0); - for (const Extruder &extruder : extruders) { + if (! extruders.empty()) { +#if ENABLE_BINARIZED_GCODE + std::pair out_filament_used_mm(PrintStatistics::FilamentUsedMmMask + " ", 0); + std::pair out_filament_used_cm3(PrintStatistics::FilamentUsedCm3Mask + " ", 0); + std::pair out_filament_used_g(PrintStatistics::FilamentUsedGMask + " ", 0); + std::pair out_filament_cost(PrintStatistics::FilamentCostMask + " ", 0); +#else + std::pair out_filament_used_mm("; filament used [mm] = ", 0); + std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); + std::pair out_filament_used_g ("; filament used [g] = ", 0); + std::pair out_filament_cost("; filament cost = ", 0); +#endif // ENABLE_BINARIZED_GCODE + for (const Extruder &extruder : extruders) { print_statistics.printing_extruders.emplace_back(extruder.id()); filament_types.emplace_back(config.filament_type.get_at(extruder.id())); - double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); - double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter - double filament_weight = extruded_volume * extruder.filament_density() * 0.001; - double filament_cost = filament_weight * extruder.filament_cost() * 0.001; + double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter + double filament_weight = extruded_volume * extruder.filament_density() * 0.001; + double filament_cost = filament_weight * extruder.filament_cost() * 0.001; auto append = [&extruder](std::pair &dst, const char *tmpl, double value) { assert(is_decimal_separator_point()); - while (dst.second < extruder.id()) { - // Fill in the non-printing extruders with zeros. - dst.first += (dst.second > 0) ? ", 0" : "0"; - ++ dst.second; - } - if (dst.second > 0) - dst.first += ", "; - char buf[64]; - sprintf(buf, tmpl, value); - dst.first += buf; - ++ dst.second; - }; - append(out_filament_used_mm, "%.2lf", used_filament); - append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); - if (filament_weight > 0.) { - print_statistics.total_weight = print_statistics.total_weight + filament_weight; - append(out_filament_used_g, "%.2lf", filament_weight); - if (filament_cost > 0.) { - print_statistics.total_cost = print_statistics.total_cost + filament_cost; - append(out_filament_cost, "%.2lf", filament_cost); - } - } - print_statistics.total_used_filament += used_filament; - print_statistics.total_extruded_volume += extruded_volume; - print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; - print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; - } - filament_stats_string_out += out_filament_used_mm.first; - filament_stats_string_out += "\n" + out_filament_used_cm3.first; - if (out_filament_used_g.second) - filament_stats_string_out += "\n" + out_filament_used_g.first; - if (out_filament_cost.second) - filament_stats_string_out += "\n" + out_filament_cost.first; + while (dst.second < extruder.id()) { + // Fill in the non-printing extruders with zeros. + dst.first += (dst.second > 0) ? ", 0" : "0"; + ++ dst.second; + } + if (dst.second > 0) + dst.first += ", "; + char buf[64]; + sprintf(buf, tmpl, value); + dst.first += buf; + ++ dst.second; + }; +#if ENABLE_BINARIZED_GCODE + if (!export_binary_data) { +#endif // ENABLE_BINARIZED_GCODE + append(out_filament_used_mm, "%.2lf", used_filament); + append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); +#if ENABLE_BINARIZED_GCODE + } +#endif // ENABLE_BINARIZED_GCODE + if (filament_weight > 0.) { + print_statistics.total_weight = print_statistics.total_weight + filament_weight; +#if ENABLE_BINARIZED_GCODE + if (!export_binary_data) +#endif // ENABLE_BINARIZED_GCODE + append(out_filament_used_g, "%.2lf", filament_weight); + if (filament_cost > 0.) { + print_statistics.total_cost = print_statistics.total_cost + filament_cost; +#if ENABLE_BINARIZED_GCODE + if (!export_binary_data) +#endif // ENABLE_BINARIZED_GCODE + append(out_filament_cost, "%.2lf", filament_cost); + } + } + print_statistics.total_used_filament += used_filament; + print_statistics.total_extruded_volume += extruded_volume; + print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; + print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; + } +#if ENABLE_BINARIZED_GCODE + if (!export_binary_data) { +#endif // ENABLE_BINARIZED_GCODE + filament_stats_string_out += out_filament_used_mm.first; + filament_stats_string_out += "\n" + out_filament_used_cm3.first; + if (out_filament_used_g.second) + filament_stats_string_out += "\n" + out_filament_used_g.first; + if (out_filament_cost.second) + filament_stats_string_out += "\n" + out_filament_cost.first; +#if ENABLE_BINARIZED_GCODE + } +#endif // ENABLE_BINARIZED_GCODE print_statistics.initial_filament_type = config.filament_type.get_at(initial_extruder_id); std::sort(filament_types.begin(), filament_types.end()); print_statistics.printing_filament_types = filament_types.front(); @@ -830,6 +873,94 @@ static inline GCode::SmoothPathCache smooth_path_interpolate_global(const Print& void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) { +#if ENABLE_BINARIZED_GCODE + const bool export_to_binary_gcode = print.full_print_config().option("gcode_binary")->value; + // if exporting gcode in binary format: + // we generate here the data to be passed to the post-processor, who is responsible to export them to file + // 1) generate the thumbnails + // 2) collect the config data + if (export_to_binary_gcode) { + bgcode::binarize::BinaryData& binary_data = m_processor.get_binary_data(); + + // Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format". + // If "thumbnails_format" is not defined, export to PNG. + if (std::vector> thumbnails = GCodeThumbnails::make_thumbnail_list(print.full_print_config()); + ! thumbnails.empty()) + GCodeThumbnails::generate_binary_thumbnails( + thumbnail_cb, binary_data.thumbnails, thumbnails, + [&print]() { print.throw_if_canceled(); }); + + // file data + binary_data.file_metadata.raw_data.emplace_back("Producer", std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION)); + + // config data + encode_full_config(print, binary_data.slicer_metadata.raw_data); + + // printer data + binary_data.printer_metadata.raw_data.emplace_back("printer_model", print.config().printer_model.value); // duplicated into config data + std::string filament_types_str; + for (size_t i = 0; i < print.config().filament_type.values.size(); ++i) { + filament_types_str += print.config().filament_type.values[i]; + if (i < print.config().filament_type.values.size() - 1) + filament_types_str += ";"; + } + binary_data.printer_metadata.raw_data.emplace_back("filament_type", filament_types_str); // duplicated into config data + char buf[1024]; + std::string nozzle_diameters_str; + for (size_t i = 0; i < print.config().nozzle_diameter.values.size(); ++i) { + sprintf(buf, i < print.config().nozzle_diameter.values.size() - 1 ? "%.2g," : "%.2g", print.config().nozzle_diameter.values[i]); + nozzle_diameters_str += buf; + } + binary_data.printer_metadata.raw_data.emplace_back("nozzle_diameter", nozzle_diameters_str); // duplicated into config data + std::string bed_temperatures_str; + for (size_t i = 0; i < print.config().bed_temperature.values.size(); ++i) { + sprintf(buf, i < print.config().bed_temperature.values.size() - 1 ? "%d," : "%d", print.config().bed_temperature.values[i]); + bed_temperatures_str += buf; + } + binary_data.printer_metadata.raw_data.emplace_back("bed_temperature", bed_temperatures_str); // duplicated into config data + + const DynamicPrintConfig& cfg = print.full_print_config(); + if (auto opt = cfg.option("brim_width"); opt != nullptr) { + sprintf(buf, "%.2g", dynamic_cast(opt)->value); + binary_data.printer_metadata.raw_data.emplace_back("brim_width", buf); // duplicated into config data + } + if (auto opt = cfg.option("fill_density"); opt != nullptr) { + sprintf(buf, "%.2g%%", dynamic_cast(opt)->value); + binary_data.printer_metadata.raw_data.emplace_back("fill_density", buf); // duplicated into config data + } + if (auto opt = cfg.option("layer_height"); opt != nullptr) { + sprintf(buf, "%.2g", dynamic_cast(opt)->value); + binary_data.printer_metadata.raw_data.emplace_back("layer_height", buf); // duplicated into config data + } + if (auto opt = cfg.option("temperature"); opt != nullptr) { + auto values = dynamic_cast(opt)->values; + std::string temperatures_str; + for (size_t i = 0; i < values.size(); ++i) { + sprintf(buf, i < values.size() - 1 ? "%d," : "%d", values[i]); + temperatures_str += buf; + } + binary_data.printer_metadata.raw_data.emplace_back("temperature", temperatures_str); // duplicated into config data + } + if (auto opt = cfg.option("ironing"); opt != nullptr) + binary_data.printer_metadata.raw_data.emplace_back("ironing", dynamic_cast(opt)->value ? "1" : "0"); // duplicated into config data + if (auto opt = cfg.option("support_material"); opt != nullptr) + binary_data.printer_metadata.raw_data.emplace_back("support_material", dynamic_cast(opt)->value ? "1" : "0"); // duplicated into config data + if (auto opt = cfg.option("extruder_colour"); opt != nullptr) { + auto values = dynamic_cast(opt)->values; + std::string extruder_colours_str; + if (values.size() == 1 && values.front().empty()) + extruder_colours_str = "\"\""; + else { + for (size_t i = 0; i < values.size(); ++i) { + sprintf(buf, i < values.size() - 1 ? "%s;" : "%s", values[i].c_str()); + extruder_colours_str += buf; + } + } + binary_data.printer_metadata.raw_data.emplace_back("extruder_colour", extruder_colours_str); // duplicated into config data + } + } +#endif // ENABLE_BINARIZED_GCODE + // modifies m_silent_time_estimator_enabled DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled); @@ -883,42 +1014,24 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail this->m_avoid_crossing_curled_overhangs.init_bed_shape(get_bed_shape(print.config())); } - // Write information on the generator. - file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); +#if ENABLE_BINARIZED_GCODE + if (!export_to_binary_gcode) +#endif // ENABLE_BINARIZED_GCODE + // Write information on the generator. + file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - // ??? Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format". - // ??? If "thumbnails_format" is not defined, export to PNG. - - // generate thumbnails data to process it - - std::vector> thumbnails_list; - if (const auto thumbnails_value = print.full_print_config().option("thumbnails")) { - std::string str = thumbnails_value->value; - std::istringstream is(str); - std::string point_str; - while (std::getline(is, point_str, ',')) { - Vec2d point(Vec2d::Zero()); - GCodeThumbnailsFormat format; - std::istringstream iss(point_str); - std::string coord_str; - if (std::getline(iss, coord_str, 'x')) { - std::istringstream(coord_str) >> point(0); - if (std::getline(iss, coord_str, '/')) { - std::istringstream(coord_str) >> point(1); - std::string ext_str; - if (std::getline(iss, ext_str, '/')) - format = ext_str == "JPG" ? GCodeThumbnailsFormat::JPG : - ext_str == "QOI" ? GCodeThumbnailsFormat::QOI :GCodeThumbnailsFormat::PNG; - } - } - thumbnails_list.emplace_back(std::make_pair(format, point)); - } +#if ENABLE_BINARIZED_GCODE + // if exporting gcode in ascii format, generate the thumbnails here + if (! export_to_binary_gcode) { +#endif // ENABLE_BINARIZED_GCODE + if (std::vector> thumbnails = GCodeThumbnails::make_thumbnail_list(print.full_print_config()); + ! thumbnails.empty()) + GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, thumbnails, + [&file](const char* sz) { file.write(sz); }, + [&print]() { print.throw_if_canceled(); }); +#if ENABLE_BINARIZED_GCODE } - - if (!thumbnails_list.empty()) - GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, thumbnails_list, - [&file](const char* sz) { file.write(sz); }, - [&print]() { print.throw_if_canceled(); }); +#endif // ENABLE_BINARIZED_GCODE // Write notes (content of the Print Settings tab -> Notes) { @@ -940,20 +1053,26 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail const double layer_height = first_object->config().layer_height.value; assert(! print.config().first_layer_height.percent); const double first_layer_height = print.config().first_layer_height.value; - for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) { - const PrintRegion ®ion = print.get_print_region(region_id); - file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); - file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); - file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); - file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); - file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); - if (print.has_support_material()) - file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); - if (print.config().first_layer_extrusion_width.value > 0) - file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width()); - file.write_format("\n"); +#if ENABLE_BINARIZED_GCODE + if (!export_to_binary_gcode) { +#endif // ENABLE_BINARIZED_GCODE + for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) { + const PrintRegion ®ion = print.get_print_region(region_id); + file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); + file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); + file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); + file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); + file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); + if (print.has_support_material()) + file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); + if (print.config().first_layer_extrusion_width.value > 0) + file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width()); + file.write_format("\n"); + } + print.throw_if_canceled(); +#if ENABLE_BINARIZED_GCODE } - print.throw_if_canceled(); +#endif // ENABLE_BINARIZED_GCODE // adds tags for time estimators if (print.config().remaining_times.value) @@ -1267,31 +1386,72 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail print.throw_if_canceled(); // Get filament stats. - file.write(DoExport::update_print_stats_and_format_filament_stats( - // Const inputs +#if ENABLE_BINARIZED_GCODE + const std::string filament_stats_string_out = DoExport::update_print_stats_and_format_filament_stats( + // Const inputs has_wipe_tower, print.wipe_tower_data(), this->config(), m_writer.extruders(), initial_extruder_id, // Modifies - print.m_print_statistics)); - file.write("\n"); - file.write_format("; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight); - file.write_format("; total filament cost = %.2lf\n", print.m_print_statistics.total_cost); - if (print.m_print_statistics.total_toolchanges > 0) - file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); - file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); + print.m_print_statistics, + export_to_binary_gcode, + m_processor.get_binary_data() + ); - // Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end. - // The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer. - { - file.write("\n; prusaslicer_config = begin\n"); - std::string full_config; - append_full_config(print, full_config); - if (!full_config.empty()) - file.write(full_config); - file.write("; prusaslicer_config = end\n"); + if (!export_to_binary_gcode) + file.write(filament_stats_string_out); + + if (export_to_binary_gcode) { + bgcode::binarize::BinaryData& binary_data = m_processor.get_binary_data(); + if (print.m_print_statistics.total_toolchanges > 0) + binary_data.print_metadata.raw_data.emplace_back("total toolchanges", std::to_string(print.m_print_statistics.total_toolchanges)); + char buf[1024]; + sprintf(buf, "%.2lf", m_max_layer_z); + binary_data.printer_metadata.raw_data.emplace_back("max_layer_z", buf); } + else { +#else + file.write(DoExport::update_print_stats_and_format_filament_stats( + // Const inputs + has_wipe_tower, print.wipe_tower_data(), + this->config(), + m_writer.extruders(), + initial_extruder_id, + // Modifies + print.m_print_statistics)); +#endif // ENABLE_BINARIZED_GCODE +#if ENABLE_BINARIZED_GCODE + // if exporting gcode in ascii format, statistics export is done here +#endif // ENABLE_BINARIZED_GCODE + file.write("\n"); +#if ENABLE_BINARIZED_GCODE + file.write_format(PrintStatistics::TotalFilamentUsedGValueMask.c_str(), print.m_print_statistics.total_weight); + file.write_format(PrintStatistics::TotalFilamentCostValueMask.c_str(), print.m_print_statistics.total_cost); +#else + file.write_format("; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight); + file.write_format("; total filament cost = %.2lf\n", print.m_print_statistics.total_cost); +#endif // ENABLE_BINARIZED_GCODE + if (print.m_print_statistics.total_toolchanges > 0) + file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); + +#if ENABLE_BINARIZED_GCODE + // if exporting gcode in ascii format, config export is done here +#endif // ENABLE_BINARIZED_GCODE + // Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end. + // The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer. + { + file.write("\n; prusaslicer_config = begin\n"); + std::string full_config; + append_full_config(print, full_config); + if (!full_config.empty()) + file.write(full_config); + file.write("; prusaslicer_config = end\n"); + } +#if ENABLE_BINARIZED_GCODE + } +#endif // ENABLE_BINARIZED_GCODE print.throw_if_canceled(); } @@ -2400,6 +2560,13 @@ void GCodeGenerator::apply_print_config(const PrintConfig &print_config) void GCodeGenerator::append_full_config(const Print &print, std::string &str) { +#if ENABLE_BINARIZED_GCODE + std::vector> config; + encode_full_config(print, config); + for (const auto& [key, value] : config) { + str += "; " + key + " = " + value + "\n"; + } +#else const DynamicPrintConfig &cfg = print.full_print_config(); // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. static constexpr auto banned_keys = { @@ -2417,8 +2584,35 @@ void GCodeGenerator::append_full_config(const Print &print, std::string &str) for (const std::string &key : cfg.keys()) if (! is_banned(key) && ! cfg.option(key)->is_nil()) str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; +#endif // ENABLE_BINARIZED_GCODE } +#if ENABLE_BINARIZED_GCODE +void GCodeGenerator::encode_full_config(const Print& print, std::vector>& config) +{ + const DynamicPrintConfig& cfg = print.full_print_config(); + // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. + static constexpr auto banned_keys = { + "compatible_printers"sv, + "compatible_prints"sv, + //FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed. + "print_host"sv, + "printhost_apikey"sv, + "printhost_cafile"sv + }; + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); + auto is_banned = [](const std::string& key) { + return std::binary_search(banned_keys.begin(), banned_keys.end(), key); + }; + config.reserve(config.size() + cfg.keys().size()); + for (const std::string& key : cfg.keys()) { + if (!is_banned(key) && !cfg.option(key)->is_nil()) + config.emplace_back(key, cfg.opt_serialize(key)); + } + config.shrink_to_fit(); +} +#endif // ENABLE_BINARIZED_GCODE + void GCodeGenerator::set_extruders(const std::vector &extruder_ids) { m_writer.set_extruders(extruder_ids); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index c3b5bc8c11..1d602a9260 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -30,6 +30,12 @@ #include #include +//#include "GCode/PressureEqualizer.hpp" + +#if ENABLE_BINARIZED_GCODE +#include +#endif // ENABLE_BINARIZED_GCODE + namespace Slic3r { // Forward declarations. @@ -148,6 +154,10 @@ public: // append full config to the given string static void append_full_config(const Print& print, std::string& str); +#if ENABLE_BINARIZED_GCODE + // translate full config into a list of items + static void encode_full_config(const Print& print, std::vector>& config); +#endif // ENABLE_BINARIZED_GCODE // Object and support extrusions of the same PrintObject at the same print_z. // public, so that it could be accessed by free helper functions from GCode.cpp diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index ecd59987d8..2bab2887cb 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -26,6 +26,10 @@ #endif #include +#if ENABLE_BINARIZED_GCODE_WIN_DEBUG +#include +#include +#endif // ENABLE_BINARIZED_GCODE_WIN_DEBUG static const float DEFAULT_TOOLPATH_WIDTH = 0.4f; static const float DEFAULT_TOOLPATH_HEIGHT = 0.2f; @@ -64,6 +68,21 @@ const std::vector GCodeProcessor::Reserved_Tags = { const float GCodeProcessor::Wipe_Width = 0.05f; const float GCodeProcessor::Wipe_Height = 0.05f; +#if ENABLE_BINARIZED_GCODE +bgcode::binarize::BinarizerConfig GCodeProcessor::s_binarizer_config{ + { + bgcode::core::ECompressionType::None, // file metadata + bgcode::core::ECompressionType::None, // printer metadata + bgcode::core::ECompressionType::Deflate, // print metadata + bgcode::core::ECompressionType::Deflate, // slicer metadata + bgcode::core::ECompressionType::Heatshrink_12_4, // gcode + }, + bgcode::core::EGCodeEncodingType::MeatPackComments, + bgcode::core::EMetadataEncodingType::INI, + bgcode::core::EChecksumType::CRC32 +}; +#endif // ENABLE_BINARIZED_GCODE + #if ENABLE_GCODE_VIEWER_DATA_CHECKING const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:"; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -455,6 +474,9 @@ void GCodeProcessorResult::reset() { #else void GCodeProcessorResult::reset() { +#if ENABLE_BINARIZED_GCODE + is_binary_file = false; +#endif // ENABLE_BINARIZED_GCODE moves.clear(); lines_ends.clear(); bed_shape = Pointfs(); @@ -553,6 +575,11 @@ void GCodeProcessor::apply_config(const PrintConfig& config) { m_parser.apply_config(config); +#if ENABLE_BINARIZED_GCODE + m_binarizer.set_enabled(config.gcode_binary); + m_result.is_binary_file = config.gcode_binary; +#endif // ENABLE_BINARIZED_GCODE + m_producer = EProducer::PrusaSlicer; m_flavor = config.gcode_flavor; @@ -1022,6 +1049,25 @@ static inline const char* remove_eols(const char *begin, const char *end) { // Load a G-code into a stand-alone G-code viewer. // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) +#if ENABLE_BINARIZED_GCODE +{ + FILE* file = boost::nowide::fopen(filename.c_str(), "rb"); + if (file == nullptr) + throw Slic3r::RuntimeError("Error opening the file: " + filename + "\n"); + + using namespace bgcode::core; + std::vector cs_buffer(65536); + const bool is_binary = is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == EResult::Success; + fclose(file); + + if (is_binary) + process_binary_file(filename, cancel_callback); + else + process_ascii_file(filename, cancel_callback); +} + +void GCodeProcessor::process_ascii_file(const std::string& filename, std::function cancel_callback) +#endif // ENABLE_BINARIZED_GCODE { CNumericLocalesSetter locales_setter; @@ -1072,6 +1118,9 @@ void GCodeProcessor::process_file(const std::string& filename, std::functionfinalize(false); } +#if ENABLE_BINARIZED_GCODE +static void update_lines_ends_and_out_file_pos(const std::string& out_string, std::vector& lines_ends, size_t* out_file_pos) +{ + for (size_t i = 0; i < out_string.size(); ++i) { + if (out_string[i] == '\n') + lines_ends.emplace_back((out_file_pos != nullptr) ? *out_file_pos + i + 1 : i + 1); + } + if (out_file_pos != nullptr) + *out_file_pos += out_string.size(); +} + +void GCodeProcessor::process_binary_file(const std::string& filename, std::function cancel_callback) +{ +#if ENABLE_GCODE_VIEWER_STATISTICS + m_start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + FilePtr file{ boost::nowide::fopen(filename.c_str(), "rb") }; + if (file.f == nullptr) + throw Slic3r::RuntimeError("Unable to open file: " + filename + "\n"); + + fseek(file.f, 0, SEEK_END); + const long file_size = ftell(file.f); + rewind(file.f); + + // read file header + using namespace bgcode::core; + using namespace bgcode::binarize; + FileHeader file_header; + EResult res = read_header(*file.f, file_header, nullptr); + if (res != EResult::Success) + throw Slic3r::RuntimeError("File: " + filename + "does not contain a valid binary gcode\n Error: " + + std::string(translate_result(res)) + "\n"); + + // read file metadata block + BlockHeader block_header; + std::vector cs_buffer(65536); + res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + if ((EBlockType)block_header.type != EBlockType::FileMetadata) + throw Slic3r::RuntimeError("Unable to find file metadata block in file: " + filename + "\n"); + FileMetadataBlock file_metadata_block; + res = file_metadata_block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + 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"; }); + if (producer_it != file_metadata_block.raw_data.end() && boost::starts_with(producer_it->second, std::string(SLIC3R_APP_NAME))) + m_producer = EProducer::PrusaSlicer; + else + m_producer = EProducer::Unknown; + + // read printer metadata block + res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + if ((EBlockType)block_header.type != EBlockType::PrinterMetadata) + throw Slic3r::RuntimeError("Unable to find printer metadata block in file: " + filename + "\n"); + PrinterMetadataBlock printer_metadata_block; + res = printer_metadata_block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); +#if ENABLE_BINARIZED_GCODE_WIN_DEBUG + OutputDebugStringA("Printer metadata:\n"); + for (const auto& [key, value] : printer_metadata_block.raw_data) { + OutputDebugStringA(key.c_str()); + OutputDebugStringA("->"); + OutputDebugStringA(value.c_str()); + OutputDebugStringA("\n"); + } +#endif // ENABLE_BINARIZED_GCODE_WIN_DEBUG + + // read thumbnail blocks + res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + + while ((EBlockType)block_header.type == EBlockType::Thumbnail) { + ThumbnailBlock thumbnail_block; + res = thumbnail_block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); +#if ENABLE_BINARIZED_GCODE_DEBUG + if (thumbnail_block.data.size() > 0) { + auto format_filename = [](const std::string& stem, const ThumbnailBlock& block) { + std::string ret = stem + "_" + std::to_string(block.params.width) + "x" + std::to_string(block.params.height); + switch ((EThumbnailFormat)block.params.format) + { + case EThumbnailFormat::PNG: { ret += ".png"; break; } + case EThumbnailFormat::JPG: { ret += ".jpg"; break; } + case EThumbnailFormat::QOI: { ret += ".qoi"; break; } + } + return ret; + }; + + const boost::filesystem::path path(filename); + const std::string out_path = path.parent_path().string(); + const std::string out_filename = out_path + "\\" + format_filename(path.stem().string(), thumbnail_block); + FILE* outfile = boost::nowide::fopen(out_filename.c_str(), "wb"); + if (outfile != nullptr) { + fwrite((const void*)thumbnail_block.data.data(), 1, thumbnail_block.data.size(), outfile); + fclose(outfile); + } + } +#endif // ENABLE_BINARIZED_GCODE_DEBUG + + res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + } + + // read print metadata block + if ((EBlockType)block_header.type != EBlockType::PrintMetadata) + throw Slic3r::RuntimeError("Unable to find print metadata block in file: " + filename + "\n"); + PrintMetadataBlock print_metadata_block; + res = print_metadata_block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); +#if ENABLE_BINARIZED_GCODE_WIN_DEBUG + OutputDebugStringA("Print metadata:\n"); + for (const auto& [key, value] : print_metadata_block.raw_data) { + OutputDebugStringA(key.c_str()); + OutputDebugStringA("->"); + OutputDebugStringA(value.c_str()); + OutputDebugStringA("\n"); + } +#endif // ENABLE_BINARIZED_GCODE_WIN_DEBUG + + // read slicer metadata block + res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + if ((EBlockType)block_header.type != EBlockType::SlicerMetadata) + throw Slic3r::RuntimeError("Unable to find slicer metadata block in file: " + filename + "\n"); + SlicerMetadataBlock slicer_metadata_block; + res = slicer_metadata_block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); +#if ENABLE_BINARIZED_GCODE_WIN_DEBUG + OutputDebugStringA("Slicer metadata:\n"); + for (const auto& [key, value] : slicer_metadata_block.raw_data) { + OutputDebugStringA(key.c_str()); + OutputDebugStringA("->"); + OutputDebugStringA(value.c_str()); + OutputDebugStringA("\n"); + } +#endif // ENABLE_BINARIZED_GCODE_WIN_DEBUG + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + std::string str; + for (const auto& [key, value] : slicer_metadata_block.raw_data) { + str += key + " = " + value + "\n"; + } + // Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code. + // Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config, + // thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways. + config.load_from_ini_string(str, ForwardCompatibilitySubstitutionRule::EnableSilent); + apply_config(config); + + m_result.filename = filename; + m_result.is_binary_file = true; + m_result.id = ++s_result_id; + initialize_result_moves(); + + // read gcodes block + res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + if ((EBlockType)block_header.type != EBlockType::GCode) + throw Slic3r::RuntimeError("Unable to find gcode block in file: " + filename + "\n"); + while ((EBlockType)block_header.type == EBlockType::GCode) { + GCodeBlock block; + res = block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + + std::vector& lines_ends = m_result.lines_ends.emplace_back(std::vector()); + update_lines_ends_and_out_file_pos(block.raw_data, lines_ends, nullptr); + + m_parser.parse_buffer(block.raw_data, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + this->process_gcode_line(line, true); + }); + + if (ftell(file.f) == file_size) + break; + + res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + if (res != EResult::Success) + throw Slic3r::RuntimeError("Error while reading file '" + filename + "': " + std::string(translate_result(res)) + "\n"); + } + + // Don't post-process the G-code to update time stamps. + this->finalize(false); +} +#endif // ENABLE_BINARIZED_GCODE + void GCodeProcessor::initialize(const std::string& filename) { assert(is_decimal_separator_point()); @@ -1131,7 +1377,7 @@ void GCodeProcessor::finalize(bool perform_post_process) m_used_filaments.process_caches(this); - update_estimated_times_stats(); + update_estimated_statistics(); #if ENABLE_GCODE_VIEWER_DATA_CHECKING std::cout << "\n"; @@ -3457,10 +3703,72 @@ void GCodeProcessor::post_process() // temporary file to contain modified gcode std::string out_path = m_result.filename + ".postprocess"; FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; - if (out.f == nullptr) { + if (out.f == nullptr) throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nCannot open file for writing.\n")); + +#if ENABLE_BINARIZED_GCODE + std::vector filament_mm(m_result.extruders_count, 0.0); + std::vector filament_cm3(m_result.extruders_count, 0.0); + std::vector filament_g(m_result.extruders_count, 0.0); + std::vector filament_cost(m_result.extruders_count, 0.0); + + double filament_total_g = 0.0; + double filament_total_cost = 0.0; + + for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) { + filament_mm[id] = volume / (static_cast(M_PI) * sqr(0.5 * m_result.filament_diameters[id])); + filament_cm3[id] = volume * 0.001; + filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]); + filament_cost[id] = filament_g[id] * double(m_result.filament_cost[id]) * 0.001; + filament_total_g += filament_g[id]; + filament_total_cost += filament_cost[id]; } + if (m_binarizer.is_enabled()) { + // update print metadata + auto stringify = [](const std::vector& values) { + std::string ret; + char buf[1024]; + for (size_t i = 0; i < values.size(); ++i) { + sprintf(buf, i < values.size() - 1 ? "%.2lf, " : "%.2lf", values[i]); + ret += buf; + } + return ret; + }; + + // update binary data + bgcode::binarize::BinaryData& binary_data = m_binarizer.get_binary_data(); + binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedMm, stringify(filament_mm)); + binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedCm3, stringify(filament_cm3)); + binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedG, stringify(filament_g)); + binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost)); + binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentUsedG, stringify({ filament_total_g })); + binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentCost, stringify({ filament_total_cost })); + + binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedMm, stringify(filament_mm)); // duplicated into print metadata + binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedG, stringify(filament_g)); // duplicated into print metadata + binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost)); // duplicated into print metadata + binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedCm3, stringify(filament_cm3)); // duplicated into print metadata + + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + const TimeMachine& machine = m_time_processor.machines[i]; + PrintEstimatedStatistics::ETimeMode mode = static_cast(i); + if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { + char buf[128]; + sprintf(buf, "(%s mode)", (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent"); + binary_data.print_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time)); + binary_data.print_metadata.raw_data.emplace_back("estimated first layer printing time " + std::string(buf), get_time_dhms(machine.layers_time.empty() ? 0.f : machine.layers_time.front())); + + binary_data.printer_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time)); + } + } + + const bgcode::core::EResult res = m_binarizer.initialize(*out.f, s_binarizer_config); + if (res != bgcode::core::EResult::Success) + throw Slic3r::RuntimeError(std::string("Unable to initialize the gcode binarizer.\n")); + } +#endif // ENABLE_BINARIZED_GCODE + auto time_in_minutes = [](float time_in_seconds) { assert(time_in_seconds >= 0.f); return int((time_in_seconds + 0.5f) / 60.0f); @@ -3577,13 +3885,26 @@ void GCodeProcessor::post_process() size_t m_curr_g1_id{ 0 }; size_t m_out_file_pos{ 0 }; +#if ENABLE_BINARIZED_GCODE + bgcode::binarize::Binarizer& m_binarizer; +#endif // ENABLE_BINARIZED_GCODE + public: +#if ENABLE_BINARIZED_GCODE + ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, TimeMachine& machine) +#ifndef NDEBUG + : m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {} +#else + : m_binarizer(binarizer), m_write_type(type), m_machine(machine) {} +#endif // NDEBUG +#else ExportLines(EWriteType type, TimeMachine& machine) #ifndef NDEBUG : m_statistics(*this), m_write_type(type), m_machine(machine) {} #else : m_write_type(type), m_machine(machine) {} #endif // NDEBUG +#endif // ENABLE_BINARIZED_GCODE void update(size_t lines_counter, size_t g1_lines_counter) { m_gcode_lines_map.push_back({ lines_counter, 0 }); @@ -3694,7 +4015,18 @@ void GCodeProcessor::post_process() } } +#if ENABLE_BINARIZED_GCODE + if (m_binarizer.is_enabled()) { + if (m_binarizer.append_gcode(out_string) != bgcode::core::EResult::Success) + throw Slic3r::RuntimeError(std::string("Error while sending gcode to the binarizer.\n")); + } + else { + write_to_file(out, out_string, result, out_path); + update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos); + } +#else write_to_file(out, out_string, result, out_path); +#endif // ENABLE_BINARIZED_GCODE } // flush the current content of the cache to file @@ -3710,7 +4042,18 @@ void GCodeProcessor::post_process() m_statistics.remove_all_lines(); #endif // NDEBUG +#if ENABLE_BINARIZED_GCODE + if (m_binarizer.is_enabled()) { + if (m_binarizer.append_gcode(out_string) != bgcode::core::EResult::Success) + throw Slic3r::RuntimeError(std::string("Error while sending gcode to the binarizer.\n")); + } + else { + write_to_file(out, out_string, result, out_path); + update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos); + } +#else write_to_file(out, out_string, result, out_path); +#endif // ENABLE_BINARIZED_GCODE } void synchronize_moves(GCodeProcessorResult& result) const { @@ -3729,22 +4072,33 @@ void GCodeProcessor::post_process() private: void write_to_file(FilePtr& out, const std::string& out_string, GCodeProcessorResult& result, const std::string& out_path) { if (!out_string.empty()) { - fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f); - if (ferror(out.f)) { - out.close(); - boost::nowide::remove(out_path.c_str()); - throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nIs the disk full?\n")); +#if ENABLE_BINARIZED_GCODE + if (!m_binarizer.is_enabled()) { +#endif // ENABLE_BINARIZED_GCODE + fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f); + if (ferror(out.f)) { + out.close(); + boost::nowide::remove(out_path.c_str()); + throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nIs the disk full?\n")); + } +#if ENABLE_BINARIZED_GCODE } +#else for (size_t i = 0; i < out_string.size(); ++i) { if (out_string[i] == '\n') result.lines_ends.emplace_back(m_out_file_pos + i + 1); } m_out_file_pos += out_string.size(); +#endif // ENABLE_BINARIZED_GCODE } } }; +#if ENABLE_BINARIZED_GCODE + ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]); +#else ExportLines export_lines(m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]); +#endif // ENABLE_BINARIZED_GCODE // replace placeholder lines with the proper final value // gcode_line is in/out parameter, to reduce expensive memory allocation @@ -3807,6 +4161,7 @@ void GCodeProcessor::post_process() return processed; }; +#if !ENABLE_BINARIZED_GCODE std::vector filament_mm(m_result.extruders_count, 0.0); std::vector filament_cm3(m_result.extruders_count, 0.0); std::vector filament_g(m_result.extruders_count, 0.0); @@ -3823,6 +4178,7 @@ void GCodeProcessor::post_process() filament_total_g += filament_g[id]; filament_total_cost += filament_cost[id]; } +#endif // !ENABLE_BINARIZED_GCODE auto process_used_filament = [&](std::string& gcode_line) { // Prefilter for parsing speed. @@ -3844,12 +4200,21 @@ void GCodeProcessor::post_process() }; bool ret = false; +#if ENABLE_BINARIZED_GCODE + ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedMmMask, filament_mm); + ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedGMask, filament_g); + ret |= process_tag(gcode_line, PrintStatistics::TotalFilamentUsedGMask, { filament_total_g }); + ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedCm3Mask, filament_cm3); + ret |= process_tag(gcode_line, PrintStatistics::FilamentCostMask, filament_cost); + ret |= process_tag(gcode_line, PrintStatistics::TotalFilamentCostMask, { filament_total_cost }); +#else ret |= process_tag(gcode_line, "; filament used [mm] =", filament_mm); ret |= process_tag(gcode_line, "; filament used [g] =", filament_g); ret |= process_tag(gcode_line, "; total filament used [g] =", { filament_total_g }); ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_cm3); ret |= process_tag(gcode_line, "; filament cost =", filament_cost); ret |= process_tag(gcode_line, "; total filament cost =", { filament_total_cost }); +#endif // ENABLE_BINARIZED_GCODE return ret; }; @@ -3988,6 +4353,9 @@ void GCodeProcessor::post_process() }; m_result.lines_ends.clear(); +#if ENABLE_BINARIZED_GCODE + m_result.lines_ends.emplace_back(std::vector()); +#endif // ENABLE_BINARIZED_GCODE unsigned int line_id = 0; // Backtrace data for Tx gcode lines @@ -4057,9 +4425,70 @@ void GCodeProcessor::post_process() export_lines.flush(out, m_result, out_path); +#if ENABLE_BINARIZED_GCODE + if (m_binarizer.is_enabled()) { + if (m_binarizer.finalize() != bgcode::core::EResult::Success) + throw Slic3r::RuntimeError(std::string("Error while finalizing the gcode binarizer.\n")); + } +#endif // ENABLE_BINARIZED_GCODE + out.close(); in.close(); +#if ENABLE_BINARIZED_GCODE + if (m_binarizer.is_enabled()) { + // updates m_result.lines_ends from binarized gcode file + m_result.lines_ends.clear(); + + FilePtr file(boost::nowide::fopen(out_path.c_str(), "rb")); + if (file.f != nullptr) { + fseek(file.f, 0, SEEK_END); + const long file_size = ftell(file.f); + rewind(file.f); + + // read file header + using namespace bgcode::core; + using namespace bgcode::binarize; + FileHeader file_header; + EResult res = read_header(*file.f, file_header, nullptr); + if (res == EResult::Success) { + // search first GCode block + BlockHeader block_header; + res = read_next_block_header(*file.f, file_header, block_header, EBlockType::GCode, nullptr, 0); + while (res == EResult::Success) { + GCodeBlock block; + res = block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) + break; + + // extract lines ends from block + std::vector& lines_ends = m_result.lines_ends.emplace_back(std::vector()); + for (size_t i = 0; i < block.raw_data.size(); ++i) { + if (block.raw_data[i] == '\n') + lines_ends.emplace_back(i + 1); + } + + if (ftell(file.f) == file_size) + break; + + // read next block header + res = read_next_block_header(*file.f, file_header, block_header, nullptr, 0); + if (res != EResult::Success) + break; + if (block_header.type != (uint16_t)EBlockType::GCode) { + res = EResult::InvalidBlockType; + break; + } + } + } + + if (res != EResult::Success && !m_result.lines_ends.empty() && !m_result.lines_ends.front().empty()) + // some error occourred, clear lines ends + m_result.lines_ends = { std::vector() }; + } + } +#endif // ENABLE_BINARIZED_GCODE + export_lines.synchronize_moves(m_result); if (rename_file(out_path, m_result.filename)) @@ -4264,7 +4693,7 @@ void GCodeProcessor::simulate_st_synchronize(float additional_time) } } -void GCodeProcessor::update_estimated_times_stats() +void GCodeProcessor::update_estimated_statistics() { auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index a383f251c2..5e0986c71b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -7,6 +7,10 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/CustomGCode.hpp" +#if ENABLE_BINARIZED_GCODE +#include +#endif // ENABLE_BINARIZED_GCODE + #include #include #include @@ -66,10 +70,10 @@ namespace Slic3r { } }; - std::vector volumes_per_color_change; - std::map volumes_per_extruder; + std::vector volumes_per_color_change; + std::map volumes_per_extruder; std::map> used_filaments_per_role; - std::map cost_per_extruder; + std::map cost_per_extruder; std::array(ETimeMode::Count)> modes; @@ -140,10 +144,17 @@ namespace Slic3r { }; std::string filename; +#if ENABLE_BINARIZED_GCODE + bool is_binary_file; +#endif // ENABLE_BINARIZED_GCODE unsigned int id; std::vector moves; // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. +#if ENABLE_BINARIZED_GCODE + std::vector> lines_ends; +#else std::vector lines_ends; +#endif // ENABLE_BINARIZED_GCODE Pointfs bed_shape; float max_print_height; SettingsIds settings_ids; @@ -528,8 +539,16 @@ namespace Slic3r { }; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#if ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + static bgcode::binarize::BinarizerConfig& get_binarizer_config() { return s_binarizer_config; } +#endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + private: GCodeReader m_parser; +#if ENABLE_BINARIZED_GCODE + bgcode::binarize::Binarizer m_binarizer; + static bgcode::binarize::BinarizerConfig s_binarizer_config; +#endif // ENABLE_BINARIZED_GCODE EUnits m_units; EPositioningType m_global_positioning_type; @@ -627,6 +646,10 @@ namespace Slic3r { void apply_config(const PrintConfig& config); void set_print(Print* print) { m_print = print; } +#if ENABLE_BINARIZED_GCODE + bgcode::binarize::BinaryData& get_binary_data() { return m_binarizer.get_binary_data(); } + const bgcode::binarize::BinaryData& get_binary_data() const { return m_binarizer.get_binary_data(); } +#endif // ENABLE_BINARIZED_GCODE void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { @@ -669,6 +692,11 @@ namespace Slic3r { void apply_config_kissslicer(const std::string& filename); void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); +#if ENABLE_BINARIZED_GCODE + void process_ascii_file(const std::string& filename, std::function cancel_callback = nullptr); + void process_binary_file(const std::string& filename, std::function cancel_callback = nullptr); +#endif // ENABLE_BINARIZED_GCODE + // Process tags embedded into comments void process_tags(const std::string_view comment, bool producers_enabled); bool process_producers_tags(const std::string_view comment); @@ -824,7 +852,7 @@ namespace Slic3r { // Simulates firmware st_synchronize() call void simulate_st_synchronize(float additional_time = 0.0f); - void update_estimated_times_stats(); + void update_estimated_statistics(); double extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section); }; diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp index 8d70539b7c..1a14d939bf 100644 --- a/src/libslic3r/GCode/Thumbnails.cpp +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -116,4 +116,38 @@ std::unique_ptr compress_thumbnail(const ThumbnailData &d } } +std::vector> make_thumbnail_list(const DynamicPrintConfig &config) +{ + // ??? Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format". + // ??? If "thumbnails_format" is not defined, export to PNG. + + // generate thumbnails data to process it + + std::vector> thumbnails_list; + if (const auto thumbnails_value = config.option("thumbnails")) { + std::string str = thumbnails_value->value; + std::istringstream is(str); + std::string point_str; + while (std::getline(is, point_str, ',')) { + Vec2d point(Vec2d::Zero()); + GCodeThumbnailsFormat format; + std::istringstream iss(point_str); + std::string coord_str; + if (std::getline(iss, coord_str, 'x')) { + std::istringstream(coord_str) >> point(0); + if (std::getline(iss, coord_str, '/')) { + std::istringstream(coord_str) >> point(1); + std::string ext_str; + if (std::getline(iss, ext_str, '/')) + format = ext_str == "JPG" ? GCodeThumbnailsFormat::JPG : + ext_str == "QOI" ? GCodeThumbnailsFormat::QOI :GCodeThumbnailsFormat::PNG; + } + } + thumbnails_list.emplace_back(std::make_pair(format, point)); + } + } + + return thumbnails_list; +} + } // namespace Slic3r::GCodeThumbnails diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp index 68b11d15fb..bcdce5fc1f 100644 --- a/src/libslic3r/GCode/Thumbnails.hpp +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -9,6 +9,10 @@ #include #include +#if ENABLE_BINARIZED_GCODE +#include +#endif // ENABLE_BINARIZED_GCODE + #include namespace Slic3r::GCodeThumbnails { @@ -23,6 +27,8 @@ struct CompressedImageBuffer std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format); +std::vector> make_thumbnail_list(const DynamicPrintConfig &config); + template inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector>& thumbnails_list, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) { @@ -57,6 +63,39 @@ inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, } } +#if ENABLE_BINARIZED_GCODE +template +inline void generate_binary_thumbnails(ThumbnailsGeneratorCallback& thumbnail_cb, std::vector& out_thumbnails, + const std::vector> &thumbnails_list, ThrowIfCanceledCallback throw_if_canceled) +{ + using namespace bgcode::core; + using namespace bgcode::binarize; + out_thumbnails.clear(); + assert(thumbnail_cb != nullptr); + if (thumbnail_cb != nullptr) { + for (const auto& [format, size] : thumbnails_list) { + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true }); + for (const ThumbnailData &data : thumbnails) + if (data.is_valid()) { + auto compressed = compress_thumbnail(data, format); + if (compressed->data != nullptr && compressed->size > 0) { + ThumbnailBlock& block = out_thumbnails.emplace_back(ThumbnailBlock()); + block.params.width = (uint16_t)data.width; + block.params.height = (uint16_t)data.height; + switch (format) { + case GCodeThumbnailsFormat::PNG: { block.params.format = (uint16_t)EThumbnailFormat::PNG; break; } + case GCodeThumbnailsFormat::JPG: { block.params.format = (uint16_t)EThumbnailFormat::JPG; break; } + case GCodeThumbnailsFormat::QOI: { block.params.format = (uint16_t)EThumbnailFormat::QOI; break; } + } + block.data.resize(compressed->size); + memcpy(block.data.data(), compressed->data, compressed->size); + } + } + } + } +} +#endif // ENABLE_BINARIZED_GCODE + } // namespace Slic3r::GCodeThumbnails #endif // slic3r_GCodeThumbnails_hpp_ diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index cb0366757a..2b94963e93 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -195,11 +195,20 @@ bool GCodeReader::parse_file(const std::string &file, callback_t callback) return this->parse_file_internal(file, callback, [](size_t){}); } +#if ENABLE_BINARIZED_GCODE +bool GCodeReader::parse_file(const std::string& file, callback_t callback, std::vector>& lines_ends) +{ + lines_ends.clear(); + lines_ends.push_back(std::vector()); + return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos) { lines_ends.front().emplace_back(file_pos); }); +} +#else bool GCodeReader::parse_file(const std::string &file, callback_t callback, std::vector &lines_ends) { lines_ends.clear(); return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos){ lines_ends.emplace_back(file_pos); }); } +#endif // ENABLE_BINARIZED_GCODE bool GCodeReader::parse_file_raw(const std::string &filename, raw_line_callback_t line_callback) { diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 58f55fdcf7..7d1dad92a0 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -131,7 +131,11 @@ public: bool parse_file(const std::string &file, callback_t callback); // Collect positions of line ends in the binary G-code to be used by the G-code viewer when memory mapping and displaying section of G-code // as an overlay in the 3D scene. +#if ENABLE_BINARIZED_GCODE + bool parse_file(const std::string& file, callback_t callback, std::vector>& lines_ends); +#else bool parse_file(const std::string &file, callback_t callback, std::vector &lines_ends); +#endif // ENABLE_BINARIZED_GCODE // Just read the G-code file line by line, calls callback (const char *begin, const char *end). Returns false if reading the file failed. bool parse_file_raw(const std::string &file, raw_line_callback_t callback); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7fe93e151f..e858e72752 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -451,7 +451,11 @@ static std::vector s_Preset_print_options { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall", "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius", +#if ENABLE_BINARIZED_GCODE + "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "gcode_binary", "perimeter_extruder", +#else "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder", +#endif // ENABLE_BINARIZED_GCODE "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index af57c9314d..1d41bafc3b 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -22,6 +22,9 @@ #include #include +#if ENABLE_BINARIZED_GCODE +#include +#endif // ENABLE_BINARIZED_GCODE // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions. @@ -877,14 +880,33 @@ DynamicPrintConfig PresetBundle::full_sla_config() const // If the file is loaded successfully, its print / filament / printer profiles will be activated. ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule) { - if (is_gcode_file(path)) { - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); +#if ENABLE_BINARIZED_GCODE + if (is_gcode_file(path)) { + FILE* file = boost::nowide::fopen(path.c_str(), "rb"); + if (file == nullptr) + throw Slic3r::RuntimeError("Error opening the file: " + path + "\n"); + std::vector cs_buffer(65536); + const bool is_binary = bgcode::core::is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == bgcode::core::EResult::Success; + fclose(file); + + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + ConfigSubstitutions config_substitutions = is_binary ? config.load_from_binary_gcode_file(path, compatibility_rule) : + config.load_from_gcode_file(path, compatibility_rule); + Preset::normalize(config); + load_config_file_config(path, true, std::move(config)); + return config_substitutions; + } +#else + if (is_gcode_file(path)) { + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule); Preset::normalize(config); - load_config_file_config(path, true, std::move(config)); - return config_substitutions; - } + load_config_file_config(path, true, std::move(config)); + return config_substitutions; + } +#endif // ENABLE_BINARIZED_GCODE // 1) Try to load the config file into a boost property tree. boost::property_tree::ptree tree; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c63b3b1a95..2f65af51f4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -117,6 +117,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "perimeter_acceleration", "post_process", "gcode_substitutions", +#if ENABLE_BINARIZED_GCODE + "gcode_binary", +#endif // ENABLE_BINARIZED_GCODE "printer_notes", "retract_before_travel", "retract_before_wipe", @@ -1601,6 +1604,28 @@ std::string Print::output_filename(const std::string &filename_base) const return this->PrintBase::output_filename(m_config.output_filename_format.value, ".gcode", filename_base, &config); } +#if ENABLE_BINARIZED_GCODE +const std::string PrintStatistics::FilamentUsedG = "filament used [g]"; +const std::string PrintStatistics::FilamentUsedGMask = "; " + PrintStatistics::FilamentUsedG + " ="; + +const std::string PrintStatistics::TotalFilamentUsedG = "total " + PrintStatistics::FilamentUsedG; +const std::string PrintStatistics::TotalFilamentUsedGMask = "; " + PrintStatistics::TotalFilamentUsedG + " ="; +const std::string PrintStatistics::TotalFilamentUsedGValueMask = TotalFilamentUsedGMask + " %.2lf\n"; + +const std::string PrintStatistics::FilamentUsedCm3 = "filament used [cm3]"; +const std::string PrintStatistics::FilamentUsedCm3Mask = "; " + PrintStatistics::FilamentUsedCm3 + " ="; + +const std::string PrintStatistics::FilamentUsedMm = "filament used [mm]"; +const std::string PrintStatistics::FilamentUsedMmMask = "; " + PrintStatistics::FilamentUsedMm + " ="; + +const std::string PrintStatistics::FilamentCost = "filament cost"; +const std::string PrintStatistics::FilamentCostMask = "; " + PrintStatistics::FilamentCost + " ="; + +const std::string PrintStatistics::TotalFilamentCost = "total " + PrintStatistics::FilamentCost; +const std::string PrintStatistics::TotalFilamentCostMask = "; " + PrintStatistics::TotalFilamentCost + " ="; +const std::string PrintStatistics::TotalFilamentCostValueMask = PrintStatistics::TotalFilamentCostMask + " %.2lf\n"; +#endif // ENABLE_BINARIZED_GCODE + DynamicConfig PrintStatistics::config() const { DynamicConfig config; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index f16befc964..6189c15d10 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -529,6 +529,23 @@ struct PrintStatistics filament_stats.clear(); printing_extruders.clear(); } + +#if ENABLE_BINARIZED_GCODE + static const std::string FilamentUsedG; + static const std::string FilamentUsedGMask; + static const std::string TotalFilamentUsedG; + static const std::string TotalFilamentUsedGMask; + static const std::string TotalFilamentUsedGValueMask; + static const std::string FilamentUsedCm3; + static const std::string FilamentUsedCm3Mask; + static const std::string FilamentUsedMm; + static const std::string FilamentUsedMmMask; + static const std::string FilamentCost; + static const std::string FilamentCostMask; + static const std::string TotalFilamentCost; + static const std::string TotalFilamentCostMask; + static const std::string TotalFilamentCostValueMask; +#endif // ENABLE_BINARIZED_GCODE }; using PrintObjectPtrs = std::vector; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index eb804ba334..7f38314033 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1483,6 +1483,14 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionStrings()); +#if ENABLE_BINARIZED_GCODE + def = this->add("gcode_binary", coBool); + def->label = L("Export as binary G-code"); + def->tooltip = L("Exports the G-code in binary format."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(0)); +#endif // ENABLE_BINARIZED_GCODE + def = this->add("high_current_on_filament_swap", coBool); def->label = L("High extruder current on filament swap"); def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange" diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a2a4466045..91556639be 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -719,6 +719,9 @@ PRINT_CONFIG_CLASS_DEFINE( // i - case insensitive // w - whole word ((ConfigOptionStrings, gcode_substitutions)) +//#if ENABLE_BINARIZED_GCODE + ((ConfigOptionBool, gcode_binary)) +//#endif // ENABLE_BINARIZED_GCODE ((ConfigOptionString, layer_gcode)) ((ConfigOptionFloat, max_print_speed)) ((ConfigOptionFloat, max_volumetric_speed)) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 602654633c..6a1743285e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -62,4 +62,18 @@ #define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_6_0_ALPHA1) +//==================== +// 2.6.2.alpha1 techs +//==================== +#define ENABLE_2_6_2_ALPHA1 1 + +// Enable export of binarized gcode +#define ENABLE_BINARIZED_GCODE (1 && ENABLE_2_6_2_ALPHA1) +#define ENABLE_BINARIZED_GCODE_DEBUG (0 && ENABLE_BINARIZED_GCODE) +#ifdef _WIN32 +#define ENABLE_BINARIZED_GCODE_WIN_DEBUG (1 && ENABLE_BINARIZED_GCODE_DEBUG) +#endif // _WIN32 +#define ENABLE_BINARIZED_GCODE_DEBUG_WINDOW (1 && ENABLE_BINARIZED_GCODE) + + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b23fd76b70..a58d4b48f4 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -38,6 +38,11 @@ #include #include +#if ENABLE_BINARIZED_GCODE_WIN_DEBUG +#include +#include +#endif // ENABLE_BINARIZED_GCODE_WIN_DEBUG + namespace Slic3r { namespace GUI { @@ -362,15 +367,22 @@ void GCodeViewer::SequentialView::Marker::render() ImGui::PopStyleVar(); } +#if ENABLE_BINARIZED_GCODE +void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const GCodeProcessorResult& gcode_result) +{ + m_filename = gcode_result.filename; + m_is_binary_file = gcode_result.is_binary_file; + m_lines_ends = gcode_result.lines_ends; +} +#else void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector& lines_ends) { - assert(! m_file.is_open()); + assert(!m_file.is_open()); if (m_file.is_open()) return; - m_filename = filename; + m_filename = filename; m_lines_ends = lines_ends; - m_selected_line_id = 0; m_last_lines_size = 0; @@ -384,41 +396,325 @@ void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& fil reset(); } } +#endif // ENABLE_BINARIZED_GCODE +#if ENABLE_BINARIZED_GCODE +void GCodeViewer::SequentialView::GCodeWindow::add_gcode_line_to_lines_cache(const std::string& src) +{ + std::string command; + std::string parameters; + std::string comment; + + // extract comment + std::vector tokens; + boost::split(tokens, src, boost::is_any_of(";"), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) + comment = ";" + tokens.back(); + + // extract gcode command and parameters + if (!command.empty()) { + boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) { + for (size_t i = 1; i < tokens.size(); ++i) { + parameters += " " + tokens[i]; + } + } + } + + m_lines_cache.push_back({ command, parameters, comment }); +} + +void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, size_t curr_line_id) +{ + auto update_lines_ascii = [this]() { + m_lines_cache.clear(); + m_lines_cache.reserve(m_cache_range.size()); + const std::vector& lines_ends = m_lines_ends.front(); + FILE* file = boost::nowide::fopen(m_filename.c_str(), "rb"); + if (file != nullptr) { + for (size_t id = *m_cache_range.min; id <= *m_cache_range.max; ++id) { + assert(id > 0); + // read line from file + const size_t begin = id == 1 ? 0 : lines_ends[id - 2]; + const size_t len = lines_ends[id - 1] - begin; + std::string gline(len, '\0'); + fseek(file, begin, SEEK_SET); + const size_t rsize = fread((void*)gline.data(), 1, len, file); + if (ferror(file) || rsize != len) { + m_lines_cache.clear(); + break; + } + + add_gcode_line_to_lines_cache(gline); + } + fclose(file); + } + }; + + auto update_lines_binary = [this]() { + m_lines_cache.clear(); + m_lines_cache.reserve(m_cache_range.size()); + + size_t cumulative_lines_count = 0; + std::vector cumulative_lines_counts; + cumulative_lines_counts.reserve(m_lines_ends.size()); + for (size_t i = 0; i < m_lines_ends.size(); ++i) { + cumulative_lines_count += m_lines_ends[i].size(); + cumulative_lines_counts.emplace_back(cumulative_lines_count); + } + + size_t first_block_id = 0; + for (size_t i = 0; i < cumulative_lines_counts.size(); ++i) { + if (*m_cache_range.min <= cumulative_lines_counts[i]) { + first_block_id = i; + break; + } + } + size_t last_block_id = 0; + for (size_t i = 0; i < cumulative_lines_counts.size(); ++i) { + if (*m_cache_range.max <= cumulative_lines_counts[i]) { + last_block_id = i; + break; + } + } + assert(last_block_id >= first_block_id); + + FilePtr file(boost::nowide::fopen(m_filename.c_str(), "rb")); + if (file.f != nullptr) { + fseek(file.f, 0, SEEK_END); + const long file_size = ftell(file.f); + rewind(file.f); + + // read file header + using namespace bgcode::core; + using namespace bgcode::binarize; + FileHeader file_header; + EResult res = read_header(*file.f, file_header, nullptr); + if (res == EResult::Success) { + // search first GCode block + BlockHeader block_header; + res = read_next_block_header(*file.f, file_header, block_header, EBlockType::GCode, nullptr, 0); + if (res == EResult::Success) { + for (size_t i = 0; i < first_block_id; ++i) { + skip_block(*file.f, file_header, block_header); + res = read_next_block_header(*file.f, file_header, block_header, nullptr, 0); + if (res != EResult::Success || block_header.type != (uint16_t)EBlockType::GCode) { + m_lines_cache.clear(); + return; + } + } + + for (size_t i = first_block_id; i <= last_block_id; ++i) { + GCodeBlock block; + res = block.read_data(*file.f, file_header, block_header); + if (res != EResult::Success) { + m_lines_cache.clear(); + return; + } + + const size_t ref_id = (i == 0) ? 0 : i - 1; + const size_t first_line_id = (i == 0) ? *m_cache_range.min : + (*m_cache_range.min - 1 >= cumulative_lines_counts[ref_id]) ? *m_cache_range.min - cumulative_lines_counts[ref_id] : 1; + const size_t last_line_id = (*m_cache_range.max - 1 <= cumulative_lines_counts[i]) ? + (i == 0) ? *m_cache_range.max : *m_cache_range.max - cumulative_lines_counts[ref_id] : m_lines_ends[i].size() - 1; + + for (size_t j = first_line_id; j <= last_line_id; ++j) { + const size_t begin = (j == 1) ? 0 : m_lines_ends[i][j - 2]; + const size_t end = m_lines_ends[i][j - 1]; + std::string gline; + gline.insert(gline.end(), block.raw_data.begin() + begin, block.raw_data.begin() + end); + add_gcode_line_to_lines_cache(gline); + } + + if (ftell(file.f) == file_size) + break; + + res = read_next_block_header(*file.f, file_header, block_header, nullptr, 0); + if (res != EResult::Success || block_header.type != (uint16_t)EBlockType::GCode) { + m_lines_cache.clear(); + return; + } + } + } + } + } + }; + + static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT; + static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK; + static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f }; + static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; + static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; + static const ImVec4 ELLIPSIS_COLOR = { 0.0f, 0.7f, 0.0f, 1.0f }; + + if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0) + return; + + // window height + const float wnd_height = bottom - top; + + // number of visible lines + const float text_height = ImGui::CalcTextSize("0").y; + const ImGuiStyle& style = ImGui::GetStyle(); + const size_t visible_lines_count = static_cast((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y)); + + if (visible_lines_count == 0) + return; + + if (m_lines_ends.empty() || m_lines_ends.front().empty()) + return; + + auto resize_range = [&](Range& range, size_t lines_count) { + const size_t half_lines_count = lines_count / 2; + range.min = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 1; + range.max = *range.min + lines_count - 1; + size_t lines_ends_count = 0; + for (const auto& le : m_lines_ends) { + lines_ends_count += le.size(); + } + if (*range.max >= lines_ends_count) { + range.max = lines_ends_count - 1; + range.min = *range.max - lines_count + 1; + } + }; + + // visible range + Range visible_range; + resize_range(visible_range, visible_lines_count); + + // update cache if needed + if (m_cache_range.empty() || !m_cache_range.contains(visible_range)) { + resize_range(m_cache_range, 4 * visible_range.size()); + if (m_is_binary_file) + update_lines_binary(); + else + update_lines_ascii(); + } + + if (m_lines_cache.empty()) + return; + + // line number's column width + const float id_width = ImGui::CalcTextSize(std::to_string(*visible_range.max).c_str()).x; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + auto add_item_to_line = [&imgui](const std::string& txt, const ImVec4& color, float spacing, size_t& current_length) { + static const size_t LENGTH_THRESHOLD = 60; + + if (txt.empty()) + return false; + + std::string out_text = txt; + bool reduced = false; + if (current_length + out_text.length() > LENGTH_THRESHOLD) { + out_text = out_text.substr(0, LENGTH_THRESHOLD - current_length); + reduced = true; + } + + current_length += out_text.length(); + + ImGui::SameLine(0.0f, spacing); + imgui.text_colored(color, out_text); + if (reduced) { + ImGui::SameLine(0.0f, 0.0f); + imgui.text_colored(ELLIPSIS_COLOR, "..."); + } + + return reduced; + }; + + imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f); + imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + + // center the text in the window by pushing down the first line + const float f_lines_count = static_cast(visible_lines_count); + ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y)); + + // render text lines + size_t max_line_length = 0; + for (size_t id = *visible_range.min; id <= *visible_range.max; ++id) { + const Line& line = m_lines_cache[id - *m_cache_range.min]; + + // rect around the current selected line + if (id == curr_line_id) { + const float pos_y = ImGui::GetCursorScreenPos().y; + const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y; + const float half_padding_x = 0.5f * style.WindowPadding.x; + ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y }, + { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y }, + ImGui::GetColorU32(SELECTION_RECT_COLOR)); + } + + const std::string id_str = std::to_string(id); + // spacer to right align text + ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height }); + + size_t line_length = 0; + // render line number + bool stop_adding = add_item_to_line(id_str, LINE_NUMBER_COLOR, 0.0f, line_length); + if (!stop_adding && !line.command.empty()) + // render command + stop_adding = add_item_to_line(line.command, COMMAND_COLOR, -1.0f, line_length); + if (!stop_adding && !line.parameters.empty()) + // render parameters + stop_adding = add_item_to_line(line.parameters, PARAMETERS_COLOR, 0.0f, line_length); + if (!stop_adding && !line.comment.empty()) + // render comment + stop_adding = add_item_to_line(line.comment, COMMENT_COLOR, line.command.empty() ? -1.0f : 0.0f, line_length); + + max_line_length = std::max(max_line_length, line_length); + } + + imgui.end(); + ImGui::PopStyleVar(); + + // request an extra frame if window's width changed + if (m_max_line_length != max_line_length) { + m_max_line_length = max_line_length; + imgui.set_requires_extra_frame(); + } +} +#else void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const { auto update_lines = [this](uint64_t start_id, uint64_t end_id) { std::vector ret; ret.reserve(end_id - start_id + 1); - for (uint64_t id = start_id; id <= end_id; ++id) { - // read line from file - const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; - const size_t len = m_lines_ends[id - 1] - start; - std::string gline(m_file.data() + start, len); + for (uint64_t id = start_id; id <= end_id; ++id) { + // read line from file + const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; + const size_t len = m_lines_ends[id - 1] - start; + std::string gline(m_file.data() + start, len); - std::string command; - std::string parameters; - std::string comment; + std::string command; + std::string parameters; + std::string comment; - // extract comment - std::vector tokens; - boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) - comment = ";" + tokens.back(); - - // extract gcode command and parameters - if (!command.empty()) { - boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); + // extract comment + std::vector tokens; + boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); command = tokens.front(); - if (tokens.size() > 1) { - for (size_t i = 1; i < tokens.size(); ++i) { - parameters += " " + tokens[i]; + if (tokens.size() > 1) + comment = ";" + tokens.back(); + + // extract gcode command and parameters + if (!command.empty()) { + boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); + command = tokens.front(); + if (tokens.size() > 1) { + for (size_t i = 1; i < tokens.size(); ++i) { + parameters += " " + tokens[i]; + } } } + ret.push_back({ command, parameters, comment }); } - ret.push_back({ command, parameters, comment }); - } return ret; }; @@ -552,6 +848,7 @@ void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() if (m_file.is_open()) m_file.close(); } +#endif // ENABLE_BINARIZED_GCODE void GCodeViewer::SequentialView::render(float legend_height) { @@ -731,7 +1028,11 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr // release gpu memory, if used reset(); +#if ENABLE_BINARIZED_GCODE + m_sequential_view.gcode_window.load_gcode(gcode_result); +#else m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends); +#endif // ENABLE_BINARIZED_GCODE if (wxGetApp().is_gcode_viewer()) m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; @@ -2354,20 +2655,26 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } case EViewType::LayerTimeLinear: case EViewType::LayerTimeLogarithmic: { - const Path::Sub_Path& sub_path = path.sub_paths.front(); - double z = static_cast(sub_path.first.position.z()); - const std::vector& zs = m_layers.get_zs(); - const std::vector& ranges = m_layers.get_ranges(); - size_t time_mode_id = static_cast(m_time_estimate_mode); - for (size_t i = 0; i < zs.size(); ++i) { - if (std::abs(zs[i] - z) < EPSILON) { - if (ranges[i].contains(sub_path.first.s_id)) { - color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i], - (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic); - break; +#if ENABLE_BINARIZED_GCODE + if (!m_layers_times.empty() && m_layers.size() == m_layers_times.front().size()) { +#endif // ENABLE_BINARIZED_GCODE + const Path::Sub_Path& sub_path = path.sub_paths.front(); + double z = static_cast(sub_path.first.position.z()); + const std::vector& zs = m_layers.get_zs(); + const std::vector& ranges = m_layers.get_ranges(); + size_t time_mode_id = static_cast(m_time_estimate_mode); + for (size_t i = 0; i < zs.size(); ++i) { + if (std::abs(zs[i] - z) < EPSILON) { + if (ranges[i].contains(sub_path.first.s_id)) { + color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i], + (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic); + break; + } } } +#if ENABLE_BINARIZED_GCODE } +#endif // ENABLE_BINARIZED_GCODE break; } case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } @@ -3637,6 +3944,27 @@ void GCodeViewer::render_legend(float& legend_height) ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f }); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f }); +#if ENABLE_BINARIZED_GCODE + std::vector view_options; + std::vector view_options_id; + if (!m_layers_times.empty() && m_layers.size() == m_layers_times.front().size()) { + view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Fan speed (%)"), + _u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Layer time (linear)"), _u8L("Layer time (logarithmic)"), + _u8L("Tool"), _u8L("Color Print") }; + view_options_id = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + } + else { + view_options = { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), _u8L("Speed (mm/s)"), _u8L("Fan speed (%)"), + _u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), _u8L("Tool"), _u8L("Color Print") }; + view_options_id = { 0, 1, 2, 3, 4, 5, 6, 9, 10 }; + if (view_type == 7 || view_type == 8) + view_type = 0; + } + auto view_type_it = std::find(view_options_id.begin(), view_options_id.end(), view_type); + int view_type_id = (view_type_it == view_options_id.end()) ? 0 : std::distance(view_options_id.begin(), view_type_it); + if (imgui.combo(std::string(), view_options, view_type_id, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f)) + view_type = view_options_id[view_type_id]; +#else imgui.combo(std::string(), { _u8L("Feature type"), _u8L("Height (mm)"), _u8L("Width (mm)"), @@ -3648,6 +3976,7 @@ void GCodeViewer::render_legend(float& legend_height) _u8L("Layer time (logarithmic)"), _u8L("Tool"), _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f); +#endif // ENABLE_BINARIZED_GCODE ImGui::PopStyleColor(2); if (old_view_type != view_type) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 85a252881c..1f94339cbf 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -6,7 +6,9 @@ #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GLModel.hpp" +#if !ENABLE_BINARIZED_GCODE #include +#endif // !ENABLE_BINARIZED_GCODE #include #include @@ -673,6 +675,54 @@ public: void render(); }; +#if ENABLE_BINARIZED_GCODE + class GCodeWindow + { + struct Line + { + std::string command; + std::string parameters; + std::string comment; + }; + + struct Range + { + std::optional min; + std::optional max; + bool empty() const { + return !min.has_value() || !max.has_value(); + } + bool contains(const Range& other) const { + return !this->empty() && !other.empty() && *this->min <= *other.min && *this->max >= other.max; + } + size_t size() const { + return empty() ? 0 : *this->max - *this->min + 1; + } + }; + + bool m_visible{ true }; + std::string m_filename; + bool m_is_binary_file{ false }; + // map for accessing data in file by line number + std::vector> m_lines_ends; + std::vector m_lines_cache; + Range m_cache_range; + size_t m_max_line_length{ 0 }; + + public: + void load_gcode(const GCodeProcessorResult& gcode_result); + void reset() { + m_lines_ends.clear(); + m_lines_cache.clear(); + m_filename.clear(); + } + void toggle_visibility() { m_visible = !m_visible; } + void render(float top, float bottom, size_t curr_line_id); + + private: + void add_gcode_line_to_lines_cache(const std::string& src); + }; +#else class GCodeWindow { struct Line @@ -692,7 +742,7 @@ public: std::vector m_lines; public: - GCodeWindow() = default; + GCodeWindow() = default; ~GCodeWindow() { stop_mapping_file(); } void load_gcode(const std::string& filename, const std::vector& lines_ends); void reset() { @@ -708,6 +758,7 @@ public: void stop_mapping_file(); }; +#endif // ENABLE_BINARIZED_GCODE struct Endpoints { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b4c36dd635..7e58be475f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1998,6 +1998,11 @@ void GLCanvas3D::render() #endif // ENABLE_SLA_VIEW_DEBUG_WINDOW } +#if ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() != ptSLA && fff_print()->config().gcode_binary) + show_binary_gcode_debug_window(); +#endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + std::string tooltip; // Negative coordinate means out of the window, likely because the window was deactivated. @@ -7762,6 +7767,96 @@ void GLCanvas3D::GizmoHighlighter::blink() invalidate(); } +#if ENABLE_BINARIZED_GCODE_DEBUG_WINDOW +void GLCanvas3D::show_binary_gcode_debug_window() +{ + bgcode::binarize::BinarizerConfig& binarizer_config = GCodeProcessor::get_binarizer_config(); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Binary GCode"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + using namespace bgcode::core; + if (ImGui::BeginTable("BinaryGCodeConfig", 2)) { + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "File metadata compression"); + ImGui::TableSetColumnIndex(1); + std::vector options = { "None", "Deflate", "heatshrink 11,4", "heatshrink 12,4" }; + int option_id = (int)binarizer_config.compression.file_metadata; + if (imgui.combo(std::string("##file_metadata_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.compression.file_metadata = (ECompressionType)option_id; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Printer metadata compression"); + ImGui::TableSetColumnIndex(1); + option_id = (int)binarizer_config.compression.printer_metadata; + if (imgui.combo(std::string("##printer_metadata_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.compression.printer_metadata = (ECompressionType)option_id; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Print metadata compression"); + ImGui::TableSetColumnIndex(1); + option_id = (int)binarizer_config.compression.print_metadata; + if (imgui.combo(std::string("##print_metadata_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.compression.print_metadata = (ECompressionType)option_id; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Slicer metadata compression"); + ImGui::TableSetColumnIndex(1); + option_id = (int)binarizer_config.compression.slicer_metadata; + if (imgui.combo(std::string("##slicer_metadata_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.compression.slicer_metadata = (ECompressionType)option_id; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "GCode compression"); + ImGui::TableSetColumnIndex(1); + option_id = (int)binarizer_config.compression.gcode; + if (imgui.combo(std::string("##gcode_compression"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.compression.gcode = (ECompressionType)option_id; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "GCode encoding"); + ImGui::TableSetColumnIndex(1); + options = { "None", "MeatPack", "MeatPack Comments" }; + option_id = (int)binarizer_config.gcode_encoding; + if (imgui.combo(std::string("##gcode_encoding"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.gcode_encoding = (EGCodeEncodingType)option_id; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Metadata encoding"); + ImGui::TableSetColumnIndex(1); + options = { "INI" }; + option_id = (int)binarizer_config.metadata_encoding; + if (imgui.combo(std::string("##metadata_encoding"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.metadata_encoding = (EMetadataEncodingType)option_id; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Checksum type"); + ImGui::TableSetColumnIndex(1); + options = { "None", "CRC32" }; + option_id = (int)binarizer_config.checksum; + if (imgui.combo(std::string("##4"), options, option_id, ImGuiComboFlags_HeightLargest, 0.0f, 175.0f)) + binarizer_config.checksum = (EChecksumType)option_id; + + ImGui::EndTable(); + + ImGui::Separator(); + imgui.text("!!! WARNING !!!"); + imgui.text("Changing values does NOT invalidate the current slice"); + } + + imgui.end(); +} +#endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) { const ModelVolume * ret = nullptr; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 3c91ab1b41..de067e2454 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1097,6 +1097,10 @@ private: bool _deactivate_arrange_menu(); float get_overlay_window_width() { return LayersEditing::get_overlay_window_width(); } + +#if ENABLE_BINARIZED_GCODE_DEBUG_WINDOW + void show_binary_gcode_debug_window(); +#endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW }; const ModelVolume *get_model_volume(const GLVolume &v, const Model &model); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 0023a4f63b..f2c73cf64c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -785,7 +785,8 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& bool ImGuiWrapper::combo(const std::string& label, const std::vector& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/) { // this is to force the label to the left of the widget: - if (!label.empty()) { + const bool hidden_label = boost::starts_with(label, "##"); + if (!label.empty() && !hidden_label) { text(label); ImGui::SameLine(label_width); } @@ -795,7 +796,7 @@ bool ImGuiWrapper::combo(const std::string& label, const std::vector= 0 ? options[selection].c_str() : ""; - if (ImGui::BeginCombo(("##" + label).c_str(), selection_str, flags)) { + if (ImGui::BeginCombo(hidden_label ? label.c_str() : ("##" + label).c_str(), selection_str, flags)) { for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; 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 3c6c2c7e77..cf4eae63f8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -36,6 +36,10 @@ #include #endif +#if ENABLE_BINARIZED_GCODE +#include +#endif // ENABLE_BINARIZED_GCODE + #include "libslic3r/libslic3r.h" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" @@ -5440,6 +5444,101 @@ void Plater::reload_gcode_from_disk() load_gcode(filename); } +#if ENABLE_BINARIZED_GCODE +void Plater::convert_gcode_to_ascii() +{ + // Ask user for a gcode file name. + wxString input_file; + wxGetApp().load_gcode(this, input_file); + if (input_file.empty()) + return; + + // Open source file + FilePtr in_file{ boost::nowide::fopen(into_u8(input_file).c_str(), "rb") }; + if (in_file.f == nullptr) { + MessageDialog msg_dlg(this, _L("Unable to open the selected file."), _L("Error"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); + return; + } + + // 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 + FilePtr out_file{ boost::nowide::fopen(output_file.c_str(), "wb") }; + if (out_file.f == nullptr) { + MessageDialog msg_dlg(this, _L("Unable to open output file."), _L("Error"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); + return; + } + + // Perform conversion + { + wxBusyCursor busy; + using namespace bgcode::core; + EResult res = bgcode::convert::from_binary_to_ascii(*in_file.f, *out_file.f, true); + if (res != EResult::Success) { + MessageDialog msg_dlg(this, _L(std::string(translate_result(res))), _L("Error converting gcode file"), wxICON_INFORMATION | wxOK); + msg_dlg.ShowModal(); + out_file.close(); + boost::nowide::remove(output_file.c_str()); + 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() +{ + // Ask user for a gcode file name. + wxString input_file; + wxGetApp().load_gcode(this, input_file); + if (input_file.empty()) + return; + + // Open source file + FilePtr in_file{ boost::nowide::fopen(into_u8(input_file).c_str(), "rb") }; + if (in_file.f == nullptr) { + MessageDialog msg_dlg(this, _L("Unable to open the selected file."), _L("Error"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); + return; + } + + // Set out filename + boost::filesystem::path path(into_u8(input_file)); + const std::string output_file = path.parent_path().string() + "/" + path.stem().string() + "_binary" + path.extension().string(); + + // Open destination file + FilePtr out_file{ boost::nowide::fopen(output_file.c_str(), "wb") }; + if (out_file.f == nullptr) { + MessageDialog msg_dlg(this, _L("Unable to open output file."), _L("Error"), wxICON_ERROR | wxOK); + msg_dlg.ShowModal(); + return; + } + + // Perform conversion + { + wxBusyCursor busy; + using namespace bgcode::core; + const bgcode::binarize::BinarizerConfig& binarizer_config = GCodeProcessor::get_binarizer_config(); + EResult res = bgcode::convert::from_ascii_to_binary(*in_file.f, *out_file.f, binarizer_config); + if (res != EResult::Success) { + MessageDialog msg_dlg(this, _L(std::string(translate_result(res))), _L("Error converting gcode file"), wxICON_INFORMATION | wxOK); + msg_dlg.ShowModal(); + out_file.close(); + boost::nowide::remove(output_file.c_str()); + return; + } + } + + MessageDialog msg_dlg(this, _L("Succesfully created gcode binary file:\n") + output_file, _L("Convert gcode file to binary format"), 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 cc51a78628..f80e5be4c3 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); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8b06b8f0a3..4fa4e2f07e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1698,6 +1698,9 @@ void TabPrint::build() Option option = optgroup->get_option("output_filename_format"); option.opt.full_width = true; optgroup->append_single_option_line(option); +#if ENABLE_BINARIZED_GCODE + optgroup->append_single_option_line("gcode_binary"); +#endif // ENABLE_BINARIZED_GCODE optgroup = page->new_optgroup(L("Other")); diff --git a/t/layers.t b/t/layers.t index 4d958808a6..fef94457c2 100644 --- a/t/layers.t +++ b/t/layers.t @@ -62,6 +62,7 @@ use Slic3r::Test qw(_eq); { my $config = Slic3r::Config->new; $config->set('fill_density', 0); # just for making the test faster + $config->set('gcode_binary', 0); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); my @z = ();