diff options
author | enricoturri1966 <enricoturri@seznam.cz> | 2021-09-09 12:01:49 +0300 |
---|---|---|
committer | enricoturri1966 <enricoturri@seznam.cz> | 2021-09-09 12:01:49 +0300 |
commit | 3369e1b599c66d6abe86ce316cecdc38732d7017 (patch) | |
tree | 5139e55968f5be054333a1c60551d711a2092001 /src/libslic3r | |
parent | f908197a77ed3eac9c43a96bb2d588fc5ecf2040 (diff) | |
parent | 3e0b7910eab5085c9903464f0b2af93bfe3e7d3c (diff) |
Fixed conflicts after merge with master
Diffstat (limited to 'src/libslic3r')
-rw-r--r-- | src/libslic3r/BlacklistedLibraryCheck.cpp | 13 | ||||
-rw-r--r-- | src/libslic3r/Brim.cpp | 40 | ||||
-rw-r--r-- | src/libslic3r/Format/3mf.cpp | 74 | ||||
-rw-r--r-- | src/libslic3r/GCode.cpp | 193 | ||||
-rw-r--r-- | src/libslic3r/GCode.hpp | 60 | ||||
-rw-r--r-- | src/libslic3r/GCode/GCodeProcessor.cpp | 482 | ||||
-rw-r--r-- | src/libslic3r/GCode/GCodeProcessor.hpp | 62 | ||||
-rw-r--r-- | src/libslic3r/GCodeReader.cpp | 45 | ||||
-rw-r--r-- | src/libslic3r/GCodeReader.hpp | 31 | ||||
-rw-r--r-- | src/libslic3r/GCodeWriter.cpp | 160 | ||||
-rw-r--r-- | src/libslic3r/Model.cpp | 27 | ||||
-rw-r--r-- | src/libslic3r/Model.hpp | 13 | ||||
-rw-r--r-- | src/libslic3r/Polyline.cpp | 4 | ||||
-rw-r--r-- | src/libslic3r/Polyline.hpp | 2 | ||||
-rw-r--r-- | src/libslic3r/Preset.cpp | 6 | ||||
-rw-r--r-- | src/libslic3r/PresetBundle.cpp | 31 | ||||
-rw-r--r-- | src/libslic3r/Print.cpp | 2 | ||||
-rw-r--r-- | src/libslic3r/PrintConfig.cpp | 25 | ||||
-rw-r--r-- | src/libslic3r/PrintConfig.hpp | 4 | ||||
-rw-r--r-- | src/libslic3r/PrintObject.cpp | 28 | ||||
-rw-r--r-- | src/libslic3r/QuadricEdgeCollapse.cpp | 162 | ||||
-rw-r--r-- | src/libslic3r/SupportMaterial.cpp | 14 | ||||
-rw-r--r-- | src/libslic3r/Technologies.hpp | 31 | ||||
-rw-r--r-- | src/libslic3r/utils.cpp | 15 |
24 files changed, 1094 insertions, 430 deletions
diff --git a/src/libslic3r/BlacklistedLibraryCheck.cpp b/src/libslic3r/BlacklistedLibraryCheck.cpp index 3d2ee3482..76f675c70 100644 --- a/src/libslic3r/BlacklistedLibraryCheck.cpp +++ b/src/libslic3r/BlacklistedLibraryCheck.cpp @@ -12,7 +12,7 @@ namespace Slic3r { #ifdef WIN32 //only dll name with .dll suffix - currently case sensitive -const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll" }); +const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll" }); bool BlacklistedLibraryCheck::get_blacklisted(std::vector<std::wstring>& names) { @@ -33,22 +33,20 @@ std::wstring BlacklistedLibraryCheck::get_blacklisted_string() bool BlacklistedLibraryCheck::perform_check() { - // Get a handle to the process. - HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId()); - if (NULL == hProcess) - return false; + // Get the pseudo-handle for the current process. + HANDLE hCurrentProcess = GetCurrentProcess(); // Get a list of all the modules in this process. HMODULE hMods[1024]; DWORD cbNeeded; - if (EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL)) + if (EnumProcessModulesEx(hCurrentProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL)) { //printf("Total Dlls: %d\n", cbNeeded / sizeof(HMODULE)); for (unsigned int i = 0; i < cbNeeded / sizeof(HMODULE); ++ i) { wchar_t szModName[MAX_PATH]; // Get the full path to the module's file. - if (GetModuleFileNameExW(hProcess, hMods[i], szModName, MAX_PATH)) + if (GetModuleFileNameExW(hCurrentProcess, hMods[i], szModName, MAX_PATH)) { // Add to list if blacklisted if (BlacklistedLibraryCheck::is_blacklisted(szModName)) { @@ -61,7 +59,6 @@ bool BlacklistedLibraryCheck::perform_check() } } - CloseHandle(hProcess); //printf("\n"); return !m_found.empty(); } diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index f4455fdd5..db31975e3 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -134,10 +134,10 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev Polygons islands; for (const PrintObject *object : top_level_objects_with_brim) { //FIXME how about the brim type? - auto brim_offset = float(scale_(object->config().brim_offset.value)); + auto brim_separation = float(scale_(object->config().brim_separation.value)); Polygons islands_object; for (const ExPolygon &ex_poly : get_print_object_bottom_layer_expolygons(*object)) { - Polygons contour_offset = offset(ex_poly.contour, brim_offset); + Polygons contour_offset = offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare); for (Polygon &poly : contour_offset) poly.douglas_peucker(SCALED_RESOLUTION); @@ -166,7 +166,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) { const PrintObject *object = print.objects()[print_object_idx]; const BrimType brim_type = object->config().brim_type.value; - const float brim_offset = scale_(object->config().brim_offset.value); + const float brim_separation = scale_(object->config().brim_separation.value); const float brim_width = scale_(object->config().brim_width.value); const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); @@ -174,16 +174,16 @@ static ExPolygons top_level_outer_brim_area(const Print &print ExPolygons no_brim_area_object; for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) { if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim) - append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); + append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset)); + append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes)); + append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); if (brim_type != BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_offset)); + append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_separation, ClipperLib::jtSquare)); no_brim_area_object.emplace_back(ex_poly.contour); } @@ -212,11 +212,11 @@ static ExPolygons inner_brim_area(const Print &print, ExPolygons no_brim_area; Polygons holes; for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) { - const PrintObject *object = print.objects()[print_object_idx]; - const BrimType brim_type = object->config().brim_type.value; - const float brim_offset = scale_(object->config().brim_offset.value); - const float brim_width = scale_(object->config().brim_width.value); - const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); + const PrintObject *object = print.objects()[print_object_idx]; + const BrimType brim_type = object->config().brim_type.value; + const float brim_separation = scale_(object->config().brim_separation.value); + const float brim_width = scale_(object->config().brim_width.value); + const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); ExPolygons brim_area_object; ExPolygons no_brim_area_object; @@ -226,21 +226,21 @@ static ExPolygons inner_brim_area(const Print &print, if (top_outer_brim) no_brim_area_object.emplace_back(ex_poly); else - append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); + append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare))); } if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) - append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset))); + append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes)); + append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset)); + append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); append(holes_object, ex_poly.holes); } - append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_offset)); + append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_separation, ClipperLib::jtSquare)); for (const PrintInstance &instance : object->instances()) { append_and_translate(brim_area, brim_area_object, instance); @@ -356,12 +356,12 @@ static void make_inner_brim(const Print &print, Flow flow = print.brim_flow(); ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing())); Polygons loops; - islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), jtSquare); + islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), ClipperLib::jtSquare); for (size_t i = 0; !islands_ex.empty(); ++i) { for (ExPolygon &poly_ex : islands_ex) poly_ex.douglas_peucker(SCALED_RESOLUTION); polygons_append(loops, to_polygons(islands_ex)); - islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), jtSquare); + islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), ClipperLib::jtSquare); } loops = union_pt_chained_outside_in(loops); @@ -385,7 +385,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing())); for (size_t i = 0; i < num_loops; ++i) { try_cancel(); - islands = offset(islands, float(flow.scaled_spacing()), jtSquare); + islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); for (Polygon &poly : islands) poly.douglas_peucker(SCALED_RESOLUTION); polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 57b0d8115..295c1413d 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -49,6 +49,17 @@ const unsigned int VERSION_3MF = 1; const unsigned int VERSION_3MF_COMPATIBLE = 2; const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file +// Painting gizmos data version numbers +// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them. +// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data. +const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1; +const unsigned int SEAM_PAINTING_VERSION = 1; +const unsigned int MM_PAINTING_VERSION = 1; + +const std::string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = "slic3rpe:FdmSupportsPaintingVersion"; +const std::string SLIC3RPE_SEAM_PAINTING_VERSION = "slic3rpe:SeamPaintingVersion"; +const std::string SLIC3RPE_MM_PAINTING_VERSION = "slic3rpe:MmPaintingVersion"; + const std::string MODEL_FOLDER = "3D/"; const std::string MODEL_EXTENSION = ".model"; const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA @@ -393,6 +404,10 @@ namespace Slic3r { unsigned int m_version; bool m_check_version; + unsigned int m_fdm_supports_painting_version = 0; + unsigned int m_seam_painting_version = 0; + unsigned int m_mm_painting_version = 0; + XML_Parser m_xml_parser; // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state // after returning from XML_Parse() function, thus we keep the error state here. @@ -420,6 +435,7 @@ namespace Slic3r { ~_3MF_Importer(); bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); + unsigned int version() const { return m_version; } private: void _destroy_xml_parser(); @@ -542,6 +558,9 @@ namespace Slic3r { bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) { m_version = 0; + m_fdm_supports_painting_version = 0; + m_seam_painting_version = 0; + m_mm_painting_version = 0; m_check_version = check_version; m_model = &model; m_unit_factor = 1.0f; @@ -1668,6 +1687,12 @@ namespace Slic3r { return true; } + inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg) + { + if (loaded_version > highest_supported_version) + throw version_error(error_msg); + } + bool _3MF_Importer::_handle_end_metadata() { if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) { @@ -1680,6 +1705,24 @@ namespace Slic3r { } } + if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { + m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, + _(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible."))); + } + + if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { + m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, + _(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible."))); + } + + if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { + m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, + _(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible."))); + } + return true; } @@ -2293,6 +2336,16 @@ namespace Slic3r { stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n"; + + if (model.is_fdm_support_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "</" << METADATA_TAG << ">\n"; + + if (model.is_seam_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n"; + + if (model.is_mm_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n"; + std::string name = xml_escape(boost::filesystem::path(filename).stem().string()); stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "</" << METADATA_TAG << ">\n"; stream << " <" << METADATA_TAG << " name=\"Designer\">" << "</" << METADATA_TAG << ">\n"; @@ -2613,9 +2666,16 @@ namespace Slic3r { bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) { +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + // This happens for empty projects +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED if (build_items.size() == 0) { add_error("No build item found"); +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + return true; +#else return false; +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED } stream << " <" << BUILD_TAG << ">\n"; @@ -2990,6 +3050,19 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv return true; } +// Perform conversions based on the config values available. +//FIXME provide a version of PrusaSlicer that stored the project file (3MF). +static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) +{ + if (! config.has("brim_separation")) { + if (auto *opt_elephant_foot = config.option<ConfigOptionFloat>("elefant_foot_compensation", false); opt_elephant_foot) { + // Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation. + auto *opt_brim_separation = config.option<ConfigOptionFloat>("brim_separation", true); + opt_brim_separation->value = opt_elephant_foot->value; + } + } +} + bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) { if (path == nullptr || model == nullptr) @@ -3000,6 +3073,7 @@ bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionCo _3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); importer.log_errors(); + handle_legacy_project_loaded(importer.version(), config); return res; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 85e6f810b..efea240e5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -744,27 +744,28 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re std::string path_tmp(path); path_tmp += ".tmp"; - FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); - if (file == nullptr) + m_processor.initialize(path_tmp); + 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"); try { m_placeholder_parser_failed_templates.clear(); this->_do_export(*print, file, thumbnail_cb); - fflush(file); - if (ferror(file)) { - fclose(file); + file.flush(); + if (file.is_error()) { + file.close(); boost::nowide::remove(path_tmp.c_str()); throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } } catch (std::exception & /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. // Close and remove the file. - fclose(file); + file.close(); boost::nowide::remove(path_tmp.c_str()); throw; } - fclose(file); + file.close(); if (! m_placeholder_parser_failed_templates.empty()) { // G-code export proceeded, but some of the PlaceholderParser substitutions failed. @@ -782,7 +783,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re } BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); - m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); }); + m_processor.finalize(); // DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); if (result != nullptr) { @@ -1046,7 +1047,7 @@ std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Pri return instances; } -void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb) +void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) { PROFILE_FUNC(); @@ -1111,10 +1112,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu #endif /* HAS_PRESSURE_EQUALIZER */ // Write information on the generator. - _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); + file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, - [this, file](const char* sz) { this->_write(file, sz); }, + [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); // Write notes (content of the Print Settings tab -> Notes) @@ -1125,10 +1126,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Remove the trailing '\r' from the '\r\n' sequence. if (! line.empty() && line.back() == '\r') line.pop_back(); - _write_format(file, "; %s\n", line.c_str()); + file.write_format("; %s\n", line.c_str()); } if (! lines.empty()) - _write(file, "\n"); + file.write("\n"); } print.throw_if_canceled(); @@ -1139,22 +1140,22 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu 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); - _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); - _write_format(file, "; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); - _write_format(file, "; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); - _write_format(file, "; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); - _write_format(file, "; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); + 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()) - _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); + file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); if (print.config().first_layer_extrusion_width.value > 0) - _write_format(file, "; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width()); - _write_format(file, "\n"); + 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(); // adds tags for time estimators if (print.config().remaining_times.value) - _write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str()); + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str()); // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); @@ -1218,7 +1219,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Disable fan. if (! print.config().cooling.get_at(initial_extruder_id) || print.config().disable_fan_first_layers.get_at(initial_extruder_id)) - _write(file, m_writer.set_fan(0, true)); + file.write(m_writer.set_fan(0, true)); // Let the start-up script prime the 1st printing tool. m_placeholder_parser.set("initial_tool", initial_extruder_id); @@ -1261,10 +1262,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); // adds tag for processor - _write_format(file, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); // Write the custom start G-code - _writeln(file, start_gcode); + file.writeln(start_gcode); // Process filament-specific gcode. /* if (has_wipe_tower) { @@ -1272,14 +1273,14 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } else { DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); + file.writeln(this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); } */ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); print.throw_if_canceled(); // Set other general things. - _write(file, this->preamble()); + file.write(this->preamble()); // Calculate wiping points if needed DoExport::init_ooze_prevention(print, m_ooze_prevention); @@ -1291,7 +1292,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. - _write(file, this->set_extruder(initial_extruder_id, 0.)); + file.write(this->set_extruder(initial_extruder_id, 0.)); } // Do all objects for each layer. @@ -1317,8 +1318,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); - _write(file, this->retract()); - _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); + file.write(this->retract()); + file.write(this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1330,7 +1331,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false); - _writeln(file, between_objects_gcode); + file.writeln(between_objects_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). m_cooling_buffer->reset(); @@ -1346,7 +1347,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } #ifdef HAS_PRESSURE_EQUALIZER if (m_pressure_equalizer) - _write(file, m_pressure_equalizer->process("", true)); + file.write(m_pressure_equalizer->process("", true)); #endif /* HAS_PRESSURE_EQUALIZER */ ++ finished_objects; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. @@ -1361,9 +1362,9 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); - _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); + file.write(m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); if (print.config().single_extruder_multi_material_priming) { - _write(file, m_wipe_tower->prime(*this)); + file.write(m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. BoundingBoxf bbox_print(get_print_extrusions_extents(print)); coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; @@ -1375,15 +1376,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu bool overlap = bbox_prime.overlap(bbox_print); if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) { - _write(file, this->retract()); - _write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. + file.write(this->retract()); + file.write("M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. if (overlap) { // Wait for the user to remove the priming extrusions. - _write(file, "M1 Remove priming towers and click button.\n"); + file.write("M1 Remove priming towers and click button.\n"); } else { // Just wait for a bit to let the user check, that the priming succeeded. //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. - _write(file, "M1 S10\n"); + file.write("M1 S10\n"); } } else { // This is not Marlin, M1 command is probably not supported. @@ -1410,19 +1411,19 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } #ifdef HAS_PRESSURE_EQUALIZER if (m_pressure_equalizer) - _write(file, m_pressure_equalizer->process("", true)); + file.write(m_pressure_equalizer->process("", true)); #endif /* HAS_PRESSURE_EQUALIZER */ if (m_wipe_tower) // Purge the extruder, pull out the active filament. - _write(file, m_wipe_tower->finalize(*this)); + file.write(m_wipe_tower->finalize(*this)); } // Write end commands to file. - _write(file, this->retract()); - _write(file, m_writer.set_fan(false)); + file.write(this->retract()); + file.write(m_writer.set_fan(false)); // adds tag for processor - _write_format(file, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); // Process filament-specific gcode in extruder order. { @@ -1434,48 +1435,48 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Process the end_filament_gcode for the active filament only. int extruder_id = m_writer.extruder()->id(); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config)); + file.writeln(this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config)); } else { for (const std::string &end_gcode : print.config().end_filament_gcode.values) { int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - _writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config)); + file.writeln(this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config)); } } - _writeln(file, this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id(), &config)); + file.writeln(this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id(), &config)); } - _write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% - _write(file, m_writer.postamble()); + file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% + file.write(m_writer.postamble()); // adds tags for time estimators if (print.config().remaining_times.value) - _write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); print.throw_if_canceled(); // Get filament stats. - _write(file, DoExport::update_print_stats_and_format_filament_stats( + file.write(DoExport::update_print_stats_and_format_filament_stats( // Const inputs has_wipe_tower, print.wipe_tower_data(), m_writer.extruders(), // Modifies print.m_print_statistics)); - _write(file, "\n"); - _write_format(file, "; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight); - _write_format(file, "; total filament cost = %.2lf\n", print.m_print_statistics.total_cost); + 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) - _write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); - _write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); + 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()); // 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. { - _write(file, "\n; prusaslicer_config = begin\n"); + file.write("\n; prusaslicer_config = begin\n"); std::string full_config; append_full_config(print, full_config); if (!full_config.empty()) - _write(file, full_config); - _write(file, "; prusaslicer_config = end\n"); + file.write(full_config); + file.write("; prusaslicer_config = end\n"); } print.throw_if_canceled(); } @@ -1565,16 +1566,16 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. -void GCode::print_machine_envelope(FILE *file, Print &print) +void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) { if ((print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { - fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", + file.write_format("M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), int(print.config().machine_max_acceleration_y.values.front() + 0.5), int(print.config().machine_max_acceleration_z.values.front() + 0.5), int(print.config().machine_max_acceleration_e.values.front() + 0.5)); - fprintf(file, "M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n", + file.write_format("M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n", int(print.config().machine_max_feedrate_x.values.front() + 0.5), int(print.config().machine_max_feedrate_y.values.front() + 0.5), int(print.config().machine_max_feedrate_z.values.front() + 0.5), @@ -1587,18 +1588,18 @@ void GCode::print_machine_envelope(FILE *file, Print &print) int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); - fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", + file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), travel_acc); assert(is_decimal_separator_point()); - fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", + file.write_format("M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", print.config().machine_max_jerk_x.values.front(), print.config().machine_max_jerk_y.values.front(), print.config().machine_max_jerk_z.values.front(), print.config().machine_max_jerk_e.values.front()); - fprintf(file, "M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n", + file.write_format("M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n", int(print.config().machine_min_extruding_rate.values.front() + 0.5), int(print.config().machine_min_travel_rate.values.front() + 0.5)); } @@ -1608,7 +1609,7 @@ void GCode::print_machine_envelope(FILE *file, Print &print) // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M140 - Set Extruder Temperature // M190 - Set Extruder Temperature and Wait -void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { // Initial bed temperature based on the first extruder. int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id); @@ -1621,7 +1622,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s // the custom start G-code emited these. std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait); if (! temp_set_by_gcode) - _write(file, set_temp_gcode); + file.write(set_temp_gcode); } // Write 1st layer extruder temperatures into the G-code. @@ -1629,7 +1630,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait // RepRapFirmware: G10 Sxx -void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; @@ -1646,7 +1647,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c // Set temperature of the first printing extruder only. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp > 0) - _write(file, m_writer.set_temperature(temp, wait, first_printing_extruder_id)); + file.write(m_writer.set_temperature(temp, wait, first_printing_extruder_id)); } else { // Set temperatures of all the printing extruders. for (unsigned int tool_id : print.extruders()) { @@ -1654,7 +1655,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c if (print.config().ooze_prevention.value) temp += print.config().standby_temperature_delta.value; if (temp > 0) - _write(file, m_writer.set_temperature(temp, wait, tool_id)); + file.write(m_writer.set_temperature(temp, wait, tool_id)); } } } @@ -1891,7 +1892,7 @@ namespace Skirt { // and performing the extruder specific extrusions together. void GCode::process_layer( // Write into the output file. - FILE *file, + GCodeOutputStream &file, const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector<LayerToPrint> &layers, @@ -1977,6 +1978,7 @@ void GCode::process_layer( } gcode += this->change_layer(print_z); // this will increase m_layer_index m_layer = &layer; + m_object_layer_over_raft = false; if (! print.config().layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); @@ -2235,8 +2237,13 @@ void GCode::process_layer( gcode+="; PURGING FINISHED\n"; for (InstanceToPrint &instance_to_print : instances_to_print) { + const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; + // To control print speed of the 1st object layer printed over raft interface. + bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && + instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); m_config.apply(instance_to_print.print_object.config(), true); - m_layer = layers[instance_to_print.layer_id].layer(); + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer(*m_layer); if (this->config().gcode_label_objects) @@ -2249,11 +2256,13 @@ void GCode::process_layer( m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layers[instance_to_print.layer_id].support_layer; + m_layer = layer_to_print.support_layer; + m_object_layer_over_raft = false; gcode += this->extrude_support( // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); - m_layer = layers[instance_to_print.layer_id].layer(); + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; } //FIXME order islands? // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) @@ -2298,7 +2307,7 @@ void GCode::process_layer( // printf("G-code after filter:\n%s\n", out.c_str()); #endif /* HAS_PRESSURE_EQUALIZER */ - _write(file, gcode); + file.write(gcode); BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); } @@ -2634,22 +2643,42 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill return gcode; } -void GCode::_write(FILE* file, const char *what) +bool GCode::GCodeOutputStream::is_error() const +{ + return ::ferror(this->f); +} + +void GCode::GCodeOutputStream::flush() +{ + ::fflush(this->f); +} + +void GCode::GCodeOutputStream::close() +{ + if (this->f) { + ::fclose(this->f); + this->f = nullptr; + } +} + +void GCode::GCodeOutputStream::write(const char *what) { if (what != nullptr) { const char* gcode = what; // writes string to file - fwrite(gcode, 1, ::strlen(gcode), file); + fwrite(gcode, 1, ::strlen(gcode), this->f); + //FIXME don't allocate a string, maybe process a batch of lines? + m_processor.process_buffer(std::string(gcode)); } } -void GCode::_writeln(FILE* file, const std::string &what) +void GCode::GCodeOutputStream::writeln(const std::string &what) { if (! what.empty()) - _write(file, (what.back() == '\n') ? what : (what + '\n')); + this->write(what.back() == '\n' ? what : what + '\n'); } -void GCode::_write_format(FILE* file, const char* format, ...) +void GCode::GCodeOutputStream::write_format(const char* format, ...) { va_list args; va_start(args, format); @@ -2673,7 +2702,7 @@ void GCode::_write_format(FILE* file, const char* format, ...) char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer; int res = ::vsnprintf(bufptr, buflen, format, args); if (res > 0) - _write(file, bufptr); + this->write(bufptr); if (buffer_dynamic) free(bufptr); @@ -2705,6 +2734,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double acceleration; if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) { acceleration = m_config.first_layer_acceleration.value; + } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { + acceleration = m_config.first_layer_acceleration_over_raft.value; } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { acceleration = m_config.perimeter_acceleration.value; } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { @@ -2749,6 +2780,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = m_volumetric_speed / path.mm3_per_mm; if (this->on_first_layer()) speed = m_config.get_abs_value("first_layer_speed", speed); + else if (this->object_layer_over_raft()) + speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); if (m_config.max_volumetric_speed.value > 0) { // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min( diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 08ab83002..1dbc5b70a 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -125,7 +125,8 @@ public: m_last_processor_extrusion_role(erNone), m_layer_count(0), m_layer_index(-1), - m_layer(nullptr), + m_layer(nullptr), + m_object_layer_over_raft(false), m_volumetric_speed(0), m_last_pos_defined(false), m_last_extrusion_role(erNone), @@ -138,7 +139,7 @@ public: m_silent_time_estimator_enabled(false), m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max())) {} - ~GCode() {} + ~GCode() = default; // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). @@ -183,13 +184,40 @@ public: }; private: - void _do_export(Print &print, FILE *file, ThumbnailsGeneratorCallback thumbnail_cb); + class GCodeOutputStream { + public: + GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {} + ~GCodeOutputStream() { this->close(); } + + bool is_open() const { return f; } + bool is_error() const; + + void flush(); + void close(); + + // Write a string into a file. + void write(const std::string& what) { this->write(what.c_str()); } + void write(const char* what); + + // Write a string into a file. + // Add a newline, if the string does not end with a newline already. + // Used to export a custom G-code section processed by the PlaceholderParser. + void writeln(const std::string& what); + + // Formats and write into a file the given data. + void write_format(const char* format, ...); + + private: + FILE *f = nullptr; + GCodeProcessor &m_processor; + }; + void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb); static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object); static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print); void process_layer( // Write into the output file. - FILE *file, + GCodeOutputStream &file, const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector<LayerToPrint> &layers, @@ -316,9 +344,11 @@ private: unsigned int m_layer_count; // Progress bar indicator. Increments from -1 up to layer_count. int m_layer_index; - // Current layer processed. Insequential printing mode, only a single copy will be printed. + // Current layer processed. In sequential printing mode, only a single copy will be printed. // In non-sequential mode, all its copies will be printed. const Layer* m_layer; + // m_layer is an object layer and it is being printed over raft surface. + bool m_object_layer_over_raft; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? ExtrusionRole m_last_extrusion_role; @@ -355,24 +385,14 @@ private: // Processor GCodeProcessor m_processor; - // Write a string into a file. - void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); } - void _write(FILE* file, const char *what); - - // Write a string into a file. - // Add a newline, if the string does not end with a newline already. - // Used to export a custom G-code section processed by the PlaceholderParser. - void _writeln(FILE* file, const std::string& what); - - // Formats and write into a file the given data. - void _write_format(FILE* file, const char* format, ...); - std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); - void print_machine_envelope(FILE *file, Print &print); - void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); - void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void print_machine_envelope(GCodeOutputStream &file, Print &print); + void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); // On the first printing layer. This flag triggers first layer speeds. bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } + // To control print speed of 1st object layer over raft interface. + bool object_layer_over_raft() const { return m_object_layer_over_raft; } friend ObjectByExtruder& object_by_extruder( std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder, diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 0d71a19f5..d16cfb9cf 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -21,6 +21,9 @@ #include <chrono> +static const float DEFAULT_TOOLPATH_WIDTH = 0.4f; +static const float DEFAULT_TOOLPATH_HEIGHT = 0.2f; + static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 @@ -344,20 +347,34 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } -void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<MoveVertex>& moves) +struct FilePtr { + FilePtr(FILE *f) : f(f) {} + ~FilePtr() { this->close(); } + void close() { + if (this->f) { + ::fclose(this->f); + this->f = nullptr; + } + } + FILE* f = nullptr; +}; + +void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<MoveVertex>& moves, std::vector<size_t>& lines_ends) { - boost::nowide::ifstream in(filename); - if (!in.good()) + FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; + if (in.f == nullptr) throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); // temporary file to contain modified gcode std::string out_path = filename + ".postprocess"; - FILE* out = boost::nowide::fopen(out_path.c_str(), "wb"); - if (out == nullptr) + FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; + if (out.f == nullptr) { throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + } auto time_in_minutes = [](float time_in_seconds) { - return int(::roundf(time_in_seconds / 60.0f)); + assert(time_in_seconds >= 0.f); + return int((time_in_seconds + 0.5f) / 60.0f); }; auto time_in_last_minute = [](float time_in_seconds) { @@ -389,7 +406,6 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st return std::string(line_M73); }; - GCodeReader parser; std::string gcode_line; size_t g1_lines_counter = 0; // keeps track of last exported pair <percent, remaining time> @@ -408,11 +424,12 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st std::string export_line; // replace placeholder lines with the proper final value - auto process_placeholders = [&](const std::string& gcode_line) { + // gcode_line is in/out parameter, to reduce expensive memory allocation + auto process_placeholders = [&](std::string& gcode_line) { unsigned int extra_lines_count = 0; // remove trailing '\n' - std::string line = gcode_line.substr(0, gcode_line.length() - 1); + auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1); std::string ret; if (line.length() > 1) { @@ -453,7 +470,10 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st } } - return std::tuple(!ret.empty(), ret.empty() ? gcode_line : ret, (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); + if (! ret.empty()) + // Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed. + gcode_line = ret; + return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); }; // check for temporary lines @@ -476,11 +496,19 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st g1_times_cache_it.emplace_back(machine.g1_times_cache.begin()); // add lines M73 to exported gcode - auto process_line_G1 = [&]() { + auto process_line_G1 = [ + // Lambdas, mostly for string formatting, all with an empty capture block. + time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute, + &self = std::as_const(*this), + // Caches, to be modified + &g1_times_cache_it, &last_exported_main, &last_exported_stop, + // String output + &export_line] + (const size_t g1_lines_counter) { unsigned int exported_lines_count = 0; - if (export_remaining_time_enabled) { + if (self.export_remaining_time_enabled) { for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = machines[i]; + const TimeMachine& machine = self.machines[i]; if (machine.enabled) { // export pair <percent, remaining time> // Skip all machine.g1_times_cache below g1_lines_counter. @@ -544,60 +572,81 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st }; // helper function to write to disk - auto write_string = [&](const std::string& str) { - fwrite((const void*)export_line.c_str(), 1, export_line.length(), out); - if (ferror(out)) { - in.close(); - fclose(out); + size_t out_file_pos = 0; + lines_ends.clear(); + auto write_string = [&export_line, &out, &out_path, &out_file_pos, &lines_ends](const std::string& str) { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f); + if (ferror(out.f)) { + out.close(); boost::nowide::remove(out_path.c_str()); throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } + for (size_t i = 0; i < export_line.size(); ++ i) + if (export_line[i] == '\n') + lines_ends.emplace_back(out_file_pos + i + 1); + out_file_pos += export_line.size(); export_line.clear(); }; unsigned int line_id = 0; std::vector<std::pair<unsigned int, unsigned int>> offsets; - while (std::getline(in, gcode_line)) { - if (!in.good()) { - fclose(out); - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); - } - - ++line_id; - - gcode_line += "\n"; - // replace placeholder lines - auto [processed, result, lines_added_count] = process_placeholders(gcode_line); - if (processed && lines_added_count > 0) - offsets.push_back({ line_id, lines_added_count }); - gcode_line = result; - if (!processed) { - // remove temporary lines - if (is_temporary_decoration(gcode_line)) - continue; - - // add lines M73 where needed - parser.parse_line(gcode_line, - [&](GCodeReader& reader, const GCodeReader::GCodeLine& line) { - if (line.cmd_is("G1")) { - unsigned int extra_lines_count = process_line_G1(); - ++g1_lines_counter; + { + // Read the input stream 64kB at a time, extract lines and process them. + std::vector<char> buffer(65536 * 10, 0); + // Line buffer. + assert(gcode_line.empty()); + for (;;) { + size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); + if (::ferror(in.f)) + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + bool eof = cnt_read == 0; + auto it = buffer.begin(); + auto it_bufend = buffer.begin() + cnt_read; + while (it != it_bufend || (eof && ! gcode_line.empty())) { + // Find end of line. + bool eol = false; + auto it_end = it; + for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; + // End of line is indicated also if end of file was reached. + eol |= eof && it_end == it_bufend; + gcode_line.insert(gcode_line.end(), it, it_end); + if (eol) { + ++line_id; + + gcode_line += "\n"; + // replace placeholder lines + auto [processed, lines_added_count] = process_placeholders(gcode_line); + if (processed && lines_added_count > 0) + offsets.push_back({ line_id, lines_added_count }); + if (! processed && ! is_temporary_decoration(gcode_line) && GCodeReader::GCodeLine::cmd_is(gcode_line, "G1")) { + // remove temporary lines, add lines M73 where needed + unsigned int extra_lines_count = process_line_G1(g1_lines_counter ++); if (extra_lines_count > 0) offsets.push_back({ line_id, extra_lines_count }); } - }); - } - export_line += gcode_line; - if (export_line.length() > 65535) - write_string(export_line); + export_line += gcode_line; + if (export_line.length() > 65535) + write_string(export_line); + gcode_line.clear(); + } + // Skip EOL. + it = it_end; + if (it != it_bufend && *it == '\r') + ++ it; + if (it != it_bufend && *it == '\n') + ++ it; + } + if (eof) + break; + } } if (!export_line.empty()) write_string(export_line); - fclose(out); + out.close(); in.close(); // updates moves' gcode ids which have been modified by the insertion of the M73 lines @@ -698,7 +747,9 @@ void GCodeProcessor::Result::reset() { } #else void GCodeProcessor::Result::reset() { - moves = std::vector<GCodeProcessor::MoveVertex>(); + + moves.clear(); + lines_ends.clear(); bed_shape = Pointfs(); settings_ids.reset(); extruders_count = 0; @@ -777,6 +828,9 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i } GCodeProcessor::GCodeProcessor() +#if ENABLE_FIX_PREVIEW_OPTIONS_Z +: m_options_z_corrector(m_result) +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z { reset(); m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; @@ -951,10 +1005,9 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } // replace missing values with default - std::string default_color = "#FF8000"; for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { if (m_result.extruder_colors[i].empty()) - m_result.extruder_colors[i] = default_color; + m_result.extruder_colors[i] = "#FF8000"; } m_extruder_colors.resize(m_result.extruder_colors.size()); @@ -1139,7 +1192,6 @@ void GCodeProcessor::reset() m_cp_color.reset(); m_producer = EProducer::Unknown; - m_producers_enabled = false; m_time_processor.reset(); m_used_filaments.reset(); @@ -1152,6 +1204,10 @@ void GCodeProcessor::reset() m_last_default_color_id = 0; #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.reset(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z + #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_mm3_per_mm_compare.reset(); m_height_compare.reset(); @@ -1159,27 +1215,26 @@ void GCodeProcessor::reset() #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } -void GCodeProcessor::process_file(const std::string& filename, bool apply_postprocess, std::function<void()> cancel_callback) +void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback) { - auto last_cancel_callback_time = std::chrono::high_resolution_clock::now(); - CNumericLocalesSetter locales_setter; #if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); + m_start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // pre-processing // parse the gcode file to detect its producer - if (m_producers_enabled) { + { m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { const std::string_view cmd = line.cmd(); - if (cmd.length() == 0) { + if (cmd.empty()) { const std::string_view comment = line.comment(); if (comment.length() > 1 && detect_producer(comment)) m_parser.quit_parsing(); } }); + m_parser.reset(); // if the gcode was produced by PrusaSlicer, // extract the config from it @@ -1201,18 +1256,45 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr m_result.id = ++s_result_id; // 1st move must be a dummy move m_result.moves.emplace_back(MoveVertex()); - m_parser.parse_file(filename, [this, cancel_callback, &last_cancel_callback_time](GCodeReader& reader, const GCodeReader::GCodeLine& line) { - if (cancel_callback != nullptr) { - // call the cancel callback every 100 ms - auto curr_time = std::chrono::high_resolution_clock::now(); - if (std::chrono::duration_cast<std::chrono::milliseconds>(curr_time - last_cancel_callback_time).count() > 100) { + size_t parse_line_callback_cntr = 10000; + m_parser.parse_file(filename, [this, cancel_callback, &parse_line_callback_cntr](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + if (-- parse_line_callback_cntr == 0) { + // Don't call the cancel_callback() too often, do it every at every 10000'th line. + parse_line_callback_cntr = 10000; + if (cancel_callback) cancel_callback(); - last_cancel_callback_time = curr_time; - } } - process_gcode_line(line); - }); + this->process_gcode_line(line, true); + }); + + this->finalize(); +} + +void GCodeProcessor::initialize(const std::string& filename) +{ + assert(is_decimal_separator_point()); + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + // process gcode + m_result.filename = filename; + m_result.id = ++s_result_id; + // 1st move must be a dummy move + m_result.moves.emplace_back(MoveVertex()); +} +void GCodeProcessor::process_buffer(const std::string &buffer) +{ + //FIXME maybe cache GCodeLine gline to be over multiple parse_buffer() invocations. + m_parser.parse_buffer(buffer, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { + this->process_gcode_line(line, false); + }); +} + +void GCodeProcessor::finalize() +{ // update width/height of wipe moves for (MoveVertex& move : m_result.moves) { if (move.type == EMoveType::Wipe) { @@ -1234,10 +1316,6 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr update_estimated_times_stats(); - // post-process to add M73 lines into the gcode - if (apply_postprocess) - m_time_processor.post_process(filename, m_result.moves); - #if ENABLE_GCODE_VIEWER_DATA_CHECKING std::cout << "\n"; m_mm3_per_mm_compare.output(); @@ -1245,8 +1323,9 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr m_width_compare.output(); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends); #if ENABLE_GCODE_VIEWER_STATISTICS - m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count(); + m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } @@ -1340,7 +1419,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) if (pos != cmt.npos) { std::string data_str = cmt.substr(pos + 1); std::vector<std::string> values_str; - boost::split(values_str, data_str, boost::is_any_of("|"), boost::token_compress_on); + boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on); for (const std::string& s : values_str) { out.emplace_back(static_cast<float>(string_to_double_decimal_point(s))); } @@ -1364,10 +1443,16 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) m_result.filament_densities.clear(); extract_floats(comment, "filamentDensities", m_result.filament_densities); } + else if (comment.find("extruderDiameter") != comment.npos) { + std::vector<float> extruder_diameters; + extract_floats(comment, "extruderDiameter", extruder_diameters); + m_result.extruders_count = extruder_diameters.size(); + } } }); - m_result.extruders_count = std::max<size_t>(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); + if (m_result.extruders_count == 0) + m_result.extruders_count = std::max<size_t>(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); if (bed_size.is_defined()) { m_result.bed_shape = { @@ -1379,7 +1464,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) } } -void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled) { /* std::cout << line.raw() << std::endl; */ @@ -1391,61 +1476,170 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) const std::string_view cmd = line.cmd(); if (cmd.length() > 1) { // process command lines - switch (::toupper(cmd[0])) + switch (cmd[0]) { + case 'g': case 'G': - { - switch (::atoi(&cmd[1])) - { - case 0: { process_G0(line); break; } // Move - case 1: { process_G1(line); break; } // Move - case 10: { process_G10(line); break; } // Retract - case 11: { process_G11(line); break; } // Unretract - case 20: { process_G20(line); break; } // Set Units to Inches - case 21: { process_G21(line); break; } // Set Units to Millimeters - case 22: { process_G22(line); break; } // Firmware controlled retract - case 23: { process_G23(line); break; } // Firmware controlled unretract - case 28: { process_G28(line); break; } // Move to origin - case 90: { process_G90(line); break; } // Set to Absolute Positioning - case 91: { process_G91(line); break; } // Set to Relative Positioning - case 92: { process_G92(line); break; } // Set Position - default: { break; } + switch (cmd.size()) { + case 2: + switch (cmd[1]) { + case '0': { process_G0(line); break; } // Move + case '1': { process_G1(line); break; } // Move + default: break; + } + break; + case 3: + switch (cmd[1]) { + case '1': + switch (cmd[2]) { + case '0': { process_G10(line); break; } // Retract + case '1': { process_G11(line); break; } // Unretract + default: break; + } + break; + case '2': + switch (cmd[2]) { + case '0': { process_G20(line); break; } // Set Units to Inches + case '1': { process_G21(line); break; } // Set Units to Millimeters + case '2': { process_G22(line); break; } // Firmware controlled retract + case '3': { process_G23(line); break; } // Firmware controlled unretract + case '8': { process_G28(line); break; } // Move to origin + default: break; + } + break; + case '9': + switch (cmd[2]) { + case '0': { process_G90(line); break; } // Set to Absolute Positioning + case '1': { process_G91(line); break; } // Set to Relative Positioning + case '2': { process_G92(line); break; } // Set Position + default: break; + } + break; } break; + default: + break; } + break; + case 'm': case 'M': - { - switch (::atoi(&cmd[1])) - { - case 1: { process_M1(line); break; } // Sleep or Conditional stop - case 82: { process_M82(line); break; } // Set extruder to absolute mode - case 83: { process_M83(line); break; } // Set extruder to relative mode - case 104: { process_M104(line); break; } // Set extruder temperature - case 106: { process_M106(line); break; } // Set fan speed - case 107: { process_M107(line); break; } // Disable fan - case 108: { process_M108(line); break; } // Set tool (Sailfish) - case 109: { process_M109(line); break; } // Set extruder temperature and wait - case 132: { process_M132(line); break; } // Recall stored home offsets - case 135: { process_M135(line); break; } // Set tool (MakerWare) - case 201: { process_M201(line); break; } // Set max printing acceleration - case 203: { process_M203(line); break; } // Set maximum feedrate - case 204: { process_M204(line); break; } // Set default acceleration - case 205: { process_M205(line); break; } // Advanced settings - case 221: { process_M221(line); break; } // Set extrude factor override percentage - case 401: { process_M401(line); break; } // Repetier: Store x, y and z position - case 402: { process_M402(line); break; } // Repetier: Go to stored position - case 566: { process_M566(line); break; } // Set allowable instantaneous speed change - case 702: { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. - default: { break; } + switch (cmd.size()) { + case 2: + switch (cmd[1]) { + case '1': { process_M1(line); break; } // Sleep or Conditional stop + default: break; } break; - } - case 'T': - { - process_T(line); // Select Tool + case 3: + switch (cmd[1]) { + case '8': + switch (cmd[2]) { + case '2': { process_M82(line); break; } // Set extruder to absolute mode + case '3': { process_M83(line); break; } // Set extruder to relative mode + default: break; + } + break; + default: + break; + } + break; + case 4: + switch (cmd[1]) { + case '1': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '4': { process_M104(line); break; } // Set extruder temperature + case '6': { process_M106(line); break; } // Set fan speed + case '7': { process_M107(line); break; } // Disable fan + case '8': { process_M108(line); break; } // Set tool (Sailfish) + case '9': { process_M109(line); break; } // Set extruder temperature and wait + default: break; + } + break; + case '3': + switch (cmd[3]) { + case '2': { process_M132(line); break; } // Recall stored home offsets + case '5': { process_M135(line); break; } // Set tool (MakerWare) + default: break; + } + break; + default: + break; + } + break; + case '2': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '1': { process_M201(line); break; } // Set max printing acceleration + case '3': { process_M203(line); break; } // Set maximum feedrate + case '4': { process_M204(line); break; } // Set default acceleration + case '5': { process_M205(line); break; } // Advanced settings + default: break; + } + break; + case '2': + switch (cmd[3]) { + case '1': { process_M221(line); break; } // Set extrude factor override percentage + default: break; + } + break; + default: + break; + } + break; + case '4': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '1': { process_M401(line); break; } // Repetier: Store x, y and z position + case '2': { process_M402(line); break; } // Repetier: Go to stored position + default: break; + } + break; + default: + break; + } + break; + case '5': + switch (cmd[2]) { + case '6': + switch (cmd[3]) { + case '6': { process_M566(line); break; } // Set allowable instantaneous speed change + default: break; + } + break; + default: + break; + } + break; + case '7': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '2': { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. + default: break; + } + break; + default: + break; + } + break; + default: + break; + } + break; + default: break; } - default: { break; } + break; + case 't': + case 'T': + process_T(line); // Select Tool + break; + default: + break; } } else { @@ -1453,7 +1647,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) if (comment.length() > 2 && comment.front() == ';') // Process tags embedded into comments. Tag comments always start at the start of a line // with a comment and continue with a tag without any whitespace separator. - process_tags(comment.substr(1)); + process_tags(comment.substr(1), producers_enabled); } } @@ -1502,10 +1696,10 @@ template<typename T> } } -void GCodeProcessor::process_tags(const std::string_view comment) +void GCodeProcessor::process_tags(const std::string_view comment, bool producers_enabled) { // producers tags - if (m_producers_enabled && process_producers_tags(comment)) + if (producers_enabled && process_producers_tags(comment)) return; // extrusion role tag @@ -1529,7 +1723,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) return; } - if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) { + if (!producers_enabled || m_producer == EProducer::PrusaSlicer) { // height tag if (boost::starts_with(comment, reserved_tag(ETags::Height))) { if (!parse_number(comment.substr(reserved_tag(ETags::Height).size()), m_forced_height)) @@ -1619,6 +1813,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER CustomGCode::Item item = { static_cast<double>(m_end_position[2]), CustomGCode::ColorChange, extruder_id + 1, color, "" }; m_result.custom_gcode_per_print_z.emplace_back(item); +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.set(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z process_custom_gcode_time(CustomGCode::ColorChange); process_filaments(CustomGCode::ColorChange); #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER @@ -1638,6 +1835,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER CustomGCode::Item item = { static_cast<double>(m_end_position[2]), CustomGCode::PausePrint, m_extruder_id + 1, "", "" }; m_result.custom_gcode_per_print_z.emplace_back(item); +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.set(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER process_custom_gcode_time(CustomGCode::PausePrint); return; @@ -1649,6 +1849,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER CustomGCode::Item item = { static_cast<double>(m_end_position[2]), CustomGCode::Custom, m_extruder_id + 1, "", "" }; m_result.custom_gcode_per_print_z.emplace_back(item); +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.set(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER return; } @@ -2210,9 +2413,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) return; EMoveType type = move_type(delta_pos); - if (type == EMoveType::Extrude && m_end_position[Z] == 0.0f) - type = EMoveType::Travel; - if (type == EMoveType::Extrude) { float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; @@ -2232,10 +2432,23 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) else { if (m_end_position[Z] > m_extruded_last_z + EPSILON) { m_height = m_end_position[Z] - m_extruded_last_z; +#if !ENABLE_FIX_PREVIEW_OPTIONS_Z m_extruded_last_z = m_end_position[Z]; +#endif // !ENABLE_FIX_PREVIEW_OPTIONS_Z } } + if (m_height == 0.0f) + m_height = DEFAULT_TOOLPATH_HEIGHT; + + if (m_end_position[Z] == 0.0f) + m_end_position[Z] = m_height; + +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_extruded_last_z = m_end_position[Z]; + m_options_z_corrector.update(m_height); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z + #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_height_compare.update(m_height, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -2252,17 +2465,17 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // cross section: rectangle + 2 semicircles m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(1.0 - 0.25 * M_PI) * m_height; + if (m_width == 0.0f) + m_width = DEFAULT_TOOLPATH_WIDTH; + // clamp width to avoid artifacts which may arise from wrong values of m_height - m_width = std::min(m_width, std::max(1.0f, 4.0f * m_height)); + m_width = std::min(m_width, std::max(2.0f, 4.0f * m_height)); #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_width_compare.update(m_width, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } - if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) - type = EMoveType::Travel; - // time estimate section auto move_length = [](const AxisCoords& delta_pos) { float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); @@ -2916,7 +3129,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_line_id + 1 : ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); - MoveVertex vertex = { + m_result.moves.push_back({ m_last_line_id, type, m_extrusion_role, @@ -2931,8 +3144,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_fan_speed, m_extruder_temps[m_extruder_id], static_cast<float>(m_result.moves.size()) - }; - m_result.moves.emplace_back(vertex); + }); // stores stop time placeholders for later use if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 4fcdd8df3..040f7432a 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -306,7 +306,7 @@ namespace Slic3r { // post process the file with the given filename to add remaining time lines M73 // and updates moves' gcode ids accordingly - void post_process(const std::string& filename, std::vector<MoveVertex>& moves); + void post_process(const std::string& filename, std::vector<MoveVertex>& moves, std::vector<size_t>& lines_ends); }; struct UsedFilaments // filaments per ColorChange @@ -350,6 +350,8 @@ namespace Slic3r { std::string filename; unsigned int id; std::vector<MoveVertex> moves; + // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. + std::vector<size_t> lines_ends; Pointfs bed_shape; SettingsIds settings_ids; size_t extruders_count; @@ -388,6 +390,45 @@ namespace Slic3r { bool has_first_vertex() const { return m_first_vertex.has_value(); } }; +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + // Helper class used to fix the z for color change, pause print and + // custom gcode markes + class OptionsZCorrector + { + Result& m_result; + std::optional<size_t> m_move_id; + std::optional<size_t> m_custom_gcode_per_print_z_id; + + public: + explicit OptionsZCorrector(Result& result) : m_result(result) { + } + + void set() { + m_move_id = m_result.moves.size() - 1; + m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1; + } + + void update(float height) { + if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value()) + return; + + const Vec3f position = m_result.moves.back().position; + + MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); + move.position = position; + move.height = height; + m_result.moves.erase(m_result.moves.begin() + *m_move_id); + m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z(); + reset(); + } + + void reset() { + m_move_id.reset(); + m_custom_gcode_per_print_z_id.reset(); + } + }; +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z + #if ENABLE_GCODE_VIEWER_DATA_CHECKING struct DataChecker { @@ -492,9 +533,15 @@ namespace Slic3r { CpColor m_cp_color; bool m_use_volumetric_e; SeamsDetector m_seams_detector; +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + OptionsZCorrector m_options_z_corrector; +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER size_t m_last_default_color_id; #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER +#if ENABLE_GCODE_VIEWER_STATISTICS + std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time; +#endif // ENABLE_GCODE_VIEWER_STATISTICS enum class EProducer { @@ -511,7 +558,6 @@ namespace Slic3r { static const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> Producers; EProducer m_producer; - bool m_producers_enabled; TimeProcessor m_time_processor; UsedFilaments m_used_filaments; @@ -534,7 +580,6 @@ namespace Slic3r { return m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; } void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } - void enable_producers(bool enabled) { m_producers_enabled = enabled; } void reset(); const Result& get_result() const { return m_result; } @@ -542,7 +587,12 @@ namespace Slic3r { // Process the gcode contained in the file with the given filename // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). - void process_file(const std::string& filename, bool apply_postprocess, std::function<void()> cancel_callback = nullptr); + void process_file(const std::string& filename, std::function<void()> cancel_callback = nullptr); + + // Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion. + void initialize(const std::string& filename); + void process_buffer(const std::string& buffer); + void finalize(); float get_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; @@ -555,10 +605,10 @@ namespace Slic3r { private: void apply_config(const DynamicPrintConfig& config); void apply_config_simplify3d(const std::string& filename); - void process_gcode_line(const GCodeReader::GCodeLine& line); + void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); // Process tags embedded into comments - void process_tags(const std::string_view comment); + void process_tags(const std::string_view comment, bool producers_enabled); bool process_producers_tags(const std::string_view comment); bool process_prusaslicer_tags(const std::string_view comment); bool process_cura_tags(const std::string_view comment); diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 7dc3c9a8a..4c4bebee4 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -2,6 +2,7 @@ #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/split.hpp> #include <boost/nowide/fstream.hpp> +#include <charconv> #include <fstream> #include <iostream> #include <iomanip> @@ -32,7 +33,7 @@ void GCodeReader::apply_config(const DynamicPrintConfig &config) m_extrusion_axis = get_extrusion_axis_char(m_config); } -const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command) +const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command) { PROFILE_FUNC(); @@ -70,9 +71,16 @@ const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, } if (axis != NUM_AXES_WITH_UNKNOWN) { // Try to parse the numeric value. +#ifdef WIN32 + double v; + auto [pend, ec] = std::from_chars(++ c, end, v); + if (pend != c && is_end_of_word(*pend)) { +#else + // The older version of GCC and Clang support std::from_chars just for integers, so strtod we used it instead. char *pend = nullptr; double v = strtod(++ c, &pend); if (pend != nullptr && is_end_of_word(*pend)) { +#endif // The axis value has been parsed correctly. if (axis != UNKNOWN_AXIS) gline.m_axis[int(axis)] = float(v); @@ -125,13 +133,42 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co } } -void GCodeReader::parse_file(const std::string &file, callback_t callback) +bool GCodeReader::parse_file(const std::string &file, callback_t callback) { boost::nowide::ifstream f(file); + f.sync_with_stdio(false); + std::vector<char> buffer(65536 * 10, 0); std::string line; m_parsing = true; - while (m_parsing && std::getline(f, line)) - this->parse_line(line, callback); + GCodeLine gline; + while (m_parsing && ! f.eof()) { + f.read(buffer.data(), buffer.size()); + if (! f.eof() && ! f.good()) + // Reading the input file failed. + return false; + auto it = buffer.begin(); + auto it_bufend = buffer.begin() + f.gcount(); + while (it != it_bufend) { + bool eol = false; + auto it_end = it; + for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; + eol |= f.eof() && it_end == it_bufend; + if (eol) { + gline.reset(); + if (line.empty()) + this->parse_line(&(*it), &(*it_end), gline, callback); + else { + line.insert(line.end(), it, it_end); + this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback); + line.clear(); + } + } else + line.insert(line.end(), it, it_end); + // Skip all the empty lines. + for (it = it_end; it != it_bufend && (*it == '\r' || *it == '\n'); ++ it) ; + } + } + return true; } bool GCodeReader::GCodeLine::has(char axis) const diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 6b58608e6..15376c0fc 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -44,11 +44,7 @@ public: float y = this->has(Y) ? (this->y() - reader.y()) : 0; return sqrt(x*x + y*y); } - bool cmd_is(const char *cmd_test) const { - const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str()); - size_t len = strlen(cmd_test); - return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]); - } + bool cmd_is(const char *cmd_test) const { return cmd_is(m_raw, cmd_test); } bool extruding(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) > 0; } bool retracting(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) < 0; } bool travel() const { return this->cmd_is("G1") && ! this->has(E); } @@ -66,6 +62,12 @@ public: float e() const { return m_axis[E]; } float f() const { return m_axis[F]; } + static bool cmd_is(const std::string &gcode_line, const char *cmd_test) { + const char *cmd = GCodeReader::skip_whitespaces(gcode_line.c_str()); + size_t len = strlen(cmd_test); + return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]); + } + private: std::string m_raw; float m_axis[NUM_AXES]; @@ -75,7 +77,8 @@ public: typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t; - GCodeReader() : m_verbose(false), m_extrusion_axis('E') { memset(m_position, 0, sizeof(m_position)); } + GCodeReader() : m_verbose(false), m_extrusion_axis('E') { this->reset(); } + void reset() { memset(m_position, 0, sizeof(m_position)); } void apply_config(const GCodeConfig &config); void apply_config(const DynamicPrintConfig &config); @@ -83,11 +86,12 @@ public: void parse_buffer(const std::string &buffer, Callback callback) { const char *ptr = buffer.c_str(); + const char *end = ptr + buffer.size(); GCodeLine gline; m_parsing = true; while (m_parsing && *ptr != 0) { gline.reset(); - ptr = this->parse_line(ptr, gline, callback); + ptr = this->parse_line(ptr, end, gline, callback); } } @@ -95,20 +99,21 @@ public: { this->parse_buffer(buffer, [](GCodeReader&, const GCodeReader::GCodeLine&){}); } template<typename Callback> - const char* parse_line(const char *ptr, GCodeLine &gline, Callback &callback) + const char* parse_line(const char *ptr, const char *end, GCodeLine &gline, Callback &callback) { std::pair<const char*, const char*> cmd; - const char *end = parse_line_internal(ptr, gline, cmd); + const char *line_end = parse_line_internal(ptr, end, gline, cmd); callback(*this, gline); update_coordinates(gline, cmd); - return end; + return line_end; } template<typename Callback> void parse_line(const std::string &line, Callback callback) - { GCodeLine gline; this->parse_line(line.c_str(), gline, callback); } + { GCodeLine gline; this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback); } - void parse_file(const std::string &file, callback_t callback); + // Returns false if reading the file failed. + bool parse_file(const std::string &file, callback_t callback); void quit_parsing() { m_parsing = false; } float& x() { return m_position[X]; } @@ -127,7 +132,7 @@ public: // void set_extrusion_axis(char axis) { m_extrusion_axis = axis; } private: - const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command); + const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command); void update_coordinates(GCodeLine &gline, std::pair<const char*, const char*> &command); static bool is_whitespace(char c) { return c == ' ' || c == '\t'; } diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 24549fd89..c97180982 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -1,17 +1,21 @@ #include "GCodeWriter.hpp" #include "CustomGCode.hpp" #include <algorithm> +#include <charconv> #include <iomanip> #include <iostream> #include <map> #include <assert.h> +#define XYZF_EXPORT_DIGITS 3 +#define E_EXPORT_DIGITS 5 + #define FLAVOR_IS(val) this->config.gcode_flavor == val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val #define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment; #define PRECISION(val, precision) std::fixed << std::setprecision(precision) << (val) -#define XYZF_NUM(val) PRECISION(val, 3) -#define E_NUM(val) PRECISION(val, 5) +#define XYZF_NUM(val) PRECISION(val, XYZF_EXPORT_DIGITS) +#define E_NUM(val) PRECISION(val, E_EXPORT_DIGITS) namespace Slic3r { @@ -288,16 +292,89 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id) return gcode.str(); } +class G1Writer { +private: + static constexpr const size_t buflen = 256; + char buf[buflen]; + char *buf_end; + std::to_chars_result ptr_err; + +public: + G1Writer() { + this->buf[0] = 'G'; + this->buf[1] = '1'; + this->buf_end = this->buf + buflen; + this->ptr_err.ptr = this->buf + 2; + } + + void emit_axis(const char axis, const double v, size_t digits) { + *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = axis; +#ifdef WIN32 + this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end, v, std::chars_format::fixed, digits); +#else + int buf_capacity = int(this->buf_end - this->ptr_err.ptr); + int ret = snprintf(this->ptr_err.ptr, buf_capacity, "%.*lf", int(digits), v); + if (ret <= 0 || ret > buf_capacity) + ptr_err.ec = std::errc::value_too_large; + else + this->ptr_err.ptr = this->ptr_err.ptr + ret; +#endif + } + + void emit_xy(const Vec2d &point) { + this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); + this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); + } + + void emit_xyz(const Vec3d &point) { + this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); + this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); + this->emit_z(point.z()); + } + + void emit_z(const double z) { + this->emit_axis('Z', z, XYZF_EXPORT_DIGITS); + } + + void emit_e(const std::string &axis, double v) { + if (! axis.empty()) { + // not gcfNoExtrusion + this->emit_axis(axis[0], v, E_EXPORT_DIGITS); + } + } + + void emit_f(double speed) { + this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); + } + + void emit_string(const std::string &s) { + strncpy(ptr_err.ptr, s.c_str(), s.size()); + ptr_err.ptr += s.size(); + } + + void emit_comment(bool allow_comments, const std::string &comment) { + if (allow_comments && ! comment.empty()) { + *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; + this->emit_string(comment); + } + } + + std::string string() { + *ptr_err.ptr ++ = '\n'; + return std::string(this->buf, ptr_err.ptr - buf); + } +}; + std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const { assert(F > 0.); assert(F < 100000.); - std::ostringstream gcode; - gcode << "G1 F" << XYZF_NUM(F); - COMMENT(comment); - gcode << cooling_marker; - gcode << "\n"; - return gcode.str(); + + G1Writer w; + w.emit_f(F); + w.emit_comment(this->config.gcode_comments, comment); + w.emit_string(cooling_marker); + return w.string(); } std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) @@ -305,13 +382,11 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com m_pos(0) = point(0); m_pos(1) = point(1); - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)) - << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_xy(point); + w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment) @@ -340,14 +415,11 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co m_lifted = 0; m_pos = point; - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)) - << " Z" << XYZF_NUM(point(2)) - << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_xyz(point); + w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::travel_to_z(double z, const std::string &comment) @@ -377,12 +449,11 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) if (speed == 0.) speed = this->config.travel_speed.value; - std::ostringstream gcode; - gcode << "G1 Z" << XYZF_NUM(z) - << " F" << XYZF_NUM(speed * 60.0); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_z(z); + w.emit_f(speed * 60.0); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } bool GCodeWriter::will_move_z(double z) const @@ -402,16 +473,12 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: m_pos(0) = point(0); m_pos(1) = point(1); m_extruder->extrude(dE); - - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)); - if (! m_extrusion_axis.empty()) - // not gcfNoExtrusion - gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E()); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + + G1Writer w; + w.emit_xy(point); + w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment) @@ -420,16 +487,11 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std m_lifted = 0; m_extruder->extrude(dE); - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)) - << " Z" << XYZF_NUM(point(2)); - if (! m_extrusion_axis.empty()) - // not gcfNoExtrusion - gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E()); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_xyz(point); + w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::retract(bool before_wipe) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6f9e4fd4c..fb6ab5635 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -161,8 +161,10 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig if (!result) throw Slic3r::RuntimeError("Loading of a model file failed."); +#if !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED if (model.objects.empty()) throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); +#endif // !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED for (ModelObject *o : model.objects) { @@ -557,6 +559,21 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string(); } +bool Model::is_fdm_support_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fdm_support_painted(); }); +} + +bool Model::is_seam_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_seam_painted(); }); +} + +bool Model::is_mm_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); }); +} + ModelObject::~ModelObject() { this->clear_volumes(); @@ -733,6 +750,16 @@ void ModelObject::clear_volumes() this->invalidate_bounding_box(); } +bool ModelObject::is_fdm_support_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fdm_support_painted(); }); +} + +bool ModelObject::is_seam_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_seam_painted(); }); +} + bool ModelObject::is_mm_painted() const { return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index ec6fac821..ba3156139 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -285,6 +285,10 @@ public: void clear_volumes(); void sort_volumes(bool full_sort); bool is_multiparts() const { return volumes.size() > 1; } + // Checks if any of object volume is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of object volume is painted using the seam painting gizmo. + bool is_seam_painted() const; // Checks if any of object volume is painted using the multi-material painting gizmo. bool is_mm_painted() const; @@ -723,6 +727,8 @@ public: this->mmu_segmentation_facets.set_new_unique_id(); } + bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } + bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } protected: @@ -1127,6 +1133,13 @@ public: // Propose an output path, replace extension. The new_extension shall contain the initial dot. std::string propose_export_file_name_and_path(const std::string &new_extension) const; + // Checks if any of objects is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of objects is painted using the seam painting gizmo. + bool is_seam_painted() const; + // Checks if any of objects is painted using the multi-material painting gizmo. + bool is_mm_painted() const; + private: explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } void assign_new_unique_ids_recursive(); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index a6be64299..68c40fc20 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -120,7 +120,8 @@ void Polyline::simplify(double tolerance) this->points = MultiPoint::_douglas_peucker(this->points, tolerance); } -/* This method simplifies all *lines* contained in the supplied area */ +#if 0 +// This method simplifies all *lines* contained in the supplied area template <class T> void Polyline::simplify_by_visibility(const T &area) { @@ -141,6 +142,7 @@ void Polyline::simplify_by_visibility(const T &area) } template void Polyline::simplify_by_visibility<ExPolygon>(const ExPolygon &area); template void Polyline::simplify_by_visibility<ExPolygonCollection>(const ExPolygonCollection &area); +#endif void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const { diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 51dcf9d36..758fc38cd 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -72,7 +72,7 @@ public: void extend_start(double distance); Points equally_spaced_points(double distance) const; void simplify(double tolerance); - template <class T> void simplify_by_visibility(const T &area); +// template <class T> void simplify_by_visibility(const T &area); void split_at(const Point &point, Polyline* p1, Polyline* p2) const; bool is_straight() const; bool is_closed() const { return this->points.front() == this->points.back(); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 3883c4980..f5c8235ed 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -428,9 +428,9 @@ static std::vector<std::string> s_Preset_print_options { #endif /* HAS_PRESSURE_EQUALIZER */ "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", - "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", - "min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", + "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration", + "bridge_acceleration", "first_layer_acceleration", "first_layer_acceleration_over_raft", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", + "min_skirt_length", "brim_width", "brim_separation", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 9ac8864f9..cb9f04e45 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -240,7 +240,6 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward if (! errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative); - // ysToDo : set prefered filament or sla_material (relates to print technology) and force o use of preffered printer model if it was added this->load_selections(config, preferred_selection); return substitutions; @@ -466,20 +465,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p // will be selected by the following call of this->update_compatible(PresetSelectCompatibleType::Always). const Preset *initial_printer = printers.find_preset(initial_printer_profile_name); + // If executed due to a Config Wizard update, preferred_printer contains the first newly installed printer, otherwise nullptr. const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant); - printers.select_preset_by_name( - (preferred_printer != nullptr /*&& (initial_printer == nullptr || !initial_printer->is_visible)*/) ? - preferred_printer->name : - initial_printer_profile_name, - true); - - // select preferred filament/sla_material profile if any exists and is visible - if (!preferred_selection.filament.empty()) - if (auto it = filaments.find_preset_internal(preferred_selection.filament); it != filaments.end() && it->is_visible) - initial_filament_profile_name = it->name; - if (!preferred_selection.sla_material.empty()) - if (auto it = sla_materials.find_preset_internal(preferred_selection.sla_material); it != sla_materials.end() && it->is_visible) - initial_sla_material_profile_name = it->name; + printers.select_preset_by_name(preferred_printer ? preferred_printer->name : initial_printer_profile_name, true); // Selects the profile, leaves it to -1 if the initial profile name is empty or if it was not found. prints.select_preset_by_name_strict(initial_print_profile_name); @@ -507,6 +495,21 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p this->update_compatible(PresetSelectCompatibleType::Always); this->update_multi_material_filament_presets(); + if (initial_printer != nullptr && (preferred_printer == nullptr || initial_printer == preferred_printer)) { + // Don't run the following code, as we want to activate default filament / SLA material profiles when installing and selecting a new printer. + // Only run this code if just a filament / SLA material was installed by Config Wizard for an active Printer. + auto printer_technology = printers.get_selected_preset().printer_technology(); + if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) { + if (auto it = filaments.find_preset_internal(preferred_selection.filament); it != filaments.end() && it->is_visible) { + filaments.select_preset_by_name_strict(preferred_selection.filament); + this->filament_presets.front() = filaments.get_selected_preset_name(); + } + } else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) { + if (auto it = sla_materials.find_preset_internal(preferred_selection.sla_material); it != sla_materials.end() && it->is_visible) + sla_materials.select_preset_by_name_strict(preferred_selection.sla_material); + } + } + // Parse the initial physical printer name. std::string initial_physical_printer_name = remove_ini_suffix(config.get("presets", "physical_printer")); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 48737f830..06052a62f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -88,7 +88,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "filament_cost", "filament_spool_weight", "first_layer_acceleration", + "first_layer_acceleration_over_raft", "first_layer_bed_temperature", + "first_layer_speed_over_raft", "gcode_comments", "gcode_label_objects", "infill_acceleration", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f010bad39..2917a9a19 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -505,10 +505,10 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionEnum<BrimType>(btOuterOnly)); - def = this->add("brim_offset", coFloat); - def->label = L("Brim offset"); + def = this->add("brim_separation", coFloat); + def->label = L("Brim separation gap"); def->category = L("Skirt and brim"); - def->tooltip = L("The offset of the brim from the printed object. The offset is applied after the elephant foot compensation."); + def->tooltip = L("Offset of brim from the printed object. The offset is applied after the elephant foot compensation."); def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; @@ -1152,6 +1152,15 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("first_layer_acceleration_over_raft", coFloat); + def->label = L("First object layer over raft interface"); + def->tooltip = L("This is the acceleration your printer will use for first layer of object above raft interface. Set zero " + "to disable acceleration control for first layer of object above raft interface."); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("first_layer_bed_temperature", coInts); def->label = L("First layer"); def->full_label = L("First layer bed temperature"); @@ -1194,6 +1203,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(30, false)); + def = this->add("first_layer_speed_over_raft", coFloatOrPercent); + def->label = L("Speed of object first layer over raft interface"); + def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to all the print moves " + "of the first object layer above raft interface, regardless of their type. If expressed as a percentage " + "(for example: 40%) it will scale the default speeds."); + def->sidetext = L("mm/s or %"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(30, false)); + def = this->add("first_layer_temperature", coInts); def->label = L("First layer"); def->full_label = L("First layer nozzle temperature"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 64c8445f8..d7409d12c 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -449,13 +449,15 @@ protected: \ PRINT_CONFIG_CLASS_DEFINE( PrintObjectConfig, - ((ConfigOptionFloat, brim_offset)) + ((ConfigOptionFloat, brim_separation)) ((ConfigOptionEnum<BrimType>, brim_type)) ((ConfigOptionFloat, brim_width)) ((ConfigOptionBool, clip_multipart_objects)) ((ConfigOptionBool, dont_support_bridges)) ((ConfigOptionFloat, elefant_foot_compensation)) ((ConfigOptionFloatOrPercent, extrusion_width)) + ((ConfigOptionFloat, first_layer_acceleration_over_raft)) + ((ConfigOptionFloatOrPercent, first_layer_speed_over_raft)) ((ConfigOptionBool, infill_only_where_needed)) // Force the generation of solid shells between adjacent materials/volumes. ((ConfigOptionBool, interface_shells)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d8d26baa6..d9a4f2670 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -500,7 +500,7 @@ bool PrintObject::invalidate_state_by_config_options( bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { if ( opt_key == "brim_width" - || opt_key == "brim_offset" + || opt_key == "brim_separation" || opt_key == "brim_type") { // Brim is printed below supports, support invalidates brim and skirt. steps.emplace_back(posSupportMaterial); @@ -2293,14 +2293,24 @@ void PrintObject::project_and_append_custom_facets( const indexed_triangle_set custom_facets = seam ? mv->seam_facets.get_facets_strict(*mv, type) : mv->supported_facets.get_facets_strict(*mv, type); - if (! custom_facets.indices.empty()) -#if 0 - project_triangles_to_slabs(this->layers(), custom_facets, - (this->trafo_centered() * mv->get_matrix()).cast<float>(), - seam, out); -#else - slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &out, [](){}); -#endif + if (! custom_facets.indices.empty()) { + if (seam) + project_triangles_to_slabs(this->layers(), custom_facets, + (this->trafo_centered() * mv->get_matrix()).cast<float>(), + seam, out); + else { + std::vector<Polygons> projected; + slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){}); + // Merge these projections with the output, layer by layer. + assert(! projected.empty()); + assert(out.empty() || out.size() == projected.size()); + if (out.empty()) + out = std::move(projected); + else + for (size_t i = 0; i < out.size(); ++ i) + append(out[i], std::move(projected[i])); + } + } } } diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 6efc5f4a9..7efe15bb2 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -7,6 +7,10 @@ using namespace Slic3r; +#ifndef NDEBUG +// #define EXPENSIVE_DEBUG_CHECKS +#endif // NDEBUG + // only private namespace not neccessary be in .hpp namespace QuadricEdgeCollapse { using Vertices = std::vector<stl_vertex>; @@ -79,10 +83,13 @@ namespace QuadricEdgeCollapse { init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn); std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info, uint32_t ti, const EdgeInfos& e_infos, const Indices& indices); + void reorder_edges(EdgeInfos &e_infos, const VertexInfo &v_info, uint32_t ti0, uint32_t ti1); bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info, const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its); bool degenerate(uint32_t vi, uint32_t ti0, uint32_t ti1, const VertexInfo &v_info, const EdgeInfos &e_infos, const Indices &indices); + bool create_no_volume(uint32_t vi0, uint32_t vi1, uint32_t ti0, uint32_t ti1, + const VertexInfo &v_info0, const VertexInfo &v_info1, const EdgeInfos &e_infos, const Indices &indices); // find edge with smallest error in triangle Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos); Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index); @@ -92,14 +99,25 @@ namespace QuadricEdgeCollapse { const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1); void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its); -#ifndef NDEBUG +#ifdef EXPENSIVE_DEBUG_CHECKS void store_surround(const char *obj_filename, size_t triangle_index, int depth, const indexed_triangle_set &its, const VertexInfos &v_infos, const EdgeInfos &e_infos); bool check_neighbors(const indexed_triangle_set &its, const TriangleInfos &t_infos, const VertexInfos &v_infos, const EdgeInfos &e_infos); -#endif /* NDEBUG */ +#endif /* EXPENSIVE_DEBUG_CHECKS */ -} // namespace QuadricEdgeCollapse + // constants --> may be move to config + const uint32_t check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel + const size_t max_triangle_count_for_one_vertex = 50; + // change speed of progress bargraph + const int status_init_size = 10; // in percents + // parts of init size + const int status_normal_size = 25; + const int status_sum_quadric = 25; + const int status_set_offsets = 10; + const int status_calc_errors = 30; + const int status_create_refs = 10; + } // namespace QuadricEdgeCollapse using namespace QuadricEdgeCollapse; @@ -110,10 +128,6 @@ void Slic3r::its_quadric_edge_collapse( std::function<void(void)> throw_on_cancel, std::function<void(int)> status_fn) { - // constants --> may be move to config - const int status_init_size = 10; // in percents - const int check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel - // check input if (triangle_count >= its.indices.size()) return; float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error; @@ -122,7 +136,8 @@ void Slic3r::its_quadric_edge_collapse( if (status_fn == nullptr) status_fn = [](int) {}; StatusFn init_status_fn = [&](int percent) { - status_fn(std::round((percent * status_init_size) / 100.)); + float n_percent = percent * status_init_size / 100.f; + status_fn(static_cast<int>(std::round(n_percent))); }; TriangleInfos t_infos; // only normals with information about deleted triangle @@ -145,7 +160,6 @@ void Slic3r::its_quadric_edge_collapse( mpq.reserve(its.indices.size()); for (Error &error :errors) mpq.push(error); - const size_t max_triangle_count_for_one_vertex = 50; CopyEdgeInfos ceis; ceis.reserve(max_triangle_count_for_one_vertex); EdgeInfos e_infos_swap; @@ -162,8 +176,9 @@ void Slic3r::its_quadric_edge_collapse( (1. - reduced); status_fn(static_cast<int>(std::round(status))); }; - // modulo for update status - uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100); + // modulo for update status, call each percent only once + uint32_t status_mod = std::max(uint32_t(16), + count_triangle_to_reduce / (100 - status_init_size)); uint32_t iteration_number = 0; float last_collapsed_error = 0.f; @@ -195,14 +210,21 @@ void Slic3r::its_quadric_edge_collapse( q += v_info1.q; Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices); // set of triangle indices that change quadric + uint32_t ti1 = -1; // triangle 1 index auto ti1_opt = (v_info0.count < v_info1.count)? find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) : find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ; + if (ti1_opt.has_value()) { + ti1 = *ti1_opt; + reorder_edges(e_infos, v_info0, ti0, ti1); + reorder_edges(e_infos, v_info1, ti0, ti1); + } if (!ti1_opt.has_value() || // edge has only one triangle - degenerate(vi0, ti0, *ti1_opt, v_info1, e_infos, its.indices) || - degenerate(vi1, ti0, *ti1_opt, v_info0, e_infos, its.indices) || - is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) || - is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) { + degenerate(vi0, ti0, ti1, v_info1, e_infos, its.indices) || + degenerate(vi1, ti0, ti1, v_info0, e_infos, its.indices) || + create_no_volume(vi0, vi1, ti0, ti1, v_info0, v_info1, e_infos, its.indices) || + is_flipped(new_vertex0, ti0, ti1, v_info0, t_infos, e_infos, its) || + is_flipped(new_vertex0, ti0, ti1, v_info1, t_infos, e_infos, its)) { // try other triangle's edge Vec3d errors = calculate_3errors(t0, its.vertices, v_infos); Vec3i ord = (errors[0] < errors[1]) ? @@ -227,29 +249,25 @@ void Slic3r::its_quadric_edge_collapse( mpq.push(e); continue; } - uint32_t ti1 = *ti1_opt; + last_collapsed_error = e.value; changed_triangle_indices.clear(); changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4); // for each vertex0 triangles - uint32_t v_info0_end = v_info0.start + v_info0.count; + uint32_t v_info0_end = v_info0.start + v_info0.count - 2; for (uint32_t di = v_info0.start; di < v_info0_end; ++di) { assert(di < e_infos.size()); uint32_t ti = e_infos[di].t_index; - if (ti == ti0) continue; // ti0 will be deleted - if (ti == ti1) continue; // ti1 will be deleted changed_triangle_indices.emplace_back(ti); } // for each vertex1 triangles - uint32_t v_info1_end = v_info1.start + v_info1.count; + uint32_t v_info1_end = v_info1.start + v_info1.count - 2; for (uint32_t di = v_info1.start; di < v_info1_end; ++di) { assert(di < e_infos.size()); EdgeInfo &e_info = e_infos[di]; uint32_t ti = e_info.t_index; - if (ti == ti0) continue; // ti0 will be deleted - if (ti == ti1) continue; // ti1 will be deleted Triangle &t = its.indices[ti]; t[e_info.edge] = vi0; // change index changed_triangle_indices.emplace_back(ti); @@ -282,7 +300,9 @@ void Slic3r::its_quadric_edge_collapse( t_info1.set_deleted(); // triangle counter decrementation actual_triangle_count-=2; +#ifdef EXPENSIVE_DEBUG_CHECKS assert(check_neighbors(its, t_infos, v_infos, e_infos)); +#endif // EXPENSIVE_DEBUG_CHECKS } // compact triangle @@ -384,13 +404,6 @@ SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t, std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors> QuadricEdgeCollapse::init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn) { - // change speed of progress bargraph - const int status_normal_size = 25; - const int status_sum_quadric = 25; - const int status_set_offsets = 10; - const int status_calc_errors = 30; - const int status_create_refs = 10; - int status_offset = 0; TriangleInfos t_infos(its.indices.size()); VertexInfos v_infos(its.vertices.size()); @@ -506,6 +519,38 @@ std::optional<uint32_t> QuadricEdgeCollapse::find_triangle_index1(uint32_t return {}; } +void QuadricEdgeCollapse::reorder_edges(EdgeInfos & e_infos, + const VertexInfo &v_info, + uint32_t ti0, + uint32_t ti1) +{ + // swap edge info of ti0 and ti1 to end(last one and one before) + size_t v_info_end = v_info.start + v_info.count - 2; + EdgeInfo &e_info_ti0 = e_infos[v_info_end]; + EdgeInfo &e_info_ti1 = e_infos[v_info_end+1]; + bool is_swaped = false; + for (size_t ei = v_info.start; ei < v_info_end; ++ei) { + EdgeInfo &e_info = e_infos[ei]; + if (e_info.t_index == ti0) { + std::swap(e_info, e_info_ti0); + if (is_swaped) return; + if (e_info.t_index == ti1) { + std::swap(e_info, e_info_ti1); + return; + } + is_swaped = true; + } else if (e_info.t_index == ti1) { + std::swap(e_info, e_info_ti1); + if (is_swaped) return; + if (e_info.t_index == ti0) { + std::swap(e_info, e_info_ti0); + return; + } + is_swaped = true; + } + } +} + bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, uint32_t ti0, uint32_t ti1, @@ -519,12 +564,10 @@ bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, static const float dot_thr = 0.2f; // Value from simplify mesh cca 80 DEG // for each vertex triangles - size_t v_info_end = v_info.start + v_info.count; + size_t v_info_end = v_info.start + v_info.count-2; for (size_t ei = v_info.start; ei < v_info_end; ++ei) { assert(ei < e_infos.size()); const EdgeInfo &e_info = e_infos[ei]; - if (e_info.t_index == ti0) continue; // ti0 will be deleted - if (e_info.t_index == ti1) continue; // ti1 will be deleted const Triangle &t = its.indices[e_info.t_index]; const Vec3f &normal = t_infos[e_info.t_index].n; const Vec3f &vf = its.vertices[t[(e_info.edge + 1) % 3]]; @@ -554,12 +597,10 @@ bool QuadricEdgeCollapse::degenerate(uint32_t vi, { // check surround triangle do not contain vertex index // protect from creation of triangle with two same vertices inside - size_t v_info_end = v_info.start + v_info.count; + size_t v_info_end = v_info.start + v_info.count - 2; for (size_t ei = v_info.start; ei < v_info_end; ++ei) { assert(ei < e_infos.size()); const EdgeInfo &e_info = e_infos[ei]; - if (e_info.t_index == ti0) continue; // ti0 will be deleted - if (e_info.t_index == ti1) continue; // ti1 will be deleted const Triangle &t = indices[e_info.t_index]; for (size_t i = 0; i < 3; ++i) if (static_cast<uint32_t>(t[i]) == vi) return true; @@ -567,6 +608,52 @@ bool QuadricEdgeCollapse::degenerate(uint32_t vi, return false; } +bool QuadricEdgeCollapse::create_no_volume( + uint32_t vi0 , uint32_t vi1, + uint32_t ti0 , uint32_t ti1, + const VertexInfo &v_info0, const VertexInfo &v_info1, + const EdgeInfos & e_infos, const Indices &indices) +{ + // check that triangles around vertex0 doesn't have half edge + // with opposit order in set of triangles around vertex1 + // protect from creation of two triangles with oposit order - no volume space + size_t v_info0_end = v_info0.start + v_info0.count - 2; + size_t v_info1_end = v_info1.start + v_info1.count - 2; + for (size_t ei0 = v_info0.start; ei0 < v_info0_end; ++ei0) { + const EdgeInfo &e_info0 = e_infos[ei0]; + const Triangle &t0 = indices[e_info0.t_index]; + // edge CCW vertex indices are t0vi0, t0vi1 + size_t t0i = 0; + uint32_t t0vi0 = static_cast<uint32_t>(t0[t0i]); + if (t0vi0 == vi0) { + ++t0i; + t0vi0 = static_cast<uint32_t>(t0[t0i]); + } + ++t0i; + uint32_t t0vi1 = static_cast<uint32_t>(t0[t0i]); + if (t0vi1 == vi0) { + ++t0i; + t0vi1 = static_cast<uint32_t>(t0[t0i]); + } + for (size_t ei1 = v_info1.start; ei1 < v_info1_end; ++ei1) { + const EdgeInfo &e_info1 = e_infos[ei1]; + const Triangle &t1 = indices[e_info1.t_index]; + size_t t1i = 0; + for (; t1i < 3; ++t1i) if (static_cast<uint32_t>(t1[t1i]) == t0vi1) break; + if (t1i >= 3) continue; // without vertex index from triangle 0 + // check if second index is same too + ++t1i; + if (t1i == 3) t1i = 0; // triangle loop(modulo 3) + if (static_cast<uint32_t>(t1[t1i]) == vi1) { + ++t1i; + if (t1i == 3) t1i = 0; // triangle loop(modulo 3) + } + if (static_cast<uint32_t>(t1[t1i]) == t0vi0) return true; + } + } + return false; +} + Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t, const Vertices & vertices, const VertexInfos &v_infos) @@ -732,7 +819,8 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos, its.indices.erase(its.indices.begin() + ti_new, its.indices.end()); } -#ifndef NDEBUG +#ifdef EXPENSIVE_DEBUG_CHECKS + // store triangle surrounding to file void QuadricEdgeCollapse::store_surround(const char *obj_filename, size_t triangle_index, @@ -842,4 +930,4 @@ bool QuadricEdgeCollapse::check_neighbors(const indexed_triangle_set &its, } return true; } -#endif /* NDEBUG */ +#endif /* EXPENSIVE_DEBUG_CHECKS */ diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 2e4a47954..1b66bcc53 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -2810,22 +2810,22 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf Polygons brim; if (object.has_brim()) { // Calculate the area covered by the brim. - const BrimType brim_type = object.config().brim_type; - const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; - const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - const auto brim_offset = scaled<float>(object.config().brim_offset.value + object.config().brim_width.value); + const BrimType brim_type = object.config().brim_type; + const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; + const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; + const auto brim_separation = scaled<float>(object.config().brim_separation.value + object.config().brim_width.value); for (const ExPolygon &ex : object.layers().front()->lslices) { if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_offset)); + polygons_append(brim, offset(ex, brim_separation)); else { if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_offset, ClipperLib::jtRound, float(scale_(0.1)))); + polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1)))); else brim.emplace_back(ex.contour); if (brim_inner) { Polygons holes = ex.holes; polygons_reverse(holes); - holes = offset(holes, - brim_offset, ClipperLib::jtRound, float(scale_(0.1))); + holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1))); polygons_reverse(holes); polygons_append(brim, std::move(holes)); } else diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 6e1d6cdd7..3305822a4 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -37,24 +37,37 @@ //==================== -// 2.4.0.alpha0 techs +// 2.4.0.alpha1 techs //==================== -#define ENABLE_2_4_0_ALPHA0 1 +#define ENABLE_2_4_0_ALPHA1 1 // Enable delayed rendering of transparent volumes -#define ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING (1 && ENABLE_2_4_0_ALPHA1) // Enable the fix of importing color print view from gcode files into GCodeViewer -#define ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER (1 && ENABLE_2_4_0_ALPHA1) // Enable drawing contours, at cut level, for sinking volumes -#define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA1) // Enable implementation of retract acceleration in gcode processor -#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA1) // Enable the fix for exporting and importing to/from 3mf file of mirrored volumes -#define ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT (1 && ENABLE_2_4_0_ALPHA1) // Enable rendering seams (and other options) in preview using models -#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA1) +// Enable save and save as commands to be enabled also when the plater is empty and allow to load empty projects +#define ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED (1 && ENABLE_2_4_0_ALPHA1) + + +//==================== +// 2.4.0.alpha2 techs +//==================== +#define ENABLE_2_4_0_ALPHA2 1 + +// Enable rendering seams (and other options) in preview using batched models on systems not supporting OpenGL 3.3 +#define ENABLE_SEAMS_USING_BATCHED_MODELS (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_2_4_0_ALPHA2) +// Enable fixing the z position of color change, pause print and custom gcode markers in preview +#define ENABLE_FIX_PREVIEW_OPTIONS_Z (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER && ENABLE_2_4_0_ALPHA2) // Enable replacing a missing file during reload from disk command -#define ENABLE_RELOAD_FROM_DISK_REPLACE_FILE (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_RELOAD_FROM_DISK_REPLACE_FILE (1 && ENABLE_2_4_0_ALPHA2) #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index c330f34b2..c5dbdac9c 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -984,16 +984,11 @@ std::string log_memory_info(bool ignore_loglevel) } PROCESS_MEMORY_COUNTERS_EX, *PPROCESS_MEMORY_COUNTERS_EX; #endif /* PROCESS_MEMORY_COUNTERS_EX */ - - HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); - if (hProcess != nullptr) { - PROCESS_MEMORY_COUNTERS_EX pmc; - if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) - out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + "; PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + "; Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; - else - out += " Used memory: N/A"; - CloseHandle(hProcess); - } + PROCESS_MEMORY_COUNTERS_EX pmc; + if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) + out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + "; PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + "; Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; + else + out += " Used memory: N/A"; #elif defined(__linux__) or defined(__APPLE__) // Get current memory usage. #ifdef __APPLE__ |