From dd0d4c8c4a6d63ace2d493ba3b5747cd1a841c03 Mon Sep 17 00:00:00 2001 From: QIDI TECH <893239786@qq.com> Date: Tue, 27 Jun 2023 11:07:34 +0800 Subject: [PATCH] update to latest version --- deps/OpenEXR/OpenEXR.cmake | 3 +- deps/OpenEXR/OpenEXR.patch | 30 ++ deps/wxWidgets/wxWidgets.cmake | 4 +- resources/profiles/QIDITechnology.idx | 2 + resources/profiles/QIDITechnology.ini | 4 +- src/imgui/imgui_widgets.cpp | 4 +- src/libslic3r/Algorithm/RegionExpansion.cpp | 11 +- src/libslic3r/Algorithm/RegionExpansion.hpp | 4 + src/libslic3r/Arrange.cpp | 18 +- src/libslic3r/Arrange.hpp | 19 ++ src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/ExtrusionRole.hpp | 1 + src/libslic3r/Fill/Fill.cpp | 16 +- src/libslic3r/Fill/FillLightning.cpp | 2 +- src/libslic3r/Fill/FillLightning.hpp | 1 - .../Format/SLAArchiveFormatRegistry.cpp | 58 ++-- .../Format/SLAArchiveFormatRegistry.hpp | 6 +- src/libslic3r/GCode.cpp | 13 +- src/libslic3r/GCode/ConflictChecker.cpp | 290 ++++++++++++++++++ src/libslic3r/GCode/ConflictChecker.hpp | 129 ++++++++ src/libslic3r/GCode/ExtrusionProcessor.hpp | 4 +- src/libslic3r/GCode/GCodeProcessor.cpp | 217 +++++++------ src/libslic3r/GCode/GCodeProcessor.hpp | 25 ++ src/libslic3r/GCode/WipeTower.cpp | 18 +- src/libslic3r/GCode/WipeTower.hpp | 1 + src/libslic3r/GCodeReader.cpp | 7 +- src/libslic3r/Layer.cpp | 16 +- src/libslic3r/LayerRegion.cpp | 142 ++++++--- src/libslic3r/Model.cpp | 36 --- src/libslic3r/Model.hpp | 8 +- src/libslic3r/PNGReadWrite.hpp | 1 + src/libslic3r/PlaceholderParser.cpp | 3 + src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 100 +++++- src/libslic3r/Print.hpp | 37 +++ src/libslic3r/PrintConfig.cpp | 24 +- src/libslic3r/PrintConfig.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 14 +- src/libslic3r/SupportSpotsGenerator.cpp | 14 +- src/libslic3r/Thread.cpp | 12 +- src/libslic3r/Thread.hpp | 28 +- src/libslic3r/TriangleMesh.cpp | 2 +- src/libslic3r/utils.cpp | 1 + src/slic3r/CMakeLists.txt | 5 +- src/slic3r/GUI/3DScene.cpp | 13 +- src/slic3r/GUI/3DScene.hpp | 4 +- src/slic3r/GUI/ConfigWizard.cpp | 71 +++-- src/slic3r/GUI/GCodeViewer.cpp | 27 +- src/slic3r/GUI/GCodeViewer.hpp | 8 + src/slic3r/GUI/GLCanvas3D.cpp | 95 +++++- src/slic3r/GUI/GLCanvas3D.hpp | 4 +- src/slic3r/GUI/GUI_App.cpp | 61 ++-- src/slic3r/GUI/GUI_App.hpp | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 29 +- src/slic3r/GUI/GUI_Preview.cpp | 6 + src/slic3r/GUI/GUI_Preview.hpp | 2 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 77 +++-- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 1 + src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 5 +- src/slic3r/GUI/ImGuiWrapper.cpp | 2 +- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 63 ++-- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 11 +- src/slic3r/GUI/Jobs/CancellableJob.hpp | 60 ++++ src/slic3r/GUI/Jobs/FillBedJob.cpp | 34 +- src/slic3r/GUI/Jobs/FillBedJob.hpp | 2 +- src/slic3r/GUI/NotificationManager.cpp | 4 +- src/slic3r/GUI/NotificationManager.hpp | 2 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 20 +- src/slic3r/GUI/Plater.cpp | 90 +++++- src/slic3r/GUI/Plater.hpp | 2 + src/slic3r/GUI/PresetComboBoxes.cpp | 280 ++++++++++++----- src/slic3r/GUI/Tab.cpp | 151 +++++++-- src/slic3r/GUI/Tab.hpp | 4 +- .../Utils/{Mainsail.cpp => Moonraker.cpp} | 25 +- .../Utils/{Mainsail.hpp => Moonraker.hpp} | 10 +- src/slic3r/Utils/OctoPrint.cpp | 15 +- src/slic3r/Utils/PrintHost.cpp | 4 +- version.inc | 6 +- 80 files changed, 1931 insertions(+), 599 deletions(-) create mode 100644 deps/OpenEXR/OpenEXR.patch create mode 100644 src/libslic3r/GCode/ConflictChecker.cpp create mode 100644 src/libslic3r/GCode/ConflictChecker.hpp create mode 100644 src/slic3r/GUI/Jobs/CancellableJob.hpp rename src/slic3r/Utils/{Mainsail.cpp => Moonraker.cpp} (93%) rename src/slic3r/Utils/{Mainsail.hpp => Moonraker.hpp} (91%) diff --git a/deps/OpenEXR/OpenEXR.cmake b/deps/OpenEXR/OpenEXR.cmake index a39139c..9a0f092 100644 --- a/deps/OpenEXR/OpenEXR.cmake +++ b/deps/OpenEXR/OpenEXR.cmake @@ -4,6 +4,7 @@ qidislicer_add_cmake_project(OpenEXR URL_HASH SHA256=0307a3d7e1fa1e77e9d84d7e9a8694583fbbbfd50bdc6884e2c96b8ef6b902de DEPENDS ${ZLIB_PKG} GIT_TAG v2.5.5 + PATCH_COMMAND COMMAND ${PATCH_CMD} ${CMAKE_CURRENT_LIST_DIR}/OpenEXR.patch CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_TESTING=OFF @@ -14,4 +15,4 @@ qidislicer_add_cmake_project(OpenEXR if (MSVC) add_debug_dep(dep_OpenEXR) -endif () \ No newline at end of file +endif () diff --git a/deps/OpenEXR/OpenEXR.patch b/deps/OpenEXR/OpenEXR.patch new file mode 100644 index 0000000..b9f9f1c --- /dev/null +++ b/deps/OpenEXR/OpenEXR.patch @@ -0,0 +1,30 @@ +--- ../OpenEXR-orig/OpenEXR/IlmImf/ImfDwaCompressor.cpp 2021-02-12 17:56:19.000000000 +0100 ++++ ./OpenEXR/IlmImf/ImfDwaCompressor.cpp 2023-06-01 13:21:32.666695400 +0200 +@@ -159,6 +159,7 @@ + #include + + #include ++#include + + + // Windows specific addition to prevent the indirect import of the redefined min/max macros +--- ../OpenEXR-orig/OpenEXR/IlmImf/ImfHuf.cpp 2021-02-12 17:56:19.000000000 +0100 ++++ ./OpenEXR/IlmImf/ImfHuf.cpp 2023-06-01 13:21:53.018583400 +0200 +@@ -53,6 +53,7 @@ + #include + #include + #include ++#include + + + using namespace std; +--- ../OpenEXR-orig/OpenEXR/IlmImf/ImfMisc.cpp 2021-02-12 17:56:19.000000000 +0100 ++++ ./OpenEXR/IlmImf/ImfMisc.cpp 2023-06-01 13:22:15.777480000 +0200 +@@ -40,6 +40,7 @@ + // + //----------------------------------------------------------------------------- + ++#include + #include + #include + #include diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 1f9e121..7e934a0 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -30,8 +30,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() qidislicer_add_cmake_project(wxWidgets - URL https://github.com/prusa3d/wxWidgets/archive/0b49beaacce17d90f0c370ecd73221abd089667a.zip - URL_HASH SHA256=8fa978a76d6bd811b30eecc5124186b9ad54290b820f3a354e85bfa9dae6a5ce + URL https://github.com/prusa3d/wxWidgets/archive/78aa2dc0ea7ce99dc19adc1140f74c3e2e3f3a26.zip + URL_HASH SHA256=94b7d972373503e380e5a8b0ca63b1ccb956da4006402298dd89a0c5c7041b1e PATCH_COMMAND ${_patch_cmd} DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS diff --git a/resources/profiles/QIDITechnology.idx b/resources/profiles/QIDITechnology.idx index c94cd21..eb081a2 100644 --- a/resources/profiles/QIDITechnology.idx +++ b/resources/profiles/QIDITechnology.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 1.0.2 +1.0.2 Add filament property min_slic3r_version = 1.0.1 1.0.1 Optimize parameters min_slic3r_version = 1.0.0 diff --git a/resources/profiles/QIDITechnology.ini b/resources/profiles/QIDITechnology.ini index 874f07c..3680c56 100644 --- a/resources/profiles/QIDITechnology.ini +++ b/resources/profiles/QIDITechnology.ini @@ -5,7 +5,7 @@ name = QIDI Technology # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.0.1 +config_version = 1.0.2 # Where to get the updates from? config_update_url = https://github.com/QIDITECH/QIDISlicer/releases/download/QIDITechnology/ changelog_url = https://qidi3d.com/pages/software-firmware @@ -842,7 +842,7 @@ extruder_colour = "" extruder_offset = 0x0 gcode_flavor = klipper high_current_on_filament_swap = 0 -host_type = mainsail +host_type = moonraker inherits = layer_gcode = machine_limits_usage = time_estimate_only diff --git a/src/imgui/imgui_widgets.cpp b/src/imgui/imgui_widgets.cpp index ef31c7f..8f9e5fb 100644 --- a/src/imgui/imgui_widgets.cpp +++ b/src/imgui/imgui_widgets.cpp @@ -5126,13 +5126,13 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if ((flags & ImGuiColorEditFlags_NoLabel)) - Text(current_label ? current_label : "Current"); + Text("%s", current_label ? current_label : "Current"); ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); if (ref_col != NULL) { - Text(original_label ? original_label : "Original"); + Text("%s", original_label ? original_label : "Original"); ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2))) { diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index e36f5e6..ee2a5aa 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -480,10 +480,8 @@ std::vector expand_expolygons(const ExPolygons &src, const ExPolygons return out; } -std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +std::vector merge_expansions_into_expolygons(ExPolygons &&src, std::vector &&expanded) { - // expanded regions are sorted by boundary id and source id - std::vector expanded = propagate_waves(src, boundary, params); // expanded regions will be merged into source regions, thus they will be re-sorted by source id. std::sort(expanded.begin(), expanded.end(), [](const auto &l, const auto &r) { return l.src_id < r.src_id; }); uint32_t last = 0; @@ -535,5 +533,12 @@ std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygon return out; } +std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms) +{ + // expanded regions are sorted by boundary id and source id + std::vector expanded = propagate_waves(src, boundary, params); + return merge_expansions_into_expolygons(std::move(src), std::move(expanded)); +} + } // Algorithm } // Slic3r diff --git a/src/libslic3r/Algorithm/RegionExpansion.hpp b/src/libslic3r/Algorithm/RegionExpansion.hpp index 26aab19..eb99674 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.hpp +++ b/src/libslic3r/Algorithm/RegionExpansion.hpp @@ -72,6 +72,7 @@ struct RegionExpansion }; std::vector propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); +std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); std::vector propagate_waves(const ExPolygons &src, const ExPolygons &boundary, // Scaled expansion value @@ -106,6 +107,9 @@ std::vector expand_expolygons(const ExPolygons &src, const ExPolygons // Don't take more than max_nr_steps for small expansion_step. size_t max_nr_steps); +// Merge src with expansions, return the merged expolygons. +std::vector merge_expansions_into_expolygons(ExPolygons &&src, std::vector &&expanded); + std::vector expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms); } // Algorithm diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index b3293e1..ea71a1a 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -750,10 +750,26 @@ void arrange(ArrangePolygons &items, d += corr; for (auto &itm : items) - if (itm.bed_idx == bedidx) + if (itm.bed_idx == int(bedidx)) itm.translation += d; } } +BoundingBox bounding_box(const InfiniteBed &bed) +{ + BoundingBox ret; + using C = coord_t; + + // It is important for Mx and My to be strictly less than half of the + // range of type C. width(), height() and area() will not overflow this way. + C Mx = C((std::numeric_limits::lowest() + 2 * bed.center.x()) / 4.01); + C My = C((std::numeric_limits::lowest() + 2 * bed.center.y()) / 4.01); + + ret.max = bed.center - Point{Mx, My}; + ret.min = bed.center + Point{Mx, My}; + + return ret; +} + } // namespace arr } // namespace Slic3r diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 874742e..f705a1a 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -55,6 +55,25 @@ struct IrregularBed { using ArrangeBed = boost::variant; +BoundingBox bounding_box(const InfiniteBed &bed); +inline BoundingBox bounding_box(const RectangleBed &b) { return b.bb; } +inline BoundingBox bounding_box(const SegmentedRectangleBed &b) { return b.bb; } +inline BoundingBox bounding_box(const CircleBed &b) +{ + auto r = static_cast(std::round(b.radius())); + Point R{r, r}; + + return {b.center() - R, b.center() + R}; +} +inline BoundingBox bounding_box(const ArrangeBed &b) +{ + BoundingBox ret; + auto visitor = [&ret](const auto &b) { ret = bounding_box(b); }; + boost::apply_visitor(visitor, b); + + return ret; +} + ArrangeBed to_arrange_bed(const Points &bedpts); /// A logical bed representing an object not being arranged. Either the arrange diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index b578932..0de0b4e 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -138,6 +138,8 @@ set(SLIC3R_SOURCES GCode/ThumbnailData.hpp GCode/Thumbnails.cpp GCode/Thumbnails.hpp + GCode/ConflictChecker.cpp + GCode/ConflictChecker.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/FindReplace.cpp diff --git a/src/libslic3r/ExtrusionRole.hpp b/src/libslic3r/ExtrusionRole.hpp index 5952d3f..986c139 100644 --- a/src/libslic3r/ExtrusionRole.hpp +++ b/src/libslic3r/ExtrusionRole.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace Slic3r { diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ed735a3..499e7b8 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -468,11 +468,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); - if (surface_fill.params.pattern == ipLightning) { - auto *lf = dynamic_cast(f.get()); - lf->generator = lightning_generator; - lf->num_raft_layers = this->object()->slicing_parameters().raft_layers(); - } + if (surface_fill.params.pattern == ipLightning) + dynamic_cast(f.get())->generator = lightning_generator; if (surface_fill.params.pattern == ipEnsuring) { auto *fill_ensuring = dynamic_cast(f.get()); @@ -649,18 +646,15 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc // Create the filler object. std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); f->set_bounding_box(bbox); - f->layer_id = this->id(); + f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers. f->z = this->print_z; f->angle = surface_fill.params.angle; f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); - if (surface_fill.params.pattern == ipLightning) { - auto *lf = dynamic_cast(f.get()); - lf->generator = lightning_generator; - lf->num_raft_layers = this->object()->slicing_parameters().raft_layers(); - } + if (surface_fill.params.pattern == ipLightning) + dynamic_cast(f.get())->generator = lightning_generator; // calculate flow spacing for infill pattern generation double link_max_length = 0.; diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp index 8c3ac8f..36a48e5 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -13,7 +13,7 @@ void Filler::_fill_surface_single( ExPolygon expolygon, Polylines &polylines_out) { - const Layer &layer = generator->getTreesForLayer(this->layer_id - this->num_raft_layers); + const Layer &layer = generator->getTreesForLayer(this->layer_id); Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled(0.5 * this->spacing - this->overlap)); if (params.dont_connect() || fill_lines.size() <= 1) { diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 0705dc6..3413995 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -22,7 +22,6 @@ public: ~Filler() override = default; Generator *generator { nullptr }; - size_t num_raft_layers { 0 }; protected: Fill* clone() const override { return new Filler(*this); } diff --git a/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp b/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp index 17d3fa9..0a72cbc 100644 --- a/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp +++ b/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp @@ -11,20 +11,17 @@ namespace Slic3r { -static std::mutex arch_mtx; - class Registry { static std::unique_ptr registry; std::set entries; -public: Registry () { entries = { { "SL1", // id - L("SL1 archive format"), // description + L("SL1 archive"), // description "sl1", // main extension {"sl1s", "zip"}, // extension aliases @@ -38,22 +35,14 @@ public: }, { "SL1SVG", - L("SL1SVG archive files"), + L("SL1 SVG archive"), "sl1_svg", - {}, + {"zip"}, [] (const auto &cfg) { return std::make_unique(cfg); }, [] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) { return std::make_unique(fname, quality, progr); } }, - { - "SL2", - "", - "sl1_svg", - {}, - [] (const auto &cfg) { return std::make_unique(cfg); }, - nullptr - }, anycubic_sla_format("pwmo", "Photon Mono"), anycubic_sla_format("pwmx", "Photon Mono X"), anycubic_sla_format("pwms", "Photon Mono SE"), @@ -85,28 +74,26 @@ public: }; } - static Registry& get_instance() +public: + + static const Registry& get_instance() { if (!registry) - registry = std::make_unique(); + registry.reset(new Registry()); return *registry; } - static std::set& get() + static const std::set& get() { return get_instance().entries; } - - std::set& get_entries() { return entries; } }; std::unique_ptr Registry::registry = nullptr; -std::set registered_sla_archives() +const std::set& registered_sla_archives() { - std::lock_guard lk{arch_mtx}; - return Registry::get(); } @@ -123,8 +110,6 @@ std::vector get_extensions(const ArchiveEntry &entry) ArchiveWriterFactory get_writer_factory(const char *formatid) { - std::lock_guard lk{arch_mtx}; - ArchiveWriterFactory ret; auto entry = Registry::get().find(ArchiveEntry{formatid}); if (entry != Registry::get().end()) @@ -135,7 +120,6 @@ ArchiveWriterFactory get_writer_factory(const char *formatid) ArchiveReaderFactory get_reader_factory(const char *formatid) { - std::lock_guard lk{arch_mtx}; ArchiveReaderFactory ret; auto entry = Registry::get().find(ArchiveEntry{formatid}); @@ -145,4 +129,28 @@ ArchiveReaderFactory get_reader_factory(const char *formatid) return ret; } +const char *get_default_extension(const char *formatid) +{ + static constexpr const char *Empty = ""; + + const char * ret = Empty; + + auto entry = Registry::get().find(ArchiveEntry{formatid}); + if (entry != Registry::get().end()) + ret = entry->ext; + + return ret; +} + +const ArchiveEntry * get_archive_entry(const char *formatid) +{ + const ArchiveEntry *ret = nullptr; + + auto entry = Registry::get().find(ArchiveEntry{formatid}); + if (entry != Registry::get().end()) + ret = &(*entry); + + return ret; +} + } // namespace Slic3r::sla diff --git a/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp b/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp index fb1a18c..896888b 100644 --- a/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp +++ b/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp @@ -48,9 +48,9 @@ struct ArchiveEntry { : id{formatid} , desc{description} , ext{extension} - , ext_aliases{extaliases} , wrfactoryfn{wrfn} , rdfactoryfn{rdfn} + , ext_aliases{extaliases} {} bool operator <(const ArchiveEntry &other) const @@ -61,8 +61,10 @@ struct ArchiveEntry { std::vector get_extensions(const ArchiveEntry &entry); -std::set registered_sla_archives(); +const std::set& registered_sla_archives(); +const ArchiveEntry * get_archive_entry(const char *formatid); +const char * get_default_extension(const char *formatid); ArchiveWriterFactory get_writer_factory(const char *formatid); ArchiveReaderFactory get_reader_factory(const char *formatid); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f6228c3..80dfb35 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -312,7 +312,7 @@ namespace Slic3r { std::string gcode_out; std::string line; Vec2f pos = tcr.start_pos; - Vec2f transformed_pos = pos; + Vec2f transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; Vec2f old_pos(-1000.1f, -1000.1f); while (gcode_str) { @@ -901,6 +901,7 @@ namespace DoExport { silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) && config.silent_mode; processor.reset(); + processor.initialize_result_moves(); processor.apply_config(config); processor.enable_stealth_time_estimator(silent_time_estimator_enabled); } @@ -1296,8 +1297,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato this->placeholder_parser().set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); this->placeholder_parser().set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); this->placeholder_parser().set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); - - std::vector is_extruder_used(print.config().nozzle_diameter.size(), 0); + this->placeholder_parser().set("num_extruders", int(print.config().nozzle_diameter.values.size())); + // PlaceholderParser currently substitues non-existent vector values with the zero'th value, which is harmful in the case of "is_extruder_used[]" + // as Slicer may lie about availability of such non-existent extruder. + // We rather sacrifice 256B of memory before we change the behavior of the PlaceholderParser, which should really only fill in the non-existent + // vector elements for filament parameters. + std::vector is_extruder_used(std::max(size_t(255), print.config().nozzle_diameter.size()), 0); for (unsigned int extruder_id : tool_ordering.all_extruders()) is_extruder_used[extruder_id] = true; this->placeholder_parser().set("is_extruder_used", new ConfigOptionBools(is_extruder_used)); @@ -2510,7 +2515,7 @@ void GCode::process_layer_single_object( int extruder_override_id = is_anything_overridden ? layer_tools.wiping_extrusions().get_extruder_override(eec, instance_id) : -1; return print_wipe_extrusions ? extruder_override_id == int(extruder_id) : - extruder_override_id < 0 && extruder_id == correct_extruder_id; + extruder_override_id < 0 && int(extruder_id) == correct_extruder_id; }; ExtrusionEntitiesPtr temp_fill_extrusions; diff --git a/src/libslic3r/GCode/ConflictChecker.cpp b/src/libslic3r/GCode/ConflictChecker.cpp new file mode 100644 index 0000000..09ef1c8 --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.cpp @@ -0,0 +1,290 @@ +#include "libslic3r.h" +#include "ConflictChecker.hpp" + +#include +#include + +#include +#include +#include + +namespace Slic3r { + +namespace RasterizationImpl { +using IndexPair = std::pair; +using Grids = std::vector; + +inline constexpr int64_t RasteXDistance = scale_(1); +inline constexpr int64_t RasteYDistance = scale_(1); + +inline IndexPair point_map_grid_index(const Point &pt, int64_t xdist, int64_t ydist) +{ + auto x = pt.x() / xdist; + auto y = pt.y() / ydist; + return std::make_pair(x, y); +} + +inline bool nearly_equal(const Point &p1, const Point &p2) { return std::abs(p1.x() - p2.x()) < SCALED_EPSILON && std::abs(p1.y() - p2.y()) < SCALED_EPSILON; } + +inline Grids line_rasterization(const Line &line, int64_t xdist = RasteXDistance, int64_t ydist = RasteYDistance) +{ + Grids res; + Point rayStart = line.a; + Point rayEnd = line.b; + IndexPair currentVoxel = point_map_grid_index(rayStart, xdist, ydist); + IndexPair lastVoxel = point_map_grid_index(rayEnd, xdist, ydist); + + Point ray = rayEnd - rayStart; + + double stepX = ray.x() >= 0 ? 1 : -1; + double stepY = ray.y() >= 0 ? 1 : -1; + + double nextVoxelBoundaryX = (currentVoxel.first + stepX) * xdist; + double nextVoxelBoundaryY = (currentVoxel.second + stepY) * ydist; + + if (stepX < 0) { nextVoxelBoundaryX += xdist; } + if (stepY < 0) { nextVoxelBoundaryY += ydist; } + + double tMaxX = ray.x() != 0 ? (nextVoxelBoundaryX - rayStart.x()) / ray.x() : DBL_MAX; + double tMaxY = ray.y() != 0 ? (nextVoxelBoundaryY - rayStart.y()) / ray.y() : DBL_MAX; + + double tDeltaX = ray.x() != 0 ? static_cast(xdist) / ray.x() * stepX : DBL_MAX; + double tDeltaY = ray.y() != 0 ? static_cast(ydist) / ray.y() * stepY : DBL_MAX; + + res.push_back(currentVoxel); + + double tx = tMaxX; + double ty = tMaxY; + + while (lastVoxel != currentVoxel) { + if (lastVoxel.first == currentVoxel.first) { + for (int64_t i = currentVoxel.second; i != lastVoxel.second; i += (int64_t) stepY) { + currentVoxel.second += (int64_t) stepY; + res.push_back(currentVoxel); + } + break; + } + if (lastVoxel.second == currentVoxel.second) { + for (int64_t i = currentVoxel.first; i != lastVoxel.first; i += (int64_t) stepX) { + currentVoxel.first += (int64_t) stepX; + res.push_back(currentVoxel); + } + break; + } + + if (tx < ty) { + currentVoxel.first += (int64_t) stepX; + tx += tDeltaX; + } else { + currentVoxel.second += (int64_t) stepY; + ty += tDeltaY; + } + res.push_back(currentVoxel); + if (res.size() >= 100000) { // bug + assert(0); + } + } + + return res; +} +} // namespace RasterizationImpl + +void LinesBucketQueue::emplace_back_bucket(std::vector &&paths, const void *objPtr, Points offsets) +{ + if (_objsPtrToId.find(objPtr) == _objsPtrToId.end()) { + _objsPtrToId.insert({objPtr, _objsPtrToId.size()}); + _idToObjsPtr.insert({_objsPtrToId.size() - 1, objPtr}); + } + _buckets.emplace_back(std::move(paths), _objsPtrToId[objPtr], offsets); +} + +void LinesBucketQueue::build_queue() +{ + assert(_pq.empty()); + for (LinesBucket &bucket : _buckets) + _pq.push(&bucket); +} + +double LinesBucketQueue::removeLowests() +{ + auto lowest = _pq.top(); + _pq.pop(); + double curHeight = lowest->curHeight(); + std::vector lowests; + lowests.push_back(lowest); + + while (_pq.empty() == false && std::abs(_pq.top()->curHeight() - lowest->curHeight()) < EPSILON) { + lowests.push_back(_pq.top()); + _pq.pop(); + } + + for (LinesBucket *bp : lowests) { + bp->raise(); + if (bp->valid()) { _pq.push(bp); } + } + return curHeight; +} + +LineWithIDs LinesBucketQueue::getCurLines() const +{ + LineWithIDs lines; + for (const LinesBucket &bucket : _buckets) { + if (bucket.valid()) { + LineWithIDs tmpLines = bucket.curLines(); + lines.insert(lines.end(), tmpLines.begin(), tmpLines.end()); + } + } + return lines; +} + +void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths) +{ + std::function getExtrusionPathImpl = [&](const ExtrusionEntityCollection *entity, ExtrusionPaths &paths) { + for (auto entityPtr : entity->entities) { + if (const ExtrusionEntityCollection *collection = dynamic_cast(entityPtr)) { + getExtrusionPathImpl(collection, paths); + } else if (const ExtrusionPath *path = dynamic_cast(entityPtr)) { + paths.push_back(*path); + } else if (const ExtrusionMultiPath *multipath = dynamic_cast(entityPtr)) { + for (const ExtrusionPath &path : multipath->paths) { paths.push_back(path); } + } else if (const ExtrusionLoop *loop = dynamic_cast(entityPtr)) { + for (const ExtrusionPath &path : loop->paths) { paths.push_back(path); } + } + } + }; + getExtrusionPathImpl(entity, paths); +} + +ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs) +{ + ExtrusionPaths paths; + for (auto regionPtr : layerRegionPtrs) { + getExtrusionPathsFromEntity(®ionPtr->perimeters(), paths); + if (!regionPtr->perimeters().empty()) { getExtrusionPathsFromEntity(®ionPtr->fills(), paths); } + } + return paths; +} + +ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer) +{ + ExtrusionPaths paths; + getExtrusionPathsFromEntity(&supportLayer->support_fills, paths); + return paths; +} + +std::pair, std::vector> getAllLayersExtrusionPathsFromObject(PrintObject *obj) +{ + std::vector objPaths, supportPaths; + + for (auto layerPtr : obj->layers()) { objPaths.push_back(getExtrusionPathsFromLayer(layerPtr->regions())); } + + for (auto supportLayerPtr : obj->support_layers()) { supportPaths.push_back(getExtrusionPathsFromSupportLayer(supportLayerPtr)); } + + return {std::move(objPaths), std::move(supportPaths)}; +} + +ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines) +{ + using namespace RasterizationImpl; + std::map> indexToLine; + + for (int i = 0; i < (int)lines.size(); ++i) { + const LineWithID &l1 = lines[i]; + auto indexes = line_rasterization(l1._line); + for (auto index : indexes) { + const auto &possibleIntersectIdxs = indexToLine[index]; + for (auto possibleIntersectIdx : possibleIntersectIdxs) { + const LineWithID &l2 = lines[possibleIntersectIdx]; + if (auto interRes = line_intersect(l1, l2); interRes.has_value()) { return interRes; } + } + indexToLine[index].push_back(i); + } + } + return {}; +} + +ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, + std::optional wtdptr) // find the first intersection point of lines in different objects +{ + if (objs.empty() || (objs.size() == 1 && objs.front()->instances().size() == 1)) { return {}; } + + LinesBucketQueue conflictQueue; + if (wtdptr.has_value()) { // wipe tower at 0 by default + std::vector wtpaths = (*wtdptr)->getFakeExtrusionPathsFromWipeTower(); + conflictQueue.emplace_back_bucket(std::move(wtpaths), *wtdptr, Points{Point((*wtdptr)->plate_origin)}); + } + for (PrintObject *obj : objs) { + std::pair, std::vector> layers = getAllLayersExtrusionPathsFromObject(obj); + + Points instances_shifts; + for (const PrintInstance& inst : obj->instances()) + instances_shifts.emplace_back(inst.shift); + + conflictQueue.emplace_back_bucket(std::move(layers.first), obj, instances_shifts); + conflictQueue.emplace_back_bucket(std::move(layers.second), obj, instances_shifts); + } + conflictQueue.build_queue(); + + std::vector layersLines; + std::vector heights; + while (conflictQueue.valid()) { + LineWithIDs lines = conflictQueue.getCurLines(); + double curHeight = conflictQueue.removeLowests(); + heights.push_back(curHeight); + layersLines.push_back(std::move(lines)); + } + + bool find = false; + tbb::concurrent_vector> conflict; + + tbb::parallel_for(tbb::blocked_range(0, layersLines.size()), [&](tbb::blocked_range range) { + for (size_t i = range.begin(); i < range.end(); i++) { + auto interRes = find_inter_of_lines(layersLines[i]); + if (interRes.has_value()) { + find = true; + conflict.emplace_back(*interRes, heights[i]); + break; + } + } + }); + + if (find) { + std::sort(conflict.begin(), conflict.end(), [](const std::pair& i1, const std::pair& i2) { + return i1.second < i2.second; + }); + + const void *ptr1 = conflictQueue.idToObjsPtr(conflict[0].first._obj1); + const void *ptr2 = conflictQueue.idToObjsPtr(conflict[0].first._obj2); + double conflictHeight = conflict[0].second; + if (wtdptr.has_value()) { + const FakeWipeTower* wtdp = *wtdptr; + if (ptr1 == wtdp || ptr2 == wtdp) { + if (ptr2 == wtdp) { std::swap(ptr1, ptr2); } + const PrintObject *obj2 = reinterpret_cast(ptr2); + return std::make_optional("WipeTower", obj2->model_object()->name, conflictHeight, nullptr, ptr2); + } + } + const PrintObject *obj1 = reinterpret_cast(ptr1); + const PrintObject *obj2 = reinterpret_cast(ptr2); + return std::make_optional(obj1->model_object()->name, obj2->model_object()->name, conflictHeight, ptr1, ptr2); + } else + return {}; +} + +ConflictComputeOpt ConflictChecker::line_intersect(const LineWithID &l1, const LineWithID &l2) +{ + if (l1._obj_id == l2._obj_id && l1._inst_id == l2._inst_id) { return {}; } // lines are from same instance + + Point inter; + bool intersect = l1._line.intersection(l2._line, &inter); + if (intersect) { + auto dist1 = std::min(unscale(Point(l1._line.a - inter)).norm(), unscale(Point(l1._line.b - inter)).norm()); + auto dist2 = std::min(unscale(Point(l2._line.a - inter)).norm(), unscale(Point(l2._line.b - inter)).norm()); + auto dist = std::min(dist1, dist2); + if (dist > 0.01) { return std::make_optional(l1._obj_id, l2._obj_id); } // the two lines intersects if dist>0.01mm + } + return {}; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp new file mode 100644 index 0000000..344018f --- /dev/null +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -0,0 +1,129 @@ +#ifndef slic3r_ConflictChecker_hpp_ +#define slic3r_ConflictChecker_hpp_ + +#include "../Utils.hpp" +#include "../Model.hpp" +#include "../Print.hpp" +#include "../Layer.hpp" + +#include +#include +#include + +namespace Slic3r { + +struct LineWithID +{ + Line _line; + int _obj_id; + int _inst_id; + ExtrusionRole _role; + + LineWithID(const Line& line, int obj_id, int inst_id, const ExtrusionRole& role) : + _line(line), _obj_id(obj_id), _inst_id(inst_id), _role(role) {} +}; + +using LineWithIDs = std::vector; + +class LinesBucket +{ +private: + double _curHeight = 0.0; + unsigned _curPileIdx = 0; + + std::vector _piles; + int _id; + Points _offsets; + +public: + LinesBucket(std::vector &&paths, int id, Points offsets) : _piles(paths), _id(id), _offsets(offsets) {} + LinesBucket(LinesBucket &&) = default; + + bool valid() const { return _curPileIdx < _piles.size(); } + void raise() + { + if (valid()) { + if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; } + _curPileIdx++; + } + } + double curHeight() const { return _curHeight; } + LineWithIDs curLines() const + { + LineWithIDs lines; + for (const ExtrusionPath &path : _piles[_curPileIdx]) { + Polyline check_polyline; + for (int i = 0; i < (int)_offsets.size(); ++i) { + check_polyline = path.polyline; + check_polyline.translate(_offsets[i]); + Lines tmpLines = check_polyline.lines(); + for (const Line& line : tmpLines) { lines.emplace_back(line, _id, i, path.role()); } + } + } + return lines; + } + + friend bool operator>(const LinesBucket &left, const LinesBucket &right) { return left._curHeight > right._curHeight; } + friend bool operator<(const LinesBucket &left, const LinesBucket &right) { return left._curHeight < right._curHeight; } + friend bool operator==(const LinesBucket &left, const LinesBucket &right) { return left._curHeight == right._curHeight; } +}; + +struct LinesBucketPtrComp +{ + bool operator()(const LinesBucket *left, const LinesBucket *right) { return *left > *right; } +}; + +class LinesBucketQueue +{ +private: + std::vector _buckets; + std::priority_queue, LinesBucketPtrComp> _pq; + std::map _idToObjsPtr; + std::map _objsPtrToId; + +public: + void emplace_back_bucket(std::vector &&paths, const void *objPtr, Points offset); + void build_queue(); + bool valid() const { return _pq.empty() == false; } + const void *idToObjsPtr(int id) + { + if (_idToObjsPtr.find(id) != _idToObjsPtr.end()) + return _idToObjsPtr[id]; + else + return nullptr; + } + double removeLowests(); + LineWithIDs getCurLines() const; +}; + +void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths); + +ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs); + +ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer); + +std::pair, std::vector> getAllLayersExtrusionPathsFromObject(PrintObject *obj); + +struct ConflictComputeResult +{ + int _obj1; + int _obj2; + + ConflictComputeResult(int o1, int o2) : _obj1(o1), _obj2(o2) {} + ConflictComputeResult() = default; +}; + +using ConflictComputeOpt = std::optional; + +using ConflictObjName = std::optional>; + +struct ConflictChecker +{ + static ConflictResultOpt find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional wtdptr); + static ConflictComputeOpt find_inter_of_lines(const LineWithIDs &lines); + static ConflictComputeOpt line_intersect(const LineWithID &l1, const LineWithID &l2); +}; + +} // namespace Slic3r + +#endif diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index b80808c..968ba40 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -189,13 +189,13 @@ std::vector estimate_points_properties(const POINTS head_window_acc -= distances_for_curvature[point_idx - 1]; head_angle_acc -= angles_for_curvature[point_idx - 1]; } - while (tail_window_acc > window_size * 0.5 && tail_point < point_idx) { + while (tail_window_acc > window_size * 0.5 && int(tail_point) < point_idx) { tail_window_acc -= distances_for_curvature[tail_point]; tail_angle_acc -= angles_for_curvature[tail_point]; tail_point++; } - while (head_window_acc < window_size * 0.5 && head_point < int(points.size()) - 1) { + while (head_window_acc < window_size * 0.5 && int(head_point) < int(points.size()) - 1) { head_window_acc += distances_for_curvature[head_point]; head_angle_acc += angles_for_curvature[head_point]; head_point++; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3ad03e6..9c38e94 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -450,6 +450,7 @@ void GCodeProcessorResult::reset() { filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); + conflict_result = std::nullopt; time = 0; } #else @@ -468,6 +469,7 @@ void GCodeProcessorResult::reset() { filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); spiral_vase_layers = std::vector>>(); + conflict_result = std::nullopt; } #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1063,14 +1065,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function, 4> g1_axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt }; + if (line.has_x()) g1_axes[X] = (double)line.x(); + if (line.has_y()) g1_axes[Y] = (double)line.y(); + if (line.has_z()) g1_axes[Z] = (double)line.z(); + if (line.has_e()) g1_axes[E] = (double)line.e(); + std::optional g1_feedrate = std::nullopt; + if (line.has_f()) g1_feedrate = (double)line.f(); + std::optional g1_cmt = std::nullopt; + if (!line.comment().empty()) g1_cmt = line.comment(); + + process_G1(g1_axes, g1_feedrate, g1_cmt); +} + +void GCodeProcessor::process_G1(const std::array, 4>& axes, std::optional feedrate, std::optional cmt) { const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); const float filament_radius = 0.5f * filament_diameter; const float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); auto move_type = [this](const AxisCoords& delta_pos) { - EMoveType type = EMoveType::Noop; - if (m_wiping) - type = EMoveType::Wipe; + return EMoveType::Wipe; else if (delta_pos[E] < 0.0f) - type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; + return (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; else if (delta_pos[E] > 0.0f) { if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f) - type = (delta_pos[Z] == 0.0f) ? EMoveType::Unretract : EMoveType::Travel; + return (delta_pos[Z] == 0.0f) ? EMoveType::Unretract : EMoveType::Travel; else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f) - type = EMoveType::Extrude; - } + return EMoveType::Extrude; + } else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) - type = EMoveType::Travel; + return EMoveType::Travel; - return type; + return EMoveType::Noop; + }; + + auto extract_absolute_position_on_axis = [&](Axis axis, std::optional value, double area_filament_cross_section) + { + if (value.has_value()) { + bool is_relative = (m_global_positioning_type == EPositioningType::Relative); + if (axis == E) + is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); + + const double lengthsScaleFactor = (m_units == EUnits::Inches) ? double(INCHES_TO_MM) : 1.0; + double ret = *value * lengthsScaleFactor; + if (axis == E && m_use_volumetric_e) + ret /= area_filament_cross_section; + return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; + } + else + return m_start_position[axis]; }; ++m_g1_line_id; @@ -2384,27 +2416,26 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // updates axes positions from line for (unsigned char a = X; a <= E; ++a) { - m_end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); + m_end_position[a] = extract_absolute_position_on_axis((Axis)a, axes[a], double(area_filament_cross_section)); } // updates feedrate from line, if present - if (line.has_f()) - m_feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC; + if (feedrate.has_value()) + m_feedrate = m_feed_multiply.current * (*feedrate) * MMMIN_TO_MMSEC; - // calculates movement deltas - AxisCoords delta_pos; + // calculates movement deltas + AxisCoords delta_pos; for (unsigned char a = X; a <= E; ++a) - delta_pos[a] = m_end_position[a] - m_start_position[a]; - + delta_pos[a] = m_end_position[a] - m_start_position[a]; + if (std::all_of(delta_pos.begin(), delta_pos.end(), [](double d) { return d == 0.; })) - return; - + return; + const float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; if (volume_extruded_filament != 0.) - m_used_filaments.increase_caches(volume_extruded_filament, - m_extruder_id, area_filament_cross_section * m_parking_position, - area_filament_cross_section * m_extra_loading_move); + m_used_filaments.increase_caches(volume_extruded_filament, m_extruder_id, area_filament_cross_section * m_parking_position, + area_filament_cross_section * m_extra_loading_move); const EMoveType type = move_type(delta_pos); if (type == EMoveType::Extrude) { @@ -2421,7 +2452,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_height = m_forced_height; else if (m_layer_id == 0) m_height = m_first_layer_height + m_z_offset; - else if (line.comment() != INTERNAL_G2G3_TAG){ + else if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) { if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0) m_height = m_end_position[Z] - m_extruded_last_z; } @@ -2432,7 +2463,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0)) m_end_position[Z] = m_height; - if (line.comment() != INTERNAL_G2G3_TAG) + if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) m_extruded_last_z = m_end_position[Z]; m_options_z_corrector.update(m_height); @@ -2465,7 +2496,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // 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]); + const float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]); }; @@ -2486,8 +2517,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) TimeMachine::State& prev = machine.prev; std::vector& blocks = machine.blocks; - curr.feedrate = (delta_pos[E] == 0.0f) ? - minimum_travel_feedrate(static_cast(i), m_feedrate) : + curr.feedrate = (delta_pos[E] == 0.0f) ? minimum_travel_feedrate(static_cast(i), m_feedrate) : minimum_feedrate(static_cast(i), m_feedrate); TimeBlock block; @@ -2522,11 +2552,9 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } // calculates block acceleration - float acceleration = - (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : - (is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i))); + float acceleration = (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (is_extrusion_only_move(delta_pos) ? get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); for (unsigned char a = X; a <= E; ++a) { const float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); @@ -2552,8 +2580,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // calculates block entry feedrate float vmax_junction = curr.safe_feedrate; if (!blocks.empty() && prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD) { - bool prev_speed_larger = prev.feedrate > block.feedrate_profile.cruise; - float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.cruise); + const bool prev_speed_larger = prev.feedrate > block.feedrate_profile.cruise; + const float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.cruise); // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. vmax_junction = prev_speed_larger ? block.feedrate_profile.cruise : prev.feedrate; @@ -2575,18 +2603,18 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. const float jerk = - (v_exit > v_entry) ? - ((v_entry > 0.0f || v_exit < 0.0f) ? - // coasting - (v_exit - v_entry) : - // axis reversal - std::max(v_exit, -v_entry)) : - // v_exit <= v_entry - ((v_entry < 0.0f || v_exit > 0.0f) ? - // coasting - (v_entry - v_exit) : - // axis reversal - std::max(-v_exit, v_entry)); + (v_exit > v_entry) ? + ((v_entry > 0.0f || v_exit < 0.0f) ? + // coasting + (v_exit - v_entry) : + // axis reversal + std::max(v_exit, -v_entry)) : + // v_exit <= v_entry + ((v_entry < 0.0f || v_exit > 0.0f) ? + // coasting + (v_entry - v_exit) : + // axis reversal + std::max(-v_exit, v_entry)); const float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (jerk > axis_max_jerk) { @@ -2665,7 +2693,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } // store move - store_move_vertex(type, line.comment() == INTERNAL_G2G3_TAG); + store_move_vertex(type, cmt.has_value() && *cmt == INTERNAL_G2G3_TAG); } void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise) @@ -2694,7 +2722,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc double delta_z() const { return end.z() - start.z(); } double length() const { return angle * start_radius(); } - double travel_length() const { return std::sqrt(sqr(length() + sqr(delta_z()))); } + double travel_length() const { return std::sqrt(sqr(length()) + sqr(delta_z())); } double start_radius() const { return (start - center).norm(); } double end_radius() const { return (end - center).norm(); } @@ -2779,18 +2807,18 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc return ret; }; - auto internal_only_g1_line = [](const AxisCoords& target, bool has_z, const std::optional& feedrate, const std::optional& extrusion) { - std::string ret = (boost::format("G1 X%1% Y%2%") % target[X] % target[Y]).str(); + auto internal_only_g1_line = [this](const AxisCoords& target, bool has_z, const std::optional& feedrate, const std::optional& extrusion) { + std::array, 4> g1_axes = { target[X], target[Y], std::nullopt, std::nullopt }; + std::optional g1_feedrate = std::nullopt; if (has_z) - ret += (boost::format(" Z%1%") % target[Z]).str(); + g1_axes[Z] = target[Z]; if (feedrate.has_value()) - ret += (boost::format(" F%1%") % *feedrate).str(); + g1_feedrate = (double)*feedrate; if (extrusion.has_value()) - ret += (boost::format(" E%1%") % target[E]).str(); + g1_axes[E] = target[E]; + std::optional g1_cmt = INTERNAL_G2G3_TAG; - ret += (boost::format(" ;%1%\n") % INTERNAL_G2G3_TAG).str(); - - return ret; + process_G1(g1_axes, g1_feedrate, g1_cmt); }; // calculate arc segments @@ -2799,17 +2827,16 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc // https://github.com/qidi3d/QIDI-Firmware/blob/MK3/Firmware/motion_control.cpp // segments count - static const double MM_PER_ARC_SEGMENT = 0.5; - const size_t segments = std::ceil(travel_length / MM_PER_ARC_SEGMENT); - assert(segments >= 1); + static const double MM_PER_ARC_SEGMENT = 1.0; + const size_t segments = std::max(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1); - const double theta_per_segment = arc.angle / double(segments); - const double z_per_segment = arc.delta_z() / double(segments); - const double extruder_per_segment = (extrusion.has_value()) ? *extrusion / double(segments) : 0.0; + const double inv_segment = 1.0 / double(segments); + const double theta_per_segment = arc.angle * inv_segment; + const double z_per_segment = arc.delta_z() * inv_segment; + const double extruder_per_segment = (extrusion.has_value()) ? *extrusion * inv_segment : 0.0; - const double sq_theta_per_segment = sqr(theta_per_segment); - const double cos_T = 1.0 - 0.5 * sq_theta_per_segment; // Small angle approximation - const double sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6.0; // Small angle approximation + const double cos_T = 1.0 - 0.5 * sqr(theta_per_segment); // Small angle approximation + const double sin_T = theta_per_segment; AxisCoords prev_target = m_start_position; AxisCoords arc_target; @@ -2821,28 +2848,25 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc arc_target[E] = m_start_position[E]; static const size_t N_ARC_CORRECTION = 25; - Vec3d curr_rel_arc_start = arc.relative_start(); - - std::string gcode; - - size_t n_arc_correction = N_ARC_CORRECTION; + size_t count = 0; for (size_t i = 1; i < segments; ++i) { - if (n_arc_correction-- == 0) { - // Calculate the actual position for r_axis_x and r_axis_y - const double cos_Ti = ::cos((double)i * theta_per_segment); - const double sin_Ti = ::sin((double)i * theta_per_segment); - curr_rel_arc_start.x() = -double(rel_center.x()) * cos_Ti + double(rel_center.y()) * sin_Ti; - curr_rel_arc_start.y() = -double(rel_center.x()) * sin_Ti - double(rel_center.y()) * cos_Ti; - // reset n_arc_correction - n_arc_correction = N_ARC_CORRECTION; - } - else { - // Calculate X and Y using the small angle approximation + if (count < N_ARC_CORRECTION) { + // Apply vector rotation matrix const float r_axisi = curr_rel_arc_start.x() * sin_T + curr_rel_arc_start.y() * cos_T; curr_rel_arc_start.x() = curr_rel_arc_start.x() * cos_T - curr_rel_arc_start.y() * sin_T; curr_rel_arc_start.y() = r_axisi; + ++count; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + const double cos_Ti = ::cos(i * theta_per_segment); + const double sin_Ti = ::sin(i * theta_per_segment); + curr_rel_arc_start.x() = -double(rel_center.x()) * cos_Ti + double(rel_center.y()) * sin_Ti; + curr_rel_arc_start.y() = -double(rel_center.x()) * sin_Ti - double(rel_center.y()) * cos_Ti; + count = 0; } // Update arc_target location @@ -2851,23 +2875,14 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc arc_target[Z] += z_per_segment; arc_target[E] += extruder_per_segment; - gcode += internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, feedrate, extrusion); + m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line() + internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, (i == 1) ? feedrate : std::nullopt, extrusion); prev_target = arc_target; - - // feedrate is constant, we do not need to repeat it - feedrate.reset(); } // Ensure last segment arrives at target location. - gcode += internal_only_g1_line(adjust_target(end_position, prev_target), arc.delta_z() != 0.0, feedrate, extrusion); - - // process fake gcode lines - GCodeReader parser; - parser.parse_buffer(gcode, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { - // force all lines to share the same id - --m_line_id; - process_gcode_line(line, false); - }); + m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line() + internal_only_g1_line(adjust_target(end_position, prev_target), arc.delta_z() != 0.0, (segments == 1) ? feedrate : std::nullopt, extrusion); } void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) @@ -3590,14 +3605,16 @@ void GCodeProcessor::post_process() auto rev_it = m_lines.rbegin() + rev_it_dist; auto start_rev_it = rev_it; + std::string curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line); // backtrace into the cache to find the place where to insert the line - while (rev_it != m_lines.rend() && rev_it->time > time_threshold_i && GCodeReader::GCodeLine::extract_cmd(rev_it->line) != cmd) { + while (rev_it != m_lines.rend() && rev_it->time > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") { rev_it->line = line_replacer(rev_it->line); ++rev_it; + curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line); } - // we met the previous evenience of cmd. stop inserting lines - if (rev_it != m_lines.rend() && GCodeReader::GCodeLine::extract_cmd(rev_it->line) == cmd) + // we met the previous evenience of cmd, or a G28/G29 command. stop inserting lines + if (rev_it != m_lines.rend() && (curr_cmd == cmd || curr_cmd == "G28" || curr_cmd == "G29")) break; // insert the line for the current step diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 7096d68..61100ed 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -82,6 +82,22 @@ namespace Slic3r { } }; + struct ConflictResult + { + std::string _objName1; + std::string _objName2; + double _height; + const void* _obj1; // nullptr means wipe tower + const void* _obj2; + int layer = -1; + ConflictResult(const std::string& objName1, const std::string& objName2, double height, const void* obj1, const void* obj2) + : _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2) + {} + ConflictResult() = default; + }; + + using ConflictResultOpt = std::optional; + struct GCodeProcessorResult { struct SettingsIds @@ -137,6 +153,8 @@ namespace Slic3r { std::vector custom_gcode_per_print_z; std::vector>> spiral_vase_layers; + ConflictResultOpt conflict_result; + #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -621,6 +639,11 @@ namespace Slic3r { // Streaming interface, for processing G-codes just generated by QIDISlicer in a pipelined fashion. void initialize(const std::string& filename); + void initialize_result_moves() { + // 1st move must be a dummy move + assert(m_result.moves.empty()); + m_result.moves.emplace_back(GCodeProcessorResult::MoveVertex()); + } void process_buffer(const std::string& buffer); void finalize(bool post_process); @@ -657,6 +680,8 @@ namespace Slic3r { // Move void process_G0(const GCodeReader::GCodeLine& line); void process_G1(const GCodeReader::GCodeLine& line); + void process_G1(const std::array, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt }, + std::optional feedrate = std::nullopt, std::optional cmt = std::nullopt); // Arc Move void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index f526d0a..4160c48 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -581,7 +581,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar.push_back(FilamentParameters()); m_filpar[idx].material = config.filament_type.get_at(idx); - m_filpar[idx].is_soluble = config.wipe_tower_extruder == 0 ? config.filament_soluble.get_at(idx) : (idx != config.wipe_tower_extruder - 1); + m_filpar[idx].is_soluble = config.wipe_tower_extruder == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_extruder - 1)); m_filpar[idx].temperature = config.temperature.get_at(idx); m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx); @@ -1292,7 +1292,6 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // brim (first layer only) if (first_layer) { - box_coordinates box = wt_box; size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing; for (size_t i = 0; i < loops_num; ++ i) { @@ -1563,4 +1562,19 @@ void WipeTower::generate(std::vector> & } } + + +std::vector> WipeTower::get_z_and_depth_pairs() const +{ + std::vector> out = {{0.f, m_wipe_tower_depth}}; + for (const WipeTowerInfo& wti : m_plan) { + assert(wti.depth < wti.depth + WT_EPSILON); + if (wti.depth < out.back().second - WT_EPSILON) + out.emplace_back(wti.z, wti.depth); + } + if (out.back().first < m_wipe_tower_height - WT_EPSILON) + out.emplace_back(m_wipe_tower_height, 0.f); + return out; +} + } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 2a125eb..0734810 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -143,6 +143,7 @@ public: void generate(std::vector> &result); float get_depth() const { return m_wipe_tower_depth; } + std::vector> get_z_and_depth_pairs() const; float get_brim_width() const { return m_wipe_tower_brim_width_real; } float get_wipe_tower_height() const { return m_wipe_tower_height; } diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index a45ea84..cb03667 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -243,9 +243,10 @@ bool GCodeReader::GCodeLine::has_value(char axis, float &value) const if (c == nullptr) return false; // Try to parse the numeric value. - char *pend = nullptr; - double v = strtod(++ c, &pend); - if (pend != nullptr && is_end_of_word(*pend)) { + double v = 0.; + const char* end = m_raw.c_str() + m_raw.size(); + auto [pend, ec] = fast_float::from_chars(++c, end, v); + if (pend != c && is_end_of_word(*pend)) { // The axis value has been parsed correctly. value = float(v); return true; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 842544c..9d65884 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -656,12 +656,22 @@ void Layer::make_perimeters() layer_region_ids.push_back(region_id); for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) if (! (*it)->slices().empty()) { - LayerRegion* other_layerm = *it; - const PrintRegionConfig &other_config = other_layerm->region().config(); - if (config.perimeter_extruder == other_config.perimeter_extruder + LayerRegion *other_layerm = *it; + const PrintRegionConfig &other_config = other_layerm->region().config(); + bool dynamic_overhang_speed_compatibility = config.enable_dynamic_overhang_speeds == + other_config.enable_dynamic_overhang_speeds; + if (dynamic_overhang_speed_compatibility && config.enable_dynamic_overhang_speeds) { + dynamic_overhang_speed_compatibility = config.overhang_speed_0 == other_config.overhang_speed_0 && + config.overhang_speed_1 == other_config.overhang_speed_1 && + config.overhang_speed_2 == other_config.overhang_speed_2 && + config.overhang_speed_3 == other_config.overhang_speed_3; + } + + if (config.perimeter_extruder == other_config.perimeter_extruder && config.perimeters == other_config.perimeters && config.perimeter_speed == other_config.perimeter_speed && config.external_perimeter_speed == other_config.external_perimeter_speed + && dynamic_overhang_speed_compatibility && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) && config.overhangs == other_config.overhangs diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 4455d66..73f225c 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -1,4 +1,5 @@ #include "ExPolygon.hpp" +#include "Flow.hpp" #include "Layer.hpp" #include "BridgeDetector.hpp" #include "ClipperUtils.hpp" @@ -169,7 +170,10 @@ static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::init Surfaces expand_bridges_detect_orientations( Surfaces &surfaces, ExPolygons &shells, - const Algorithm::RegionExpansionParameters &expansion_params) + const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill, + ExPolygons &sparse, + const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, + const float closing_radius) { using namespace Slic3r::Algorithm; @@ -179,8 +183,23 @@ Surfaces expand_bridges_detect_orientations( return {}; // Calculate bridge anchors and their expansions in their respective shell region. - WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params.tiny_expansion, true); - std::vector bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params); + WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params_into_solid_infill.tiny_expansion, true); + std::vector bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params_into_solid_infill); + bool expanded_into_shells = ! bridge_expansions.empty(); + bool expanded_into_sparse = false; + { + WaveSeeds bridge_anchors_sparse = wave_seeds(bridges_ex, sparse, expansion_params_into_sparse_infill.tiny_expansion, true); + std::vector bridge_expansions_sparse = propagate_waves_ex(bridge_anchors_sparse, sparse, expansion_params_into_sparse_infill); + if (! bridge_expansions_sparse.empty()) { + expanded_into_sparse = true; + for (WaveSeed &seed : bridge_anchors_sparse) + seed.boundary += uint32_t(shells.size()); + for (RegionExpansionEx &expansion : bridge_expansions_sparse) + expansion.boundary_id += uint32_t(shells.size()); + append(bridge_anchors, std::move(bridge_anchors_sparse)); + append(bridge_expansions, std::move(bridge_expansions_sparse)); + } + } // Cache for detecting bridge orientation and merging regions with overlapping expansions. struct Bridge { @@ -257,7 +276,7 @@ Surfaces expand_bridges_detect_orientations( for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) { if (last_anchor_id != int(it_bridge_anchor->boundary)) { last_anchor_id = int(it_bridge_anchor->boundary); - append(anchor_areas, to_polygons(shells[last_anchor_id])); + append(anchor_areas, to_polygons(last_anchor_id < int32_t(shells.size()) ? shells[last_anchor_id] : sparse[last_anchor_id - int32_t(shells.size())])); } // if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) { // reserve_more_power_of_2(lines, polyline.size() - 1); @@ -268,17 +287,18 @@ Surfaces expand_bridges_detect_orientations( lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON)))); auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); - // #if 1 - // coordf_t stroke_width = scale_(0.06); - // BoundingBox bbox = get_extents(initial); - // bbox.offset(scale_(1.)); - // ::Slic3r::SVG - // svg(debug_out_path(("bridge"+std::to_string(bridges[idx_last].bridge_angle)+"_"+std::to_string(this->layer()->bottom_z())).c_str()), - // bbox); - - // svg.draw(initial, "cyan"); - // svg.draw(to_lines(lower_layer->lslices), "green", stroke_width); - // #endif +#if 0 + coordf_t stroke_width = scale_(0.06); + BoundingBox bbox = get_extents(anchor_areas); + bbox.merge(get_extents(bridge.expolygon)); + bbox.offset(scale_(1.)); + ::Slic3r::SVG + svg(debug_out_path(("bridge" + std::to_string(bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()), + bbox); + svg.draw(bridge.expolygon, "cyan"); + svg.draw(lines, "green", stroke_width); + svg.draw(anchor_areas, "red"); +#endif } } @@ -309,14 +329,22 @@ Surfaces expand_bridges_detect_orientations( } //FIXME try to be smart and pick the best bridging angle for all? templ.bridge_angle = bridges[bridge_id].angle; + //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) + // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. + // look for narrow_ensure_vertical_wall_thickness_region_radius filter. + ExPolygons final = closing_ex(acc, closing_radius); // without safety offset, artifacts are generated (GH #2494) - for (ExPolygon &ex : union_safety_offset_ex(acc)) + // union_safety_offset_ex(acc) + for (ExPolygon &ex : final) out.emplace_back(templ, std::move(ex)); } } - // Clip the shells by the expanded bridges. - shells = diff_ex(shells, out); + // Clip by the expanded bridges. + if (expanded_into_shells) + shells = diff_ex(shells, out); + if (expanded_into_sparse) + sparse = diff_ex(sparse, out); return out; } @@ -325,18 +353,43 @@ Surfaces expand_bridges_detect_orientations( static Surfaces expand_merge_surfaces( Surfaces &surfaces, SurfaceType surface_type, - ExPolygons &shells, - const Algorithm::RegionExpansionParameters ¶ms, + ExPolygons &shells, + const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill, + ExPolygons &sparse, + const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill, + const float closing_radius, const double bridge_angle = -1.) { + using namespace Slic3r::Algorithm; + double thickness; ExPolygons src = fill_surfaces_extract_expolygons(surfaces, {surface_type}, thickness); if (src.empty()) return {}; - std::vector expanded = expand_merge_expolygons(std::move(src), shells, params); + std::vector expansions = propagate_waves(src, shells, expansion_params_into_solid_infill); + bool expanded_into_shells = !expansions.empty(); + bool expanded_into_sparse = false; + { + std::vector expansions2 = propagate_waves(src, sparse, expansion_params_into_sparse_infill); + if (! expansions2.empty()) { + expanded_into_sparse = true; + for (RegionExpansion &expansion : expansions2) + expansion.boundary_id += uint32_t(shells.size()); + append(expansions, std::move(expansions2)); + } + } + + std::vector expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions)); + //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) + // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. + // look for narrow_ensure_vertical_wall_thickness_region_radius filter. + expanded = closing_ex(expanded, closing_radius); // Trim the shells by the expanded expolygons. - shells = diff_ex(shells, expanded); + if (expanded_into_shells) + shells = diff_ex(shells, expanded); + if (expanded_into_sparse) + sparse = diff_ex(sparse, expanded); Surface templ{ surface_type, {} }; templ.bridge_angle = bridge_angle; @@ -349,20 +402,25 @@ static Surfaces expand_merge_surfaces( void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) { + using namespace Slic3r::Algorithm; + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Width of the perimeters. float shell_width = 0; + float expansion_min = 0; if (int num_perimeters = this->region().config().perimeters; num_perimeters > 0) { Flow external_perimeter_flow = this->flow(frExternalPerimeter); Flow perimeter_flow = this->flow(frPerimeter); - shell_width += 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing(); + shell_width = 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing(); shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1); + expansion_min = perimeter_flow.scaled_spacing(); } else { // TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation - shell_width = float(SCALED_EPSILON); + shell_width = float(SCALED_EPSILON); + expansion_min = float(SCALED_EPSILON);; } // Scaled expansions of the respective external surfaces. @@ -373,19 +431,23 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly static constexpr const float expansion_step = scaled(0.1); // Don't take more than max_nr_steps for small expansion_step. static constexpr const size_t max_nr_expansion_steps = 5; + // Radius (with added epsilon) to absorb empty regions emering from regularization of ensuring, viz const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; + const float closing_radius = 0.55f * 0.65f * 1.05f * this->flow(frSolidInfill).scaled_spacing(); // Expand the top / bottom / bridge surfaces into the shell thickness solid infills. double layer_thickness; - ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, {stInternalSolid}, layer_thickness)); + ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternalSolid }, layer_thickness)); + ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternal }, layer_thickness)); SurfaceCollection bridges; + const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); { BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; const double custom_angle = this->region().config().bridge_angle.value; - const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); + const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); bridges.surfaces = custom_angle > 0 ? - expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, Geometry::deg2rad(custom_angle)) : - expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params); + expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, Geometry::deg2rad(custom_angle)) : + expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; #if 0 { @@ -396,15 +458,25 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly } Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells, - Algorithm::RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps)); + RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius); Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells, - Algorithm::RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps)); + RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps), + sparse, expansion_params_into_sparse_infill, closing_radius); - m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternalSolid }); - reserve_more(m_fill_surfaces.surfaces, shells.size() + bridges.size() + bottoms.size() + tops.size()); - Surface solid_templ(stInternalSolid, {}); - solid_templ.thickness = layer_thickness; - m_fill_surfaces.append(std::move(shells), solid_templ); +// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); + m_fill_surfaces.clear(); + reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size()); + { + Surface solid_templ(stInternalSolid, {}); + solid_templ.thickness = layer_thickness; + m_fill_surfaces.append(std::move(shells), solid_templ); + } + { + Surface sparse_templ(stInternal, {}); + sparse_templ.thickness = layer_thickness; + m_fill_surfaces.append(std::move(sparse), sparse_templ); + } m_fill_surfaces.append(std::move(bridges.surfaces)); m_fill_surfaces.append(std::move(bottoms)); m_fill_surfaces.append(std::move(tops)); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f6056cb..5d283ed 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1353,37 +1353,6 @@ void ModelObject::delete_connectors() } } -void ModelObject::synchronize_model_after_cut() -{ - for (ModelObject* obj : m_model->objects) { - if (obj == this || obj->cut_id.is_equal(this->cut_id)) - continue; - if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id)) - obj->cut_id.copy(this->cut_id); - } - this->invalidate_cut(); -} - -void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes) -{ - // we don't save cut information, if result will not contains all parts of initial object - if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || - !attributes.has(ModelObjectCutAttribute::KeepLower) || - attributes.has(ModelObjectCutAttribute::InvalidateCutInfo)) - return; - - if (cut_id.id().invalid()) - cut_id.init(); - { - int cut_obj_cnt = -1; - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_obj_cnt++; - if (attributes.has(ModelObjectCutAttribute::KeepLower)) cut_obj_cnt++; - if (attributes.has(ModelObjectCutAttribute::CreateDowels)) cut_obj_cnt++; - if (cut_obj_cnt > 0) - cut_id.increase_check_sum(size_t(cut_obj_cnt)); - } -} - void ModelObject::clone_for_cut(ModelObject** obj) { (*obj) = ModelObject::new_clone(*this); @@ -1636,9 +1605,6 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; - // apply cut attributes for object - apply_cut_attributes(attributes); - // Clone the object to duplicate instances, materials etc. ModelObject* upper{ nullptr }; if (attributes.has(ModelObjectCutAttribute::KeepUpper)) @@ -1709,8 +1675,6 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; - synchronize_model_after_cut(); - return res; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7dcc427..bedd264 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -469,13 +469,7 @@ public: void delete_connectors(); void clone_for_cut(ModelObject **obj); - void apply_cut_attributes(ModelObjectCutAttributes attributes); private: - // FIXME: These functions would best not be here at all. It might make sense to separate the - // cut-related methods elsewhere. Same holds for cut_connectors data member, which is currently - // just a temporary variable used by cut gizmo only. - void synchronize_model_after_cut(); - void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, std::vector& dowels); @@ -515,6 +509,8 @@ public: bool has_solid_mesh() const; // Detect if object has at least one negative volume mash bool has_negative_volume_mesh() const; + // Detect if object has at least one sla drain hole + bool has_sla_drain_holes() const { return !sla_drain_holes.empty(); } bool is_cut() const { return cut_id.id().valid(); } bool has_connectors() const; diff --git a/src/libslic3r/PNGReadWrite.hpp b/src/libslic3r/PNGReadWrite.hpp index 01e1f47..399c622 100644 --- a/src/libslic3r/PNGReadWrite.hpp +++ b/src/libslic3r/PNGReadWrite.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Slic3r { namespace png { diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 786f409..4012642 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1090,6 +1090,7 @@ namespace client static void scalar_variable_assign_scalar(const MyContext *ctx, OptWithPos &lhs, const expr &rhs) { + assert(! ctx->skipping()); assert(lhs.opt->is_scalar()); check_writable(ctx, lhs); ConfigOption *wropt = const_cast(lhs.opt); @@ -1121,6 +1122,7 @@ namespace client static void vector_variable_element_assign_scalar(const MyContext *ctx, OptWithPos &lhs, const expr &rhs) { + assert(! ctx->skipping()); assert(lhs.opt->is_vector()); check_writable(ctx, lhs); if (! lhs.has_index()) @@ -1158,6 +1160,7 @@ namespace client static void vector_variable_assign_expr_with_count(const MyContext *ctx, OptWithPos &lhs, const expr &rhs_count, const expr &rhs_value) { + assert(! ctx->skipping()); size_t count = evaluate_count(rhs_count); auto *opt = const_cast(lhs.opt); switch (lhs.opt->type()) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 169bc66..c89ce92 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2282,7 +2282,7 @@ namespace PresetUtils { { const VendorProfile::PrinterModel *out = nullptr; if (preset.vendor != nullptr) { - auto *printer_model = preset.config.opt("printer_model"); + const auto *printer_model = preset.config.opt("printer_model"); if (printer_model != nullptr && ! printer_model->value.empty()) { auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) { return pm.id == printer_model->value; }); if (it != preset.vendor->models.end()) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b9abf85..95d914c 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -11,6 +11,7 @@ #include "Thread.hpp" #include "GCode.hpp" #include "GCode/WipeTower.hpp" +#include "GCode/ConflictChecker.hpp" #include "Utils.hpp" #include "BuildVolume.hpp" #include "format.hpp" @@ -26,7 +27,6 @@ #include #include - namespace Slic3r { template class PrintState; @@ -964,6 +964,18 @@ void Print::process() this->finalize_first_layer_convex_hull(); this->set_done(psSkirtBrim); } + + std::optional wipe_tower_opt = {}; + if (this->has_wipe_tower()) { + m_fake_wipe_tower.set_pos_and_rotation({ m_config.wipe_tower_x, m_config.wipe_tower_y }, m_config.wipe_tower_rotation_angle); + wipe_tower_opt = std::make_optional(&m_fake_wipe_tower); + } + auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(m_objects, wipe_tower_opt); + + m_conflict_result = conflictRes; + if (conflictRes.has_value()) + BOOST_LOG_TRIVIAL(error) << boost::format("gcode path conflicts found between %1% and %2%") % conflictRes->_objName1 % conflictRes->_objName2; + BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } @@ -989,6 +1001,10 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor // Create GCode on heap, it has quite a lot of data. std::unique_ptr gcode(new GCode); gcode->do_export(this, path.c_str(), result, thumbnail_cb); + + if (m_conflict_result.has_value()) + result->conflict_result = *m_conflict_result; + return path.c_str(); } @@ -1487,6 +1503,7 @@ void Print::_make_wipe_tower() m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); wipe_tower.generate(m_wipe_tower_data.tool_changes); m_wipe_tower_data.depth = wipe_tower.get_depth(); + m_wipe_tower_data.z_and_depth_pairs = wipe_tower.get_z_and_depth_pairs(); m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height(); @@ -1511,6 +1528,10 @@ void Print::_make_wipe_tower() m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); + const Vec3d origin = Vec3d::Zero(); + m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(), config().first_layer_height, m_wipe_tower_data.depth, + m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width, config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle, {scale_(origin.x()), scale_(origin.y())}); + } // Generate a recommended G-code output file name based on the format template, default extension, and template parameters @@ -1522,6 +1543,7 @@ std::string Print::output_filename(const std::string &filename_base) const // These values will be just propagated into the output file name. DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); config.set_key_value("num_extruders", new ConfigOptionInt((int)m_config.nozzle_diameter.size())); + config.set_key_value("default_output_extension", new ConfigOptionString(".gcode")); return this->PrintBase::output_filename(m_config.output_filename_format.value, ".gcode", filename_base, &config); } @@ -1578,4 +1600,80 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co return final_path; } + std::vector FakeWipeTower::getFakeExtrusionPathsFromWipeTower() const + { + float h = height; + float lh = layer_height; + int d = scale_(depth); + int w = scale_(width); + int bd = scale_(brim_width); + Point minCorner = { -bd, -bd }; + Point maxCorner = { minCorner.x() + w + bd, minCorner.y() + d + bd }; + + const auto [cone_base_R, cone_scale_x] = WipeTower::get_wipe_tower_cone_base(width, height, depth, cone_angle); + + std::vector paths; + for (float hh = 0.f; hh < h; hh += lh) { + + if (hh != 0.f) { + // The wipe tower may be getting smaller. Find the depth for this layer. + size_t i = 0; + for (i=0; i= z_and_depth_pairs[i].first && hh < z_and_depth_pairs[i+1].first) + break; + d = scale_(z_and_depth_pairs[i].second); + minCorner = {0.f, -d/2 + scale_(z_and_depth_pairs.front().second/2.f)}; + maxCorner = { minCorner.x() + w, minCorner.y() + d }; + } + + + ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh); + path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }; + paths.push_back({ path }); + + // We added the border, now add several parallel lines so we can detect an object that is fully inside the tower. + // For now, simply use fixed spacing of 3mm. + for (coord_t y=minCorner.y()+scale_(3.); y 0.) { + path.polyline.clear(); + double r = cone_base_R * (1 - hh/height); + for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.) + path.polyline.points.emplace_back(Point::new_scale(width/2. + r * std::cos(alpha)/cone_scale_x, depth/2. + r * std::sin(alpha))); + paths.back().emplace_back(path); + if (hh == 0.f) { // Cone brim. + for (float bw=brim_width; bw>0.f; bw-=3.f) { + path.polyline.clear(); + for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.) // see load_wipe_tower_preview, where the same is a bit clearer + path.polyline.points.emplace_back(Point::new_scale( + width/2. + cone_base_R * std::cos(alpha)/cone_scale_x * (1. + cone_scale_x*bw/cone_base_R), + depth/2. + cone_base_R * std::sin(alpha) * (1. + bw/cone_base_R)) + ); + paths.back().emplace_back(path); + } + } + } + + // Only the first layer has brim. + if (hh == 0.f) { + minCorner = minCorner + Point(bd, bd); + maxCorner = maxCorner - Point(bd, bd); + } + } + + // Rotate and translate the tower into the final position. + for (ExtrusionPaths& ps : paths) { + for (ExtrusionPath& p : ps) { + p.polyline.rotate(Geometry::deg2rad(rotation_angle)); + p.polyline.translate(scale_(pos.x()), scale_(pos.y())); + } + } + + return paths; + } + } // namespace Slic3r diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 5c42709..0594919 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -418,6 +418,39 @@ private: FillLightning::GeneratorPtr m_lightning_generator; }; +struct FakeWipeTower +{ + // generate fake extrusion + Vec2f pos; + float width; + float height; + float layer_height; + float depth; + std::vector> z_and_depth_pairs; + float brim_width; + float rotation_angle; + float cone_angle; + Vec2d plate_origin; + + void set_fake_extrusion_data(const Vec2f& p, float w, float h, float lh, float d, const std::vector>& zad, float bd, float ra, float ca, const Vec2d& o) + { + pos = p; + width = w; + height = h; + layer_height = lh; + depth = d; + z_and_depth_pairs = zad; + brim_width = bd; + rotation_angle = ra; + cone_angle = ca; + plate_origin = o; + } + + void set_pos_and_rotation(const Vec2f& p, float rotation) { pos = p; rotation_angle = rotation; } + + std::vector getFakeExtrusionPathsFromWipeTower() const; +}; + struct WipeTowerData { // Following section will be consumed by the GCodeGenerator. @@ -433,6 +466,7 @@ struct WipeTowerData // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: float depth; + std::vector> z_and_depth_pairs; float brim_width; float height; @@ -668,6 +702,9 @@ private: friend class GCodeProcessor; // Allow PrintObject to access m_mutex and m_cancel_callback. friend class PrintObject; + + ConflictResultOpt m_conflict_result; + FakeWipeTower m_fake_wipe_tower; }; } /* slic3r_Print_hpp_ */ diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d6d37b3..246a51a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -68,7 +68,7 @@ static const t_config_enum_values s_keys_map_PrintHostType { { "qidilink", htQIDILink }, { "qidiconnect", htQIDIConnect }, { "octoprint", htOctoPrint }, - { "mainsail", htMainSail }, + { "moonraker", htMoonraker }, { "duet", htDuet }, { "flashair", htFlashAir }, { "astrobox", htAstroBox }, @@ -736,7 +736,6 @@ void PrintConfigDef::init_fff_params() "and fan speed according to layer printing time."); def->set_default_value(new ConfigOptionBools { true }); - def = this->add("cooling_tube_retraction", coFloat); def->label = L("Cooling tube position"); def->tooltip = L("Distance of the center-point of the cooling tube from the extruder tip."); @@ -1535,8 +1534,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("gap_fill_speed", coFloat); def->label = L("Gap fill"); def->category = L("Speed"); @@ -2134,7 +2131,7 @@ void PrintConfigDef::init_fff_params() { "qidilink", "QIDILink" }, { "qidiconnect", "QIDIConnect" }, { "octoprint", "OctoPrint" }, - { "mainsail", "Mainsail/Fluidd" }, + { "moonraker", "Klipper (via Moonraker)" }, { "duet", "Duet" }, { "flashair", "FlashAir" }, { "astrobox", "AstroBox" }, @@ -2164,7 +2161,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("You can use all configuration options as variables inside this template. " "For example: [layer_height], [fill_density] etc. You can also use [timestamp], " "[year], [month], [day], [hour], [minute], [second], [version], [input_filename], " - "[input_filename_base]."); + "[input_filename_base], [default_output_extension]."); def->full_width = true; def->mode = comExpert; def->set_default_value(new ConfigOptionString("[input_filename_base].gcode")); @@ -4420,6 +4417,9 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va else if (value == "marlinfirmware") // the "new" marlin firmware flavor used to be called "marlinfirmware" for some time during QIDISlicer 2.4.0-alpha development. value = "marlin2"; + } else if (opt_key == "host_type" && value == "mainsail") { + // the "mainsail" key (introduced in 2.6.0-alpha6) was renamed to "moonraker" (in 2.6.0-rc1). + value = "moonraker"; } else if (opt_key == "fill_density" && value.find("%") == std::string::npos) { try { // fill_density was turned into a percent value @@ -5185,21 +5185,21 @@ std::string get_sla_suptree_prefix(const DynamicPrintConfig &config) return slatree; } -static bool is_XL_printer(const std::string& printer_model) +static bool is_XL_printer(const std::string& printer_notes) { - static constexpr const char *ALIGN_ONLY_FOR = "XL"; - return boost::algorithm::contains(printer_model, ALIGN_ONLY_FOR); + return boost::algorithm::contains(printer_notes, "PRINTER_VENDOR_PRUSA3D") + && boost::algorithm::contains(printer_notes, "PRINTER_MODEL_XL"); } bool is_XL_printer(const DynamicPrintConfig &cfg) { - auto *printer_model = cfg.opt("printer_model"); - return printer_model && is_XL_printer(printer_model->value); + auto *printer_notes = cfg.opt("printer_notes"); + return printer_notes && is_XL_printer(printer_notes->value); } bool is_XL_printer(const PrintConfig &cfg) { - return is_XL_printer(cfg.printer_model.value); + return is_XL_printer(cfg.printer_notes.value); } } // namespace Slic3r diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2be0883..8081e38 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -44,7 +44,7 @@ enum class MachineLimitsUsage { }; enum PrintHostType { - htQIDILink, htQIDIConnect, htOctoPrint, htMainSail, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS + htPrusaLink, htPrusaConnect, htOctoPrint, htMoonraker, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS }; enum AuthorizationType { diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 1598cd4..9df2862 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -4,6 +4,8 @@ #include "CSGMesh/PerformCSGMeshBooleans.hpp" #include "format.hpp" +#include "Format/SLAArchiveFormatRegistry.hpp" + #include "Geometry.hpp" #include "Thread.hpp" @@ -522,6 +524,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con #endif /* _DEBUG */ m_full_print_config = std::move(config); + return static_cast(apply_status); } @@ -531,7 +534,16 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con std::string SLAPrint::output_filename(const std::string &filename_base) const { DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); - return this->PrintBase::output_filename(m_print_config.output_filename_format.value, ".sl1", filename_base, &config); + std::string default_ext = get_default_extension(m_printer_config.sla_archive_format.value.c_str()); + if (default_ext.empty()) + default_ext = "sl1"; + + default_ext.insert(default_ext.begin(), '.'); + + config.set_key_value("default_output_extension", + new ConfigOptionString(default_ext)); + + return this->PrintBase::output_filename(m_print_config.output_filename_format.value, default_ext, filename_base, &config); } std::string SLAPrint::validate(std::vector*) const diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 4c13cdd..8936830 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -650,7 +650,7 @@ std::tuple build_object_part_from_slice(const size_t &slice_i new_object_part.volume += volume; new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume; - if (l->id() == params.raft_layers_count) { // layer attached on bed/raft + if (int(l->id()) == params.raft_layers_count) { // layer attached on bed/raft new_object_part.connected_to_bed = true; float sticking_area = line.len * flow_width; new_object_part.sticking_area += sticking_area; @@ -673,7 +673,7 @@ std::tuple build_object_part_from_slice(const size_t &slice_i for (const auto &island : slice.islands) { const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); - for (const auto &perimeter_idx : island.perimeters) { + for (size_t perimeter_idx : island.perimeters) { for (const ExtrusionEntity *perimeter : static_cast(perimeter_region->perimeters().entities[perimeter_idx])->entities) { add_extrusions_to_object(perimeter, perimeter_region); @@ -681,20 +681,20 @@ std::tuple build_object_part_from_slice(const size_t &slice_i } for (const LayerExtrusionRange &fill_range : island.fills) { const LayerRegion *fill_region = layer->get_region(fill_range.region()); - for (const auto &fill_idx : fill_range) { + for (size_t fill_idx : fill_range) { for (const ExtrusionEntity *fill : static_cast(fill_region->fills().entities[fill_idx])->entities) { add_extrusions_to_object(fill, fill_region); } } } - for (const auto &thin_fill_idx : island.thin_fills) { + for (size_t thin_fill_idx : island.thin_fills) { add_extrusions_to_object(perimeter_region->thin_fills().entities[thin_fill_idx], perimeter_region); } } // BRIM HANDLING - if (layer->id() == params.raft_layers_count && params.raft_layers_count == 0 && params.brim_type != BrimType::btNoBrim && + if (int(layer->id()) == params.raft_layers_count && params.raft_layers_count == 0 && params.brim_type != BrimType::btNoBrim && params.brim_width > 0.0) { // TODO: The algorithm here should take into account that multiple slices may have coliding Brim areas and the final brim area is // smaller, @@ -911,7 +911,7 @@ std::tuple check_stability(const PrintObject for (const auto &island : slice.islands) { for (const LayerExtrusionRange &fill_range : island.fills) { const LayerRegion *fill_region = layer->get_region(fill_range.region()); - for (const auto &fill_idx : fill_range) { + for (size_t fill_idx : fill_range) { for (const ExtrusionEntity *e : get_flat_entities(fill_region->fills().entities[fill_idx])) { if (e->role() == ExtrusionRole::BridgeInfill) { entities_to_check.push_back({e, fill_region, slice_idx}); @@ -921,7 +921,7 @@ std::tuple check_stability(const PrintObject } const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); - for (const size_t &perimeter_idx : island.perimeters) { + for (size_t perimeter_idx : island.perimeters) { for (const ExtrusionEntity *e : get_flat_entities(perimeter_region->perimeters().entities[perimeter_idx])) { entities_to_check.push_back({e, perimeter_region, slice_idx}); } diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index d4b5048..9f8b670 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -211,6 +211,12 @@ bool is_main_thread_active() return get_main_thread_id() == boost::this_thread::get_id(); } +static thread_local ThreadData s_thread_data; +ThreadData& thread_data() +{ + return s_thread_data; +} + // Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID. // Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator. void name_tbb_thread_pool_threads_set_locale() @@ -274,16 +280,16 @@ void set_current_thread_qos() #endif // __APPLE__ } -void TBBLocalesSetter::on_scheduler_entry(bool is_worker) +void ThreadData::tbb_worker_thread_set_c_locales() { // static std::atomic cnt = 0; // std::cout << "TBBLocalesSetter Entering " << cnt ++ << " ID " << std::this_thread::get_id() << "\n"; - if (bool& is_locales_sets = m_is_locales_sets.local(); !is_locales_sets) { + if (! m_tbb_worker_thread_c_locales_set) { // Set locales of the worker thread to "C". set_c_locales(); // OSX specific: Elevate QOS on Apple Silicon. set_current_thread_qos(); - is_locales_sets = true; + m_tbb_worker_thread_c_locales_set = true; } } diff --git a/src/libslic3r/Thread.hpp b/src/libslic3r/Thread.hpp index 19ad298..61629ad 100644 --- a/src/libslic3r/Thread.hpp +++ b/src/libslic3r/Thread.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -67,6 +68,27 @@ template inline boost::thread create_thread(Fn &&fn) return create_thread(attrs, std::forward(fn)); } +class ThreadData { +public: + std::mt19937& random_generator() { + if (! m_random_generator_initialized) { + std::random_device rd; + m_random_generator.seed(rd()); + m_random_generator_initialized = true; + } + return m_random_generator; + } + + void tbb_worker_thread_set_c_locales(); + +private: + std::mt19937 m_random_generator; + bool m_random_generator_initialized { false }; + bool m_tbb_worker_thread_c_locales_set { false }; +}; + +ThreadData& thread_data(); + // For unknown reasons and in sporadic cases when GCode export is processing, some participating thread // in tbb::parallel_pipeline has not set locales to "C", probably because this thread is newly spawned. // So in this class method on_scheduler_entry is called for every thread before it starts participating @@ -79,11 +101,7 @@ class TBBLocalesSetter : public tbb::task_scheduler_observer public: TBBLocalesSetter() { this->observe(true); } ~TBBLocalesSetter() override { this->observe(false); }; - - void on_scheduler_entry(bool is_worker) override; - -private: - tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets{ false }; + void on_scheduler_entry(bool /* is_worker */) override { thread_data().tbb_worker_thread_set_c_locales(); } }; } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 66bf913..4bf9fd4 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -903,7 +903,7 @@ Polygon its_convex_hull_2d_above(const indexed_triangle_set& its, const Transfor chs.push_back(collect_mesh_projection_points_above(range)); }); - const Polygons polygons(chs.begin(), chs.end()); + const Polygons polygons(std::make_move_iterator(chs.begin()), std::make_move_iterator(chs.end())); return Geometry::convex_hull(polygons); } diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 42202ed..e3ee0bf 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -9,6 +9,7 @@ #include "Platform.hpp" #include "Time.hpp" +#include "format.hpp" #include "libslic3r.h" #ifdef WIN32 diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 7873d90..6f72433 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -203,6 +203,7 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/BoostThreadWorker.cpp GUI/Jobs/UIThreadWorker.hpp GUI/Jobs/BusyCursorJob.hpp + GUI/Jobs/CancellableJob.hpp GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp GUI/Jobs/ArrangeJob.cpp @@ -261,8 +262,8 @@ set(SLIC3R_GUI_SOURCES Utils/Http.hpp Utils/FixModelByWin10.cpp Utils/FixModelByWin10.hpp - Utils/Mainsail.cpp - Utils/Mainsail.hpp + Utils/Moonraker.cpp + Utils/Moonraker.hpp Utils/OctoPrint.cpp Utils/OctoPrint.hpp Utils/Duet.cpp diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c1eeaec..55b1188 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -484,11 +484,11 @@ int GLVolumeCollection::load_object_volume( #if ENABLE_OPENGL_ES int GLVolumeCollection::load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float cone_angle, + float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh) #else int GLVolumeCollection::load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float cone_angle, + float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width) #endif // ENABLE_OPENGL_ES { @@ -538,8 +538,13 @@ int GLVolumeCollection::load_wipe_tower_preview( mesh.scale(Vec3f(width / (n * min_width), 1.f, height)); // Scaling to proper width } - else - mesh = make_cube(width, depth, height); + else { + for (size_t i=1; i>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr); #else int load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width); + float pos_x, float pos_y, float width, float depth, const std::vector>& z_and_depth_pairs, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width); #endif // ENABLE_OPENGL_ES // Load SLA auxiliary GLVolumes (for support trees or pad). diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 505141e..af4dd98 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -1161,8 +1162,8 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat // then the rest // in alphabetical order - std::vector> qidi_profiles; - std::vector> other_profiles; + std::vector> prusa_profiles; + std::vector>> other_profiles; // first is lower case id for sorting bool add_TEMPLATES_item = false; for (int i = 0 ; i < list->size(); ++i) { const std::string& data = list->get_data(i); @@ -1175,7 +1176,7 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat if (!material_type_ordering && data.find("QIDI") != std::string::npos) qidi_profiles.push_back(data); else - other_profiles.push_back(data); + other_profiles.emplace_back(boost::algorithm::to_lower_copy(boost::nowide::widen(data)),data); } if (material_type_ordering) { @@ -1185,10 +1186,10 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) { // find instead compare because PET vs PETG - if (other_profiles[profs].get().find(value) != std::string::npos) { + if (other_profiles[profs].second.get().find(value) != std::string::npos) { //swap if(profs != end_of_sorted) { - std::reference_wrapper aux = other_profiles[end_of_sorted]; + std::pair> aux = other_profiles[end_of_sorted]; other_profiles[end_of_sorted] = other_profiles[profs]; other_profiles[profs] = aux; } @@ -1201,8 +1202,8 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat std::sort(qidi_profiles.begin(), qidi_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { return a.get() < b.get(); }); - std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); + std::sort(other_profiles.begin(), other_profiles.end(), [](const std::pair>& a, const std::pair>& b) { + return a.first append(item, &const_cast(item.get())); for (const auto& item : other_profiles) - list->append(item, &const_cast(item.get())); + list->append(item.second, &const_cast(item.second.get())); } @@ -1225,20 +1226,19 @@ void PageMaterials::sort_list_data(PresetList* list, const std::vector qidi_profiles; - std::vector other_profiles; - //for (int i = 0; i < data.size(); ++i) { + std::vector> other_profiles; // first is lower case id for sorting for (const auto& item : data) { const std::string& name = item.name; if (name.find("QIDI") != std::string::npos) qidi_profiles.emplace_back(item); else - other_profiles.emplace_back(item); + other_profiles.emplace_back(boost::algorithm::to_lower_copy(boost::nowide::widen(name)), item); } std::sort(qidi_profiles.begin(), qidi_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { return a.name.get() < b.name.get(); }); - std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); + std::sort(other_profiles.begin(), other_profiles.end(), [](const std::pair& a, const std::pair& b) { + return a.first < b.first; }); list->Clear(); for (size_t i = 0; i < qidi_profiles.size(); ++i) { @@ -1246,8 +1246,8 @@ void PageMaterials::sort_list_data(PresetList* list, const std::vectorCheck(i, qidi_profiles[i].checked); } for (size_t i = 0; i < other_profiles.size(); ++i) { - list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent || template_shown ? "" : " *"), &const_cast(other_profiles[i].name.get())); - list->Check(i + qidi_profiles.size(), other_profiles[i].checked); + list->append(std::string(other_profiles[i].second.name) + (other_profiles[i].second.omnipresent || template_shown ? "" : " *"), &const_cast(other_profiles[i].second.name.get())); + list->Check(i + qidi_profiles.size(), other_profiles[i].second.checked); } } @@ -1670,9 +1670,17 @@ PageVendors::PageVendors(ConfigWizard *parent) auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); boldfont.SetWeight(wxFONTWEIGHT_BOLD); + // Copy vendors from bundle map to vector, so we can sort it without case sensitivity + std::vector> vendors; + for (const auto& pair : wizard_p()->bundles) { + vendors.emplace_back(boost::algorithm::to_lower_copy(boost::nowide::widen(pair.second.vendor_profile->name)),pair.second.vendor_profile); + } + std::sort(vendors.begin(), vendors.end(), [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); - for (const auto &pair : wizard_p()->bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; + for (const std::pair& v : vendors) { + const VendorProfile* vendor = v.second; if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } if (vendor && vendor->templates_profile) continue; @@ -1682,8 +1690,8 @@ PageVendors::PageVendors(ConfigWizard *parent) wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); }); - const auto &vendors = appconfig.vendors(); - const bool enabled = vendors.find(pair.first) != vendors.end(); + const auto &acvendors = appconfig.vendors(); + const bool enabled = acvendors.find(vendor->id) != acvendors.end(); if (enabled) { cbox->SetValue(true); @@ -2317,12 +2325,25 @@ void ConfigWizard::priv::load_pages() // index->add_page(page_msla); if (!only_sla_mode) { //B9 - // index->add_page(page_vendors); - // for (const auto &pages : pages_3rdparty) { - // for ( PagePrinters* page : { pages.second.first, pages.second.second }) - // if (page && page->install) - // index->add_page(page); - // } + /*index->add_page(page_vendors); + + // Copy pages names from map to vector, so we can sort it without case sensitivity + std::vector> sorted_vendors; + for (const auto& pages : pages_3rdparty) { + sorted_vendors.emplace_back(boost::algorithm::to_lower_copy(boost::nowide::widen(pages.first)), pages.first); + } + std::sort(sorted_vendors.begin(), sorted_vendors.end(), [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + + for (const std::pair v : sorted_vendors) { + const auto& pages = pages_3rdparty.find(v.second); + if (pages == pages_3rdparty.end()) + continue; // Should not happen + for ( PagePrinters* page : { pages->second.first, pages->second.second }) + if (page && page->install) + index->add_page(page); + }*/ index->add_page(page_custom); if (page_custom->custom_wanted()) { diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c27fc5b..bcff8ef 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -800,6 +800,9 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; } + + m_conflict_result = gcode_result.conflict_result; + if (m_conflict_result.has_value()) { m_conflict_result->layer = m_layers.get_l_at(m_conflict_result->_height); } } void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) @@ -2148,6 +2151,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) // layers zs / roles / extruder ids -> extract from result size_t last_travel_s_id = 0; + size_t first_travel_s_id = 0; seams_count = 0; for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; @@ -2161,8 +2165,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) // layers zs const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); const double z = static_cast(move.position.z()); - if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) - m_layers.append(z, { last_travel_s_id, move_id }); + if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) { + const size_t start_it = (m_layers.empty() && first_travel_s_id != 0) ? first_travel_s_id : last_travel_s_id; + m_layers.append(z, { start_it, move_id }); + } else m_layers.get_ranges().back().last = move_id; } @@ -2175,7 +2181,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) else if (move.type == EMoveType::Travel) { if (move_id - last_travel_s_id > 1 && !m_layers.empty()) m_layers.get_ranges().back().last = move_id; - + else if (m_layers.empty() && first_travel_s_id == 0) + first_travel_s_id = move_id; last_travel_s_id = move_id; } } @@ -2258,9 +2265,11 @@ void GCodeViewer::load_shells(const Print& print) if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count); const float depth = wipe_tower_data.depth; + const std::vector> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs; const float brim_width = wipe_tower_data.brim_width; - m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle, - !print.is_step_done(psWipeTower), brim_width); + if (depth != 0.) + m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, z_and_depth_pairs, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle, + !print.is_step_done(psWipeTower), brim_width); } } @@ -2429,8 +2438,10 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool for (size_t i = 0; i < buffer.paths.size(); ++i) { const Path& path = buffer.paths[i]; if (path.type == EMoveType::Travel) { - if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) - continue; + if (path.sub_paths.front().first.s_id > m_layers_z_range[0]) { + if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) + continue; + } } else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) continue; @@ -3963,7 +3974,7 @@ void GCodeViewer::render_legend(float& legend_height) const auto custom_it = std::find(m_roles.begin(), m_roles.end(), GCodeExtrusionRole::Custom); if (custom_it != m_roles.end()) { const bool custom_visible = is_visible(GCodeExtrusionRole::Custom); - const wxString btn_text = custom_visible ? _u8L("Hide Custom G-code") : _u8L("Show Custom G-code"); + const wxString btn_text = custom_visible ? _L("Hide Custom G-code") : _L("Show Custom G-code"); ImGui::Separator(); if (imgui.button(btn_text, ImVec2(-1.0f, 0.0f), true)) { m_extrusions.role_visibility_flags = custom_visible ? m_extrusions.role_visibility_flags & ~(1 << int(GCodeExtrusionRole::Custom)) : diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 0216569..19a7a51 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -526,6 +526,10 @@ class GCodeViewer std::vector& get_ranges() { return m_ranges; } double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); } + int get_l_at(double z) const { + auto iter = std::upper_bound(m_zs.begin(), m_zs.end(), z); + return std::distance(m_zs.begin(), iter); + } bool operator != (const Layers& other) const { if (m_zs != other.m_zs) @@ -784,6 +788,8 @@ private: bool m_contained_in_bed{ true }; + ConflictResultOpt m_conflict_result; + public: GCodeViewer(); ~GCodeViewer() { reset(); } @@ -841,6 +847,8 @@ public: void invalidate_legend() { m_legend_resizer.reset(); } + const ConflictResultOpt& get_conflict_result() const { return m_conflict_result; } + private: void load_toolpaths(const GCodeProcessorResult& gcode_result); void load_shells(const Print& print); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f4fc022..ee00fde 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1176,6 +1176,10 @@ static bool object_contains_negative_volumes(const Model& model, int obj_id) { return (0 <= obj_id && obj_id < (int)model.objects.size()) ? model.objects[obj_id]->has_negative_volume_mesh() : false; } +static bool object_has_sla_drain_holes(const Model& model, int obj_id) { + return (0 <= obj_id && obj_id < (int)model.objects.size()) ? model.objects[obj_id]->has_sla_drain_holes() : false; +} + void GLCanvas3D::SLAView::detect_type_from_volumes(const GLVolumePtrs& volumes) { for (auto& [id, type] : m_instances_cache) { @@ -1184,7 +1188,8 @@ void GLCanvas3D::SLAView::detect_type_from_volumes(const GLVolumePtrs& volumes) for (const GLVolume* v : volumes) { if (v->volume_idx() == -(int)slaposDrillHoles) { - if (object_contains_negative_volumes(*m_parent.get_model(), v->composite_id.object_id)) { + if (object_contains_negative_volumes(*m_parent.get_model(), v->composite_id.object_id) || + object_has_sla_drain_holes(*m_parent.get_model(), v->composite_id.object_id)) { const InstancesCacheItem* instance = find_instance_item(v->composite_id); assert(instance != nullptr); set_type(instance->first, ESLAViewType::Processed); @@ -2569,22 +2574,25 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const Print *print = m_process->fff_print(); const float depth = print->wipe_tower_data(extruders_count).depth; + const std::vector> z_and_depth_pairs = print->wipe_tower_data(extruders_count).z_and_depth_pairs; const float height_real = print->wipe_tower_data(extruders_count).height; // -1.f = unknown // Height of a print (Show at least a slab). const double height = height_real < 0.f ? std::max(m_model->max_z(), 10.0) : height_real; -#if ENABLE_OPENGL_ES - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower), - bw, &m_wipe_tower_mesh); -#else - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower), - bw); -#endif // ENABLE_OPENGL_ES - if (volume_idx_wipe_tower_old != -1) - map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; + if (depth != 0.) { + #if ENABLE_OPENGL_ES + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !print->is_step_done(psWipeTower), + bw, &m_wipe_tower_mesh); + #else + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + x, y, w, depth, z_and_depth_pairs, (float)height, ca, a, !print->is_step_done(psWipeTower), + bw); + #endif // ENABLE_OPENGL_ES + if (volume_idx_wipe_tower_old != -1) + map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; + } } } @@ -2714,6 +2722,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co if (wxGetApp().is_editor()) { m_gcode_viewer.update_shells_color_by_extruder(m_config); _set_warning_notification_if_needed(EWarning::ToolpathOutside); + _set_warning_notification_if_needed(EWarning::GCodeConflict); } m_gcode_viewer.refresh(gcode_result, str_tool_colors); @@ -4706,6 +4715,13 @@ std::pair> GLCanvas3D::get_layers_h return ret; } +void GLCanvas3D::detect_sla_view_type() +{ + m_sla_view.detect_type_from_volumes(m_volumes.volumes); + m_sla_view.update_volumes_visibility(m_volumes.volumes); + m_dirty = true; +} + void GLCanvas3D::set_sla_view_type(ESLAViewType type) { m_sla_view.set_type(type); @@ -7477,8 +7493,12 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) } else { if (wxGetApp().is_editor()) { - if (current_printer_technology() != ptSLA) - show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + if (current_printer_technology() != ptSLA) { + if (warning == EWarning::ToolpathOutside) + show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); + else if (warning == EWarning::GCodeConflict) + show = m_gcode_viewer.has_data() && m_gcode_viewer.is_contained_in_bed() && m_gcode_viewer.get_conflict_result().has_value(); + } } } @@ -7511,8 +7531,55 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) "Resolve the current problem to continue slicing."); error = ErrorType::PLATER_ERROR; break; + case EWarning::GCodeConflict: { + const ConflictResultOpt& conflict_result = m_gcode_viewer.get_conflict_result(); + if (!conflict_result.has_value()) { break; } + std::string objName1 = conflict_result->_objName1; + std::string objName2 = conflict_result->_objName2; + double height = conflict_result->_height; + int layer = conflict_result->layer; + // TRN %3% is name of Object1, %4% is name of Object2 + text = format(_u8L("Conflicts in G-code paths have been detected at layer %1%, z=%2$.2f mm. Please reposition the conflicting objects (%3% <-> %4%) further apart."), + layer, height, objName1, objName2); + error = ErrorType::SLICING_ERROR; + break; + } } auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); + + const ConflictResultOpt& conflict_result = m_gcode_viewer.get_conflict_result(); + if (warning == EWarning::GCodeConflict) { + if (conflict_result.has_value()) { + const PrintObject* obj2 = reinterpret_cast(conflict_result->_obj2); + auto mo = obj2->model_object(); + ObjectID id = mo->id(); + int layer_id = conflict_result->layer; + auto action_fn = [id, layer_id](wxEvtHandler*) { + auto& objects = wxGetApp().model().objects; + auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end(); + if (iter != objects.end()) { + const unsigned int obj_idx = std::distance(objects.begin(), iter); + wxGetApp().CallAfter([obj_idx, layer_id]() { + wxGetApp().plater()->set_preview_layers_slider_values_range(0, layer_id - 1); + wxGetApp().plater()->select_view_3D("3D"); + wxGetApp().plater()->canvas3D()->reset_all_gizmos(); + wxGetApp().plater()->canvas3D()->get_selection().add_object(obj_idx, true); + wxGetApp().obj_list()->update_selections(); + }); + } + return false; + }; + auto hypertext = _u8L("Jump to"); + hypertext += std::string(" [") + mo->name + "]"; + notification_manager.push_notification(NotificationType::SlicingError, NotificationManager::NotificationLevel::ErrorNotificationLevel, + _u8L("ERROR:") + "\n" + text, hypertext, action_fn); + } + else + notification_manager.close_slicing_error_notification(text); + + return; + } + switch (error) { case PLATER_WARNING: diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 3f46cc1..8ee5423 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -364,7 +364,8 @@ class GLCanvas3D ToolpathOutside, SlaSupportsOutside, SomethingNotShown, - ObjectClashed + ObjectClashed, + GCodeConflict }; class RenderStats @@ -1006,6 +1007,7 @@ public: std::pair> get_layers_height_data(int object_id); + void detect_sla_view_type(); void set_sla_view_type(ESLAViewType type); void set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type); void enable_sla_view_type_detection() { m_sla_view_type_detection_active = true; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a5fce6d..a0ac52a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -54,6 +54,7 @@ #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Color.hpp" +#include "libslic3r/Format/SLAArchiveFormatRegistry.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" @@ -493,15 +494,13 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, + /* FT_SL1 (deprecated, overriden by sla_wildcards) */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, /* FT_ZIP */ { "Zip files"sv, { ".zip"sv } }, }; -#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR -wxString file_wildcards(FileType file_type) +static wxString file_wildcards(const FileWildcards& data) { - const FileWildcards& data = file_wildcards_by_type[file_type]; std::string title; std::string mask; @@ -535,6 +534,14 @@ wxString file_wildcards(FileType file_type) return ret; } + +#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR +wxString file_wildcards(FileType file_type) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + + return file_wildcards(data); +} #else // This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. // The function accepts a custom extension parameter. If the parameter is provided, the custom extension @@ -593,6 +600,35 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) } #endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR +wxString sla_wildcards(const char *formatid) +{ + const ArchiveEntry *entry = get_archive_entry(formatid); + wxString ret; + + if (entry) { + FileWildcards wc; + std::string tr_title = I18N::translate_utf8(entry->desc); + // TRN %s = type of file + tr_title = GUI::format(_u8L("%s files"), tr_title); + wc.title = tr_title; + + std::vector exts = get_extensions(*entry); + + wc.file_extensions.reserve(exts.size()); + for (std::string &ext : exts) { + ext.insert(ext.begin(), '.'); + wc.file_extensions.emplace_back(ext); + } + + ret = file_wildcards(wc); + } + + if (ret.empty()) + ret = file_wildcards(FT_SL1); + + return ret; +} + static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } #ifdef WIN32 @@ -928,7 +964,7 @@ void GUI_App::init_app_config() { // Profiles for the alpha are stored into the QIDISlicer-alpha directory to not mix with the current release. - SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_KEY); // SetAppName(SLIC3R_APP_KEY "-alpha"); //B7 // SetAppName(SLIC3R_APP_KEY "-beta"); @@ -1460,8 +1496,7 @@ bool GUI_App::dark_mode() const wxColour GUI_App::get_label_default_clr_system() { - //B10 - return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); + return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); } const wxColour GUI_App::get_label_default_clr_modified() @@ -2290,14 +2325,7 @@ bool GUI_App::load_language(wxString language, bool initial) } #endif -#ifdef __APPLE__ - // ysFIXME after fix for wxWidgets issue (https://github.com/wxWidgets/wxWidgets/issues/23209) - // Workaround for wxLANGUAGE_CHINESE(...) languages => Allow to continue even if wxLocale is not available. - // Because of translation will works fine, just locales will set to EN - if (! wxLocale::IsAvailable(language_info->Language) && language_info->CanonicalName.BeforeFirst('_') != "zh" ) { -#else if (! wxLocale::IsAvailable(language_info->Language)) { -#endif // Loading the language dictionary failed. wxString message = "Switching QIDISlicer to language " + language_info->CanonicalName + " failed."; #if !defined(_WIN32) && !defined(__APPLE__) @@ -2404,7 +2432,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu) local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); - //B5 local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) @@ -3275,9 +3302,7 @@ bool GUI_App::config_wizard_startup() if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { run_wizard(ConfigWizard::RR_DATA_EMPTY); return true; - } - //B7 - else if (get_app_config()->legacy_datadir()) { + } else if (get_app_config()->legacy_datadir()) { // Looks like user has legacy pre-vendorbundle data directory, // explain what this is and run the wizard diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6590524..4404976 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -86,6 +86,8 @@ extern wxString file_wildcards(FileType file_type); extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string{}); #endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR +wxString sla_wildcards(const char *formatid); + enum ConfigMenuIDs { ConfigMenuWizard, ConfigMenuSnapshots, diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d668ca6..dd6318f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1685,6 +1685,8 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset; new_volume->set_offset(inv_inst_transform * offset); } + else + new_volume->set_offset(new_volume->source.mesh_offset - model_object.volumes.front()->source.mesh_offset); added_volumes.push_back(new_volume); } @@ -2306,38 +2308,18 @@ void ObjectList::merge(bool to_multipart_object) for (int obj_idx : obj_idxs) { ModelObject* object = (*m_objects)[obj_idx]; - const Geometry::Transformation& transformation = object->instances[0]->get_transformation(); - const Vec3d scale = transformation.get_scaling_factor(); - const Vec3d mirror = transformation.get_mirror(); - const Vec3d rotation = transformation.get_rotation(); - if (object->id() == (*m_objects)[obj_idxs.front()]->id()) { new_object->add_instance(); new_object->instances[0]->printable = false; } new_object->instances[0]->printable |= object->instances[0]->printable; - const Transform3d& volume_offset_correction = transformation.get_matrix(); + const Transform3d new_inst_trafo = new_object->instances[0]->get_matrix().inverse() * object->instances[0]->get_matrix(); // merge volumes for (const ModelVolume* volume : object->volumes) { ModelVolume* new_volume = new_object->add_volume(*volume); - - //set rotation - const Vec3d vol_rot = new_volume->get_rotation() + rotation; - new_volume->set_rotation(vol_rot); - - // set scale - const Vec3d vol_sc_fact = new_volume->get_scaling_factor().cwiseProduct(scale); - new_volume->set_scaling_factor(vol_sc_fact); - - // set mirror - const Vec3d vol_mirror = new_volume->get_mirror().cwiseProduct(mirror); - new_volume->set_mirror(vol_mirror); - - // set offset - const Vec3d vol_offset = volume_offset_correction* new_volume->get_offset(); - new_volume->set_offset(vol_offset); + new_volume->set_transformation(new_inst_trafo * new_volume->get_matrix()); } new_object->sort_volumes(wxGetApp().app_config->get_bool("order_volumes")); @@ -4400,7 +4382,8 @@ void ObjectList::update_and_show_object_settings_item() const wxDataViewItem item = GetSelection(); if (!item) return; - const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item; + // To get object item use GetTopParent(item). This function guarantees return of item with itObject type + const wxDataViewItem obj_item = m_objects_model->GetTopParent(item); select_item([this, obj_item](){ return add_settings_item(obj_item, &get_item_config(obj_item).get()); }); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 656bc0b..74a8719 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -181,6 +181,12 @@ Preview::Preview( load_print(); } +void Preview::set_layers_slider_values_range(int bottom, int top) +{ + m_layers_slider->SetHigherValue(std::min(top, m_layers_slider->GetMaxValue())); + m_layers_slider->SetLowerValue(std::max(bottom, m_layers_slider->GetMinValue())); +} + bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) { if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 25d9fe0..c7b7fdc 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -151,6 +151,8 @@ public: void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; } + void set_layers_slider_values_range(int bottom, int top); + private: bool init(wxWindow* parent, Bed3D& bed, Model* model); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3225fe2..e298dbe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1488,8 +1488,7 @@ void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_mo if (!m_parts[id].is_modifier && normal && ((is_looking_forward && m_parts[id].selected) || (!is_looking_forward && !m_parts[id].selected) ) ) continue; - const Vec3d volume_offset = model_object()->volumes[id]->get_offset(); - shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset)); + shader->set_uniform("view_model_matrix", view_inst_matrix * model_object()->volumes[id]->get_matrix()); if (m_parts[id].is_modifier) { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -2429,7 +2428,7 @@ bool GLGizmoCut3D::has_valid_contour() const return clipper && clipper->has_valid_contour(); } -void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object) +void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count) { if (m_connector_mode == CutConnectorMode::Manual) { clear_selection(); @@ -2440,7 +2439,7 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel if (connector.attribs.type == CutConnectorType::Dowel) { if (connector.attribs.style == CutConnectorStyle::Prism) connector.height *= 2; - create_dowels_as_separate_object = true; + dowels_count ++; } else { // calculate shift of the connector center regarding to the position on the cut plane @@ -2469,6 +2468,34 @@ Transform3d GLGizmoCut3D::get_cut_matrix(const Selection& selection) return translation_transform(cut_center_offset) * m_rotation_m; } +void update_object_cut_id(CutObjectBase& cut_id, ModelObjectCutAttributes attributes, const int dowels_count) +{ + // we don't save cut information, if result will not contains all parts of initial object + if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || + !attributes.has(ModelObjectCutAttribute::KeepLower) || + attributes.has(ModelObjectCutAttribute::InvalidateCutInfo)) + return; + + if (cut_id.id().invalid()) + cut_id.init(); + // increase check sum, if it's needed + { + int cut_obj_cnt = -1; + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::KeepLower)) cut_obj_cnt++; + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) cut_obj_cnt+= dowels_count; + if (cut_obj_cnt > 0) + cut_id.increase_check_sum(size_t(cut_obj_cnt)); + } +} + +void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id) +{ + for (ModelObject* obj : model.objects) + if (obj->is_cut() && obj->cut_id.has_same_id(cut_id) && !obj->cut_id.is_equal(cut_id)) + obj->cut_id.copy(cut_id); +} + void GLGizmoCut3D::perform_cut(const Selection& selection) { if (!can_perform_cut()) @@ -2497,11 +2524,13 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr; if (cut_mo) cut_mo->cut_connectors = mo->cut_connectors; + else + cut_mo = mo; - bool create_dowels_as_separate_object = false; + int dowels_count = 0; const bool has_connectors = !mo->cut_connectors.empty(); // update connectors pos as offset of its center before cut performing - apply_connectors_in_model(cut_mo ? cut_mo : mo , create_dowels_as_separate_object); + apply_connectors_in_model(cut_mo , dowels_count); wxBusyCursor wait; @@ -2514,33 +2543,27 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_place_on_cut_lower, ModelObjectCutAttribute::PlaceOnCutLower) | only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | - only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels) | - only_if(!has_connectors, ModelObjectCutAttribute::InvalidateCutInfo); + only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) | + only_if(!has_connectors && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo); + + // update cut_id for the cut object in respect to the attributes + update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); ModelObjectPtrs cut_object_ptrs; if (cut_by_contour) { - // apply cut attributes for object - if (m_keep_upper && m_keep_lower) - cut_mo->apply_cut_attributes(ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | - only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels)); - // Clone the object to duplicate instances, materials etc. ModelObject* upper{ nullptr }; if (m_keep_upper) cut_mo->clone_for_cut(&upper); ModelObject* lower{ nullptr }; if (m_keep_lower) cut_mo->clone_for_cut(&lower); - auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower, bool invalidate_cut = true) { + auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) { if (upper && !upper->volumes.empty()) { ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); - if (invalidate_cut) - upper->invalidate_cut(); cut_objects.push_back(upper); } if (lower && !lower->volumes.empty()) { ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower); - if (invalidate_cut) - lower->invalidate_cut(); cut_objects.push_back(lower); } }; @@ -2578,7 +2601,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) if (volumes.size() == cut_parts_cnt) { // Means that object is cut without connectors - // Just add Upper and Lower objects to cut_object_ptrs and invalidate any cut information + // Just add Upper and Lower objects to cut_object_ptrs add_cut_objects(cut_object_ptrs, upper, lower); } else if (volumes.size() > cut_parts_cnt) { @@ -2592,7 +2615,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // Perform cut just to get connectors const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); - assert(create_dowels_as_separate_object ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); + assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); // Connectors from upper object for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) @@ -2602,8 +2625,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) lower->add_volume(*volume, volume->type()); - // Add Upper and Lower objects to cut_object_ptrs with saved cut information - add_cut_objects(cut_object_ptrs, upper, lower, false); + // Add Upper and Lower objects to cut_object_ptrs + add_cut_objects(cut_object_ptrs, upper, lower); // Add Dowel-connectors as separate objects to cut_object_ptrs if (cut_connectors_obj.size() >= 3) @@ -2637,14 +2660,18 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } } else - cut_object_ptrs = mo->cut(instance_idx, cut_matrix, attributes); + cut_object_ptrs = cut_mo->cut(instance_idx, cut_matrix, attributes); + // save cut_id to post update synchronization + const CutObjectBase cut_id = cut_mo->cut_id; + + // update cut results on plater and in the model plater->cut(object_idx, cut_object_ptrs); + + synchronize_model_after_cut(plater->model(), cut_id); } } - - // Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // Return false if no intersection was found, true otherwise. bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour/* = true*/) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 6d1a8e4..51174a5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -315,7 +315,7 @@ private: bool can_perform_cut() const; bool has_valid_contour() const; - void apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object); + void apply_connectors_in_model(ModelObject* mo, int &dowels_count); bool cut_line_processing() const; void discard_cut_line_processing(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index e7cd63f..ec6f665 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -855,6 +855,7 @@ void GLGizmoSlaSupports::on_set_state() m_old_mo_id = -1; } + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); m_c->instances_hider()->set_hide_full_scene(false); m_c->selection_info()->set_use_shift(false); // see top of on_render for details diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 79c7769..02f30d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -391,7 +391,7 @@ void ObjectClipper::render_cut(const std::vector* ignore_idxs) const // Now update the ignore idxs. Find the first element belonging to the next clipper, // and remove everything before it and decrement everything by current number of contours. const int num_of_contours = clipper.first->get_number_of_contours(); - ignore_idxs_local.erase(ignore_idxs_local.begin(), std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) { return idx >= num_of_contours; } )); + ignore_idxs_local.erase(ignore_idxs_local.begin(), std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) { return idx >= size_t(num_of_contours); } )); for (size_t& idx : ignore_idxs_local) idx -= num_of_contours; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index ad67b99..3103cd9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -173,8 +173,7 @@ void GLGizmosManager::reset_all_states() const EType current = get_current_type(); if (current != Undefined) // close any open gizmo - if (!open_gizmo(current)) - return; + open_gizmo(current); activate_gizmo(Undefined); m_hover = Undefined; @@ -960,6 +959,8 @@ bool GLGizmosManager::activate_gizmo(EType type) if (type == Undefined) { // it is deactivation of gizmo m_current = Undefined; + if (m_parent.current_printer_technology() == ptSLA) + m_parent.detect_sla_view_type(); return true; } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index c355874..e5a7e1b 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -180,7 +180,7 @@ void ImGuiWrapper::set_language(const std::string &language) m_font_cjk = false; if (lang == "cs" || lang == "pl" || lang == "hu") { ranges = ranges_latin2; - } else if (lang == "ru" || lang == "uk") { + } else if (lang == "ru" || lang == "uk" || lang == "be") { ranges = ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters } else if (lang == "tr") { ranges = ranges_turkish; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2aeb525..2d6333f 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -99,7 +99,6 @@ void ArrangeJob::prepare_selected() { clear_input(); Model &model = m_plater->model(); - double stride = bed_stride(m_plater); std::vector obj_sel(model.objects.size(), nullptr); @@ -143,12 +142,6 @@ void ArrangeJob::prepare_selected() { // If the selection was empty arrange everything if (m_selected.empty()) m_selected.swap(m_unselected); - - // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items bed_idx is ignored and the - // translation is irrelevant. - for (auto &p : m_unselected) - p.translation(X) -= p.bed_idx * stride; } static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, @@ -173,14 +166,7 @@ static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, auto omesh = po.get_mesh_to_print(); auto &smesh = po.support_mesh(); - Vec3d rotation = inst.get_rotation(); - rotation.z() = 0.; - Transform3f trafo_instance = - Geometry::assemble_transform(inst.get_offset().z() * Vec3d::UnitZ(), - rotation, - inst.get_scaling_factor(), - inst.get_mirror()).cast(); - + Transform3f trafo_instance = inst.get_matrix().cast(); trafo_instance = trafo_instance * po.trafo().cast().inverse(); Polygons polys; @@ -189,8 +175,6 @@ static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, if (omesh) { polys.emplace_back(its_convex_hull_2d_above(*omesh, trafo_instance, zlvl)); - ret.poly.contour = polys.back(); - ret.poly.holes = {}; } polys.emplace_back(its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl)); @@ -243,7 +227,8 @@ coord_t get_skirt_offset(const Plater* plater) { void ArrangeJob::prepare() { - wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); + m_selection_only ? prepare_selected() : + prepare_all(); coord_t min_offset = 0; for (auto &ap : m_selected) { @@ -261,6 +246,10 @@ void ArrangeJob::prepare() } m_min_bed_inset = min_offset; } + + double stride = bed_stride(m_plater); + get_bed_shape(*m_plater->config(), m_bed); + assign_logical_beds(m_unselected, m_bed, stride); } void ArrangeJob::process(Ctl &ctl) @@ -268,11 +257,9 @@ void ArrangeJob::process(Ctl &ctl) static const auto arrangestr = _u8L("Arranging"); arrangement::ArrangeParams params; - arrangement::ArrangeBed bed; - ctl.call_on_main_thread([this, ¶ms, &bed]{ + ctl.call_on_main_thread([this, ¶ms]{ prepare(); params = get_arrange_params(m_plater); - get_bed_shape(*m_plater->config(), bed); coord_t min_inset = get_skirt_offset(m_plater) + m_min_bed_inset; params.min_bed_distance = std::max(params.min_bed_distance, min_inset); }).wait(); @@ -293,13 +280,13 @@ void ArrangeJob::process(Ctl &ctl) ctl.update_status(0, arrangestr); - arrangement::arrange(m_selected, m_unselected, bed, params); + arrangement::arrange(m_selected, m_unselected, m_bed, params); params.progressind = [this, count, &ctl](unsigned st) { if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); }; - arrangement::arrange(m_unprintable, {}, bed, params); + arrangement::arrange(m_unprintable, {}, m_bed, params); // finalize just here. ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ? @@ -307,7 +294,10 @@ void ArrangeJob::process(Ctl &ctl) _u8L("Arranging done.")); } -ArrangeJob::ArrangeJob() : m_plater{wxGetApp().plater()} {} +ArrangeJob::ArrangeJob(Mode mode) + : m_plater{wxGetApp().plater()}, + m_selection_only{mode == Mode::SelectionOnly} +{} static std::string concat_strings(const std::set &strings, const std::string &delim = "\n") @@ -355,7 +345,9 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { ap.apply(); } - m_plater->update((unsigned int)Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH); + m_plater->update(static_cast( + Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH)); + wxGetApp().obj_manipul()->set_dirty(); if (!m_unarranged.empty()) { @@ -439,4 +431,25 @@ arrangement::ArrangeParams get_arrange_params(Plater *p) return params; } +void assign_logical_beds(std::vector &items, + const arrangement::ArrangeBed &bed, + double stride) +{ + // The strides have to be removed from the fixed items. For the + // arrangeable (selected) items bed_idx is ignored and the + // translation is irrelevant. + coord_t bedx = bounding_box(bed).min.x(); + for (auto &itm : items) { + auto bedidx = std::max(arrangement::UNARRANGED, + static_cast(std::floor( + (get_extents(itm.transformed_poly()).min.x() - bedx) / + stride))); + + itm.bed_idx = bedidx; + + if (bedidx >= 0) + itm.translation.x() -= bedidx * stride; + } +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 6ff2592..c7070f2 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -21,9 +21,11 @@ class ArrangeJob : public Job ArrangePolygons m_selected, m_unselected, m_unprintable; std::vector m_unarranged; + arrangement::ArrangeBed m_bed; coord_t m_min_bed_inset = 0.; Plater *m_plater; + bool m_selection_only = false; // clear m_selected and m_unselected, reserve space for next usage void clear_input(); @@ -39,11 +41,13 @@ class ArrangeJob : public Job public: + enum Mode { Full, SelectionOnly }; + void prepare(); void process(Ctl &ctl) override; - ArrangeJob(); + ArrangeJob(Mode mode = Full); int status_range() const { @@ -86,7 +90,6 @@ arrangement::ArrangePolygon get_arrange_poly(T obj, const Plater *plater) using ArrangePolygon = arrangement::ArrangePolygon; ArrangePolygon ap = obj.get_arrange_polygon(); - ap.bed_idx = get_extents(ap.transformed_poly()).min.x() / bed_stride(plater); ap.setter = [obj, plater](const ArrangePolygon &p) { if (p.is_arranged()) { Vec2d t = p.translation.cast(); @@ -106,6 +109,10 @@ arrangement::ArrangeParams get_arrange_params(Plater *p); coord_t get_skirt_offset(const Plater* plater); +void assign_logical_beds(std::vector &items, + const arrangement::ArrangeBed &bed, + double stride); + }} // namespace Slic3r::GUI #endif // ARRANGEJOB_HPP diff --git a/src/slic3r/GUI/Jobs/CancellableJob.hpp b/src/slic3r/GUI/Jobs/CancellableJob.hpp new file mode 100644 index 0000000..ea8d676 --- /dev/null +++ b/src/slic3r/GUI/Jobs/CancellableJob.hpp @@ -0,0 +1,60 @@ +#ifndef CANCELLABLEJOB_HPP +#define CANCELLABLEJOB_HPP + +#include + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +template +class CancellableJob: public Job { + JobSubclass m_job; + std::atomic_bool &m_flag; + +public: + template + CancellableJob(std::atomic_bool &flag, Args &&...args) + : m_job{std::forward(args)...}, m_flag{flag} + {} + + void process(Ctl &ctl) override + { + m_flag.store(false); + + struct CancelCtl : public Ctl + { + Ctl &basectl; + std::atomic_bool &cflag; + + CancelCtl (Ctl &c, std::atomic_bool &f): basectl{c}, cflag{f} {} + + void update_status(int st, const std::string &msg = "") override + { + basectl.update_status(st, msg); + } + + bool was_canceled() const override + { + return cflag.load() || basectl.was_canceled(); + } + + std::future call_on_main_thread( + std::function fn) override + { + return basectl.call_on_main_thread(fn); + } + } cctl(ctl, m_flag); + + m_job.process(cctl); + } + + void finalize(bool canceled, std::exception_ptr &eptr) override + { + m_job.finalize(m_flag.load() || canceled, eptr); + } +}; + +}} // namespace Slic3r::GUI + +#endif // CANCELLABLEJOB_HPP diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 11d9450..7594d32 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -17,7 +17,6 @@ void FillBedJob::prepare() { m_selected.clear(); m_unselected.clear(); - m_bedpts.clear(); m_min_bed_inset = 0.; m_object_idx = m_plater->get_selected_object_idx(); @@ -41,10 +40,10 @@ void FillBedJob::prepare() if (m_selected.empty()) return; - m_bedpts = get_bed_shape(*m_plater->config()); + Points bedpts = get_bed_shape(*m_plater->config()); auto &objects = m_plater->model().objects; - BoundingBox bedbb = get_extents(m_bedpts); + BoundingBox bedbb = get_extents(bedpts); for (size_t idx = 0; idx < objects.size(); ++idx) if (int(idx) != m_object_idx) @@ -72,7 +71,7 @@ void FillBedJob::prepare() }) / sc; double fixed_area = unsel_area + m_selected.size() * poly_area; - double bed_area = Polygon{m_bedpts}.area() / sc; + double bed_area = Polygon{bedpts}.area() / sc; // This is the maximum number of items, the real number will always be close but less. int needed_items = (bed_area - fixed_area) / poly_area; @@ -85,13 +84,11 @@ void FillBedJob::prepare() for (int i = 0; i < needed_items; ++i) { ArrangePolygon ap = template_ap; - ap.poly = m_selected.front().poly; ap.bed_idx = arrangement::UNARRANGED; auto m = mi->get_transformation(); - ap.setter = [this, mi, m](const ArrangePolygon &p) { + ap.setter = [this, m](const ArrangePolygon &p) { ModelObject *mo = m_plater->model().objects[m_object_idx]; - ModelInstance *inst = mo->add_instance(*mi); - inst->set_transformation(m); + ModelInstance *inst = mo->add_instance(m); inst->apply_arrange_result(p.translation.cast(), p.rotation); }; m_selected.emplace_back(ap); @@ -99,14 +96,6 @@ void FillBedJob::prepare() m_status_range = m_selected.size(); - // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items bed_idx is ignored and the - // translation is irrelevant. - double stride = bed_stride(m_plater); - for (auto &p : m_unselected) - if (p.bed_idx > 0) - p.translation(X) -= p.bed_idx * stride; - coord_t min_offset = 0; for (auto &ap : m_selected) { min_offset = std::max(ap.inflation, min_offset); @@ -123,6 +112,14 @@ void FillBedJob::prepare() } m_min_bed_inset = min_offset; } + + // The strides have to be removed from the fixed items. For the + // arrangeable (selected) items bed_idx is ignored and the + // translation is irrelevant. + double stride = bed_stride(m_plater); + + m_bed = arrangement::to_arrange_bed(bedpts); + assign_logical_beds(m_unselected, m_bed, stride); } void FillBedJob::process(Ctl &ctl) @@ -154,7 +151,7 @@ void FillBedJob::process(Ctl &ctl) do_stop = ap.bed_idx > 0 && ap.priority == 0; }; - arrangement::arrange(m_selected, m_unselected, m_bedpts, params); + arrangement::arrange(m_selected, m_unselected, m_bed, params); // finalize just here. ctl.update_status(100, ctl.was_canceled() ? @@ -191,7 +188,8 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) model_object->ensure_on_bed(); - m_plater->update(); + m_plater->update(static_cast( + Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH)); // FIXME: somebody explain why this is needed for increase_object_instances if (inst_cnt == 1) diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index b953e0e..ae8680d 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -18,7 +18,7 @@ class FillBedJob : public Job ArrangePolygons m_unselected; coord_t m_min_bed_inset = 0.; - Points m_bedpts; + arrangement::ArrangeBed m_bed; int m_status_range = 0; Plater *m_plater; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index f7eedc2..c80ecfc 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -2118,9 +2118,9 @@ void NotificationManager::push_slicing_error_notification(const std::string& tex push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); set_slicing_progress_hidden(); } -void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step) +void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step, const std::string& hypertext, std::function callback) { - NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotificationLevel, 0, _u8L("WARNING:") + "\n" + text }; + NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotificationLevel, 0, _u8L("WARNING:") + "\n" + text , hypertext, callback}; auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = oid; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index bdfadd3..b562f08 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -172,7 +172,7 @@ public: // Creates Slicing Error notification with a custom text and no fade out. void push_slicing_error_notification(const std::string& text); // Creates Slicing Warning notification with a custom text and no fade out. - void push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step); + void push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step, const std::string& hypertext = "", std::function callback = std::function()); // marks slicing errors as gray void set_all_slicing_errors_gray(bool g); // marks slicing warings as gray diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 4e05749..80dfba5 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -116,7 +116,7 @@ void PresetForPrinter::update_full_printer_name() wxString printer_name = m_parent->get_printer_name(); wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection()); - m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); + m_full_printer_name->SetLabelText(printer_name + from_u8(PhysicalPrinter::separator()) + preset_name); } std::string PresetForPrinter::get_preset_name() @@ -654,6 +654,24 @@ wxString PhysicalPrinterDialog::get_printer_name() void PhysicalPrinterDialog::update_full_printer_names() { + // check input symbols for usability + + const char* unusable_symbols = "<>[]:/\\|?*\""; + + wxString printer_name = m_printer_name->GetValue(); + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + size_t pos = printer_name.find_first_of(unusable_symbols[i]); + if (pos != std::string::npos) { + wxString str = printer_name.SubString(pos, 1); + printer_name.Remove(pos, 1); + InfoDialog(this, format_wxstr("%1%: \"%2%\" ", _L("Unexpected character"), str), + _L("The following characters are not allowed in the name") + ": " + unusable_symbols).ShowModal(); + m_printer_name->SetValue(printer_name); + m_printer_name->SetInsertionPointEnd(); + return; + } + } + for (PresetForPrinter* preset : m_presets) preset->update_full_printer_name(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c0f8e7f..0279310 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1,4 +1,5 @@ #include "Plater.hpp" +#include "slic3r/GUI/Jobs/UIThreadWorker.hpp" #include #include @@ -1803,6 +1804,8 @@ struct Plater::priv bool init_view_toolbar(); bool init_collapse_toolbar(); + void set_preview_layers_slider_values_range(int bottom, int top); + void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable); @@ -2017,7 +2020,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extruder", - "extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_technology", + "extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_notes", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers", @@ -3438,7 +3441,7 @@ bool Plater::priv::restart_background_process(unsigned int state) void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job) { wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty"); - + if (model.objects.empty()) return; @@ -4114,11 +4117,42 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Avoid a race condition return; } - -// this->statusbar()->set_progress(evt.status.percent); -// this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); notification_manager->set_slicing_progress_percentage(evt.status.text, (float)evt.status.percent / 100.0f); } + + // Check template filaments and add warning + // This is more convinient to do here than in slicing backend, so it happens on "Slicing complete". + if (evt.status.percent >= 100 && this->printer_technology == ptFFF) { + size_t templ_cnt = 0; + const auto& preset_bundle = wxGetApp().preset_bundle; + std::string names; + for (const auto& extruder_filaments : preset_bundle->extruders_filaments) { + const Preset* preset = extruder_filaments.get_selected_preset(); + if (preset && preset->vendor && preset->vendor->templates_profile) { + names += "\n" + preset->name; + templ_cnt++; + } + } + if (templ_cnt > 0) { + const std::string message_notif = GUI::format("%1%\n%2%\n\n%3%\n\n%4% " + , _L_PLURAL("You are using template filament preset.", "You are using template filament presets.", templ_cnt) + , names + , _u8L("Please note that template presets are not customized for specific printer and should only be used as a starting point for creating your own user presets.") + ,_u8L("More info at")); + // warning dialog proccessing cuts text at first '/n' - pass the text without new lines (and without filament names). + const std::string message_dial = GUI::format("%1% %2% %3%" + , _L_PLURAL("You are using template filament preset.", "You are using template filament presets.", templ_cnt) + , _u8L("Please note that template presets are not customized for specific printer and should only be used as a starting point for creating your own user presets.") + , "https://help.prusa3d.com/" + ); + BOOST_LOG_TRIVIAL(warning) << message_notif; + notification_manager->push_slicing_warning_notification(message_notif, false, 0, 0, "https://help.prusa3d.com/", + [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://help.prusa3d.com/article/template-filaments_467599"); return false; } + ); + add_warning({ PrintStateBase::WarningLevel::CRITICAL, true, message_dial, 0}, 0); + } + } + if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { case ptFFF: @@ -4254,9 +4288,9 @@ bool Plater::priv::warnings_dialog() else text += it.first.message; } - //text += "\n\nDo you still wish to export?"; - //wxMessageDialog msg_wingow(this->q, from_u8(text), wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK); - MessageDialog msg_wingow(this->q, from_u8(text), wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK); + //MessageDialog msg_wingow(this->q, from_u8(text), wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK); + // Changed to InfoDialog so it can show hyperlinks + InfoDialog msg_wingow(this->q, format_wxstr("%1% %2%", SLIC3R_APP_NAME, _L("generated warnings")), from_u8(text), true); const auto res = msg_wingow.ShowModal(); return res == wxID_OK; @@ -4667,6 +4701,11 @@ bool Plater::priv::init_collapse_toolbar() return true; } +void Plater::priv::set_preview_layers_slider_values_range(int bottom, int top) +{ + preview->set_layers_slider_values_range(bottom, top); +} + void Plater::priv::update_preview_moves_slider() { preview->update_moves_slider(); @@ -5328,8 +5367,9 @@ void Plater::import_zip_archive() if (input_file.empty()) return; - fs::path path = into_path(input_file); - preview_zip_archive(path); + wxArrayString arr; + arr.Add(input_file); + load_files(arr, false); } void Plater::import_sl1_archive() @@ -6125,7 +6165,7 @@ void Plater::increase_instances(size_t num, int obj_idx, int inst_idx) if (const auto obj_idxs = get_selection().get_object_idxs(); !obj_idxs.empty()) { // we need a copy made here because the selection changes at every call of increase_instances() const Selection::ObjectIdxsToInstanceIdxsMap content = p->get_selection().get_content(); - for (const size_t& obj_id : obj_idxs) { + for (const unsigned int obj_id : obj_idxs) { if (auto obj_it = content.find(int(obj_id)); obj_it != content.end()) increase_instances(1, int(obj_id), *obj_it->second.rbegin()); } @@ -6346,7 +6386,9 @@ void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects) for (size_t i = 0; i < new_objects.size(); ++i) selection.add_object((unsigned int)(last_id - i), i == 0); - arrange(); + UIThreadWorker w; + replace_job(w, std::make_unique(ArrangeJob::SelectionOnly)); + w.process_events(); } void Plater::export_gcode(bool prefer_removable) @@ -6401,9 +6443,11 @@ void Plater::export_gcode(bool prefer_removable) start_dir, from_path(default_output_file.filename()), #if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR - GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_SL1), + printer_technology() == ptFFF ? GUI::file_wildcards(FT_GCODE) : + GUI::sla_wildcards(p->sla_print.printer_config().sla_archive_format.value.c_str()), #else - GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_SL1, ext), + printer_technology() == ptFFF ? GUI::file_wildcards(FT_GCODE, ext) : + GUI::sla_wildcards(p->sla_print.printer_config().sla_archive_format.value.c_str()), #endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); @@ -6501,7 +6545,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only) const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); - if (auto m = object->get_mesh_to_print(); !m || m->empty()) + if (!object || !object->get_mesh_to_print() || object->get_mesh_to_print()->empty()) mesh = mesh_to_export_fff(mo, instance_id); else { const Transform3d mesh_trafo_inv = object->trafo().inverse(); @@ -6663,11 +6707,14 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path) if (ret) { // Success // p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); + BOOST_LOG_TRIVIAL(info) << "3MF file exported to " << path; p->set_project_filename(path); } else { // Failure // p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); + const wxString what = GUI::format_wxstr("%1%: %2%", _L("Unable to save file") , path_u8); + show_error(this, what); } return ret; } @@ -7231,7 +7278,11 @@ void Plater::arrange() if (p->can_arrange()) { auto &w = get_ui_job_worker(); p->take_snapshot(_L("Arrange")); - replace_job(w, std::make_unique()); + + auto mode = wxGetKeyState(WXK_SHIFT) ? ArrangeJob::SelectionOnly : + ArrangeJob::Full; + + replace_job(w, std::make_unique(mode)); } } @@ -7541,6 +7592,11 @@ GLToolbar& Plater::get_collapse_toolbar() return p->collapse_toolbar; } +void Plater::set_preview_layers_slider_values_range(int bottom, int top) +{ + p->set_preview_layers_slider_values_range(bottom, top); +} + void Plater::update_preview_moves_slider() { p->update_preview_moves_slider(); @@ -7708,7 +7764,7 @@ PlaterAfterLoadAutoArrange::PlaterAfterLoadAutoArrange() Plater* plater = wxGetApp().plater(); m_enabled = plater->model().objects.empty() && plater->printer_technology() == ptFFF && - plater->fff_print().config().printer_model.value == "XL"; + is_XL_printer(plater->fff_print().config()); } PlaterAfterLoadAutoArrange::~PlaterAfterLoadAutoArrange() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f35a62c..fd37720 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -401,6 +401,8 @@ public: const GLToolbar& get_collapse_toolbar() const; GLToolbar& get_collapse_toolbar(); + void set_preview_layers_slider_values_range(int bottom, int top); + void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index a086e77..b6b70bc 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -246,8 +246,15 @@ void PresetComboBox::update(std::string select_preset_name) const std::deque& presets = m_collection->get_presets(); - std::map> nonsys_presets; - std::map incomp_presets; + struct PresetData { + wxString name; + wxString lower_name; + wxBitmapBundle* bitmap; + bool enabled; // not used in incomp_presets + }; + std::vector system_presets; + std::vector nonsys_presets; + std::vector incomp_presets; wxString selected = ""; if (!presets.front().is_visible) @@ -276,28 +283,31 @@ void PresetComboBox::update(std::string select_preset_name) assert(bmp); if (!is_enabled) { - incomp_presets.emplace(get_preset_name(preset), bmp); + incomp_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp, false}); if (preset.is_dirty && m_show_modif_preset_separately) - incomp_presets.emplace(get_preset_name_with_suffix(preset), bmp); + incomp_presets.push_back({get_preset_name_with_suffix(preset), get_preset_name_with_suffix(preset).Lower(), bmp, false}); } else if (preset.is_default || preset.is_system) { - Append(get_preset_name(preset), *bmp); - validate_selection(preset.name == select_preset_name); + system_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled}); + if (preset.name == select_preset_name) + selected = preset.name; + if (preset.is_dirty && m_show_modif_preset_separately) { wxString preset_name = get_preset_name_with_suffix(preset); - Append(preset_name, *bmp); - validate_selection(into_u8(preset_name) == select_preset_name); + system_presets.push_back({preset_name, preset_name.Lower(), bmp, is_enabled}); + if (into_u8(preset_name) == select_preset_name) + selected = preset_name; } } else { - nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + nonsys_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled}); if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)) selected = get_preset_name(preset); if (preset.is_dirty && m_show_modif_preset_separately) { wxString preset_name = get_preset_name_with_suffix(preset); - nonsys_presets.emplace(preset_name, std::pair(bmp, is_enabled)); + nonsys_presets.push_back({preset_name, preset_name.Lower(), bmp, is_enabled}); if (preset_name == select_preset_name || (select_preset_name.empty() && is_enabled)) selected = preset_name; } @@ -305,25 +315,46 @@ void PresetComboBox::update(std::string select_preset_name) if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } + + if (!system_presets.empty()) + { + std::sort(system_presets.begin(), system_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + + for (std::vector::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { + int item_id = Append(it->name, *it->bitmap); + if (!it->enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->name == selected); + } + } if (!nonsys_presets.empty()) { + std::sort(nonsys_presets.begin(), nonsys_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); - for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - bool is_enabled = it->second.second; - if (!is_enabled) + for (std::vector::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->name, *it->bitmap); + if (!it->enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); + validate_selection(it->name == selected); } } if (!incomp_presets.empty()) { + std::sort(incomp_presets.begin(), incomp_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + set_label_marker(Append(separator(L("Incompatible presets")), NullBitmapBndl())); - for (std::map::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) { - set_label_marker(Append(it->first, *it->second), LABEL_ITEM_DISABLED); + for (std::vector ::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) { + set_label_marker(Append(it->name, *it->bitmap), LABEL_ITEM_DISABLED); } } - + update_selection(); Thaw(); } @@ -613,13 +644,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) { - // In a case of a physical printer, for its editing open PhysicalPrinterDialog - if (m_type == Preset::TYPE_PRINTER -#ifdef __linux__ - // To edit extruder color from the sidebar - || m_type == Preset::TYPE_FILAMENT -#endif //__linux__ - ) + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FILAMENT) show_edit_menu(); else switch_to_tab(); @@ -753,15 +778,18 @@ void PlaterPresetComboBox::show_edit_menu() append_menu_item(menu, wxID_ANY, _L("Edit preset"), "", [this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater()); -#ifdef __linux__ - // To edit extruder color from the sidebar if (m_type == Preset::TYPE_FILAMENT) { +#ifdef __linux__ + // To edit extruder color from the sidebar append_menu_item(menu, wxID_ANY, _L("Change extruder color"), "", [this](wxCommandEvent&) { this->change_extruder_color(); }, "funnel", menu, []() { return true; }, wxGetApp().plater()); +#endif //__linux__ + append_menu_item(menu, wxID_ANY, _L("Show/Hide template presets"), "", + [this](wxCommandEvent&) { wxGetApp().open_preferences("no_templates", "General"); }, "spool", menu, []() { return true; }, wxGetApp().plater()); + wxGetApp().plater()->PopupMenu(menu); return; } -#endif //__linux__ if (this->is_selected_physical_printer()) { append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "", @@ -833,8 +861,14 @@ void PlaterPresetComboBox::update() null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width; - std::map nonsys_presets; - std::map template_presets; + struct PresetData { + wxString name; + wxString lower_name; + wxBitmapBundle* bitmap; + }; + std::vector system_presets; + std::vector nonsys_presets; + std::vector template_presets; const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates"); @@ -888,22 +922,23 @@ void PlaterPresetComboBox::update() if (preset.is_default || preset.is_system) { if (preset.vendor && preset.vendor->templates_profile) { if (allow_templates) { - template_presets.emplace(get_preset_name(preset), bmp); + template_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp}); if (is_selected) { selected_user_preset = get_preset_name(preset); tooltip = from_u8(preset.name); } } } else { - Append(get_preset_name(preset), *bmp); - validate_selection(is_selected); - if (is_selected) + system_presets.push_back({ get_preset_name(preset), get_preset_name(preset).Lower(), bmp }); + if (is_selected) { + selected_user_preset = get_preset_name(preset); tooltip = from_u8(preset.name); + } } } else { - nonsys_presets.emplace(get_preset_name(preset), bmp); + nonsys_presets.push_back({ get_preset_name(preset), get_preset_name(preset).Lower(), bmp }); if (is_selected) { selected_user_preset = get_preset_name(preset); tooltip = from_u8(preset.name); @@ -912,22 +947,41 @@ void PlaterPresetComboBox::update() if (i + 1 == m_collection->num_default_presets()) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } - + + if(!system_presets.empty()) + { + std::sort(system_presets.begin(), system_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + + for (std::vector::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { + Append(it->name, *it->bitmap); + validate_selection(it->name == selected_user_preset); + } + } if (!nonsys_presets.empty()) { + std::sort(nonsys_presets.begin(), nonsys_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - Append(it->first, *it->second); - validate_selection(it->first == selected_user_preset); + for (std::vector::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + Append(it->name, *it->bitmap); + validate_selection(it->name == selected_user_preset); } } if (!template_presets.empty()) { + std::sort(template_presets.begin(), template_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { - Append(it->first, *it->second); - validate_selection(it->first == selected_user_preset); + for (std::vector::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + Append(it->name, *it->bitmap); + validate_selection(it->name == selected_user_preset); } } @@ -938,19 +992,37 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("Physical printers")), NullBitmapBndl())); const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; + // Sort Physical printers in preset_data vector and than Append it in correct order + struct PhysicalPrinterPresetData + { + wxString lower_name; // just for sorting + std::string name; // preset_name + std::string fullname; // full name + bool selected; // is selected + }; + std::vector preset_data; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { for (const std::string& preset_name : it->get_preset_names()) { - Preset* preset = m_collection->find_preset(preset_name); - if (!preset || !preset->is_visible) - continue; - std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - auto bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); - assert(bmp); - - set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - validate_selection(ph_printers.is_selected(it, preset_name)); + preset_data.push_back({ wxString::FromUTF8(it->get_full_name(preset_name)).Lower(), preset_name, it->get_full_name(preset_name), ph_printers.is_selected(it, preset_name) }); } } + std::sort(preset_data.begin(), preset_data.end(), [](const PhysicalPrinterPresetData& a, const PhysicalPrinterPresetData& b) { + return a.lower_name < b.lower_name; + }); + + for (const PhysicalPrinterPresetData& data : preset_data) + { + Preset* preset = m_collection->find_preset(data.name); + if (!preset || !preset->is_visible) + continue; + std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + + auto bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); + assert(bmp); + + set_label_marker(Append(from_u8(data.fullname + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + validate_selection(data.selected); + } } } @@ -1069,12 +1141,18 @@ void TabPresetComboBox::update() const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_active_extruder_idx]; const std::deque& presets = m_collection->get_presets(); - - std::map> nonsys_presets; - std::map> template_presets; + + struct PresetData { + wxString name; + wxString lower_name; + wxBitmapBundle* bitmap; + bool enabled; + }; + std::vector system_presets; + std::vector nonsys_presets; + std::vector template_presets; const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates"); - wxString selected = ""; if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); @@ -1113,23 +1191,20 @@ void TabPresetComboBox::update() if (preset.is_default || preset.is_system) { if (preset.vendor && preset.vendor->templates_profile) { if (allow_templates) { - template_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + template_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled}); if (i == idx_selected) selected = get_preset_name(preset); } } else { - int item_id = Append(get_preset_name(preset), *bmp); - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(i == idx_selected); + system_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled}); + if (i == idx_selected) + selected = get_preset_name(preset); } - - } else { std::pair pair(bmp, is_enabled); - nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + nonsys_presets.push_back({get_preset_name(preset), get_preset_name(preset).Lower(), bmp, is_enabled}); if (i == idx_selected) selected = get_preset_name(preset); } @@ -1137,26 +1212,47 @@ void TabPresetComboBox::update() set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } + if (!system_presets.empty()) + { + std::sort(system_presets.begin(), system_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + + for (std::vector::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { + int item_id = Append(it->name, *it->bitmap); + if (!it->enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->name == selected); + } + } + if (!nonsys_presets.empty()) { + std::sort(nonsys_presets.begin(), nonsys_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); - for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - bool is_enabled = it->second.second; - if (!is_enabled) + for (std::vector::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->name, *it->bitmap); + if (!it->enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); + validate_selection(it->name == selected); } } - if (!template_presets.empty()) { + if (!template_presets.empty()) + { + std::sort(template_presets.begin(), template_presets.end(), [](const PresetData& a, const PresetData& b) { + return a.lower_name < b.lower_name; + }); + set_label_marker(Append(separator(L("Template presets")), wxNullBitmap)); - for (std::map>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - bool is_enabled = it->second.second; - if (!is_enabled) + for (std::vector::iterator it = template_presets.begin(); it != template_presets.end(); ++it) { + int item_id = Append(it->name, *it->bitmap); + if (!it->enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); + validate_selection(it->name == selected); } } @@ -1167,20 +1263,36 @@ void TabPresetComboBox::update() set_label_marker(Append(separator(L("Physical printers")), NullBitmapBndl())); const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; + // Sort Physical printers in preset_data vector and than Append it in correct order + struct PhysicalPrinterPresetData + { + wxString lower_name; // just for sorting + std::string name; // preset_name + std::string fullname; // full name + bool selected; // is selected + }; + std::vector preset_data; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { for (const std::string& preset_name : it->get_preset_names()) { - Preset* preset = m_collection->find_preset(preset_name); - if (!preset || !preset->is_visible) - continue; - std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - - auto bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); - assert(bmp); - - set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - validate_selection(ph_printers.is_selected(it, preset_name)); + preset_data.push_back({wxString::FromUTF8(it->get_full_name(preset_name)).Lower(), preset_name, it->get_full_name(preset_name), ph_printers.is_selected(it, preset_name)}); } } + std::sort(preset_data.begin(), preset_data.end(), [](const PhysicalPrinterPresetData& a, const PhysicalPrinterPresetData& b) { + return a.lower_name < b.lower_name; + }); + for (const PhysicalPrinterPresetData& data : preset_data) + { + Preset* preset = m_collection->find_preset(data.name); + if (!preset || !preset->is_visible) + continue; + std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + + auto bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); + assert(bmp); + + set_label_marker(Append(from_u8(data.fullname + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + validate_selection(data.selected); + } } // add "Add/Remove printers" item diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index cbcdd7f..e7f45bb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -216,10 +216,10 @@ void Tab::create_preset_tab() m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this))); const float scale_factor = em_unit(this)*0.1;// GetContentScaleFactor(); - m_hsizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); - m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); - m_hsizer->AddSpacer(int(4*scale_factor)); + m_top_hsizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_top_hsizer, 0, wxEXPAND | wxBOTTOM | wxALIGN_CENTER_VERTICAL, 3); + m_top_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); + m_top_hsizer->AddSpacer(int(4*scale_factor)); m_h_buttons_sizer = new wxBoxSizer(wxHORIZONTAL); m_h_buttons_sizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); @@ -243,9 +243,8 @@ void Tab::create_preset_tab() m_h_buttons_sizer->AddSpacer(int(8*scale_factor)); m_h_buttons_sizer->Add(m_btn_compare_preset, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->Add(m_h_buttons_sizer, 1, wxEXPAND); - m_hsizer->AddSpacer(int(16*scale_factor)); - // m_hsizer->AddStretchSpacer(32); + m_top_hsizer->Add(m_h_buttons_sizer, 1, wxEXPAND | wxALIGN_CENTRE_VERTICAL); + m_top_hsizer->AddSpacer(int(16*scale_factor)); // StretchSpacer has a strange behavior under OSX, so // There is used just additional sizer for m_mode_sizer with right alignment if (m_mode_sizer) { @@ -253,8 +252,10 @@ void Tab::create_preset_tab() // Don't set the 2nd parameter to 1, making the sizer rubbery scalable in Y axis may lead // to wrong vertical size assigned to wxBitmapComboBoxes, see GH issue #7176. mode_sizer->Add(m_mode_sizer, 0, wxALIGN_RIGHT); - m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10); + m_top_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10); } + // hide whole top sizer to correct layout later + m_top_hsizer->ShowItems(false); //Horizontal sizer to hold the tree and the selected page. m_hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -461,20 +462,24 @@ void Tab::OnActivate() activate_selected_page([](){}); m_hsizer->Layout(); -#ifdef _MSW_DARK_MODE - // Because of DarkMode we use our own Notebook (inherited from wxSiplebook) instead of wxNotebook - // And it looks like first Layout of the page doesn't update a size of the m_presets_choice - // So we have to set correct size explicitely - if (wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y); - ok_sz != m_presets_choice->GetSize()) { - m_presets_choice->SetMinSize(ok_sz); - m_presets_choice->SetSize(ok_sz); - GetSizer()->GetItem(size_t(0))->GetSizer()->Layout(); - if (wxGetApp().tabs_as_menu()) - m_presets_choice->update(); + if (m_presets_choice->IsShown()) + Refresh(); // Just refresh page, if m_presets_choice is already shown + else { + // From the tab creation whole top sizer is hidden to correct update of preset combobox's size + // (see https://github.com/prusa3d/PrusaSlicer/issues/10746) + + // On first OnActivate call show top sizer + m_top_hsizer->ShowItems(true); + // Size and layouts of all items are correct now, + // but ALL items of top sizer are visible. + // So, update visibility of each item according to the ui settings + update_btns_enabling(); + m_btn_hide_incompatible_presets->Show(m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER); + if (TabFilament* tab = dynamic_cast(this)) + tab->update_extruder_combobox(); + + Layout(); } -#endif // _MSW_DARK_MODE - Refresh(); } void Tab::update_label_colours() @@ -1863,7 +1868,6 @@ void TabFilament::create_line_with_near_label_widget(ConfigOptionsGroupShp optgr optgroup->append_line(line); } - void TabFilament::update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/, bool is_checked/* = true*/) { if (!m_overrides_options[opt_key]) @@ -1951,6 +1955,9 @@ void TabFilament::create_extruder_combobox() void TabFilament::update_extruder_combobox() { + if (!m_presets_choice->IsShown()) + return; // it will be updated later, on OnActive() + const size_t extruder_cnt = static_cast(m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); m_extruders_cb->Show(extruder_cnt > 1); @@ -2025,6 +2032,7 @@ void TabFilament::build() optgroup = page->new_optgroup(L("Temperature")); create_line_with_near_label_widget(optgroup, "idle_temperature"); + Line line = { L("Nozzle"), "" }; line.append_option(optgroup->get_option("first_layer_temperature")); line.append_option(optgroup->get_option("temperature")); @@ -2343,6 +2351,14 @@ void TabFilament::msw_rescale() Tab::msw_rescale(); } +void TabFilament::sys_color_changed() +{ + m_extruders_cb->Clear(); + update_extruder_combobox(); + + Tab::sys_color_changed(); +} + void TabFilament::load_current_preset() { assert(m_active_extruder >= 0 && m_active_extruder < m_preset_bundle->extruders_filaments.size()); @@ -5041,6 +5057,97 @@ void TabSLAMaterial::build() m_presets = &m_preset_bundle->sla_materials; load_initial_data(); + auto page = add_options_page(L("Material"), "resin"); + + auto optgroup = page->new_optgroup(L("Material")); + optgroup->append_single_option_line("material_colour"); + optgroup->append_single_option_line("bottle_cost"); + optgroup->append_single_option_line("bottle_volume"); + optgroup->append_single_option_line("bottle_weight"); + optgroup->append_single_option_line("material_density"); + + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) + { + if (opt_key == "material_colour") { + update_dirty(); + on_value_change(opt_key, value); + return; + } + + DynamicPrintConfig new_conf = *m_config; + + if (opt_key == "bottle_volume") { + double new_bottle_weight = boost::any_cast(value)*(new_conf.option("material_density")->getFloat() / 1000); + new_conf.set_key_value("bottle_weight", new ConfigOptionFloat(new_bottle_weight)); + } + if (opt_key == "bottle_weight") { + double new_bottle_volume = boost::any_cast(value)/new_conf.option("material_density")->getFloat() * 1000; + new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); + } + if (opt_key == "material_density") { + double new_bottle_volume = new_conf.option("bottle_weight")->getFloat() / boost::any_cast(value) * 1000; + new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); + } + + load_config(new_conf); + + update_dirty(); + + // Change of any from those options influences for an update of "Sliced Info" + wxGetApp().sidebar().update_sliced_info_sizer(); + wxGetApp().sidebar().Layout(); + }; + + optgroup = page->new_optgroup(L("Layers")); + optgroup->append_single_option_line("initial_layer_height"); + + optgroup = page->new_optgroup(L("Exposure")); + optgroup->append_single_option_line("exposure_time"); + optgroup->append_single_option_line("initial_exposure_time"); + + optgroup = page->new_optgroup(L("Corrections")); + auto line = Line{ m_config->def()->get("material_correction")->full_label, "" }; + for (auto& axis : { "X", "Y", "Z" }) { + auto opt = optgroup->get_option(std::string("material_correction_") + char(std::tolower(axis[0]))); + opt.opt.label = axis; + line.append_option(opt); + } + + optgroup->append_line(line); + + page = add_options_page(L("Notes"), "note"); + optgroup = page->new_optgroup(L("Notes"), 0); + optgroup->label_width = 0; + Option option = optgroup->get_option("material_notes"); + option.opt.full_width = true; + option.opt.height = 25;//250; + optgroup->append_single_option_line(option); + + page = add_options_page(L("Dependencies"), "wrench"); + optgroup = page->new_optgroup(L("Profile dependencies")); + + create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_printers); + }); + + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) { + return compatible_widget_create(parent, m_compatible_prints); + }); + + option = optgroup->get_option("compatible_prints_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + build_preset_description_line(optgroup.get()); + + page = add_options_page(L("Material printing profile"), "note"); + optgroup = page->new_optgroup(L("Material printing profile")); + option = optgroup->get_option("material_print_speed"); + optgroup->append_single_option_line(option); } void TabSLAMaterial::toggle_options() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index cc6e601..9fb921a 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -172,6 +172,7 @@ protected: ScalableButton* m_btn_delete_preset; ScalableButton* m_btn_edit_ph_printer {nullptr}; ScalableButton* m_btn_hide_incompatible_presets; + wxBoxSizer* m_top_hsizer; wxBoxSizer* m_hsizer; wxBoxSizer* m_h_buttons_sizer; wxBoxSizer* m_left_sizer; @@ -464,6 +465,7 @@ public: void update() override; void clear_pages() override; void msw_rescale() override; + void sys_color_changed() override; bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } void load_current_preset() override; @@ -545,7 +547,7 @@ public: void build() override; void toggle_options() override; void update() override; - bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } }; class TabSLAPrint : public Tab diff --git a/src/slic3r/Utils/Mainsail.cpp b/src/slic3r/Utils/Moonraker.cpp similarity index 93% rename from src/slic3r/Utils/Mainsail.cpp rename to src/slic3r/Utils/Moonraker.cpp index 200bcfa..e0293c8 100644 --- a/src/slic3r/Utils/Mainsail.cpp +++ b/src/slic3r/Utils/Moonraker.cpp @@ -1,4 +1,4 @@ -#include "Mainsail.hpp" +#include "Moonraker.hpp" #include #include @@ -63,28 +63,28 @@ std::string substitute_host(const std::string& orig_addr, std::string sub_addr) } #endif } -Mainsail::Mainsail(DynamicPrintConfig *config) : +Moonraker::Moonraker(DynamicPrintConfig *config) : m_host(config->opt_string("print_host")), m_apikey(config->opt_string("printhost_apikey")), m_cafile(config->opt_string("printhost_cafile")), m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke")) {} -const char* Mainsail::get_name() const { return "Mainsail"; } +const char* Moonraker::get_name() const { return "Moonraker"; } -wxString Mainsail::get_test_ok_msg () const +wxString Moonraker::get_test_ok_msg () const { - return _(L("Connection to Mainsail works correctly.")); + return _(L("Connection to Moonraker works correctly.")); } -wxString Mainsail::get_test_failed_msg (wxString &msg) const +wxString Moonraker::get_test_failed_msg (wxString &msg) const { return GUI::format_wxstr("%s: %s" - , _L("Could not connect to Mainsail") + , _L("Could not connect to Moonraker") , msg); } -bool Mainsail::test(wxString& msg) const +bool Moonraker::test(wxString& msg) const { // GET /server/info @@ -142,7 +142,7 @@ bool Mainsail::test(wxString& msg) const return res; } -bool Mainsail::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const +bool Moonraker::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const { // POST /server/files/upload @@ -232,15 +232,20 @@ bool Mainsail::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error return res; } -void Mainsail::set_auth(Http &http) const +void Moonraker::set_auth(Http &http) const { if (!m_apikey.empty()) http.header("X-Api-Key", m_apikey); if (!m_cafile.empty()) http.ca_file(m_cafile); } +<<<<<<< Updated upstream:src/slic3r/Utils/Mainsail.cpp //B4 std::string Mainsail::make_url(const std::string &path) const +======= + +std::string Moonraker::make_url(const std::string &path) const +>>>>>>> Stashed changes:src/slic3r/Utils/Moonraker.cpp { std::string m_host_add = m_host + ":10088"; if (m_host_add.find("http://") == 0 || m_host_add.find("https://") == 0) { diff --git a/src/slic3r/Utils/Mainsail.hpp b/src/slic3r/Utils/Moonraker.hpp similarity index 91% rename from src/slic3r/Utils/Mainsail.hpp rename to src/slic3r/Utils/Moonraker.hpp index 136c7dc..09a231f 100644 --- a/src/slic3r/Utils/Mainsail.hpp +++ b/src/slic3r/Utils/Moonraker.hpp @@ -1,5 +1,5 @@ -#ifndef slic3r_Mainsail_hpp_ -#define slic3r_Mainsail_hpp_ +#ifndef slic3r_Moonraker_hpp_ +#define slic3r_Moonraker_hpp_ #include #include @@ -16,11 +16,11 @@ class DynamicPrintConfig; class Http; // https://moonraker.readthedocs.io/en/latest/web_api -class Mainsail : public PrintHost +class Moonraker : public PrintHost { public: - Mainsail(DynamicPrintConfig *config); - ~Mainsail() override = default; + Moonraker(DynamicPrintConfig *config); + ~Moonraker() override = default; const char* get_name() const override; diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index 63d3ed0..4726b3e 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -32,6 +32,7 @@ namespace pt = boost::property_tree; namespace Slic3r { namespace { +#ifdef WIN32 std::string get_host_from_url(const std::string& url_in) { std::string url = url_in; @@ -63,7 +64,7 @@ std::string get_host_from_url(const std::string& url_in) BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url"; return out; } -#ifdef WIN32 + // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail. std::string substitute_host(const std::string& orig_addr, std::string sub_addr) { @@ -470,15 +471,17 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p % upload_parent_path.string() % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false"); - std::string host = get_host_from_url(m_host); auto http = Http::post(std::move(url)); +#ifdef WIN32 // "Host" header is necessary here. In the workaround above (two mDNS..) we have got IP address from test connection and subsituted it into "url" variable. // And when creating Http object above, libcurl automatically includes "Host" header from address it got. // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). // Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays. // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + std::string host = get_host_from_url(m_host); http.header("Host", host); +#endif // _WIN32 set_auth(http); http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false") .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? @@ -1036,14 +1039,16 @@ bool QIDILink::put_inner(PrintHostUpload upload_data, std::string url, const std bool res = true; // Percent escape all filenames in on path and add it to the url. This is different from POST. url += "/" + escape_path_by_element(upload_data.upload_path); - std::string host = get_host_from_url(m_host); Http http = Http::put(std::move(url)); +#ifdef WIN32 // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable. // And when creating Http object above, libcurl automatically includes "Host" header from address it got. // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + std::string host = get_host_from_url(m_host); http.header("Host", host); +#endif // _WIN32 set_auth(http); // This is ugly, but works. There was an error at QIDILink side that accepts any string at Print-After-Upload as true, thus False was also triggering print after upload. if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) @@ -1083,15 +1088,17 @@ bool QIDILink::post_inner(PrintHostUpload upload_data, std::string url, const st bool res = true; const auto upload_filename = upload_data.upload_path.filename(); const auto upload_parent_path = upload_data.upload_path.parent_path(); - std::string host = get_host_from_url(m_host); Http http = Http::post(std::move(url)); +#ifdef WIN32 // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable. // And when creating Http object above, libcurl automatically includes "Host" header from address it got. // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + std::string host = get_host_from_url(m_host); http.header("Host", host); +#endif // _WIN32 set_auth(http); set_http_post_header_args(http, upload_data.post_action); http.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index c325fd5..57c4ef0 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -19,7 +19,7 @@ #include "AstroBox.hpp" #include "Repetier.hpp" #include "MKS.hpp" -#include "Mainsail.hpp" +#include "Moonraker.hpp" #include "../GUI/PrintHostDialogs.hpp" namespace fs = boost::filesystem; @@ -55,7 +55,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) case htQIDILink: return new QIDILink(config); case htQIDIConnect: return new QIDIConnect(config); case htMKS: return new MKS(config); - case htMainSail: return new Mainsail(config); + case htMoonraker: return new Moonraker(config); default: return nullptr; } } else { diff --git a/version.inc b/version.inc index 8e16935..9a95932 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "QIDISlicer") set(SLIC3R_APP_KEY "QIDISlicer") -set(SLIC3R_VERSION "1.0.1") +set(SLIC3R_VERSION "1.0.2") set(SLIC3R_BUILD_ID "QIDISlicer-${SLIC3R_VERSION}+Win64") -set(SLIC3R_RC_VERSION "1,0,1,0") -set(SLIC3R_RC_VERSION_DOTS "1.0.1.0") +set(SLIC3R_RC_VERSION "1,0,2,0") +set(SLIC3R_RC_VERSION_DOTS "1.0.2.0")