diff --git a/src/imgui/imgui.cpp b/src/imgui/imgui.cpp index 5704209..c19d57d 100644 --- a/src/imgui/imgui.cpp +++ b/src/imgui/imgui.cpp @@ -4860,7 +4860,7 @@ void ImGui::CaptureMouseFromApp(bool capture) bool ImGui::IsItemActive() { ImGuiContext& g = *GImGui; - if (g.ActiveId) + if (g.ActiveId && g.CurrentWindow) { ImGuiWindow* window = g.CurrentWindow; return g.ActiveId == window->DC.LastItemId; diff --git a/src/imgui/imgui_widgets.cpp b/src/imgui/imgui_widgets.cpp index f945951..26deb89 100644 --- a/src/imgui/imgui_widgets.cpp +++ b/src/imgui/imgui_widgets.cpp @@ -130,6 +130,9 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInpu static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); +const ImVec4 IMGUI_COL_QIDISTUDIO = { 68.f / 255.0f, 121.f / 255.0f, 251.0f / 255, 1.0f}; +const ImVec4 IMGUI_COL_WHITE = {1.0f, 1.0f, 1.0f, 1.0f}; + //------------------------------------------------------------------------- // [SECTION] Widgets: Text, etc. //------------------------------------------------------------------------- @@ -1508,29 +1511,27 @@ bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value) return CheckboxFlagsT(label, flags, flags_value); } -bool ImGui::RadioButton(const char* label, bool active) +bool ImGui::RadioButton(const char *label, bool active) { - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; + ImGuiWindow *window = GetCurrentWindow(); + if (!window || window->SkipItems) return false; - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + ImGuiContext & g = *GImGui; + const ImGuiStyle &style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); - const float square_sz = GetFrameHeight(); - const ImVec2 pos = window->DC.CursorPos; + const float square_sz = GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id)) - return false; + if (!ItemAdd(total_bb, id)) return false; - ImVec2 center = check_bb.GetCenter(); - center.x = IM_ROUND(center.x); - center.y = IM_ROUND(center.y); - const float radius = (square_sz - 1.0f) * 0.5f; + ImVec2 center = check_bb.GetCenter(); + center.x = IM_ROUND(center.x); + center.y = IM_ROUND(center.y); + const float radius = (square_sz - 1.0f) * 0.35f; bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); @@ -1538,17 +1539,33 @@ bool ImGui::RadioButton(const char* label, bool active) MarkItemEdited(id); RenderNavHighlight(total_bb, id); - window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); - if (active) - { - const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); - window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16); + + float hover_scale = 1.0f; + if (held && hovered) { + window->DrawList->AddCircleFilled(center, radius, GetColorU32(IMGUI_COL_QIDISTUDIO), 16); + } else if (hovered) { + if (active) { + hover_scale = 1.1f; + window->DrawList->AddCircleFilled(center, radius * hover_scale, GetColorU32(IMGUI_COL_QIDISTUDIO), 16); + } else { + window->DrawList->AddCircleFilled(center, radius * hover_scale, GetColorU32(ImGuiCol_FrameBgHovered), 16); + } + } else { + window->DrawList->AddCircleFilled(center, radius, GetColorU32(ImGuiCol_FrameBg), 16); + } + + if (active) { // selected + if (!hovered){ + window->DrawList->AddCircleFilled(center, radius, GetColorU32(IMGUI_COL_QIDISTUDIO), 16); + } + const float pad = radius * 0.6f; + window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(IMGUI_COL_WHITE), 16); } if (style.FrameBorderSize > 0.0f) { - window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); - window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center + ImVec2(1, 1), radius * hover_scale, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius * hover_scale, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); } ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); @@ -4416,7 +4433,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { - return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); + return InputTextEx(label, NULL, buf, (int) buf_size, size, flags , callback, user_data);//ImGuiInputTextFlags_Multiline should manual input } bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) @@ -5056,8 +5073,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) { IM_ASSERT(state != NULL); - IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here. - +#ifndef __APPLE__ + IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && + "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here. +#endif const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); state->Stb.row_count_per_page = row_count_per_page; @@ -7315,10 +7334,10 @@ bool ImGui::QDTSelectable(const char *label, bool selected, ImGuiSelectableFlags bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (hovered || g.ActiveId == id) { ImGui::PushStyleColor(ImGuiCol_Border, GetColorU32(ImGuiCol_BorderActive)); - if(arrow_size == 0) { + if (arrow_size == 0) { RenderFrameBorder(bb.Min, ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), style.FrameRounding); } else { - RenderFrameBorder(ImVec2(bb.Min.x + style.WindowPadding.x,bb.Min.y), ImVec2(bb.Max.x - style.WindowPadding.x,bb.Max.y), style.FrameRounding); + RenderFrameBorder(ImVec2(bb.Min.x + style.WindowPadding.x, bb.Min.y), ImVec2(bb.Max.x - style.WindowPadding.x, bb.Max.y), style.FrameRounding); } ImGui::PopStyleColor(1); } diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index c18dc31..3208c76 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -22,7 +22,7 @@ set(LIBNEST2D_SRCFILES ) add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES}) - +message(STATUS "Boost_INCLUDE_DIRS = ${Boost_INCLUDE_DIRS}") target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) target_link_libraries(libnest2d PUBLIC NLopt::nlopt TBB::tbb Boost::boost libslic3r) target_compile_definitions(libnest2d PUBLIC LIBNEST2D_THREADING_tbb LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_libslic3r) diff --git a/src/libnest2d/include/libnest2d/nester.hpp b/src/libnest2d/include/libnest2d/nester.hpp index e57220e..8397a1c 100644 --- a/src/libnest2d/include/libnest2d/nester.hpp +++ b/src/libnest2d/include/libnest2d/nester.hpp @@ -11,10 +11,11 @@ #include #define LARGE_COST_TO_REJECT 1e7 #define COST_OF_NEW_PLATE 0.1 +#define MIN_SEPARATION scale_(0.5) // ensure minimal separation between items namespace libnest2d { - static const constexpr int BIN_ID_UNSET = -1; + static const constexpr int BIN_ID_UNSET = -2; static const constexpr int BIN_ID_UNFIT = -1; /** @@ -88,7 +89,7 @@ public: bool is_wipe_tower{ false }; bool is_extrusion_cali_object{ false }; bool has_tried_without_extrusion_cali_obj{ false }; - std::vector allowed_rotations{0.}; + std::vector> allowed_rotations{{0., 0.}}; // /// The type of the shape which was handed over as the template argument. using ShapeType = RawShape; diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index afaa85a..04f7767 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -574,6 +574,10 @@ public: if (r) { r.item_ptr_->translation(r.move_); r.item_ptr_->rotation(r.rot_); + + if (r.item_ptr_->inflation() < MIN_SEPARATION) { + r.item_ptr_->inflation(MIN_SEPARATION); + } items_.emplace_back(*(r.item_ptr_)); merged_pile_ = nfp::merge(merged_pile_, r.item_ptr_->transformedShape()); score_ += r.score(); @@ -721,6 +725,7 @@ private: auto initial_rot = item.rotation(); Vertex final_tr = {0, 0}; Radians final_rot = initial_rot; + Coord final_infl = item.inflation(); Shapes nfps; auto& bin = bin_; @@ -775,53 +780,11 @@ private: .str()); } - bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; }); - - if (first_object) { - setInitialPosition(item); - auto best_tr = item.translation(); - auto best_rot = item.rotation(); - best_overfit = std::numeric_limits::max(); - - // try normal inflation first, then 0 inflation if not fit. See STUDIO-5566. - // Note for by-object printing, bed is expanded by -config_.bed_shrink.x(). - Coord inflation_back = item.inflation(); - Coord inflations[2]={inflation_back, 0}; - for (size_t i = 0; i < 2; i++) { - item.inflation(inflations[i]); - for (auto rot : item.allowed_rotations) { - item.translation(initial_tr); - item.rotation(initial_rot + rot); - setInitialPosition(item); - if (!overlapWithVirtObject(item, binbb)) { - double of = overfit(item.transformedShape(), bin_); - if (of < best_overfit) { - best_overfit = of; - best_tr = item.translation(); - best_rot = item.rotation(); - if (best_overfit <= 0) { - config_.progressFunc("First object " + item.name + " can fit with rot=" + std::to_string(rot)); - break; - } - } - } - } - can_pack = best_overfit <= 0; - if(can_pack) break; - } - item.inflation(inflation_back); - - if (can_pack) - global_score = 0.2; - item.rotation(best_rot); - item.translation(best_tr); - } - if (can_pack == false) { + { Pile merged_pile = merged_pile_; - - for(auto rot : item.allowed_rotations) { - + for (auto [rot, infl] : item.allowed_rotations) { + item.inflation(infl); item.translation(initial_tr); item.rotation(initial_rot + rot); item.boundingBox(); // fill the bb cache @@ -829,10 +792,8 @@ private: // place the new item outside of the print bed to make sure // it is disjunct from the current merged pile placeOutsideOfBin(item); - nfps = calcnfp(item, binbb, Lvl()); - auto iv = item.referenceVertex(); auto startpos = item.translation(); @@ -860,27 +821,6 @@ private: ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); }; - auto alignment = config_.alignment; - - auto boundaryCheck = [alignment, &merged_pile, &getNfpPoint, - &item, &bin, &iv, &startpos] (const Optimum& o) - { - auto v = getNfpPoint(o); - auto d = (v - iv) + startpos; - item.translation(d); - - merged_pile.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(merged_pile); - merged_pile.pop_back(); - - double miss = 0; - if(alignment == Config::Alignment::DONT_ALIGN) - miss = sl::isInside(chull, bin) ? -1.0 : 1.0; - else miss = overfit(chull, bin); - - return miss; - }; - Optimum optimum(0, 0); double best_score = std::numeric_limits::max(); std::launch policy = std::launch::deferred; @@ -929,23 +869,16 @@ private: } }, policy); - auto resultcomp = - []( const OptResult& r1, const OptResult& r2 ) { + auto resultcomp = []( const OptResult& r1, const OptResult& r2 ) { return r1.score < r2.score; }; - auto mr = *std::min_element(results.begin(), results.end(), - resultcomp); + auto mr = *std::min_element(results.begin(), results.end(), resultcomp); if(mr.score < best_score) { Optimum o(std::get<0>(mr.optimum), ch, -1); - double miss = boundaryCheck(o); - if(miss <= 0) { - best_score = mr.score; - optimum = o; - } else { - best_overfit = std::min(miss, best_overfit); - } + best_score = mr.score; + optimum = o; } for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { @@ -981,20 +914,12 @@ private: } }, policy); - auto hmr = *std::min_element(results.begin(), - results.end(), - resultcomp); + auto hmr = *std::min_element(results.begin(), results.end(), resultcomp); if(hmr.score < best_score) { - Optimum o(std::get<0>(hmr.optimum), - ch, hidx); - double miss = boundaryCheck(o); - if(miss <= 0.0) { - best_score = hmr.score; - optimum = o; - } else { - best_overfit = std::min(miss, best_overfit); - } + Optimum o(std::get<0>(hmr.optimum), ch, hidx); + best_score = hmr.score; + optimum = o; } } } @@ -1003,12 +928,14 @@ private: auto d = (getNfpPoint(optimum) - iv) + startpos; final_tr = d; final_rot = initial_rot + rot; + final_infl = infl; can_pack = true; global_score = best_score; break; } } + item.inflation(final_infl); item.translation(final_tr); item.rotation(final_rot); } @@ -1038,6 +965,7 @@ private: auto initial_rot = item.rotation(); Vertex final_tr = initial_tr; Radians final_rot = initial_rot; + Coord final_infl = item.inflation(); Shapes nfps; auto binbb = sl::boundingBox(bin_); @@ -1047,9 +975,10 @@ private: unscale_(it.get().translation()[1]) % plateID()) .str()); } - + { - for (auto rot : item.allowed_rotations) { + for (auto [rot,infl] : item.allowed_rotations) { + item.inflation(infl); item.translation(initial_tr); item.rotation(initial_rot + rot); @@ -1057,11 +986,13 @@ private: can_pack = true; final_tr = initial_tr; final_rot = initial_rot + rot; + final_infl = infl; global_score = 0.3; break; } } + item.inflation(final_infl); item.translation(final_tr); item.rotation(final_rot); } @@ -1096,9 +1027,9 @@ private: std::stringstream ss; ss.setf(std::ios::fixed | std::ios::showpoint); ss.precision(1); - ss << "t=" << round(item.translation().x() / 1e6) << "," - << round(item.translation().y() / 1e6) - //<< "-rot=" << round(item.rotation().toDegrees()) + ss << "t=" << round(unscale_( item.translation().x() )) << "," + << round(unscale_(item.translation().y())) + << "-rot=" << round(item.rotation()) << "-sco=" << round(global_score); svgwriter.draw_text(20, 20, ss.str(), "blue", 10); ss.str(""); diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index 85e54f3..efc28f7 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -89,7 +89,9 @@ public: std::stringstream ss; ss << "initial order: " << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height << ", area=" << it->get().area() << ", allowed_rotations="; - for(auto r: it->get().allowed_rotations) ss << r << ", "; + for (auto [r, i] : it->get().allowed_rotations) + ss << "<" << r << "," << i << ">" + << ", "; if (this->unfitindicator_) this->unfitindicator_(ss.str()); } @@ -102,7 +104,9 @@ public: for (auto it = fixed_bins[i].begin(); it != fixed_bins[i].end(); ++it) { ss << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height << ", area=" << it->get().area() << ", allowed_rotations="; - for(auto r: it->get().allowed_rotations) ss << r << ", "; + for (auto [r, i] : it->get().allowed_rotations) + ss << "<" << r << "," << i << ">" + << ", "; ss << "; "; } if (this->unfitindicator_) diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index 80211bc..fb23abe 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace libnest2d { namespace svg { diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp new file mode 100644 index 0000000..f04517d --- /dev/null +++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp @@ -0,0 +1,576 @@ +#include +#include +#include + +#include "clipper/clipper_z.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/ClipperZUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/libslic3r.h" + +#include "LineSegmentation.hpp" + +namespace Slic3r::Algorithm::LineSegmentation { + +const constexpr coord_t POINT_IS_ON_LINE_THRESHOLD_SQR = Slic3r::sqr(scaled(EPSILON)); + +struct ZAttributes +{ + bool is_clip_point = false; + bool is_new_point = false; + uint32_t point_index = 0; + + ZAttributes() = default; + + explicit ZAttributes(const uint32_t clipper_coord) : + is_clip_point((clipper_coord >> 31) & 0x1), is_new_point((clipper_coord >> 30) & 0x1), point_index(clipper_coord & 0x3FFFFFFF) {} + + explicit ZAttributes(const ClipperLib_Z::IntPoint &clipper_pt) : ZAttributes(clipper_pt.z()) {} + + ZAttributes(const bool is_clip_point, const bool is_new_point, const uint32_t point_index) : + is_clip_point(is_clip_point), is_new_point(is_new_point), point_index(point_index) + { + assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!"); + } + + // Encode the structure to uint32_t. + constexpr uint32_t encode() const + { + assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!"); + return (this->is_clip_point << 31) | (this->is_new_point << 30) | (this->point_index & 0x3FFFFFFF); + } + + // Decode the uint32_t to the structure. + static ZAttributes decode(const uint32_t clipper_coord) + { + return { bool((clipper_coord >> 31) & 0x1), bool((clipper_coord >> 30) & 0x1), clipper_coord & 0x3FFFFFFF }; + } + + static ZAttributes decode(const ClipperLib_Z::IntPoint &clipper_pt) { return ZAttributes::decode(clipper_pt.z()); } +}; + +struct LineRegionRange +{ + size_t begin_idx; // Index of the line on which the region begins. + double begin_t; // Scalar position on the begin_idx line in which the region begins. The value is from range <0., 1.>. + size_t end_idx; // Index of the line on which the region ends. + double end_t; // Scalar position on the end_idx line in which the region ends. The value is from range <0., 1.>. + size_t clip_idx; // Index of clipping ExPolygons to identified which ExPolygons group contains this line. + + LineRegionRange(size_t begin_idx, double begin_t, size_t end_idx, double end_t, size_t clip_idx) + : begin_idx(begin_idx), begin_t(begin_t), end_idx(end_idx), end_t(end_t), clip_idx(clip_idx) {} + + // Check if 'other' overlaps with this LineRegionRange. + bool is_overlap(const LineRegionRange &other) const + { + if (this->end_idx < other.begin_idx || this->begin_idx > other.end_idx) { + return false; + } else if (this->end_idx == other.begin_idx && this->end_t <= other.begin_t) { + return false; + } else if (this->begin_idx == other.end_idx && this->begin_t >= other.end_t) { + return false; + } + + return true; + } + + // Check if 'inner' is whole inside this LineRegionRange. + bool is_inside(const LineRegionRange &inner) const + { + if (!this->is_overlap(inner)) { + return false; + } + + const bool starts_after = (this->begin_idx < inner.begin_idx) || (this->begin_idx == inner.begin_idx && this->begin_t <= inner.begin_t); + const bool ends_before = (this->end_idx > inner.end_idx) || (this->end_idx == inner.end_idx && this->end_t >= inner.end_t); + + return starts_after && ends_before; + } + + bool is_zero_length() const { return this->begin_idx == this->end_idx && this->begin_t == this->end_t; } + + bool operator<(const LineRegionRange &rhs) const + { + return this->begin_idx < rhs.begin_idx || (this->begin_idx == rhs.begin_idx && this->begin_t < rhs.begin_t); + } +}; + +using LineRegionRanges = std::vector; + +inline Point make_point(const ClipperLib_Z::IntPoint &clipper_pt) { return { clipper_pt.x(), clipper_pt.y() }; } + +inline ClipperLib_Z::Paths to_clip_zpaths(const ExPolygons &clips) { return ClipperZUtils::expolygons_to_zpaths_with_same_z(clips, coord_t(ZAttributes(true, false, 0).encode())); } + +static ClipperLib_Z::Path subject_to_zpath(const Points &subject, const bool is_closed) +{ + ZAttributes z_attributes(false, false, 0); + + ClipperLib_Z::Path out; + if (!subject.empty()) { + out.reserve((subject.size() + is_closed) ? 1 : 0); + for (const Point &p : subject) { + out.emplace_back(p.x(), p.y(), z_attributes.encode()); + ++z_attributes.point_index; + } + + if (is_closed) { + // If it is closed, then duplicate the first point at the end to make a closed path open. + out.emplace_back(subject.front().x(), subject.front().y(), z_attributes.encode()); + } + } + + return out; +} + +static ClipperLib_Z::Path subject_to_zpath(const Arachne::ExtrusionLine &subject) +{ + // Closed Arachne::ExtrusionLine already has duplicated the last point. + ZAttributes z_attributes(false, false, 0); + + ClipperLib_Z::Path out; + if (!subject.empty()) { + out.reserve(subject.size()); + for (const Arachne::ExtrusionJunction &junction : subject) { + out.emplace_back(junction.p.x(), junction.p.y(), z_attributes.encode()); + ++z_attributes.point_index; + } + } + + return out; +} + +static ClipperLib_Z::Path subject_to_zpath(const Polyline &subject) { return subject_to_zpath(subject.points, false); } + +[[maybe_unused]] static ClipperLib_Z::Path subject_to_zpath(const Polygon &subject) { return subject_to_zpath(subject.points, true); } + +struct ProjectionInfo +{ + double projected_t; + double distance_sqr; +}; + +static ProjectionInfo project_point_on_line(const Point &line_from_pt, const Point &line_to_pt, const Point &query_pt) +{ + const Vec2d line_vec = (line_to_pt - line_from_pt).template cast(); + const Vec2d query_vec = (query_pt - line_from_pt).template cast(); + const double line_length_sqr = line_vec.squaredNorm(); + + if (line_length_sqr <= 0.) { + return { std::numeric_limits::max(), std::numeric_limits::max() }; + } + + const double projected_t = query_vec.dot(line_vec); + const double projected_t_normalized = std::clamp(projected_t / line_length_sqr, 0., 1.); + // Projected point have to line on the line. + if (projected_t < 0. || projected_t > line_length_sqr) { + return { projected_t_normalized, std::numeric_limits::max() }; + } + + const Vec2d projected_vec = projected_t_normalized * line_vec; + const double distance_sqr = (projected_vec - query_vec).squaredNorm(); + + return { projected_t_normalized, distance_sqr }; +} + +static int32_t find_closest_line_to_point(const ClipperLib_Z::Path &subject, const ClipperLib_Z::IntPoint &query) +{ + auto it_min = subject.end(); + double distance_sqr_min = std::numeric_limits::max(); + + const Point query_pt = make_point(query); + Point prev_pt = make_point(subject.front()); + for (auto it_curr = std::next(subject.begin()); it_curr != subject.end(); ++it_curr) { + const Point curr_pt = make_point(*it_curr); + + const double distance_sqr = project_point_on_line(prev_pt, curr_pt, query_pt).distance_sqr; + if (distance_sqr <= POINT_IS_ON_LINE_THRESHOLD_SQR) { + return int32_t(std::distance(subject.begin(), std::prev(it_curr))); + } + + if (distance_sqr < distance_sqr_min) { + distance_sqr_min = distance_sqr; + it_min = std::prev(it_curr); + } + + prev_pt = curr_pt; + } + + if (it_min != subject.end()) { + return int32_t(std::distance(subject.begin(), it_min)); + } + + return -1; +} + +std::optional create_line_region_range(ClipperLib_Z::Path &&intersection, const ClipperLib_Z::Path &subject, const size_t region_idx) +{ + if (intersection.size() < 2) { + return std::nullopt; + } + + auto need_reverse = [&subject](const ClipperLib_Z::Path &intersection) -> bool { + for (size_t curr_idx = 1; curr_idx < intersection.size(); ++curr_idx) { + Point pre_pos = Point(intersection[curr_idx - 1].x(), intersection[curr_idx - 1].y()); + Point cur_pos = Point(intersection[curr_idx].x(), intersection[curr_idx].y()); + ZAttributes prev_z(intersection[curr_idx - 1]); + ZAttributes curr_z(intersection[curr_idx]); + + if (!prev_z.is_clip_point && !curr_z.is_clip_point) { + if ((prev_z.point_index > curr_z.point_index) && (pre_pos != cur_pos)){ + return true; + } else if (curr_z.point_index == prev_z.point_index) { + assert(curr_z.point_index < subject.size()); + const Point subject_pt = make_point(subject[curr_z.point_index]); + const Point prev_pt = make_point(intersection[curr_idx - 1]); + const Point curr_pt = make_point(intersection[curr_idx]); + + const double prev_dist = (prev_pt - subject_pt).cast().squaredNorm(); + const double curr_dist = (curr_pt - subject_pt).cast().squaredNorm(); + if (prev_dist > curr_dist) { + return true; + } + } + } + } + + return false; + }; + + for (ClipperLib_Z::IntPoint &clipper_pt : intersection) { + const ZAttributes clipper_pt_z(clipper_pt); + if (!clipper_pt_z.is_clip_point) { + continue; + } + + // FIXME @hejllukas: We could save searing for the source line in some cases using other intersection points, + // but in reality, the clip point will be inside the intersection in very rare cases. + if (int32_t subject_line_idx = find_closest_line_to_point(subject, clipper_pt); subject_line_idx != -1) { + clipper_pt.z() = coord_t(ZAttributes(false, true, subject_line_idx).encode()); + } + + assert(!ZAttributes(clipper_pt).is_clip_point); + if (ZAttributes(clipper_pt).is_clip_point) { + return std::nullopt; + } + } + + // Ensure that indices of source input are ordered in increasing order. + if (need_reverse(intersection)) { + std::reverse(intersection.begin(), intersection.end()); + } + + ZAttributes begin_z(intersection.front()); + ZAttributes end_z(intersection.back()); + + assert(begin_z.point_index <= subject.size() && end_z.point_index <= subject.size()); + const size_t begin_idx = begin_z.point_index; + const size_t end_idx = end_z.point_index; + const double begin_t = begin_z.is_new_point ? project_point_on_line(make_point(subject[begin_idx]), make_point(subject[begin_idx + 1]), make_point(intersection.front())).projected_t : 0.; + const double end_t = end_z.is_new_point ? project_point_on_line(make_point(subject[end_idx]), make_point(subject[end_idx + 1]), make_point(intersection.back())).projected_t : 0.; + + if (begin_t == std::numeric_limits::max() || end_t == std::numeric_limits::max()) { + return std::nullopt; + } + + return LineRegionRange{ begin_idx, begin_t, end_idx, end_t, region_idx }; +} + +LineRegionRanges intersection_with_region(const ClipperLib_Z::Path &subject, const ClipperLib_Z::Paths &clips, const size_t region_config_idx) +{ + ClipperLib_Z::Clipper clipper; + clipper.PreserveCollinear(true); // Especially with Arachne, we don't want to remove collinear edges. + clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, + const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, + ClipperLib_Z::IntPoint &new_pt) { + const ZAttributes e1bot_z(e1bot), e1top_z(e1top), e2bot_z(e2bot), e2top_z(e2top); + + assert(e1bot_z.is_clip_point == e1top_z.is_clip_point); + assert(e2bot_z.is_clip_point == e2top_z.is_clip_point); + + if (!e1bot_z.is_clip_point && !e1top_z.is_clip_point) { + assert(e1bot_z.point_index + 1 == e1top_z.point_index || e1bot_z.point_index == e1top_z.point_index + 1); + new_pt.z() = coord_t(ZAttributes(false, true, std::min(e1bot_z.point_index, e1top_z.point_index)).encode()); + } else if (!e2bot_z.is_clip_point && !e2top_z.is_clip_point) { + assert(e2bot_z.point_index + 1 == e2top_z.point_index || e2bot_z.point_index == e2top_z.point_index + 1); + new_pt.z() = coord_t(ZAttributes(false, true, std::min(e2bot_z.point_index, e2top_z.point_index)).encode()); + } else { + assert(false && "At least one of the conditions above has to be met."); + } + }); + + clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); + clipper.AddPaths(clips, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::Paths intersections; + { + ClipperLib_Z::PolyTree clipped_polytree; + clipper.Execute(ClipperLib_Z::ctIntersection, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), intersections); + } + + LineRegionRanges line_region_ranges; + line_region_ranges.reserve(intersections.size()); + for (ClipperLib_Z::Path &intersection : intersections) { + if (std::optional region_range = create_line_region_range(std::move(intersection), subject, region_config_idx); region_range.has_value()) { + line_region_ranges.emplace_back(*region_range); + } + } + + return line_region_ranges; +} + +LineRegionRanges create_continues_line_region_ranges(LineRegionRanges &&line_region_ranges, const size_t default_clip_idx, const size_t total_lines_cnt) +{ + if (line_region_ranges.empty()) { + return line_region_ranges; + } + + std::sort(line_region_ranges.begin(), line_region_ranges.end()); + + // Resolve overlapping regions if it happens, but it should never happen. + for (size_t region_range_idx = 1; region_range_idx < line_region_ranges.size(); ++region_range_idx) { + LineRegionRange &prev_range = line_region_ranges[region_range_idx - 1]; + LineRegionRange &curr_range = line_region_ranges[region_range_idx]; + + assert(!prev_range.is_overlap(curr_range)); + if (prev_range.is_inside(curr_range)) { + // Make the previous range zero length to remove it later. + curr_range = prev_range; + prev_range.begin_idx = curr_range.begin_idx; + prev_range.begin_t = curr_range.begin_t; + prev_range.end_idx = curr_range.begin_idx; + prev_range.end_t = curr_range.begin_t; + } else if (prev_range.is_overlap(curr_range)) { + curr_range.begin_idx = prev_range.end_idx; + curr_range.begin_t = prev_range.end_t; + } + } + + // Fill all gaps between regions with the default region. + LineRegionRanges line_region_ranges_out; + size_t prev_line_idx = 0.; + double prev_t = 0.; + for (const LineRegionRange &curr_line_region : line_region_ranges) { + if (curr_line_region.is_zero_length()) { + continue; + } + + assert(prev_line_idx < curr_line_region.begin_idx || (prev_line_idx == curr_line_region.begin_idx && prev_t <= curr_line_region.begin_t)); + + // Fill the gap if it is necessary. + if (prev_line_idx != curr_line_region.begin_idx || prev_t != curr_line_region.begin_t) { + line_region_ranges_out.emplace_back(prev_line_idx, prev_t, curr_line_region.begin_idx, curr_line_region.begin_t, default_clip_idx); + } + + // Add the current region. + line_region_ranges_out.emplace_back(curr_line_region); + prev_line_idx = curr_line_region.end_idx; + prev_t = curr_line_region.end_t; + } + + // Fill the last remaining gap if it exists. + const size_t last_line_idx = total_lines_cnt - 1; + if ((prev_line_idx == last_line_idx && prev_t == 1.) || ((prev_line_idx == total_lines_cnt && prev_t == 0.))) { + // There is no gap at the end. + return line_region_ranges_out; + } + + // Fill the last remaining gap. + line_region_ranges_out.emplace_back(prev_line_idx, prev_t, last_line_idx, 1., default_clip_idx); + + return line_region_ranges_out; +} + +LineRegionRanges subject_segmentation(const ClipperLib_Z::Path &subject, const std::vector &expolygons_clips, const size_t default_clip_idx = 0) +{ + LineRegionRanges line_region_ranges; + for (const ExPolygons &expolygons_clip : expolygons_clips) { + const size_t expolygons_clip_idx = &expolygons_clip - expolygons_clips.data(); + const ClipperLib_Z::Paths clips = to_clip_zpaths(expolygons_clip); + Slic3r::append(line_region_ranges, intersection_with_region(subject, clips, expolygons_clip_idx + default_clip_idx + 1)); + } + + return create_continues_line_region_ranges(std::move(line_region_ranges), default_clip_idx, subject.size() - 1); +} + +PolylineSegment create_polyline_segment(const LineRegionRange &line_region_range, const Polyline &subject) +{ + Polyline polyline_out; + if (line_region_range.begin_t == 0.) { + polyline_out.points.emplace_back(subject[line_region_range.begin_idx]); + } else { + assert(line_region_range.begin_idx <= subject.size()); + Point interpolated_start_pt = lerp(subject[line_region_range.begin_idx], subject[line_region_range.begin_idx + 1], line_region_range.begin_t); + polyline_out.points.emplace_back(interpolated_start_pt); + } + + for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) { + polyline_out.points.emplace_back(subject[line_idx]); + } + + if (line_region_range.end_t == 0.) { + polyline_out.points.emplace_back(subject[line_region_range.end_idx]); + } else if (line_region_range.end_t == 1.) { + assert(line_region_range.end_idx <= subject.size()); + polyline_out.points.emplace_back(subject[line_region_range.end_idx + 1]); + } else { + assert(line_region_range.end_idx <= subject.size()); + Point interpolated_end_pt = lerp(subject[line_region_range.end_idx], subject[line_region_range.end_idx + 1], line_region_range.end_t); + polyline_out.points.emplace_back(interpolated_end_pt); + } + + return { polyline_out, line_region_range.clip_idx }; +} + +PolylineSegments create_polyline_segments(const LineRegionRanges &line_region_ranges, const Polyline &subject) +{ + PolylineSegments polyline_segments; + polyline_segments.reserve(line_region_ranges.size()); + for (const LineRegionRange ®ion_range : line_region_ranges) { + polyline_segments.emplace_back(create_polyline_segment(region_range, subject)); + } + + return polyline_segments; +} + +ExtrusionSegment create_extrusion_segment(const LineRegionRange &line_region_range, const Arachne::ExtrusionLine &subject) +{ + // When we call this function, we split ExtrusionLine into at least two segments, so none of those segments are closed. + Arachne::ExtrusionLine extrusion_out(subject.inset_idx, subject.is_odd); + if (line_region_range.begin_t == 0.) { + extrusion_out.junctions.emplace_back(subject[line_region_range.begin_idx]); + } else { + assert(line_region_range.begin_idx <= subject.size()); + const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.begin_idx]; + const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.begin_idx + 1]; + + const Point interpolated_start_pt = lerp(junction_from.p, junction_to.p, line_region_range.begin_t); + const coord_t interpolated_start_w = lerp(junction_from.w, junction_to.w, line_region_range.begin_t); + + assert(junction_from.perimeter_index == junction_to.perimeter_index); + extrusion_out.junctions.emplace_back(interpolated_start_pt, interpolated_start_w, junction_from.perimeter_index); + } + + for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) { + extrusion_out.junctions.emplace_back(subject[line_idx]); + } + + if (line_region_range.end_t == 0.) { + extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx]); + } else if (line_region_range.end_t == 1.) { + assert(line_region_range.end_idx <= subject.size()); + extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx + 1]); + } else { + assert(line_region_range.end_idx <= subject.size()); + const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.end_idx]; + const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.end_idx + 1]; + + const Point interpolated_end_pt = lerp(junction_from.p, junction_to.p, line_region_range.end_t); + const coord_t interpolated_end_w = lerp(junction_from.w, junction_to.w, line_region_range.end_t); + + assert(junction_from.perimeter_index == junction_to.perimeter_index); + extrusion_out.junctions.emplace_back(interpolated_end_pt, interpolated_end_w, junction_from.perimeter_index); + } + + return { extrusion_out, line_region_range.clip_idx }; +} + +ExtrusionSegments create_extrusion_segments(const LineRegionRanges &line_region_ranges, const Arachne::ExtrusionLine &subject) +{ + ExtrusionSegments extrusion_segments; + extrusion_segments.reserve(line_region_ranges.size()); + for (const LineRegionRange ®ion_range : line_region_ranges) { + extrusion_segments.emplace_back(create_extrusion_segment(region_range, subject)); + } + + return extrusion_segments; +} + +PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx); + if (line_region_ranges.empty()) { + return { PolylineSegment{subject, default_clip_idx} }; + } else if (line_region_ranges.size() == 1) { + return { PolylineSegment{subject, line_region_ranges.front().clip_idx} }; + } + + return create_polyline_segments(line_region_ranges, subject); +} + +PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + return polyline_segmentation(to_polyline(subject), expolygons_clips, default_clip_idx); +} + +ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx); + if (line_region_ranges.empty()) { + return { ExtrusionSegment{subject, default_clip_idx} }; + } else if (line_region_ranges.size() == 1) { + return { ExtrusionSegment{subject, line_region_ranges.front().clip_idx} }; + } + + return create_extrusion_segments(line_region_ranges, subject); +} + +inline std::vector to_expolygons_clips(const PerimeterRegions &perimeter_regions_clips) +{ + std::vector expolygons_clips; + expolygons_clips.reserve(perimeter_regions_clips.size()); + for (const PerimeterRegion &perimeter_region_clip : perimeter_regions_clips) { + expolygons_clips.emplace_back(perimeter_region_clip.expolygons); + } + + return expolygons_clips; +} + +PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips)); + if (line_region_ranges.empty()) { + return { PolylineRegionSegment{subject, base_config} }; + } else if (line_region_ranges.size() == 1) { + return { PolylineRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} }; + } + + PolylineRegionSegments segments_out; + for (PolylineSegment &segment : create_polyline_segments(line_region_ranges, subject)) { + const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config(); + segments_out.emplace_back(std::move(segment.polyline), config); + } + + return segments_out; +} + +PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + return polyline_segmentation(to_polyline(subject), base_config, perimeter_regions_clips); +} + +ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips)); + if (line_region_ranges.empty()) { + return { ExtrusionRegionSegment{subject, base_config} }; + } else if (line_region_ranges.size() == 1) { + return { ExtrusionRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} }; + } + + ExtrusionRegionSegments segments_out; + for (ExtrusionSegment &segment : create_extrusion_segments(line_region_ranges, subject)) { + const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config(); + segments_out.emplace_back(std::move(segment.extrusion), config); + } + + return segments_out; +} + +} // namespace Slic3r::Algorithm::LineSegmentation \ No newline at end of file diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp new file mode 100644 index 0000000..ab881ec --- /dev/null +++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp @@ -0,0 +1,69 @@ +#ifndef libslic3r_LineSegmentation_hpp_ +#define libslic3r_LineSegmentation_hpp_ + +#include + +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" + +namespace Slic3r { +class ExPolygon; +class Polyline; +class Polygon; +class PrintRegionConfig; + +struct PerimeterRegion; + +using ExPolygons = std::vector; +using PerimeterRegions = std::vector; +} // namespace Slic3r + +namespace Slic3r::Arachne { +struct ExtrusionLine; +} + +namespace Slic3r::Algorithm::LineSegmentation { + +struct PolylineSegment +{ + Polyline polyline; + size_t clip_idx; +}; + +struct PolylineRegionSegment +{ + Polyline polyline; + const PrintRegionConfig &config; + + PolylineRegionSegment(const Polyline &polyline, const PrintRegionConfig &config) : polyline(polyline), config(config) {} +}; + +struct ExtrusionSegment +{ + Arachne::ExtrusionLine extrusion; + size_t clip_idx; +}; + +struct ExtrusionRegionSegment +{ + Arachne::ExtrusionLine extrusion; + const PrintRegionConfig &config; + + ExtrusionRegionSegment(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &config) : extrusion(extrusion), config(config) {} +}; + +using PolylineSegments = std::vector; +using ExtrusionSegments = std::vector; +using PolylineRegionSegments = std::vector; +using ExtrusionRegionSegments = std::vector; + +PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); +PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); +ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); + +PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); +PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); +ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); + +} // namespace Slic3r::Algorithm::LineSegmentation + +#endif // libslic3r_LineSegmentation_hpp_ \ No newline at end of file diff --git a/src/libslic3r/AnyPtr.hpp b/src/libslic3r/AnyPtr.hpp index c40d100..9ea01c6 100644 --- a/src/libslic3r/AnyPtr.hpp +++ b/src/libslic3r/AnyPtr.hpp @@ -15,12 +15,19 @@ namespace Slic3r { // The stored pointer is not checked for being null when dereferenced. // // This is a movable only object due to the fact that it can possibly hold -// a unique_ptr which a non-copy. -template -class AnyPtr { - enum { RawPtr, UPtr, ShPtr, WkPtr }; +// a unique_ptr which can only be moved. +// +// Drawbacks: +// No custom deleters are supported when storing a unique_ptr, but overloading +// std::default_delete for a particular type could be a workaround +// +// raw array types are problematic, since std::default_delete also does not +// support them well. +template class AnyPtr +{ + enum { RawPtr, UPtr, ShPtr }; - boost::variant, std::shared_ptr, std::weak_ptr> ptr; + boost::variant, std::shared_ptr> ptr; template static T *get_ptr(Self &&s) { @@ -28,101 +35,108 @@ class AnyPtr { case RawPtr: return boost::get(s.ptr); case UPtr: return boost::get>(s.ptr).get(); case ShPtr: return boost::get>(s.ptr).get(); - case WkPtr: { - auto shptr = boost::get>(s.ptr).lock(); - return shptr.get(); - } } return nullptr; } -public: - template>> - AnyPtr(TT *p = nullptr) : ptr{p} - {} - template>> - AnyPtr(std::unique_ptr p) : ptr{std::unique_ptr(std::move(p))} - {} - template>> - AnyPtr(std::shared_ptr p) : ptr{std::shared_ptr(std::move(p))} - {} - template>> - AnyPtr(std::weak_ptr p) : ptr{std::weak_ptr(std::move(p))} - {} + template friend class AnyPtr; - ~AnyPtr() = default; + template using SimilarPtrOnly = std::enable_if_t>; + +public: + AnyPtr() noexcept = default; + + AnyPtr(T *p) noexcept : ptr{p} {} + + AnyPtr(std::nullptr_t) noexcept {}; + + template> AnyPtr(TT *p) noexcept : ptr{p} {} + template> AnyPtr(std::unique_ptr p) noexcept : ptr{std::unique_ptr(std::move(p))} {} + template> AnyPtr(std::shared_ptr p) noexcept : ptr{std::shared_ptr(std::move(p))} {} AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {} + + template> AnyPtr(AnyPtr &&other) noexcept { this->operator=(std::move(other)); } + AnyPtr(const AnyPtr &other) = delete; - AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; } + AnyPtr &operator=(AnyPtr &&other) noexcept + { + ptr = std::move(other.ptr); + return *this; + } + AnyPtr &operator=(const AnyPtr &other) = delete; - template>> - AnyPtr &operator=(TT *p) { ptr = p; return *this; } + template> AnyPtr &operator=(AnyPtr &&other) noexcept + { + switch (other.ptr.which()) { + case RawPtr: *this = boost::get(other.ptr); break; + case UPtr: *this = std::move(boost::get>(other.ptr)); break; + case ShPtr: *this = std::move(boost::get>(other.ptr)); break; + } - template>> - AnyPtr &operator=(std::unique_ptr p) { ptr = std::move(p); return *this; } + return *this; + } - template>> - AnyPtr &operator=(std::shared_ptr p) { ptr = p; return *this; } + template> AnyPtr &operator=(TT *p) noexcept + { + ptr = static_cast(p); + return *this; + } - template>> - AnyPtr &operator=(std::weak_ptr p) { ptr = std::move(p); return *this; } + template> AnyPtr &operator=(std::unique_ptr p) noexcept + { + ptr = std::unique_ptr(std::move(p)); + return *this; + } - const T &operator*() const { return *get_ptr(*this); } - T &operator*() { return *get_ptr(*this); } + template> AnyPtr &operator=(std::shared_ptr p) noexcept + { + ptr = std::shared_ptr(std::move(p)); + return *this; + } - T *operator->() { return get_ptr(*this); } - const T *operator->() const { return get_ptr(*this); } + const T &operator*() const noexcept { return *get_ptr(*this); } + T & operator*() noexcept { return *get_ptr(*this); } - T *get() { return get_ptr(*this); } - const T *get() const { return get_ptr(*this); } + T * operator->() noexcept { return get_ptr(*this); } + const T *operator->() const noexcept { return get_ptr(*this); } - operator bool() const + T * get() noexcept { return get_ptr(*this); } + const T *get() const noexcept { return get_ptr(*this); } + + operator bool() const noexcept { switch (ptr.which()) { case RawPtr: return bool(boost::get(ptr)); case UPtr: return bool(boost::get>(ptr)); case ShPtr: return bool(boost::get>(ptr)); - case WkPtr: { - auto shptr = boost::get>(ptr).lock(); - return bool(shptr); - } } return false; } - // If the stored pointer is a shared or weak pointer, returns a reference + // If the stored pointer is a shared pointer, returns a reference // counted copy. Empty shared pointer is returned otherwise. - std::shared_ptr get_shared_cpy() const + std::shared_ptr get_shared_cpy() const noexcept { std::shared_ptr ret; - switch (ptr.which()) { - case ShPtr: ret = boost::get>(ptr); break; - case WkPtr: ret = boost::get>(ptr).lock(); break; - default: - ; - } + if (ptr.which() == ShPtr) ret = boost::get>(ptr); return ret; } // If the underlying pointer is unique, convert to shared pointer - void convert_unique_to_shared() + void convert_unique_to_shared() noexcept { - if (ptr.which() == UPtr) - ptr = std::shared_ptr{std::move(boost::get>(ptr))}; + if (ptr.which() == UPtr) ptr = std::shared_ptr{std::move(boost::get>(ptr))}; } // Returns true if the data is owned by this AnyPtr instance - bool is_owned() const noexcept - { - return ptr.which() == UPtr || ptr.which() == ShPtr; - } + bool is_owned() const noexcept { return ptr.which() == UPtr || ptr.which() == ShPtr; } }; } // namespace Slic3r diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 52a6ccd..842e9da 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -43,7 +43,7 @@ static const std::string MODELS_STR = "models"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; - +const std::string AppConfig::SECTION_EMBOSS_STYLE = "font"; std::string AppConfig::get_language_code() { std::string get_lang = get("language"); @@ -177,12 +177,19 @@ void AppConfig::set_defaults() if (get("ams_sync_match_full_use_color_dist").empty()) set_bool("ams_sync_match_full_use_color_dist", false); if (get("enable_sidebar_resizable").empty()) - set_bool("enable_sidebar_resizable", false); + set_bool("enable_sidebar_resizable", true); + if (get("enable_sidebar_floatable").empty()) + set_bool("enable_sidebar_floatable", false); + + if (get("export_sources_full_pathnames").empty()) + set_bool("export_sources_full_pathnames", false); if (get("zoom_to_mouse").empty()) set_bool("zoom_to_mouse", false); if (get("show_shells_in_preview").empty()) set_bool("show_shells_in_preview", true); + if (get("enable_text_styles").empty()) + set_bool("enable_text_styles", false); if (get("enable_lod").empty()) set_bool("enable_lod", true); if (get("gamma_correct_in_import_obj").empty()) @@ -201,6 +208,10 @@ void AppConfig::set_defaults() if (get("show_hints").empty()) set_bool("show_hints", false); //#endif + if (get("support_backup_fonts").empty()) + set_bool("support_backup_fonts", true); + if (get("custom_back_font_name").empty()) + set("custom_back_font_name", ""); if (get("enable_multi_machine").empty()) set_bool("enable_multi_machine", false); @@ -387,11 +398,10 @@ void AppConfig::set_defaults() set_bool("backup_switch", true); } - //1.9.7.52 if (get("liveview", "auto_stop_liveview").empty()) { set("liveview", "auto_stop_liveview", true); } - + if (get("backup_interval").empty()) { set("backup_interval", "10"); } @@ -450,7 +460,6 @@ void AppConfig::set_defaults() if (get("is_split_compound").empty()) { set_bool("is_split_compound", false); } - if (get("play_slicing_video").empty()) { set_bool("play_slicing_video", true); } @@ -544,7 +553,6 @@ std::string AppConfig::load() BOOST_LOG_TRIVIAL(info) << "AppConfig::load() open fail:" << AppConfig::loading_path(); return "Line break format may be incorrect."; } - #ifdef WIN32 std::stringstream input_stream; input_stream << ifs.rdbuf(); @@ -691,8 +699,11 @@ std::string AppConfig::load() m_filament_presets = iter.value().get>(); } else if (iter.key() == "filament_colors") { m_filament_colors = iter.value().get>(); - } - else { + } else if(iter.key() == "filament_multi_colors") { + m_filament_multi_colors = iter.value().get>(); + } else if(iter.key() == "filament_color_types") { + m_filament_color_types = iter.value().get>(); + } else { if (iter.value().is_string()) m_storage[it.key()][iter.key()] = iter.value().get(); else { @@ -776,6 +787,13 @@ void AppConfig::save() for (const auto &filament_color : m_filament_colors) { j["app"]["filament_colors"].push_back(filament_color); } + for (const auto &filament_multi_color : m_filament_multi_colors) { + j["app"]["filament_multi_colors"].push_back(filament_multi_color); + } + + for (const auto &filament_color_type : m_filament_color_types) { + j["app"]["filament_color_types"].push_back(filament_color_type); + } for (const auto &cali_info : m_printer_cali_infos) { json cali_json; @@ -811,7 +829,7 @@ void AppConfig::save() } else if (category.first == "presets") { json j_filament_array; for(const auto& kvp : category.second) { - if (boost::starts_with(kvp.first, "filament") && kvp.first != "filament_colors") { + if (boost::starts_with(kvp.first, "filament") && kvp.first != "filament_colors" && kvp.first != "filament_multi_colors" && kvp.first != "filament_color_types") { j_filament_array.push_back(kvp.second); } else { j[category.first][kvp.first] = kvp.second; diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 469ddf7..c414818 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -71,7 +71,7 @@ public: if (it == m_storage.end()) return false; auto it2 = it->second.find(key); - if (it2 == it->second.end()) + if (it2 == it->second.end()) return false; value = it2->second; return true; @@ -80,7 +80,7 @@ public: { std::string value; this->get(section, key, value); return value; } std::string get(const std::string &key) const { std::string value; this->get("app", key, value); return value; } - bool get_bool(const std::string &key) const { return this->get(key) == "true" || this->get(key) == "1"; } + bool get_bool(const std::string &key) const { return this->get(key) == "true" || this->get(key) == "1"; } void set(const std::string §ion, const std::string &key, const std::string &value) { #ifndef NDEBUG @@ -253,7 +253,8 @@ public: static const std::string SECTION_FILAMENTS; static const std::string SECTION_MATERIALS; - + static const std::string SECTION_EMBOSS_STYLE; + //w13 bool get_seal() { return m_seal; } void set_seal(bool val) { m_seal = val; } @@ -291,6 +292,8 @@ private: std::vector m_filament_presets; std::vector m_filament_colors; + std::vector m_filament_multi_colors; + std::vector m_filament_color_types; std::vector m_printer_cali_infos; //w13 diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp index c2a5fb4..3ea0fc6 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp @@ -41,7 +41,7 @@ struct ExtrusionJunction */ size_t perimeter_index; - ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const bool hole_compensation); + ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index, const bool hole_compensation = false); bool operator==(const ExtrusionJunction& other) const; }; @@ -58,6 +58,7 @@ inline const Point& make_point(const ExtrusionJunction& ej) } using LineJunctions = std::vector; //; } #endif // UTILS_EXTRUSION_JUNCTION_H diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index 4c6677f..16845b2 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -12,6 +12,8 @@ namespace Slic3r::Arachne ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx), is_odd(is_odd), is_closed(false) {} +ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd, const bool is_closed) : inset_idx(inset_idx), is_odd(is_odd), is_closed(is_closed) {} + int64_t ExtrusionLine::getLength() const { if (junctions.empty()) diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index 2c094a9..7f3592b 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -69,6 +69,7 @@ struct ExtrusionLine std::vector junctions; ExtrusionLine(const size_t inset_idx, const bool is_odd); + ExtrusionLine(size_t inset_idx, bool is_odd, bool is_closed); ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {} ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {} diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 187acdb..585e3c8 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -212,10 +212,10 @@ void fill_config(PConf& pcfg, const ArrangeParams ¶ms) { static double fixed_overfit(const std::tuple& result, const Box &binbb) { double score = std::get<0>(result); - Box pilebb = std::get<1>(result); - Box fullbb = sl::boundingBox(pilebb, binbb); - auto diff = double(fullbb.area()) - binbb.area(); - if(diff > 0) score += diff; + //Box pilebb = std::get<1>(result); + //Box fullbb = sl::boundingBox(pilebb, binbb); + //auto diff = double(fullbb.area()) - binbb.area(); + //if(diff > 0) score += diff; return score; } @@ -714,7 +714,7 @@ public: } else return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) : - i1.extrude_id_filament_types != i2.extrude_id_filament_types ? (i1.extrude_id_filament_types.begin()->first < i2.extrude_id_filament_types.begin()->first) : + i1.extrude_id_filament_types != i2.extrude_id_filament_types ? (i1.extrude_id_filament_types.begin()->first < i2.extrude_id_filament_types.begin()->first) : std::abs(i1.height/params.printable_height - i2.height/params.printable_height)>0.05 ? i1.height > i2.height: (i1.area() > i2.area()); } @@ -874,39 +874,65 @@ void _arrange( ArrangeParams mod_params = params; mod_params.min_obj_distance = 0; // items are already inflated - // Use the minimum bounding box rotation as a starting point. - // TODO: This only works for convex hull. If we ever switch to concave - // polygon nesting, a convex hull needs to be calculated. - if (params.align_to_y_axis) { - for (auto &itm : shapes) { - itm.allowed_rotations = {0.0}; - // only rotate the object if its long axis is significanly larger than its short axis (more than 10%) - try { - auto bbox = minAreaBoundingBox, boost::rational>(itm.transformedShape()); - auto w = bbox.width(), h = bbox.height(); - if (w > h * 1.1 || h > w * 1.1) { - itm.allowed_rotations = {bbox.angleToX() + PI / 2, 0.0}; - } - } catch (const std::exception &e) { - // min_area_boundingbox_rotation may throw exception of dividing 0 if the object is already perfectly aligned to X - BOOST_LOG_TRIVIAL(error) << "arranging min_area_boundingbox_rotation fails, msg=" << e.what(); + for (auto &itm : shapes) { + // If the item is too big, try to find a rotation that makes it fit + if constexpr (std::is_same_v) { + std::vector allowed_angles{0.}; + auto bb = itm.boundingBox(); + auto pure_bin_width = bin.width() + scale_(params.bed_shrink_x) * 2; + auto pure_bin_height = bin.height() + scale_(params.bed_shrink_y) * 2; + auto pure_item_width = bb.width() - itm.inflation() * 2; + auto pure_item_height = bb.height() - itm.inflation() * 2; + if (pure_item_width >= pure_bin_width || pure_item_height >= pure_bin_height) { + auto angle = fit_into_box_rotation(itm.transformedShape(), bin); + BOOST_LOG_TRIVIAL(debug) << itm.name << " too big, rotate to fit_into_box_rotation=" << angle; + allowed_angles = {angle}; } - } - } else if (params.allow_rotations) { - for (auto &itm : shapes) { - auto angle = min_area_boundingbox_rotation(itm.transformedShape()); - BOOST_LOG_TRIVIAL(debug) << itm.name << " min_area_boundingbox_rotation=" << angle << ", original angle=" << itm.rotation(); - itm.rotate(angle); - // If the item is too big, try to find a rotation that makes it fit - if constexpr (std::is_same_v) { - auto bb = itm.boundingBox(); - if (bb.width() >= bin.width() || bb.height() >= bin.height()) { - BOOST_LOG_TRIVIAL(debug) << itm.name << " too big, rotate to " << fit_into_box_rotation(itm.transformedShape(), bin); - itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin)); + // Use the minimum bounding box rotation as a starting point. + // TODO: This only works for convex hull. If we ever switch to concave + // polygon nesting, a convex hull needs to be calculated. + else if (params.align_to_y_axis) { + // only rotate the object if its long axis is significanly larger than its short axis (more than 10%) + try { + auto bbox = minAreaBoundingBox, boost::rational>(itm.transformedShape()); + auto w = bbox.width(), h = bbox.height(); + if (w > h * 1.1 || h > w * 1.1) { + if (fabs(bbox.angleToX() + PI / 2) > EPSILON) + allowed_angles = {bbox.angleToX() + PI / 2, 0.0}; + else + allowed_angles = {0.0}; + } + } catch (const std::exception &e) { + // min_area_boundingbox_rotation may throw exception of dividing 0 if the object is already perfectly aligned to X + BOOST_LOG_TRIVIAL(error) << "arranging min_area_boundingbox_rotation fails, msg=" << e.what(); + } + } else if (params.allow_rotations) { + auto angle = min_area_boundingbox_rotation(itm.transformedShape()); + BOOST_LOG_TRIVIAL(debug) << itm.name << " min_area_boundingbox_rotation=" << angle << ", original angle=" << itm.rotation(); + + if (fabs(angle) < EPSILON) { + allowed_angles = {0., PI * 0.25, PI * 0.5, PI * 0.75}; + } else { + allowed_angles = {0., angle, angle + PI * 0.25, angle + PI * 0.5, angle + PI * 0.75}; + } + } + + itm.allowed_rotations.clear(); + itm.allowed_rotations.reserve(allowed_angles.size()); + double original_angle = itm.rotation(); + auto original_infl = itm.inflation(); + for (auto angle : allowed_angles) { + auto rotsh = itm.rawShape(); + sl::rotate(rotsh, angle); + bb = sl::boundingBox(rotsh); + bp2d::Coord infl = std::min(original_infl, (bp2d::Coord)(std::min(pure_bin_width - bb.width(), pure_bin_height - bb.height())) / 2); + if (infl >= 0/* && itm.height <= params.printable_height*/) { + // if the bed is expanded, the item should also be expanded + if (params.bed_shrink_x < 0) infl = std::max(infl,(bp2d::Coord) scale_(-params.bed_shrink_x)); + itm.allowed_rotations.push_back({angle, infl}); } } - itm.allowed_rotations = {0., PI / 4., PI / 2, 3. * PI / 4.}; } } @@ -923,7 +949,7 @@ void _arrange( std::vector> inp; inp.reserve(shapes.size() + excludes.size()); - for (auto &itm : shapes ) inp.emplace_back(itm); + for (auto &itm : shapes) inp.emplace_back(itm); for (auto &itm : excludes) inp.emplace_back(itm); diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 4e44ede..b16f994 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -1023,11 +1023,6 @@ static ExPolygons outer_inner_brim_area(const Print& print, if (brimAreaMap.find(object->id()) != brimAreaMap.end()) expolygons_append(brim_area, brimAreaMap[object->id()]); } - - if (!object->support_layers().empty() && object->support_layers().front()->print_z < print.config().initial_layer_print_height + EPSILON && - !object->support_layers().front()->support_islands.empty()) - brim_area_support = offset_ex(object->support_layers().front()->support_islands, brim_width); - support_material_extruder = object->config().support_filament; if (support_material_extruder == 0 && object->has_support_material()) { if (print.config().print_sequence == PrintSequence::ByObject) @@ -1144,8 +1139,6 @@ static ExPolygons outer_inner_brim_area(const Print& print, brim_area.push_back(tempAreas[index]); } } - if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end()) - append(brim_area, supportBrimAreaMap[object->id()]); } return brim_area; } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 494ddaa..c04340d 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -6,6 +6,16 @@ include(PrecompiledHeader) string(TIMESTAMP COMPILE_TIME %Y%m%d-%H%M%S) set(SLIC3R_BUILD_TIME ${COMPILE_TIME}) +if("${QDT_RELEASE_TO_PUBLIC}" STREQUAL "0") +execute_process( + COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_SHA1 + OUTPUT_STRIP_TRAILING_WHITESPACE +) +set(SLIC3R_COMPILE_VERSION "${GIT_SHA1}") +endif() + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY) if (MINGW) @@ -26,6 +36,8 @@ set(lisbslic3r_sources AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp + Algorithm/LineSegmentation/LineSegmentation.cpp + Algorithm/LineSegmentation/LineSegmentation.hpp AnyPtr.hpp AStar.hpp BoundingBox.cpp @@ -120,6 +132,8 @@ set(lisbslic3r_sources Frustum.hpp FlushVolCalc.cpp FlushVolCalc.hpp + FuzzySkin.cpp + FuzzySkin.hpp format.hpp Format/3mf.cpp Format/3mf.hpp @@ -464,7 +478,7 @@ if (APPLE) ) endif () -add_library(libslic3r STATIC ${lisbslic3r_sources} +add_library(libslic3r STATIC ${lisbslic3r_sources} "${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h" ${OpenVDBUtils_SOURCES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${lisbslic3r_sources}) @@ -504,6 +518,13 @@ if (_opts) target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}") endif() +if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14.1) + set_property(TARGET libslic3r PROPERTY CXX_STANDARD 17) + set_property(TARGET libslic3r PROPERTY CXX_EXTENSIONS OFF) + set_property(TARGET libslic3r_cgal PROPERTY CXX_STANDARD 17) + set_property(TARGET libslic3r_cgal PROPERTY CXX_EXTENSIONS OFF) +endif() + target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl mcut) if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp index 55a966f..5fea7aa 100644 --- a/src/libslic3r/ClipperZUtils.hpp +++ b/src/libslic3r/ClipperZUtils.hpp @@ -71,6 +71,24 @@ inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx) return out; } +// Convert multiple expolygons into z-paths with a given Z coordinate. +// If Open, then duplicate the first point of each path at its end. +template inline ZPaths expolygons_to_zpaths_with_same_z(const ExPolygons &src, const coord_t z) +{ + ZPaths out; + out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), [](const size_t acc, const ExPolygon &expoly) { + return acc + expoly.num_contours(); + })); + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath(expoly.contour.points, z)); + for (const Polygon &hole : expoly.holes) { + out.emplace_back(to_zpath(hole.points, z)); + } + } + + return out; +} + // Convert a single path to path with a given Z coordinate. // If Open, then duplicate the first point at the end. template @@ -130,7 +148,7 @@ public: } } ClipperLib_Z::ZFillCallback clipper_callback() { - return [this](const ZPoint &e1bot, const ZPoint &e1top, + return [this](const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) { return (*this)(e1bot, e1top, e2bot, e2top, pt); }; } diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index a1e0504..67e5b3a 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -849,7 +849,7 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex } } return true; - }; + }; try { boost::nowide::ifstream ifs(file); @@ -1474,7 +1474,7 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name, c << std::setw(4) << j << std::endl; c.close(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", saved config to %1%\n") % PathSanitizer::sanitize(file); } //y66 diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 509adb6..8c7d6ed 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -23,7 +23,7 @@ // Experimentaly suggested ration of font ascent by multiple fonts // to get approx center of normal text line -const double ASCENT_CENTER = 1/3.; // 0.5 is above small letter +const double ASCENT_CENTER = 1/2.5; // 0.5 is above small letter // every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value // stored in fonts (to be able represents curve by sequence of lines) @@ -753,7 +753,7 @@ std::optional get_glyph(const stbtt_fontinfo &font_info, int unicode_lett const Glyph* get_glyph( int unicode, - const FontFile & font, + const FontFile & font_file, const FontProp & font_prop, Glyphs & cache, fontinfo_opt &font_info_opt) @@ -761,19 +761,21 @@ const Glyph* get_glyph( // TODO: Use resolution by printer configuration, or add it into FontProp const float RESOLUTION = 0.0125f; // [in mm] auto glyph_item = cache.find(unicode); - if (glyph_item != cache.end()) return &glyph_item->second; + if (glyph_item != cache.end()) { + return &glyph_item->second; + } unsigned int font_index = font_prop.collection_number.value_or(0); - if (!is_valid(font, font_index)) return nullptr; + if (!is_valid(font_file, font_index)) return nullptr; if (!font_info_opt.has_value()) { - font_info_opt = load_font_info(font.data->data(), font_index); + font_info_opt = load_font_info(font_file.data->data(), font_index); // can load font info? if (!font_info_opt.has_value()) return nullptr; } - float flatness = font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm; + float flatness = font_file.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm; // Fix for very small flatness because it create huge amount of points from curve if (flatness < RESOLUTION) flatness = RESOLUTION; @@ -782,7 +784,8 @@ const Glyph* get_glyph( // IMPROVE: multiple loadig glyph without data // has definition inside of font? - if (!glyph_opt.has_value()) return nullptr; + if (!glyph_opt.has_value()) + return nullptr; Glyph &glyph = *glyph_opt; if (font_prop.char_gap.has_value()) @@ -1190,8 +1193,7 @@ int Emboss::get_line_height(const FontFile &font, const FontProp &prop) { } namespace { -ExPolygons letter2shapes( - wchar_t letter, Point &cursor, FontFileWithCache &font_with_cache, const FontProp &font_prop, fontinfo_opt& font_info_cache) +ExPolygons letter2shapes(wchar_t letter, Point &cursor, FontFileWithCache &font_with_cache, const FontProp &font_prop, fontinfo_opt &font_info_cache) { assert(font_with_cache.has_value()); if (!font_with_cache.has_value()) @@ -1225,7 +1227,6 @@ ExPolygons letter2shapes( const Glyph *glyph_ptr = (it != cache.end()) ? &it->second : get_glyph(unicode, font, font_prop, cache, font_info_cache); if (glyph_ptr == nullptr) return {}; - // move glyph to cursor position ExPolygons expolygons = glyph_ptr->shape; // copy for (ExPolygon &expolygon : expolygons) @@ -1235,6 +1236,84 @@ ExPolygons letter2shapes( return expolygons; } +void letter2shapes(ExPolygons & result, + FontFileWithCache &real_use_font, + float & real_scale, + wchar_t letter, + Point & cursor, + FontFileWithCache &font_with_cache, + const FontProp & font_prop, + fontinfo_opt & font_info_cache, + BackFontCacheFn bfc_fn = nullptr) +{ + if (!font_with_cache.has_value()) + return; + + Glyphs & cache = *font_with_cache.cache; + const FontFile &font = *font_with_cache.font_file; + + if (letter == '\n') { + cursor.x() = 0; + // 2d shape has opposit direction of y + cursor.y() -= get_line_height(font, font_prop); + return; + } + if (letter == '\t') { + // '\t' = 4*space => same as imgui + const int count_spaces = 4; + const Glyph *space = get_glyph(int(' '), font, font_prop, cache, font_info_cache); + if (space == nullptr) + return ; + cursor.x() += count_spaces * space->advance_width; + return; + } + if (letter == '\r') + return; + + int unicode = static_cast(letter); + auto it = cache.find(unicode); + + // Create glyph from font file and cache it + const Glyph *glyph_ptr = (it != cache.end()) ? &it->second : get_glyph(unicode, font, font_prop, cache, font_info_cache); + if (glyph_ptr == nullptr) { + bool can_gen_from_back_font = false; + if (bfc_fn) { + auto fn_result = bfc_fn(); + for (auto temp_font : fn_result) { + if (!temp_font.has_value()) { continue; } + Glyphs & cur_cache = *temp_font.cache; + cur_cache.clear(); + const FontFile &cur_font = *temp_font.font_file; + //update boldness + auto temp_font_prop = font_prop; + const FontFile::Info &font_info = get_font_info(cur_font, temp_font_prop); + temp_font_prop.boldness = font_prop.boldness > 0 ? font_info.ascent / 4.f : 0; // from GLGizmoText::set_default_boldness(std::optional &boldness) + + auto new_scale = get_text_shape_scale(font_prop, cur_font); + // Create glyph from font file and cache it + fontinfo_opt cur_font_info_cache; + glyph_ptr = get_glyph(unicode, cur_font, temp_font_prop, cur_cache, cur_font_info_cache); + if (glyph_ptr) { + real_use_font = temp_font; + real_scale = new_scale; + can_gen_from_back_font = true; + break; + } + } + } + if (!can_gen_from_back_font) { + return; + } + } + + // move glyph to cursor position + ExPolygons expolygons = glyph_ptr->shape; // copy + for (ExPolygon &expolygon : expolygons) expolygon.translate(cursor); + + cursor.x() += glyph_ptr->advance_width; + result = expolygons; +} + // Check cancel every X letters in text // Lower number - too much checks(slows down) // Higher number - slows down response on cancelation @@ -1300,15 +1379,6 @@ void Slic3r::center(ExPolygonsWithIds &e) translate(e, -bb.center()); } -HealedExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const char *text, const FontProp &font_prop, const std::function& was_canceled) -{ - std::wstring text_w = boost::nowide::widen(text); - ExPolygonsWithIds vshapes = text2vshapes(font_with_cache, text_w, font_prop, was_canceled); - - float delta = static_cast(1. / SHAPE_SCALE); - return ::union_with_delta(vshapes, delta, MAX_HEAL_ITERATION_OF_TEXT); -} - namespace { /// /// Align shape against pivot @@ -1319,33 +1389,104 @@ namespace { /// Containe Horizontal and vertical alignment /// Needed for scale and font size void align_shape(ExPolygonsWithIds &shapes, const std::wstring &text, const FontProp &prop, const FontFile &font); +void align_shape(ExPolygonsWithIds &shapes, std::vector real_fonts, const std::wstring &text, const FontProp &prop, const FontFile &font); } -ExPolygonsWithIds Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled){ +HealedExPolygons Slic3r::Emboss::text2shapes(EmbossShape & emboss_shape, + FontFileWithCache & font_with_cache, + const char * text, + const FontProp & font_prop, + const std::function &was_canceled, + BackFontCacheFn bfc_fn, + double standard_scale) +{//for CreateFontImageJob + std::wstring text_w = boost::nowide::widen(text); + text2vshapes(emboss_shape, font_with_cache, text_w, font_prop, was_canceled, bfc_fn); // ExPolygonsWithIds vshapes = + auto &vshapes = emboss_shape.shapes_with_ids; + float delta = static_cast(1. / SHAPE_SCALE); + if (bfc_fn && standard_scale > 0) { + int32_t char_space = 0, char_width = 0, offset = 0; + for (int i = 0; i < vshapes.size(); i++) { + if (!vshapes[i].expoly.empty()) { + auto box = get_extents(vshapes[i].expoly); + if (box.size()[0] > 0) { + char_width = box.size()[0]; + break; + } + } + } + for (int i = 0; i < vshapes.size(); i++) { + if (emboss_shape.text_scales[i] > 0) { + double temp_scale = emboss_shape.text_scales[i] / standard_scale; + for (int j = 0; j < vshapes[i].expoly.size(); j++) { vshapes[i].expoly[j].scale(temp_scale); } + } + BoundingBox temp_box; + if (!vshapes[i].expoly.empty()) { + temp_box = get_extents(vshapes[i].expoly); + if (char_space == 0) { char_space = temp_box.size().x() / 5; } + for (int j = 0; j < vshapes[i].expoly.size(); j++) { // son + vshapes[i].expoly[j].translate(-temp_box.min + Point(offset, 0)); + } + offset += (get_extents(vshapes[i].expoly).size()[0] + char_space); + } else { + offset += (char_width); + } + } + } + return ::union_with_delta(vshapes, delta, MAX_HEAL_ITERATION_OF_TEXT); +} + +void Slic3r::Emboss::text2vshapes(EmbossShape & emboss_shape, + FontFileWithCache & font_with_cache, + const std::wstring & text, + const FontProp & font_prop, + const std::function &was_canceled, + BackFontCacheFn bfc_fn) +{ assert(font_with_cache.has_value()); - const FontFile &font = *font_with_cache.font_file; - unsigned int font_index = font_prop.collection_number.value_or(0); + const FontFile &font = *font_with_cache.font_file; + unsigned int font_index = font_prop.collection_number.value_or(0); if (!is_valid(font, font_index)) - return {}; + return; unsigned counter = 0; - Point cursor(0, 0); + Point cursor(0, 0); - fontinfo_opt font_info_cache; + fontinfo_opt font_info_cache; ExPolygonsWithIds result; result.reserve(text.size()); + std::vector text_scales; + text_scales.reserve(text.size()); + std::vector text_map_font; + text_map_font.reserve(text.size()); + for (wchar_t letter : text) { if (++counter == CANCEL_CHECK) { counter = 0; if (was_canceled()) - return {}; + return; } unsigned id = static_cast(letter); - result.push_back({id, letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)}); + float real_scale = -1.f; + if (bfc_fn) { // support_backup_fonts + ExPolygons exps; + FontFileWithCache real_use_font; + letter2shapes(exps, real_use_font,real_scale, letter, cursor, font_with_cache, font_prop, font_info_cache, bfc_fn); + result.push_back({id, exps}); + text_scales.emplace_back(real_scale); + text_map_font.emplace_back(real_use_font); + } else { + result.push_back({id, letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)}); + text_scales.emplace_back(real_scale); + } } - - align_shape(result, text, font_prop, font); - return result; + if (bfc_fn) {// support_backup_fonts + align_shape(result, text_map_font, text, font_prop, font); + } else { + align_shape(result, text, font_prop, font); + } + emboss_shape.text_scales = text_scales; + emboss_shape.shapes_with_ids = result; } #include @@ -1441,10 +1582,49 @@ bool Emboss::is_italic(const FontFile &font, unsigned int font_index) return false; } -std::string Emboss::create_range_text(const std::string &text, - const FontFile &font, - unsigned int font_index, - bool *exist_unknown) +std::wstring remove_duplicates(const std::wstring &input) +{ + std::set seen; + std::wstring result; + for (wchar_t c : input) { + if (seen.find(c) == seen.end()) { + seen.insert(c); + result += c; + } + } + return result; +} + +std::string Slic3r::Emboss::create_range_text(std::string &text, std::vector> fonts, unsigned int font_index, bool *exist_unknown) +{ + if (fonts.empty()) { return ""; } + *exist_unknown = false; + bool temp_exist_unknown = false; + std::wstring not_dup_text = remove_duplicates(boost::nowide::widen(text)); + std::sort(not_dup_text.begin(), not_dup_text.end()); + std::vector results; + results.reserve(fonts.size()); + for (int i = 0; i < fonts.size(); i++) { + auto temp_text = create_range_text(text, *fonts[i], font_index, &temp_exist_unknown); + results.emplace_back(temp_text); + auto valid_text = boost::nowide::widen(temp_text); + if (valid_text.size() == not_dup_text.size()) { + if (i > 0) { + *exist_unknown = true; + } + return results.back(); + } + } + *exist_unknown = true; + for (int i = 0; i < results.size(); i++) { + if (boost::nowide::widen(results[i]).size() == not_dup_text.size()) { + return results[i]; + } + } + return results[0]; +} + +std::string Emboss::create_range_text(const std::string &text, const FontFile &font, unsigned int font_index,bool *exist_unknown) { if (!is_valid(font, font_index)) return {}; @@ -1454,10 +1634,12 @@ std::string Emboss::create_range_text(const std::string &text, std::sort(ws.begin(), ws.end()); auto font_info_opt = load_font_info(font.data->data(), 0); - if (!font_info_opt.has_value()) return {}; + if (!font_info_opt.has_value()) + return {}; const stbtt_fontinfo *font_info = &(*font_info_opt); - if (exist_unknown != nullptr) *exist_unknown = false; + if (exist_unknown != nullptr) + *exist_unknown = false; int prev_unicode = -1; ws.erase(std::remove_if(ws.begin(), ws.end(), [&prev_unicode, font_info, exist_unknown](wchar_t wc) -> bool { @@ -2023,6 +2205,62 @@ void align_shape(ExPolygonsWithIds &shapes, const std::wstring &text, const Font s.translate(offset); } } + +void align_shape(ExPolygonsWithIds &shapes, std::vector real_fonts, const std::wstring &text, const FontProp &prop, const FontFile &font) {// Shapes have to match letters in text + assert(shapes.size() == text.length()); + + unsigned count_lines = get_count_lines(text); + int main_y_offset = get_align_y_offset(prop.align.second, count_lines, font, prop); + + // Speed up for left aligned text + if (prop.align.first == FontProp::HorizontalAlign::left) { + // already horizontaly aligned + int index = 0; + for (ExPolygonsWithId &shape : shapes) { + int temp_y_offset = main_y_offset; + if (real_fonts[index].has_value()) { + const FontFile &temp_font = *real_fonts[index].font_file; + temp_y_offset = get_align_y_offset(prop.align.second, count_lines, temp_font, prop); + } + for (ExPolygon &s : shape.expoly) { + s.translate(Point(0, temp_y_offset)); + } + index++; + } + return; + } + + BoundingBox shape_bb; + for (const ExPolygonsWithId &shape : shapes) + shape_bb.merge(get_extents(shape.expoly)); + + auto get_line_bb = [&](size_t j) { + BoundingBox line_bb; + for (; j < text.length() && text[j] != '\n'; ++j) line_bb.merge(get_extents(shapes[j].expoly)); + return line_bb; + }; + + // Align x line by line + Point main_offset(get_align_x_offset(prop.align.first, shape_bb, get_line_bb(0)), main_y_offset); + for (size_t i = 0; i < shapes.size(); ++i) { + wchar_t letter = text[i]; + if (letter == '\n') {//Enter the next line of text + main_offset.x() = get_align_x_offset(prop.align.first, shape_bb, get_line_bb(i + 1)); + continue; + } + ExPolygons &shape = shapes[i].expoly; + auto temp_offset = main_offset; + if (real_fonts[i].has_value()) { + const FontFile &temp_font = *real_fonts[i].font_file; + int temp_y_offset = get_align_y_offset(prop.align.second, count_lines, temp_font, prop); + Point new_offset(get_align_x_offset(prop.align.first, shape_bb, get_line_bb(0)), temp_y_offset); + temp_offset = new_offset; + } + for (ExPolygon &s : shape) { + s.translate(temp_offset); + } + } +} } // namespace double Emboss::get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned count_lines, const FontFile &ff, const FontProp &fp){ diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index fdcdf87..204b7bb 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -41,84 +41,6 @@ namespace Emboss /// File path to font when found std::optional get_font_path(const std::wstring &font_face_name); - // description of one letter - struct Glyph - { - // NOTE: shape is scaled by SHAPE_SCALE - // to be able store points without floating points - ExPolygons shape; - - // values are in font points - int advance_width=0, left_side_bearing=0; - }; - // cache for glyph by unicode - using Glyphs = std::map; - - /// - /// keep information from file about font - /// (store file data itself) - /// + cache data readed from buffer - /// - struct FontFile - { - // loaded data from font file - // must store data size for imgui rasterization - // To not store data on heap and To prevent unneccesary copy - // data are stored inside unique_ptr - std::unique_ptr> data; - - struct Info - { - // vertical position is "scale*(ascent - descent + lineGap)" - int ascent, descent, linegap; - - // for convert font units to pixel - int unit_per_em; - }; - // info for each font in data - std::vector infos; - - FontFile(std::unique_ptr> data, - std::vector &&infos) - : data(std::move(data)), infos(std::move(infos)) - { - assert(this->data != nullptr); - assert(!this->data->empty()); - } - - bool operator==(const FontFile &other) const { - if (data->size() != other.data->size()) - return false; - //if(*data != *other.data) return false; - for (size_t i = 0; i < infos.size(); i++) - if (infos[i].ascent != other.infos[i].ascent || - infos[i].descent == other.infos[i].descent || - infos[i].linegap == other.infos[i].linegap) - return false; - return true; - } - }; - - /// - /// Add caching for shape of glyphs - /// - struct FontFileWithCache - { - // Pointer on data of the font file - std::shared_ptr font_file; - - // Cache for glyph shape - // IMPORTANT: accessible only in plater job thread !!! - // main thread only clear cache by set to another shared_ptr - std::shared_ptr cache; - - FontFileWithCache() : font_file(nullptr), cache(nullptr) {} - explicit FontFileWithCache(std::unique_ptr font_file) - : font_file(std::move(font_file)) - , cache(std::make_shared()) - {} - bool has_value() const { return font_file != nullptr && cache != nullptr; } - }; /// /// Load font file into buffer @@ -152,9 +74,22 @@ namespace Emboss /// User defined property of the font /// Way to interupt processing /// Inner polygon cw(outer ccw) - HealedExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function &was_canceled = []() {return false;}); - ExPolygonsWithIds text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled = []() {return false;}); - + typedef std::function()> BackFontCacheFn; + HealedExPolygons text2shapes( + EmbossShape & emboss_shape, + FontFileWithCache & font_with_cache, + const char *text, + const FontProp &font_prop, + const std::function &was_canceled = []() { return false; }, + BackFontCacheFn bfc_fn = nullptr, + double standard_scale = -1); + void text2vshapes( + EmbossShape & emboss_shape, + FontFileWithCache & font_with_cache, + const std::wstring & text, + const FontProp & font_prop, + const std::function &was_canceled = []() { return false; }, + BackFontCacheFn bfc_fn = nullptr); HealedExPolygons union_with_delta(ExPolygons expoly, float delta, unsigned max_heal_iteration); const unsigned ENTER_UNICODE = static_cast('\n'); @@ -222,6 +157,7 @@ namespace Emboss /// Define font in collection /// True when text contain glyph unknown in font /// Unique set of character from text contained in font + std::string create_range_text(std::string &text, std::vector> fonts, unsigned int font_index, bool *exist_unknown = nullptr); std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr); /// diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index d705178..187548b 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -18,18 +18,18 @@ namespace Slic3r { struct EmbossProjection{ // Emboss depth, Size in local Z direction - double depth = 1.; // [in loacal mm] + double depth = 2.f; // [in loacal mm]//Modify By QDS 20241220 // NOTE: User should see and modify mainly world size not local // Flag that result volume use surface cutted from source objects bool use_surface = false; - - bool operator==(const EmbossProjection &other) const { - return depth == other.depth && use_surface == other.use_surface; + double embeded_depth = 0.f; // for old depth + bool operator==(const EmbossProjection &other) const { + return depth == other.depth && use_surface == other.use_surface && embeded_depth == other.embeded_depth; } // undo / redo stack recovery - template void serialize(Archive &ar) { ar(depth, use_surface); } + template void serialize(Archive &ar) { ar(depth, use_surface, embeded_depth); } }; // Extend expolygons with information whether it was successfull healed @@ -38,7 +38,77 @@ struct HealedExPolygons{ bool is_healed; operator ExPolygons&() { return expolygons; } }; +namespace Emboss { + // description of one letter + struct Glyph + { + // NOTE: shape is scaled by SHAPE_SCALE + // to be able store points without floating points + ExPolygons shape; + // values are in font points + int advance_width = 0, left_side_bearing = 0; + }; + // cache for glyph by unicode + using Glyphs = std::map; + /// + /// keep information from file about font + /// (store file data itself) + /// + cache data readed from buffer + /// + struct FontFile + { + // loaded data from font file + // must store data size for imgui rasterization + // To not store data on heap and To prevent unneccesary copy + // data are stored inside unique_ptr + std::unique_ptr> data; + + struct Info + { + // vertical position is "scale*(ascent - descent + lineGap)" + int ascent, descent, linegap; + + // for convert font units to pixel + int unit_per_em; + }; + // info for each font in data + std::vector infos; + + FontFile(std::unique_ptr> data, std::vector &&infos) : data(std::move(data)), infos(std::move(infos)) + { + assert(this->data != nullptr); + assert(!this->data->empty()); + } + + bool operator==(const FontFile &other) const + { + if (data->size() != other.data->size()) return false; + // if(*data != *other.data) return false; + for (size_t i = 0; i < infos.size(); i++) + if (infos[i].ascent != other.infos[i].ascent || infos[i].descent == other.infos[i].descent || infos[i].linegap == other.infos[i].linegap) return false; + return true; + } + }; + + /// + /// Add caching for shape of glyphs + /// + struct FontFileWithCache + { + // Pointer on data of the font file + std::shared_ptr font_file; + + // Cache for glyph shape + // IMPORTANT: accessible only in plater job thread !!! + // main thread only clear cache by set to another shared_ptr + std::shared_ptr cache; + + FontFileWithCache() : font_file(nullptr), cache(nullptr) {} + explicit FontFileWithCache(std::unique_ptr font_file) : font_file(std::move(font_file)), cache(std::make_shared()) {} + bool has_value() const { return font_file != nullptr && cache != nullptr; } + }; +} // Help structure to identify expolygons grups // e.g. emboss -> per glyph -> identify character struct ExPolygonsWithId @@ -64,7 +134,6 @@ struct EmbossShape { // shapes to to emboss separately over surface ExPolygonsWithIds shapes_with_ids; - // Only cache for final shape // It is calculated from ExPolygonsWithIds // Flag is_healed --> whether union of shapes is healed @@ -118,7 +187,7 @@ struct EmbossShape }; // When embossing shape is made by svg file this is source data std::optional svg_file; - + std::vector text_scales; // undo / redo stack recovery template void save(Archive &ar) const { diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 1746dbb..b42dd6d 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -13,6 +13,7 @@ namespace Slic3r { +extern bool compSecondMoment(const ExPolygons &expolys, double &smExpolysX, double &smExpolysY); // Brim.cpp void ExPolygon::scale(double factor) { contour.scale(factor); @@ -263,30 +264,30 @@ void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* { // init helper object Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this); - + // compute the Voronoi diagram and extract medial axis polylines ThickPolylines pp; ma.build(&pp); - + /* SVG svg("medial_axis.svg"); svg.draw(*this); svg.draw(pp); svg.Close(); */ - - /* Find the maximum width returned; we're going to use this for validating and + + /* Find the maximum width returned; we're going to use this for validating and filtering the output segments. */ double max_w = 0; for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it) max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end())); - - /* Loop through all returned polylines in order to extend their endpoints to the + + /* Loop through all returned polylines in order to extend their endpoints to the expolygon boundaries */ bool removed = false; for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; - + // extend initial and final segments of each polyline if they're actual endpoints /* We assign new endpoints to temporary variables because in case of a single-line polyline, after we extend the start point it will be caught by the intersection() @@ -315,7 +316,7 @@ void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* } polyline.points.front() = new_front; polyline.points.back() = new_back; - + /* remove too short polylines (we can't do this check before endpoints extension and clipping because we don't know how long will the endpoints be extended since it depends on polygon thickness @@ -328,19 +329,19 @@ void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* continue; } } - + /* If we removed any short polylines we now try to connect consecutive polylines - in order to allow loop detection. Note that this algorithm is greedier than - MedialAxis::process_edge_neighbors() as it will connect random pairs of - polylines even when more than two start from the same point. This has no - drawbacks since we optimize later using nearest-neighbor which would do the + in order to allow loop detection. Note that this algorithm is greedier than + MedialAxis::process_edge_neighbors() as it will connect random pairs of + polylines even when more than two start from the same point. This has no + drawbacks since we optimize later using nearest-neighbor which would do the same, but should we use a more sophisticated optimization algorithm we should not connect polylines when more than two meet. */ if (removed) { for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization - + // find another polyline starting here for (size_t j = i+1; j < pp.size(); ++j) { ThickPolyline& other = pp[j]; @@ -354,18 +355,18 @@ void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* } else if (polyline.last_point() != other.first_point()) { continue; } - + polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end()); polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end()); polyline.endpoints.second = other.endpoints.second; assert(polyline.width.size() == polyline.points.size()*2 - 2); - + pp.erase(pp.begin() + j); j = i; // restart search from i+1 } } } - + polylines->insert(polylines->end(), pp.begin(), pp.end()); } @@ -411,6 +412,24 @@ ExPolygons ExPolygon::split_expoly_with_holes(coord_t gap_width, const ExPolygon return sub_overhangs; } + +double ExPolygon::map_moment_to_expansion(double speed, double height) const +{ + if (height <= 0 || speed <= 0) return 0; + double Ixx = 0, Iyy = 0; + double props = compSecondMoment({*this}, Ixx, Iyy); + Ixx = Ixx * pow(SCALING_FACTOR, 4); + Iyy = Iyy * pow(SCALING_FACTOR, 4); + + auto bbox = get_extents(*this); + const double &bboxX = bbox.size()(0); + const double &bboxY = bbox.size()(1); + double height_to_area = std::max(height / Ixx * (bboxY * SCALING_FACTOR), height / Iyy * (bboxX * SCALING_FACTOR)) * height / 1920; + + double brim_width = height_to_area * speed; + return std::max(std::min(brim_width, 5.), 1.); +} + Lines ExPolygon::lines() const { Lines lines = this->contour.lines(); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index cc02160..5e5caf8 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -72,7 +72,7 @@ public: void simplify(double tolerance, ExPolygons* expolygons) const; void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const; void medial_axis(double min_width, double max_width, Polylines* polylines) const; - Polylines medial_axis(double min_width, double max_width) const + Polylines medial_axis(double min_width, double max_width) const { Polylines out; this->medial_axis(min_width, max_width, &out); return out; } Lines lines() const; @@ -84,6 +84,7 @@ public: const Polygon& contour_or_hole(size_t idx) const { return (idx == 0) ? this->contour : this->holes[idx - 1]; } //split expolygon-support with holes to help remove ExPolygons split_expoly_with_holes(coord_t gap_width, const ExPolygons& collision) const; + double map_moment_to_expansion(double speed, double height) const; }; inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; } @@ -92,9 +93,9 @@ inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs. inline size_t count_points(const ExPolygons &expolys) { size_t n_points = 0; - for (const auto &expoly : expolys) { + for (const auto &expoly : expolys) { n_points += expoly.contour.points.size(); - for (const auto &hole : expoly.holes) + for (const auto &hole : expoly.holes) n_points += hole.points.size(); } return n_points; @@ -103,8 +104,8 @@ inline size_t count_points(const ExPolygons &expolys) inline size_t count_points(const ExPolygon &expoly) { size_t n_points = expoly.contour.points.size(); - for (const auto &hole : expoly.holes) - n_points += hole.points.size(); + for (const auto &hole : expoly.holes) + n_points += hole.points.size(); return n_points; } @@ -118,7 +119,7 @@ inline size_t number_polygons(const ExPolygons &expolys) return n_polygons; } -inline Lines to_lines(const ExPolygon &src) +inline Lines to_lines(const ExPolygon &src) { Lines lines; lines.reserve(count_points(src)); @@ -131,7 +132,7 @@ inline Lines to_lines(const ExPolygon &src) return lines; } -inline Lines to_lines(const ExPolygons &src) +inline Lines to_lines(const ExPolygons &src) { Lines lines; lines.reserve(count_points(src)); @@ -159,7 +160,7 @@ inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0) assert(pts.size() >= 3); if (pts.size() < 2) return; bool is_first = true; - for (const Point &p : pts) { + for (const Point &p : pts) { Vec2d pd = p.cast(); if (is_first) is_first = false; else lines.emplace_back(prev_pd, pd); @@ -169,7 +170,7 @@ inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0) }; for (const ExPolygon& expoly: src) { to_lines(expoly.contour.points); - for (const Polygon &hole : expoly.holes) + for (const Polygon &hole : expoly.holes) to_lines(hole.points); } assert(lines.size() == count_lines); @@ -398,36 +399,36 @@ inline void polygons_append(Polygons &dst, const ExPolygons &src) } inline void polygons_append(Polygons &dst, ExPolygon &&src) -{ +{ dst.reserve(dst.size() + src.holes.size() + 1); - dst.push_back(std::move(src.contour)); - dst.insert(dst.end(), + dst.push_back(std::move(src.contour)); + dst.insert(dst.end(), std::make_move_iterator(src.holes.begin()), std::make_move_iterator(src.holes.end())); } inline void polygons_append(Polygons &dst, ExPolygons &&src) -{ +{ dst.reserve(dst.size() + number_polygons(src)); for (ExPolygon& expoly: src) { dst.push_back(std::move(expoly.contour)); - dst.insert(dst.end(), + dst.insert(dst.end(), std::make_move_iterator(expoly.holes.begin()), std::make_move_iterator(expoly.holes.end())); } } -inline void expolygons_append(ExPolygons &dst, const ExPolygons &src) -{ +inline void expolygons_append(ExPolygons &dst, const ExPolygons &src) +{ dst.insert(dst.end(), src.begin(), src.end()); } inline void expolygons_append(ExPolygons &dst, ExPolygons &&src) -{ +{ if (dst.empty()) { dst = std::move(src); } else { - dst.insert(dst.end(), + dst.insert(dst.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())); } @@ -532,8 +533,8 @@ namespace boost { namespace polygon { return expolygon; } }; - - + + template <> struct geometry_concept { typedef polygon_with_holes_concept type; }; @@ -560,7 +561,7 @@ namespace boost { namespace polygon { return t; } }; - + //first we register CPolygonSet as a polygon set template <> struct geometry_concept { typedef polygon_set_concept type; }; diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index cabc1f4..3c625e0 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -67,11 +67,9 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); } -//1.9.7.52 bool ExtrusionPath::can_merge(const ExtrusionPath& other) { - return overhang_degree == other.overhang_degree && - curve_degree==other.curve_degree && + return curve_degree==other.curve_degree && mm3_per_mm == other.mm3_per_mm && width == other.width && height == other.height && diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 04677e6..ef3c8e5 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -87,6 +87,7 @@ inline ExtrusionLoopRole operator |(ExtrusionLoopRole a, ExtrusionLoopRole b) { return static_cast(static_cast(a) | static_cast(b)); } + inline bool is_perimeter(ExtrusionRole role) { return role == erPerimeter @@ -125,14 +126,37 @@ inline bool is_bridge(ExtrusionRole role) { || role == erOverhangPerimeter; } + +inline bool is_support(ExtrusionRole role) { + return role == erSupportMaterial + || role == erSupportMaterialInterface + || role == erSupportTransition; +} + class ExtrusionEntity { public: ExtrusionEntity() = default; - ExtrusionEntity(const ExtrusionEntity &rhs) { m_customize_flag = rhs.m_customize_flag; }; - ExtrusionEntity(ExtrusionEntity &&rhs) { m_customize_flag = rhs.m_customize_flag; }; - ExtrusionEntity &operator=(const ExtrusionEntity &rhs) { m_customize_flag = rhs.m_customize_flag; return *this; } - ExtrusionEntity &operator=(ExtrusionEntity &&rhs) { m_customize_flag = rhs.m_customize_flag; return *this; } + ExtrusionEntity(const ExtrusionEntity &rhs){ + m_customize_flag = rhs.m_customize_flag; + m_cooling_node = rhs.m_cooling_node; + }; + ExtrusionEntity(ExtrusionEntity &&rhs){ + m_customize_flag = rhs.m_customize_flag; + m_cooling_node = rhs.m_cooling_node; + }; + ExtrusionEntity &operator=(const ExtrusionEntity &rhs) + { + m_customize_flag = rhs.m_customize_flag; + m_cooling_node = rhs.m_cooling_node; + return *this; + } + ExtrusionEntity &operator=(ExtrusionEntity &&rhs) + { + m_customize_flag = rhs.m_customize_flag; + m_cooling_node = rhs.m_cooling_node; + return *this; + } virtual ExtrusionRole role() const = 0; virtual bool is_collection() const { return false; } @@ -195,7 +219,7 @@ public: float width; // Height of the extrusion, used for visualization purposes. float height; - double smooth_speed = 0; //1.9.5 + double smooth_speed = 0; ExtrusionPath() : mm3_per_mm(-1), width(-1), height(-1), m_role(erNone), m_no_extrusion(false) {} ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role), m_no_extrusion(false) {} @@ -210,7 +234,7 @@ public: , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) - , smooth_speed(rhs.smooth_speed) //1.9.5 + , smooth_speed(rhs.smooth_speed) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) @@ -223,7 +247,7 @@ public: , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) - , smooth_speed(rhs.smooth_speed) //1.9.5 + , smooth_speed(rhs.smooth_speed) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) @@ -236,7 +260,7 @@ public: , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) - , smooth_speed(rhs.smooth_speed) //1.9.5 + , smooth_speed(rhs.smooth_speed) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) @@ -249,7 +273,7 @@ public: , mm3_per_mm(rhs.mm3_per_mm) , width(rhs.width) , height(rhs.height) - , smooth_speed(rhs.smooth_speed) //1.9.5 + , smooth_speed(rhs.smooth_speed) , m_can_reverse(rhs.m_can_reverse) , m_role(rhs.m_role) , m_no_extrusion(rhs.m_no_extrusion) @@ -263,7 +287,7 @@ public: this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; - this->smooth_speed = rhs.smooth_speed; //1.9.5 + this->smooth_speed = rhs.smooth_speed; this->overhang_degree = rhs.overhang_degree; this->curve_degree = rhs.curve_degree; this->polyline = rhs.polyline; @@ -277,7 +301,7 @@ public: this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; - this->smooth_speed = rhs.smooth_speed; //1.9.5 + this->smooth_speed = rhs.smooth_speed; this->overhang_degree = rhs.overhang_degree; this->curve_degree = rhs.curve_degree; this->polyline = std::move(rhs.polyline); @@ -322,12 +346,12 @@ public: double total_volume() const override { return mm3_per_mm * unscale(length()); } void set_overhang_degree(int overhang) { - if (is_perimeter(m_role)) + if (is_perimeter(m_role) || is_support(m_role)) overhang_degree = (overhang < 0)?0:(overhang > 10 ? 10 : overhang); }; int get_overhang_degree() const { // only perimeter has overhang degree. Other return 0; - if (is_perimeter(m_role)) + if (is_perimeter(m_role) || is_support(m_role)) return (int)overhang_degree; return 0; }; @@ -346,7 +370,6 @@ public: void set_reverse() override { m_can_reverse = false; } bool can_reverse() const override { return m_can_reverse; } - //1.9.7.52 bool can_merge(const ExtrusionPath& other); private: @@ -488,6 +511,7 @@ public: bool is_clockwise() { return this->polygon().is_clockwise(); } bool is_counter_clockwise() { return this->polygon().is_counter_clockwise(); } void reverse() override; + bool is_set_speed_discontinuity_area() const { return this->role() == erExternalPerimeter || this->role() == erPerimeter || this->role() == erOverhangPerimeter; } const Point& first_point() const override { return this->paths.front().polyline.points.front(); } const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } Polygon polygon() const; @@ -642,12 +666,13 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines } //QDS: a kind of special extrusion path has start and end wiping for half spacing -inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, float nozzle_diameter, bool apply_overlap_compensation) { + constexpr double overlap_rate = 0.45; dst.reserve(dst.size() + polylines.size()); - Point new_start, new_end, last_end_point; + Point last_end_point; + Vec2d last_direction; bool last_end_point_valid = false; - Vec2d temp; ExtrusionMultiPath* multi_path = new ExtrusionMultiPath(); for (Polyline& polyline : polylines) { if (polyline.is_valid()) { @@ -656,7 +681,27 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst, Point temp = polyline.first_point() - last_end_point; if (Vec2d(temp.x(), temp.y()).norm() <= 3 * scaled(width)) { multi_path->paths.push_back(ExtrusionPath(role, mm3_per_mm, width, height, true)); - multi_path->paths.back().polyline = std::move(Polyline(last_end_point, polyline.first_point())); + + if(apply_overlap_compensation){ + Polyline connect_line; + Vec2d curr_direction = (polyline.first_point()-polyline.last_point()).cast().normalized(); + Vec2d offset_vector = last_direction * overlap_rate * scaled(nozzle_diameter); + Point overlap_last_p = last_end_point + offset_vector.cast(); + + offset_vector = curr_direction * overlap_rate * scaled(nozzle_diameter); + Point overlap_curr_p = polyline.first_point() + offset_vector.cast(); + + connect_line.points.push_back(last_end_point); + connect_line.points.push_back(overlap_last_p); + connect_line.points.push_back(overlap_curr_p); + connect_line.points.push_back(polyline.first_point()); + + multi_path->paths.back().polyline = std::move(connect_line); + } + else{ + multi_path->paths.back().polyline = std::move(Polyline(last_end_point, polyline.first_point())); + } + } else { dst.push_back(multi_path); multi_path = new ExtrusionMultiPath(); @@ -667,6 +712,7 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst, multi_path->paths.back().polyline = std::move(polyline); last_end_point_valid = true; last_end_point = multi_path->paths.back().polyline.last_point(); + last_direction = (last_end_point - multi_path->paths.back().polyline.first_point()).cast().normalized(); } } if (!multi_path->empty()) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 59dd3fb..6325a93 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -26,6 +26,9 @@ struct SurfaceFillParams unsigned int extruder = 0; // Infill pattern, adjusted for the density etc. InfillPattern pattern = InfillPattern(0); + //for locked zag + InfillPattern skin_pattern = InfillPattern(0); + InfillPattern skeleton_pattern = InfillPattern(0); // FillBase // in unscaled coordinates @@ -99,6 +102,8 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis); RETURN_COMPARE_NON_EQUAL(infill_lock_depth); RETURN_COMPARE_NON_EQUAL(skin_infill_depth); + RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, skin_pattern); + RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, skeleton_pattern); return false; } @@ -124,7 +129,9 @@ struct SurfaceFillParams this->infill_rotate_step == rhs.infill_rotate_step && this->symmetric_infill_y_axis == rhs.symmetric_infill_y_axis && this->infill_lock_depth == rhs.infill_lock_depth && - this->skin_infill_depth == rhs.skin_infill_depth; + this->skin_infill_depth == rhs.skin_infill_depth && + this-> skin_pattern == rhs.skin_pattern && + this-> skeleton_pattern == rhs.skeleton_pattern; } }; @@ -166,7 +173,6 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p flow_params.insert({flow, {exp}}); else it->second.push_back(exp); - it++; }; auto append_density_param = [](std::map &density_params, float density, const ExPolygon &exp) { @@ -175,7 +181,6 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p density_params.insert({density, {exp}}); else it->second.push_back(exp); - it++; }; for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { @@ -194,7 +199,8 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p if (params.pattern == ipLockedZag) { params.infill_lock_depth = scale_(region_config.infill_lock_depth); params.skin_infill_depth = scale_(region_config.skin_infill_depth); - + params.skin_pattern = region_config.locked_skin_infill_pattern.value; + params.skeleton_pattern = region_config.locked_skeleton_infill_pattern.value; } if (params.pattern == ipCrossZag || params.pattern == ipLockedZag){ params.infill_shift_step = scale_(region_config.infill_shift_step); @@ -582,6 +588,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } else if (surface_fill.params.pattern == ipLightning){ dynamic_cast(f.get())->generator = lightning_generator; } + else if (surface_fill.params.pattern == ipMonotonicLine){ + FillMonotonicLineWGapFill* fill_monoline = dynamic_cast(f.get()); + fill_monoline->apply_gap_compensation = this->object()->print()->config().apply_top_surface_compensation; + } else if (surface_fill.params.pattern == ipFloatingConcentric) { FillFloatingConcentric* fill_contour = dynamic_cast(f.get()); assert(fill_contour != nullptr); @@ -660,6 +670,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.infill_lock_depth = surface_fill.params.infill_lock_depth; params.skin_infill_depth = surface_fill.params.skin_infill_depth; f->set_lock_region_param(lock_param); + f->set_skin_and_skeleton_pattern(surface_fill.params.skin_pattern, surface_fill.params.skeleton_pattern); } if (surface_fill.params.pattern == ipCrossZag || surface_fill.params.pattern == ipLockedZag) { if (f->layer_id % 2 == 0) { diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 60314ed..2e2d7a1 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -148,9 +148,22 @@ public: virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); virtual ThickPolylines fill_surface_arachne(const Surface* surface, const FillParams& params); virtual void set_lock_region_param(const LockRegionParam &lock_param){}; + virtual void set_skin_and_skeleton_pattern(const InfillPattern &skin_pattern, const InfillPattern &skeleton_pattern){}; // QDS: this method is used to fill the ExtrusionEntityCollection. // It call fill_surface by default virtual void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out); + virtual void copy_fill_data(const Fill *f){ + layer_id = f->layer_id; + z = f->z; + spacing = f->spacing; + overlap = f->overlap; + angle = f->angle; + link_max_length = f->link_max_length; + loop_clipping = f->loop_clipping; + bounding_box = f->bounding_box; + adapt_fill_octree = f->adapt_fill_octree; + no_overlap_expolygons = f->no_overlap_expolygons; + }; protected: Fill() : diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 2d98539..fa17aac 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3242,7 +3242,7 @@ void FillMonotonicLineWGapFill::fill_surface_extrusion(const Surface* surface, c extrusion_entities_append_paths_with_wipe( coll_nosort->entities, std::move(polylines_rectilinear), params.extrusion_role, - flow_mm3_per_mm, float(flow_width), params.flow.height()); + flow_mm3_per_mm, float(flow_width), params.flow.height(), params.flow.nozzle_diameter(), this->apply_gap_compensation); unextruded_areas = diff_ex(this->no_overlap_expolygons, union_ex(coll_nosort->polygons_covered_by_spacing(10))); } else @@ -3393,7 +3393,7 @@ void FillLockedZag::fill_surface_locked_zag (const Surface * bool zig_get = false; FillParams zig_params = params; - zig_params.horiz_move = 0; + // generate skeleton for diff density auto generate_for_different_flow = [&multi_width_polyline](const std::map &flow_params, const Polylines &polylines) { auto it = flow_params.begin(); @@ -3406,6 +3406,10 @@ void FillLockedZag::fill_surface_locked_zag (const Surface * } }; + std::unique_ptr skeleton_f = std::unique_ptr(Fill::new_from_type(this->skeleton_pattern)); + skeleton_f->copy_fill_data(static_cast(this)); + if (this->skeleton_pattern!= ipCrossZag) + zig_params.horiz_move = 0; auto it = this->lock_param.skeleton_density_params.begin(); while (it != this->lock_param.skeleton_density_params.end()) { ExPolygons region_exp = union_safety_offset_ex(it->second); @@ -3415,7 +3419,7 @@ void FillLockedZag::fill_surface_locked_zag (const Surface * for (ExPolygon &exp : exps) { zig_surface.expolygon = exp; - Polylines zig_polylines_out = this->fill_surface(&zig_surface, zig_params); + Polylines zig_polylines_out = skeleton_f->fill_surface(&zig_surface, zig_params); skeloton_lines.insert(skeloton_lines.end(), zig_polylines_out.begin(), zig_polylines_out.end()); } it++; @@ -3426,16 +3430,20 @@ void FillLockedZag::fill_surface_locked_zag (const Surface * // skin exps bool cross_get = false; - FillParams cross_params = params; - cross_params.locked_zag = false; + FillParams skin_params = params; auto skin_density = this->lock_param.skin_density_params.begin(); + std::unique_ptr skin_f = std::unique_ptr(Fill::new_from_type(this->skin_pattern)); + skin_params .locked_zag = false; + skin_f->copy_fill_data(static_cast(this)); + if (this->skeleton_pattern != ipCrossZag) + zig_params.horiz_move = 0; while (skin_density != this->lock_param.skin_density_params.end()) { ExPolygons region_exp = union_safety_offset_ex(skin_density->second); ExPolygons exps = intersection_ex(region_exp, cross_expas); - cross_params.density = skin_density->first; + skin_params.density = skin_density->first; for (ExPolygon &exp : exps) { cross_surface.expolygon = exp; - Polylines cross_polylines_out = this->fill_surface(&cross_surface, cross_params); + Polylines cross_polylines_out = skin_f->fill_surface(&cross_surface, skin_params); skin_lines.insert(skin_lines.end(), cross_polylines_out.begin(), cross_polylines_out.end()); } skin_density++; diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 69aef8b..c9ff2a6 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -133,6 +133,7 @@ public: void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; bool is_self_crossing() override { return false; } + bool apply_gap_compensation{ false }; protected: Fill* clone() const override { return new FillMonotonicLineWGapFill(*this); }; bool no_sort() const override { return true; } @@ -165,7 +166,8 @@ public: Fill *clone() const override { return new FillLockedZag(*this); } ~FillLockedZag() override = default; LockRegionParam lock_param; - + InfillPattern skin_pattern = InfillPattern(0); + InfillPattern skeleton_pattern = InfillPattern(0); void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; bool has_consistent_pattern() const override { return true; } @@ -173,6 +175,10 @@ public: void fill_surface_locked_zag(const Surface * surface, const FillParams & params, std::vector> &multi_width_polyline); + void set_skin_and_skeleton_pattern(const InfillPattern &skin_pattern, const InfillPattern &skeleton_pattern){ + this->skin_pattern = skin_pattern; + this->skeleton_pattern = skeleton_pattern; + }; }; Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); diff --git a/src/libslic3r/FlushVolCalc.cpp b/src/libslic3r/FlushVolCalc.cpp index 122223c..6073e8b 100644 --- a/src/libslic3r/FlushVolCalc.cpp +++ b/src/libslic3r/FlushVolCalc.cpp @@ -39,24 +39,15 @@ static float DeltaHS_QDS(float h1, float s1, float v1, float h2, float s2, float return std::min(1.2f, dxy); } -FlushVolCalculator::FlushVolCalculator(int min, int max, bool is_multi_extruder, NozzleVolumeType volume_type, float multiplier) - :m_min_flush_vol(min), m_max_flush_vol(max), m_multiplier(multiplier) +FlushVolCalculator::FlushVolCalculator(int min, int max, int flush_dataset, float multiplier) + :m_min_flush_vol(min), m_max_flush_vol(max), m_multiplier(multiplier), m_flush_dataset(flush_dataset) { - if (!is_multi_extruder) { - m_machine_type = FlushPredict::Standard; - return; - } - - if (volume_type == NozzleVolumeType::nvtHighFlow) - m_machine_type = FlushPredict::DualHighFlow; - else - m_machine_type = FlushPredict::DualStandard; } bool FlushVolCalculator::get_flush_vol_from_data(unsigned char src_r, unsigned char src_g, unsigned char src_b, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b, float& flush) { - GenericFlushPredictor pd(m_machine_type); + GenericFlushPredictor pd(m_flush_dataset); FlushPredict::RGBColor src(src_r, src_g, src_b); FlushPredict::RGBColor dst(dst_r, dst_g, dst_b); @@ -67,7 +58,7 @@ int FlushVolCalculator::calc_flush_vol_rgb(unsigned char src_r, unsigned char sr unsigned char dst_r, unsigned char dst_g, unsigned char dst_b) { float flush_volume; - if(m_machine_type == FlushPredict::Standard && get_flush_vol_from_data(src_r, src_g, src_b, dst_r, dst_g, dst_b, flush_volume)) + if(m_flush_dataset == 0 && get_flush_vol_from_data(src_r, src_g, src_b, dst_r, dst_g, dst_b, flush_volume)) return flush_volume; float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f; float from_hsv_h, from_hsv_s, from_hsv_v; @@ -107,7 +98,6 @@ int FlushVolCalculator::calc_flush_vol_rgb(unsigned char src_r, unsigned char sr return flush_volume; } - int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, unsigned char src_g, unsigned char src_b, unsigned char dst_a, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b) { @@ -120,7 +110,7 @@ int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, } float flush_volume; - if(m_machine_type != FlushPredict::Standard && get_flush_vol_from_data(src_r, src_g, src_b, dst_r, dst_g, dst_b, flush_volume)) + if(m_flush_dataset != 0 && get_flush_vol_from_data(src_r, src_g, src_b, dst_r, dst_g, dst_b, flush_volume)) return std::min((int)flush_volume, m_max_flush_vol); @@ -130,7 +120,7 @@ int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, constexpr float light_color_thres = 75.f/255.f; bool is_from_dark = get_luminance(src_r, src_g, src_b) > dark_color_thres; bool is_to_light = get_luminance(dst_r, dst_g, dst_b) < light_color_thres; - if (m_machine_type != FlushPredict::Standard && is_from_dark && is_to_light) + if (m_flush_dataset != 0 && is_from_dark && is_to_light) flush_volume *= 1.3; flush_volume += m_min_flush_vol; diff --git a/src/libslic3r/FlushVolCalc.hpp b/src/libslic3r/FlushVolCalc.hpp index eaa86ba..46d04d1 100644 --- a/src/libslic3r/FlushVolCalc.hpp +++ b/src/libslic3r/FlushVolCalc.hpp @@ -15,7 +15,7 @@ extern const int g_max_flush_volume; class FlushVolCalculator { public: - FlushVolCalculator(int min, int max, bool is_multi_extruder, NozzleVolumeType volume_type, float multiplier = 1.0f); + FlushVolCalculator(int min, int max, int flush_dataset, float multiplier = 1.0f); ~FlushVolCalculator() { } @@ -23,7 +23,6 @@ public: int calc_flush_vol(unsigned char src_a, unsigned char src_r, unsigned char src_g, unsigned char src_b, unsigned char dst_a, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b); - int calc_flush_vol_rgb(unsigned char src_r,unsigned char src_g,unsigned char src_b, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b); @@ -34,7 +33,7 @@ private: int m_min_flush_vol; int m_max_flush_vol; float m_multiplier; - FlushPredict::FlushMachineType m_machine_type; + int m_flush_dataset; }; diff --git a/src/libslic3r/FlushVolPredictor.cpp b/src/libslic3r/FlushVolPredictor.cpp index 6edb053..dc39284 100644 --- a/src/libslic3r/FlushVolPredictor.cpp +++ b/src/libslic3r/FlushVolPredictor.cpp @@ -173,6 +173,7 @@ class FlushVolPredictor public: bool predict(const RGB& from,const RGB& to , float& flush); FlushVolPredictor(const std::string& data_file); + int get_min_flush_volume(); FlushVolPredictor() = default; private: uint64_t generate_hash_key(const RGB& from, const RGB& to); @@ -181,6 +182,13 @@ private: bool m_valid{ false }; }; +int FlushVolPredictor::get_min_flush_volume() +{ + if(!m_valid) + return std::numeric_limits::max(); + return static_cast(std::min_element(m_flush_map.begin(), m_flush_map.end(), [](const auto& a, const auto& b) {return a.second < b.second; })->second); +} + uint64_t FlushVolPredictor::generate_hash_key(const RGB& from, const RGB& to) { uint64_t key = 0; @@ -299,24 +307,24 @@ bool FlushVolPredictor::predict(const RGB& from, const RGB& to, float& flush) } -static std::unordered_map predictor_instances; +static std::unordered_map predictor_instances; -GenericFlushPredictor::GenericFlushPredictor(const MachineType& type) +GenericFlushPredictor::GenericFlushPredictor(const int dataset_value) { - auto iter = predictor_instances.find(type); + auto iter = predictor_instances.find(dataset_value); if (iter != predictor_instances.end()) predictor = &iter->second; else { std::string path = Slic3r::resources_dir(); - if (type == MachineType::DualHighFlow) - path += "/flush/flush_data_dual_highflow.txt"; - else if (type == MachineType::DualStandard) - path += "/flush/flush_data_dual_standard.txt"; - else + if (dataset_value == 0) path += "/flush/flush_data_standard.txt"; - predictor_instances[type] = FlushVolPredictor(path); + else if (dataset_value == 1) + path += "/flush/flush_data_dual_standard.txt"; + else if (dataset_value == 2) + path += "/flush/flush_data_dual_highflow.txt"; + predictor_instances[dataset_value] = FlushVolPredictor(path); - predictor = &predictor_instances[type]; + predictor = &predictor_instances[dataset_value]; } } @@ -327,3 +335,10 @@ bool GenericFlushPredictor::predict(const RGB& from, const RGB& to, float& flush return false; return predictor->predict(from, to, flush); } + +int GenericFlushPredictor::get_min_flush_volume() +{ + if (!predictor) + return std::numeric_limits::max(); + return predictor->get_min_flush_volume(); +} \ No newline at end of file diff --git a/src/libslic3r/FlushVolPredictor.hpp b/src/libslic3r/FlushVolPredictor.hpp index 3d03766..59f5091 100644 --- a/src/libslic3r/FlushVolPredictor.hpp +++ b/src/libslic3r/FlushVolPredictor.hpp @@ -8,14 +8,6 @@ namespace FlushPredict { - enum FlushMachineType - { - Standard, - DualStandard, - DualHighFlow - }; - - struct RGBColor { unsigned char r{ 0 }; @@ -48,10 +40,10 @@ class FlushVolPredictor; class GenericFlushPredictor { using RGB = FlushPredict::RGBColor; - using MachineType = FlushPredict::FlushMachineType; public: - explicit GenericFlushPredictor(const MachineType& type); + explicit GenericFlushPredictor(const int dataset_value); bool predict(const RGB& from, const RGB& to, float& flush); + int get_min_flush_volume(); private: FlushVolPredictor* predictor{ nullptr }; }; diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 9604a43..8496965 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -359,20 +359,20 @@ std::string to_ini(const ConfMap &m) { std::string ret; for (auto ¶m : m) ret += param.first + " = " + param.second + "\n"; - + return ret; } std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) { std::string ret; - + if (cfg.has(key)) { auto opt = cfg.option(key); if (opt) ret = opt->serialize(); } - - return ret; + + return ret; } void fill_iniconf(ConfMap &m, const SLAPrint &print) @@ -390,16 +390,16 @@ void fill_iniconf(ConfMap &m, const SLAPrint &print) m["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); m["fileCreationTimestamp"] = Utils::utc_timestamp(); m["prusaSlicerVersion"] = SLIC3R_BUILD_ID; - + SLAPrintStatistics stats = print.print_statistics(); // Set statistics values to the printer - + double used_material = (stats.objects_used_material + stats.support_used_material) / 1000; - + int num_fade = print.default_object_config().faded_layers.getInt(); num_fade = num_fade >= 0 ? num_fade : 0; - + m["usedMaterial"] = std::to_string(used_material); m["numFade"] = std::to_string(num_fade); m["numSlow"] = std::to_string(stats.slow_layers_count); @@ -412,38 +412,38 @@ void fill_iniconf(ConfMap &m, const SLAPrint &print) hollow_en = (*it++)->config().hollowing_enable; m["hollow"] = hollow_en ? "1" : "0"; - + m["action"] = "print"; } void fill_slicerconf(ConfMap &m, const SLAPrint &print) { using namespace std::literals::string_view_literals; - + // Sorted list of config keys, which shall not be stored into the ini. - static constexpr auto banned_keys = { + static constexpr auto banned_keys = { "compatible_printers"sv, "compatible_prints"sv }; - + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); auto is_banned = [](const std::string &key) { return std::binary_search(banned_keys.begin(), banned_keys.end(), key); }; - + auto &cfg = print.full_print_config(); for (const std::string &key : cfg.keys()) if (! is_banned(key) && ! cfg.option(key)->is_nil()) m[key] = cfg.opt_serialize(key); - + } } // namespace std::unique_ptr SL1Archive::create_raster() const { - sla::RasterBase::Resolution res; - sla::RasterBase::PixelDim pxdim; + sla::Resolution res; + sla::PixelDim pxdim; std::array mirror; double w = m_cfg.display_width.getFloat(); @@ -453,19 +453,19 @@ std::unique_ptr SL1Archive::create_raster() const mirror[X] = m_cfg.display_mirror_x.getBool(); mirror[Y] = m_cfg.display_mirror_y.getBool(); - + auto ro = m_cfg.display_orientation.getInt(); sla::RasterBase::Orientation orientation = ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait : sla::RasterBase::roLandscape; - + if (orientation == sla::RasterBase::roPortrait) { std::swap(w, h); std::swap(pw, ph); } - res = sla::RasterBase::Resolution{pw, ph}; - pxdim = sla::RasterBase::PixelDim{w / pw, h / ph}; + res = sla::Resolution{pw, ph}; + pxdim = sla::PixelDim{w / pw, h / ph}; sla::RasterBase::Trafo tr{orientation, mirror}; double gamma = m_cfg.gamma_correction.getFloat(); @@ -486,10 +486,10 @@ void SL1Archive::export_print(Zipper& zipper, prjname.empty() ? boost::filesystem::path(zipper.get_filename()).stem().string() : prjname; - + ConfMap iniconf, slicerconf; fill_iniconf(iniconf, print); - + iniconf["jobDir"] = project; fill_slicerconf(slicerconf, print); @@ -499,13 +499,13 @@ void SL1Archive::export_print(Zipper& zipper, zipper << to_ini(iniconf); zipper.add_entry("prusaslicer.ini"); zipper << to_ini(slicerconf); - + size_t i = 0; for (const sla::EncodedRaster &rst : m_layers) { std::string imgname = project + string_printf("%.5d", i++) + "." + rst.extension(); - + zipper.add_entry(imgname.c_str(), rst.data(), rst.size()); } } catch(std::exception& e) { diff --git a/src/libslic3r/Format/qds_3mf.cpp b/src/libslic3r/Format/qds_3mf.cpp index 7d8f550..b46df7c 100644 --- a/src/libslic3r/Format/qds_3mf.cpp +++ b/src/libslic3r/Format/qds_3mf.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -232,7 +231,7 @@ static constexpr const char* SLICE_HEADER_ITEM_TAG = "header_item"; static constexpr const char* TEXT_INFO_TAG = "text_info"; static constexpr const char* TEXT_ATTR = "text"; static constexpr const char* FONT_NAME_ATTR = "font_name"; -static constexpr const char *FONT_VERSION_ATTR = "font_version"; +static constexpr const char *FONT_VERSION_ATTR = "font_version"; static constexpr const char* FONT_INDEX_ATTR = "font_index"; static constexpr const char* FONT_SIZE_ATTR = "font_size"; static constexpr const char* THICKNESS_ATTR = "thickness"; @@ -241,6 +240,7 @@ static constexpr const char* ROTATE_ANGLE_ATTR = "rotate_angle"; static constexpr const char* TEXT_GAP_ATTR = "text_gap"; static constexpr const char* BOLD_ATTR = "bold"; static constexpr const char* ITALIC_ATTR = "italic"; +static constexpr const char *SURFACE_TYPE = "surface_type"; static constexpr const char* SURFACE_TEXT_ATTR = "surface_text"; static constexpr const char* KEEP_HORIZONTAL_ATTR = "keep_horizontal"; static constexpr const char* HIT_MESH_ATTR = "hit_mesh"; @@ -280,6 +280,7 @@ static constexpr const char* OFFSET_ATTR = "offset"; static constexpr const char* PRINTABLE_ATTR = "printable"; static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports"; +static constexpr const char *CUSTOM_FUZZY_SKIN_ATTR = "paint_fuzzy_skin"; static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam"; static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color"; // QDS @@ -454,6 +455,13 @@ float qds_get_attribute_value_float(const char** attributes, unsigned int attrib return value; } +bool qds_has_attribute_value_int(const char **attributes, unsigned int attributes_size, const char *attribute_key) +{ + if (const char *text = qds_get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) + return true; + return false; +} + int qds_get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key) { int value = 0; @@ -679,6 +687,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::vector vertices; std::vector triangles; std::vector custom_supports; + std::vector custom_fuzzy_skin; std::vector custom_seam; std::vector mmu_segmentation; // QDS @@ -691,6 +700,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::swap(vertices, o.vertices); std::swap(triangles, o.triangles); std::swap(custom_supports, o.custom_supports); + std::swap(custom_fuzzy_skin, o.custom_fuzzy_skin); std::swap(custom_seam, o.custom_seam); } @@ -698,6 +708,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) vertices.clear(); triangles.clear(); custom_supports.clear(); + custom_fuzzy_skin.clear(); custom_seam.clear(); mmu_segmentation.clear(); } @@ -1317,9 +1328,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) model.set_backup_path(m_backup_path); try { if (boost::filesystem::exists(model.get_backup_path() + "/origin.txt")) - boost::filesystem::load_string_file(model.get_backup_path() + "/origin.txt", m_origin_file); + load_string_file(model.get_backup_path() + "/origin.txt", m_origin_file); } catch (...) {} - boost::filesystem::save_string_file( + save_string_file( model.get_backup_path() + "/lock.txt", boost::lexical_cast(get_current_pid())); } @@ -1334,7 +1345,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) file_version = *m_qidislicer_generator_version; // save for restore if (result && m_load_aux && !m_load_restore) { - boost::filesystem::save_string_file(model.get_backup_path() + "/origin.txt", filename); + save_string_file(model.get_backup_path() + "/origin.txt", filename); } if (m_load_restore && !result) // not clear failed backup data for later analyze model.set_backup_path("detach"); @@ -1540,8 +1551,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, pixels.data(), pixels.size(), 0); }); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%, no_light_thumbnail_file=%3%")%it->first %plate->thumbnail_file %plate->no_light_thumbnail_file; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%")%plate->top_file %plate->pick_file; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%, no_light_thumbnail_file=%3%") % it->first % PathSanitizer::sanitize(plate->thumbnail_file) % PathSanitizer::sanitize(plate->no_light_thumbnail_file); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%") % PathSanitizer::sanitize(plate->top_file) % PathSanitizer::sanitize(plate->pick_file); it++; } @@ -1783,7 +1794,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string name(stat.m_filename); std::replace(name.begin(), name.end(), '\\', '/'); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("extract %1%th file %2%, total=%3%")%(i+1)%name%num_entries; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ + << boost::format("extract %1%th file %2%, total=%3%") % (i + 1) % PathSanitizer::sanitize(name) % num_entries; if (name.find("/../") != std::string::npos) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", find file path including /../, not valid, skip it\n"); @@ -1852,6 +1864,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) add_error("Archive does not contain a valid model config"); return false; } + } else if (_is_svg_shape_file(name)) { + _extract_embossed_svg_shape_file(name, archive, stat); } else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) { m_parsing_slice_info = true; @@ -2141,7 +2155,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // model.adjust_min_z(); //QDS progress point - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_LOADING_PLATES, m_plater_data size %1%, m_backup_path %2%\n")%m_plater_data.size() %m_backup_path; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_LOADING_PLATES, m_plater_data size %1%, m_backup_path %2%\n") % m_plater_data.size() % PathSanitizer::sanitize(m_backup_path); if (proFn) { proFn(IMPORT_STAGE_LOADING_PLATES, 0, 1, cb_cancel); if (cb_cancel) @@ -2186,8 +2200,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) plate_data_list[it->first-1]->config = it->second->config; current_plate_data = plate_data_list[it->first - 1]; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%, no_light_thumbnail_file=%3%")%it->first %plate_data_list[it->first-1]->thumbnail_file %plate_data_list[it->first-1]->no_light_thumbnail_file; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%")%plate_data_list[it->first-1]->top_file %plate_data_list[it->first-1]->pick_file; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ + << boost::format(", plate %1%, thumbnail_file=%2%, no_light_thumbnail_file=%3%") % it->first % + PathSanitizer::sanitize(plate_data_list[it->first - 1]->thumbnail_file) % + PathSanitizer::sanitize(plate_data_list[it->first - 1]->no_light_thumbnail_file); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ + << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%") % + PathSanitizer::sanitize(plate_data_list[it->first - 1]->top_file) % + PathSanitizer::sanitize(plate_data_list[it->first - 1]->pick_file); it++; //update the arrange order @@ -2510,7 +2530,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string dest_file = temp_path + std::string("/") + "_temp_3.config";; std::string dest_zip_file = encode_path(dest_file.c_str()); mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % PathSanitizer::sanitize(dest_file) % stat.m_filename % res; if (res == 0) { add_error("Error while extract project config file to file"); return; @@ -2522,7 +2542,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) add_error("Error load config from json:"+reason); return; } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", load project config file successfully from %1%\n") %dest_file; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", load project config file successfully from %1%\n") % PathSanitizer::sanitize(dest_file); } } @@ -2539,7 +2559,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::string dest_file = m_backup_path + std::string("/") + "_temp_2.config";; std::string dest_zip_file = encode_path(dest_file.c_str()); mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % PathSanitizer::sanitize(dest_file) % stat.m_filename % res; if (res == 0) { add_error("Error while extract auxiliary file to file"); return; @@ -2673,7 +2693,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) boost::filesystem::path dest_path = boost::filesystem::path(m_backup_path + "/" + src_file); std::string dest_zip_file = encode_path(dest_path.string().c_str()); mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_path % stat.m_filename % res; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % PathSanitizer::sanitize(dest_path) % stat.m_filename % res; if (res == 0) { add_error("Error while extract file to temp directory"); return; @@ -3061,7 +3081,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) if (!es.has_value()) continue; std::optional &svg = es->svg_file; if (!svg.has_value()) continue; - if (filename.compare(svg->path_in_3mf) == 0) svg->file_data = m_path_to_emboss_shape_files[filename]; + if (filename.compare(svg->path_in_3mf) == 0) + svg->file_data = m_path_to_emboss_shape_files[filename]; } } @@ -3622,6 +3643,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) qds_get_attribute_value_int(attributes, num_attributes, V3_ATTR)); m_curr_object->geometry.custom_supports.push_back(qds_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); + m_curr_object->geometry.custom_fuzzy_skin.push_back(qds_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR)); m_curr_object->geometry.custom_seam.push_back(qds_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); m_curr_object->geometry.mmu_segmentation.push_back(qds_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); // QDS @@ -3832,7 +3854,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) } // Definition of read/write method for EmbossShape - static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive); + static void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive,bool export_full_path); static std::optional read_emboss_shape(const char **attributes, unsigned int num_attributes); bool _QDS_3MF_Importer::_handle_start_shape_configuration(const char **attributes, unsigned int num_attributes) @@ -4466,8 +4488,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) TextInfo text_info; text_info.m_text = xml_unescape(qds_get_attribute_value_string(attributes, num_attributes, TEXT_ATTR)); text_info.m_font_name = qds_get_attribute_value_string(attributes, num_attributes, FONT_NAME_ATTR); + text_info.text_configuration.style.prop.face_name = text_info.m_font_name; text_info.m_font_version = qds_get_attribute_value_string(attributes, num_attributes, FONT_VERSION_ATTR); - + text_info.text_configuration.style.name = qds_get_attribute_value_string(attributes, num_attributes, STYLE_NAME_ATTR); text_info.m_curr_font_idx = qds_get_attribute_value_int(attributes, num_attributes, FONT_INDEX_ATTR); text_info.m_font_size = qds_get_attribute_value_float(attributes, num_attributes, FONT_SIZE_ATTR); @@ -4478,8 +4501,27 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) text_info.m_bold = qds_get_attribute_value_int(attributes, num_attributes, BOLD_ATTR); text_info.m_italic = qds_get_attribute_value_int(attributes, num_attributes, ITALIC_ATTR); - text_info.m_is_surface_text = qds_get_attribute_value_int(attributes, num_attributes, SURFACE_TEXT_ATTR); - text_info.m_keep_horizontal = qds_get_attribute_value_int(attributes, num_attributes, KEEP_HORIZONTAL_ATTR); + if (qds_has_attribute_value_int(attributes, num_attributes, SURFACE_TYPE)) { + text_info.m_surface_type = qds_get_attribute_value_int(attributes, num_attributes, SURFACE_TYPE); + } else {//old version + text_info.m_is_surface_text = qds_get_attribute_value_int(attributes, num_attributes, SURFACE_TEXT_ATTR); + text_info.m_keep_horizontal = qds_get_attribute_value_int(attributes, num_attributes, KEEP_HORIZONTAL_ATTR); + + auto is_surface_text = text_info.m_is_surface_text; + auto is_keep_horizontal = text_info.m_keep_horizontal; + auto convert_text_type = [](bool is_surface_text, bool is_keep_horizontal, int &text_type) { + if (is_surface_text && is_keep_horizontal) { + text_type = (int) TextInfo::TextType::SURFACE_HORIZONAL; + } else if (is_surface_text) { + text_type = (int) TextInfo::TextType::SURFACE; + } else if (is_keep_horizontal) { + text_type = (int) TextInfo::TextType::HORIZONAL; + } else { + text_type = (int) TextInfo::TextType::HORIZONAL; + } + }; + convert_text_type(is_surface_text, is_keep_horizontal, text_info.m_surface_type); + } text_info.m_rr.mesh_id = qds_get_attribute_value_int(attributes, num_attributes, HIT_MESH_ATTR); @@ -4750,20 +4792,25 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // recreate custom supports, seam and mmu segmentation from previously loaded attribute { volume->supported_facets.reserve(triangles_count); + volume->fuzzy_skin_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); volume->mmu_segmentation_facets.reserve(triangles_count); for (size_t i=0; igeometry.custom_supports.size()); + assert(i < sub_object->geometry.custom_fuzzy_skin.size()); assert(i < sub_object->geometry.custom_seam.size()); assert(i < sub_object->geometry.mmu_segmentation.size()); if (! sub_object->geometry.custom_supports[i].empty()) volume->supported_facets.set_triangle_from_string(i, sub_object->geometry.custom_supports[i]); + if (!sub_object->geometry.custom_fuzzy_skin[i].empty()) + volume->fuzzy_skin_facets.set_triangle_from_string(i, sub_object->geometry.custom_fuzzy_skin[i]); if (! sub_object->geometry.custom_seam[i].empty()) volume->seam_facets.set_triangle_from_string(i, sub_object->geometry.custom_seam[i]); if (! sub_object->geometry.mmu_segmentation[i].empty()) volume->mmu_segmentation_facets.set_triangle_from_string(i, sub_object->geometry.mmu_segmentation[i]); } volume->supported_facets.shrink_to_fit(); + volume->fuzzy_skin_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); volume->mmu_segmentation_facets.shrink_to_fit(); volume->mmu_segmentation_facets.touch(); @@ -4905,21 +4952,26 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // recreate custom supports, seam and mmu segmentation from previously loaded attribute volume->supported_facets.reserve(triangles_count); + volume->fuzzy_skin_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); volume->mmu_segmentation_facets.reserve(triangles_count); for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); + if (!geometry.custom_fuzzy_skin[index].empty()) + volume->fuzzy_skin_facets.set_triangle_from_string(i, geometry.custom_fuzzy_skin[index]); if (! geometry.custom_seam[index].empty()) volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); if (! geometry.mmu_segmentation[index].empty()) volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); } volume->supported_facets.shrink_to_fit(); + volume->fuzzy_skin_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); volume->mmu_segmentation_facets.shrink_to_fit(); @@ -5222,6 +5274,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) qds_get_attribute_value_int(attributes, num_attributes, V3_ATTR)); current_object->geometry.custom_supports.push_back(qds_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); + current_object->geometry.custom_fuzzy_skin.push_back(qds_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR)); current_object->geometry.custom_seam.push_back(qds_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); current_object->geometry.mmu_segmentation.push_back(qds_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); // QDS @@ -5516,7 +5569,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) typedef std::vector BuildItemsList; typedef std::map ObjectToObjectDataMap; - bool m_fullpath_sources{ true }; + bool m_fullpath_sources{ false }; bool m_zip64 { true }; bool m_production_ext { false }; // save with Production Extention bool m_skip_static{ false }; // not save mesh and other big static contents @@ -5647,7 +5700,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return false; } if (!(store_params.strategy & SaveStrategy::Silence)) - boost::filesystem::save_string_file(store_params.model->get_backup_path() + "/origin.txt", filename); + save_string_file(store_params.model->get_backup_path() + "/origin.txt", filename); } return result; } @@ -6190,9 +6243,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) #endif if (!result) { add_error("Unable to add file " + src_file_path + " to archive"); - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", Unable to add file %1% to archive %2%\n") % src_file_path % path_in_zip; + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add file %1% to archive %2%\n") % PathSanitizer::sanitize(src_file_path) % path_in_zip; } else { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add file %1% to archive %2%\n") % src_file_path % path_in_zip; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add file %1% to archive %2%\n") % PathSanitizer::sanitize(src_file_path) % path_in_zip; } return result; } @@ -6418,7 +6471,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) bool sub_model = !objects_data.empty(); bool write_object = sub_model || !m_split_model; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", filename %1%, m_split_model %2%, sub_model %3%")%filename % m_split_model % sub_model; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", filename %1%, m_split_model %2%, sub_model %3%") % PathSanitizer::sanitize(filename) % m_split_model % sub_model; #if WRITE_ZIP_LANGUAGE_ENCODING auto & zip_filename = filename; @@ -6612,6 +6665,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) { const ModelVolume* shared_volume = iter->second.second; if ((shared_volume->supported_facets.equals(volume->supported_facets)) + && (shared_volume->fuzzy_skin_facets.equals(volume->fuzzy_skin_facets)) && (shared_volume->seam_facets.equals(volume->seam_facets)) && (shared_volume->mmu_segmentation_facets.equals(volume->mmu_segmentation_facets))) { @@ -6996,6 +7050,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) output_buffer += "\""; } + std::string custom_fuzzy_skin_string = volume->fuzzy_skin_facets.get_triangle_as_string(i); + if (!custom_fuzzy_skin_string.empty()) { + output_buffer += " "; + output_buffer += CUSTOM_FUZZY_SKIN_ATTR; + output_buffer += "=\""; + output_buffer += custom_fuzzy_skin_string; + output_buffer += "\""; + } + std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i); if (! custom_seam_data_string.empty()) { output_buffer += " "; @@ -7381,7 +7444,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << TEXT_ATTR << "=\"" << xml_escape(text_info.m_text) << "\" "; stream << FONT_NAME_ATTR << "=\"" << text_info.m_font_name << "\" "; stream << FONT_VERSION_ATTR << "=\"" << text_info.m_font_version << "\" "; - + stream << STYLE_NAME_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(text_info.text_configuration.style.name) << "\" "; + stream << FONT_INDEX_ATTR << "=\"" << text_info.m_curr_font_idx << "\" "; stream << FONT_SIZE_ATTR << "=\"" << text_info.m_font_size << "\" "; @@ -7392,8 +7456,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << BOLD_ATTR << "=\"" << (text_info.m_bold ? 1 : 0) << "\" "; stream << ITALIC_ATTR << "=\"" << (text_info.m_italic ? 1 : 0) << "\" "; - stream << SURFACE_TEXT_ATTR << "=\"" << (text_info.m_is_surface_text ? 1 : 0) << "\" "; - stream << KEEP_HORIZONTAL_ATTR << "=\"" << (text_info.m_keep_horizontal ? 1 : 0) << "\" "; + float temp_version = text_info.m_font_version.empty() ? 0.f : float(std::atof(text_info.m_font_version.c_str())); + if (temp_version < 2.0f) { + stream << SURFACE_TEXT_ATTR << "=\"" << (text_info.m_is_surface_text ? 1 : 0) << "\" "; + stream << KEEP_HORIZONTAL_ATTR << "=\"" << (text_info.m_keep_horizontal ? 1 : 0) << "\" "; + } else { + stream << SURFACE_TYPE << "=\"" << ((int) text_info.m_surface_type) << "\" "; + } stream << HIT_MESH_ATTR << "=\"" << text_info.m_rr.mesh_id << "\" "; @@ -7408,6 +7477,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << "/>\n"; } + boost::filesystem::path get_dealed_platform_path(std::string path_str) { +#if defined(__linux__) || defined(__LINUX__) || defined(__APPLE__) + std::string translated_input = path_str; + std::replace(translated_input.begin(), translated_input.end(), '\\', '/'); + + boost::filesystem::path file_path(translated_input); +#else + boost::filesystem::path file_path(path_str); +#endif + return file_path; + } + bool _QDS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx, bool save_gcode, bool use_loaded_id) { std::stringstream stream; @@ -7484,7 +7565,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // stores volume's source data { - std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); + auto file_path =get_dealed_platform_path(volume->source.input_file); + std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : file_path.filename().string()); //std::string prefix = std::string(" <") + METADATA_TAG + " " + KEY_ATTR + "=\""; std::string prefix = std::string(" <") + METADATA_TAG + " " + KEY_ATTR + "=\""; if (! volume->source.input_file.empty()) { @@ -7507,8 +7589,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << " <" << METADATA_TAG << " "<< KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; } - if (const std::optional &es = volume->emboss_shape; es.has_value()) - to_xml(stream, *es, *volume, archive); + if (const std::optional &es = volume->emboss_shape; es.has_value()) { + to_xml(stream, *es, *volume, archive, m_fullpath_sources); + } const TextInfo &text_info = volume->get_text_info(); if (!text_info.m_text.empty()) @@ -7998,7 +8081,7 @@ bool _QDS_3MF_Exporter::_add_gcode_file_to_archive(mz_zip_archive& archive, cons MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0); boost::filesystem::path src_gcode_path(src_gcode_file); if (!boost::filesystem::exists(src_gcode_path)) { - BOOST_LOG_TRIVIAL(error) << "Gcode is missing, filename = " << src_gcode_file; + BOOST_LOG_TRIVIAL(error) << "Gcode is missing, filename = " << PathSanitizer::sanitize(src_gcode_file); result = false; } boost::filesystem::ifstream ifs(src_gcode_file, std::ios::binary); @@ -8019,7 +8102,7 @@ bool _QDS_3MF_Exporter::_add_gcode_file_to_archive(mz_zip_archive& archive, cons mz_zip_writer_add_from_zip_reader(&root_archive, &archive, 0); } mz_zip_reader_end(&archive); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", store %1% to 3mf %2%\n") % src_gcode_file % gcode_in_3mf; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", store %1% to 3mf %2%\n") % PathSanitizer::sanitize(src_gcode_file) % PathSanitizer::sanitize(gcode_in_3mf); } }); return result; @@ -8216,8 +8299,7 @@ public: } void remove_backup(Model& model, bool removeAll) { - BOOST_LOG_TRIVIAL(info) - << "remove_backup " << model.get_backup_path() << ", " << removeAll; + BOOST_LOG_TRIVIAL(info) << "remove_backup " << PathSanitizer::sanitize(model.get_backup_path()) << ", " << removeAll; std::deque canceled_tasks; boost::unique_lock lock(m_mutex); if (removeAll && model.is_need_backup()) { @@ -8303,7 +8385,7 @@ private: "Exit"}; std::ostringstream os; os << "{ type:" << type_names[type] << ", id:" << id - << ", path:" << path + << ", path:" << PathSanitizer::sanitize(path) << ", object:" << (object ? object->id().id : 0) << ", extra:" << delay << "}"; return os.str(); } @@ -8389,9 +8471,9 @@ private: try { boost::filesystem::remove(t.path + "/lock.txt"); boost::filesystem::remove_all(t.path); - BOOST_LOG_TRIVIAL(info) << "process_ui_task: remove all of backup path " << t.path; + BOOST_LOG_TRIVIAL(info) << "process_ui_task: remove all of backup path " << PathSanitizer::sanitize(t.path); } catch (std::exception &ex) { - BOOST_LOG_TRIVIAL(error) << "process_ui_task: failed to remove backup path" << t.path << ": " << ex.what(); + BOOST_LOG_TRIVIAL(error) << "process_ui_task: failed to remove backup path" << PathSanitizer::sanitize(t.path) << ": " << ex.what(); } } break; @@ -8629,7 +8711,7 @@ bool has_restore_data(std::string & path, std::string& origin) } if (boost::filesystem::exists(path + "/lock.txt")) { std::string pid; - boost::filesystem::load_string_file(path + "/lock.txt", pid); + load_string_file(path + "/lock.txt", pid); try { if (get_process_name(boost::lexical_cast(pid)) == get_process_name(0)) { @@ -8646,7 +8728,7 @@ bool has_restore_data(std::string & path, std::string& origin) return false; try { if (boost::filesystem::exists(path + "/origin.txt")) - boost::filesystem::load_string_file(path + "/origin.txt", origin); + load_string_file(path + "/origin.txt", origin); } catch (...) { } @@ -8745,13 +8827,16 @@ Transform3d create_fix(const std::optional &prev, const ModelVolume return *prev * fix_trmat; } -bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const ModelVolume &volume, mz_zip_archive &archive) +bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const ModelVolume &volume, mz_zip_archive &archive, bool export_full_path) { if (svg.path_in_3mf.empty()) return true; // EmbossedText OR unwanted store .svg file into .3mf (protection of copyRight) - if (!svg.path.empty()) - stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path) << "\" "; + if (!svg.path.empty()) { + auto file_path =get_dealed_platform_path(svg.path); + std::string input_file = xml_escape(export_full_path ? svg.path : file_path.filename().string()); + stream << SVG_FILE_PATH_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(input_file) << "\" "; + } stream << SVG_FILE_PATH_IN_3MF_ATTR << "=\"" << xml_escape_double_quotes_attribute_value(svg.path_in_3mf) << "\" "; std::shared_ptr file_data = svg.file_data; @@ -8768,11 +8853,13 @@ bool to_xml(std::stringstream &stream, const EmbossShape::SvgFile &svg, const Mo } // namespace -void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive) +void to_xml(std::stringstream &stream, const EmbossShape &es, const ModelVolume &volume, mz_zip_archive &archive, bool export_full_path) { stream << " <" << SHAPE_TAG << " "; if (es.svg_file.has_value()) - if (!to_xml(stream, *es.svg_file, volume, archive)) BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf"; + if (!to_xml(stream, *es.svg_file, volume, archive, export_full_path)) { + BOOST_LOG_TRIVIAL(warning) << "Can't write svg file defiden embossed shape into 3mf"; + } stream << SHAPE_SCALE_ATTR << "=\"" << es.scale << "\" "; diff --git a/src/libslic3r/FuzzySkin.cpp b/src/libslic3r/FuzzySkin.cpp new file mode 100644 index 0000000..7f03924 --- /dev/null +++ b/src/libslic3r/FuzzySkin.cpp @@ -0,0 +1,227 @@ +#include + +#include "libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include "FuzzySkin.hpp" + +namespace Slic3r { +// Produces a random value between 0 and 1. Thread-safe. +static double random_value() +{ + thread_local std::random_device rd; + // Hash thread ID for random number seed if no hardware rng seed is available + thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); + thread_local std::uniform_real_distribution dist(0.0, 1.0); + return dist(gen); +} + +void fuzzy_polyline(Points &poly, const bool closed, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance) +{ + const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point + + Points out; + out.reserve(poly.size()); + + // Skip the first point for open polyline. + Point *p0 = closed ? &poly.back() : &poly.front(); + for (auto it_pt1 = closed ? poly.begin() : std::next(poly.begin()); it_pt1 != poly.end(); ++it_pt1) { + Point &p1 = *it_pt1; + + // 'a' is the (next) new point between p0 and p1 + Vec2d p0p1 = (p1 - *p0).cast(); + double p0p1_size = p0p1.norm(); + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); + } + + dist_left_over = p0pa_dist - p0p1_size; + p0 = &p1; + } + + while (out.size() < 3) { + size_t point_idx = poly.size() - 2; + out.emplace_back(poly[point_idx]); + if (point_idx == 0) { + break; + } + + --point_idx; + } + + if (out.size() >= 3) { + poly = std::move(out); + } +} + +void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance) +{ + fuzzy_polyline(polygon.points, true, fuzzy_skin_thickness, fuzzy_skin_point_distance); +} + +void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance) +{ + const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point + + Arachne::ExtrusionJunction *p0 = &ext_lines.front(); + Arachne::ExtrusionJunctions out; + out.reserve(ext_lines.size()); + for (auto &p1 : ext_lines) { + if (p0->p == p1.p) { + // Copy the first point. + out.emplace_back(p1.p, p1.w, p1.perimeter_index); + continue; + } + + // 'a' is the (next) new point between p0 and p1 + Vec2d p0p1 = (p1.p - p0->p).cast(); + double p0p1_size = p0p1.norm(); + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); + } + + dist_left_over = p0pa_dist - p0p1_size; + p0 = &p1; + } + + while (out.size() < 3) { + size_t point_idx = ext_lines.size() - 2; + out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index); + if (point_idx == 0) { + break; + } + + --point_idx; + } + + if (ext_lines.back().p == ext_lines.front().p) { + // Connect endpoints. + out.front().p = out.back().p; + } + + if (out.size() >= 3) { + ext_lines.junctions = std::move(out); + } +} + +bool should_fuzzify(const PrintRegionConfig &config, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + const FuzzySkinType fuzzy_skin_type = config.fuzzy_skin.value; + + if (fuzzy_skin_type == FuzzySkinType::None || layer_idx <= 0) { + return false; + } + + const bool fuzzify_contours = perimeter_idx == 0 || fuzzy_skin_type == FuzzySkinType::AllWalls; + const bool fuzzify_holes = fuzzify_contours && (fuzzy_skin_type == FuzzySkinType::All || fuzzy_skin_type == FuzzySkinType::AllWalls); + + return is_contour ? fuzzify_contours : fuzzify_holes; +} + +Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + using namespace Slic3r::Algorithm::LineSegmentation; + + auto apply_fuzzy_skin_on_polygon = [&layer_idx, &perimeter_idx, &is_contour](const Polygon &polygon, const PrintRegionConfig &config) -> Polygon { + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + Polygon fuzzified_polygon = polygon; + fuzzy_polygon(fuzzified_polygon, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_distance.value)); + + return fuzzified_polygon; + } else { + return polygon; + } + }; + + if (perimeter_regions.empty()) { + return apply_fuzzy_skin_on_polygon(polygon, base_config); + } + + PolylineRegionSegments segments = polygon_segmentation(polygon, base_config, perimeter_regions); + if (segments.size() == 1) { + const PrintRegionConfig &config = segments.front().config; + return apply_fuzzy_skin_on_polygon(polygon, config); + } + + Polygon fuzzified_polygon; + for (PolylineRegionSegment &segment : segments) { + const PrintRegionConfig &config = segment.config; + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + fuzzy_polyline(segment.polyline.points, false, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_distance.value)); + } + + assert(!segment.polyline.empty()); + if (segment.polyline.empty()) { + continue; + } else if (!fuzzified_polygon.empty() && fuzzified_polygon.back() == segment.polyline.front()) { + // Remove the last point to avoid duplicate points. + fuzzified_polygon.points.pop_back(); + } + + Slic3r::append(fuzzified_polygon.points, std::move(segment.polyline.points)); + } + + assert(!fuzzified_polygon.empty()); + if (fuzzified_polygon.front() == fuzzified_polygon.back()) { + // Remove the last point to avoid duplicity between the first and the last point. + fuzzified_polygon.points.pop_back(); + } + + return fuzzified_polygon; +} + +Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + using namespace Slic3r::Algorithm::LineSegmentation; + using namespace Slic3r::Arachne; + + if (perimeter_regions.empty()) { + if (should_fuzzify(base_config, layer_idx, perimeter_idx, is_contour)) { + ExtrusionLine fuzzified_extrusion = extrusion; + fuzzy_extrusion_line(fuzzified_extrusion, scaled(base_config.fuzzy_skin_thickness.value), scaled(base_config.fuzzy_skin_point_distance.value)); + + return fuzzified_extrusion; + } else { + return extrusion; + } + } + + ExtrusionRegionSegments segments = extrusion_segmentation(extrusion, base_config, perimeter_regions); + ExtrusionLine fuzzified_extrusion(extrusion.inset_idx, extrusion.is_odd, extrusion.is_closed); + + for (ExtrusionRegionSegment &segment : segments) { + const PrintRegionConfig &config = segment.config; + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + fuzzy_extrusion_line(segment.extrusion, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_distance.value)); + } + + assert(!segment.extrusion.empty()); + if (segment.extrusion.empty()) { + continue; + } else if (!fuzzified_extrusion.empty() && fuzzified_extrusion.back().p == segment.extrusion.front().p) { + // Remove the last point to avoid duplicate points (We don't care if the width of both points is different.). + fuzzified_extrusion.junctions.pop_back(); + } + + Slic3r::append(fuzzified_extrusion.junctions, std::move(segment.extrusion.junctions)); + } + + assert(!fuzzified_extrusion.empty()); + + return fuzzified_extrusion; +} + +} // namespace Slic3r::Feature::FuzzySkin \ No newline at end of file diff --git a/src/libslic3r/FuzzySkin.hpp b/src/libslic3r/FuzzySkin.hpp new file mode 100644 index 0000000..92b6a2c --- /dev/null +++ b/src/libslic3r/FuzzySkin.hpp @@ -0,0 +1,22 @@ +#ifndef libslic3r_FuzzySkin_hpp_ +#define libslic3r_FuzzySkin_hpp_ + +namespace Slic3r::Arachne { +struct ExtrusionLine; +} // namespace Slic3r::Arachne + +namespace Slic3r { + +void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance); + +void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist); + +bool should_fuzzify(const PrintRegionConfig &config, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +} // namespace Slic3r::Feature::FuzzySkin + +#endif // libslic3r_FuzzySkin_hpp_ \ No newline at end of file diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a5a205e..6e5b1a8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -86,6 +86,8 @@ static const int g_max_flush_count = 4; static const size_t g_max_label_object = 64; static const double smooth_speed_step = 10; static const double not_split_length = scale_(1.0); +static const double max_step_length = scale_(1.0); // cut path if the path too long +static const double min_step_length = scale_(0.4); // cut step Vec2d travel_point_1; Vec2d travel_point_2; @@ -453,6 +455,72 @@ static std::vector get_path_of_change_filament(const Print& print) outer_wall_volumetric_speed = filament_max_volumetric_speed; return outer_wall_volumetric_speed; } + + + // Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code. + // Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. + static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out) + { + temp_out = -1; + if (gcode.empty()) + return false; + + const char *ptr = gcode.data(); + bool temp_set_by_gcode = false; + while (*ptr != 0) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'M' || // Line starts with 'M'. It is a machine command. + (*ptr == 'G' && include_g10)) { // Only check for G10 if requested + bool is_gcode = *ptr == 'G'; + ++ ptr; + // Parse the M or G code value. + char *endptr = nullptr; + int mgcode = int(strtol(ptr, &endptr, 10)); + if (endptr != nullptr && endptr != ptr && + is_gcode ? + // G10 found + mgcode == 10 : + // M104/M109 or M140/M190 found. + (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) { + ptr = endptr; + if (! is_gcode) + // Let the caller know that the custom M-code sets the temperature. + temp_set_by_gcode = true; + // Now try to parse the temperature value. + // While not at the end of the line: + while (strchr(";\r\n\0", *ptr) == nullptr) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'S') { + // Skip whitespaces. + for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); + // Parse an int. + endptr = nullptr; + long temp_parsed = strtol(ptr, &endptr, 10); + if (endptr > ptr) { + ptr = endptr; + temp_out = temp_parsed; + // Let the caller know that the custom G-code sets the temperature + // Only do this after successfully parsing temperature since G10 + // can be used for other reasons + temp_set_by_gcode = true; + } + } else { + // Skip this word. + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + } + } + } + } + // Skip the rest of the line. + for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); + // Skip the end of line indicators. + for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); + } + return temp_set_by_gcode; + } + // QDS // start_pos refers to the last position before the wipe_tower. // end_pos refers to the wipe tower's start_pos. @@ -577,6 +645,7 @@ static std::vector get_path_of_change_filament(const Print& print) std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const { + gcodegen.reset_last_acceleration(); if (new_filament_id != -1 && new_filament_id != tcr.new_tool) throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); @@ -910,6 +979,8 @@ static std::vector get_path_of_change_filament(const Print& print) if (gcodegen.config().enable_pressure_advance.get_at(new_filament_id)) gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_filament_id)); + gcodegen.set_extrude_acceleration(gcodegen.on_first_layer()); + // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d)); @@ -929,6 +1000,7 @@ static std::vector get_path_of_change_filament(const Print& print) // Let the planner know we are traveling between objects. gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + gcodegen.reset_last_acceleration(); return gcode; } @@ -1438,7 +1510,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path))) return; - BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon")%path; + BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon") % PathSanitizer::sanitize(path); GCodeProcessor::s_IsQDTPrinter = print->is_QDT_Printer(); m_writer.set_is_qdt_printer(print->is_QDT_Printer()); @@ -1470,7 +1542,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu fs::path folder = file_path.parent_path(); if (!fs::exists(folder)) { fs::create_directory(folder); - BOOST_LOG_TRIVIAL(error) << "[WARNING]: the parent path " + folder.string() +" is not there, create it!" << std::endl; + BOOST_LOG_TRIVIAL(error) << "[WARNING]: the parent path " + PathSanitizer::sanitize(folder) + " is not there, create it!" << std::endl; } std::string path_tmp(path); @@ -1479,12 +1551,12 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_processor.initialize(path_tmp); GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); if (! file.is_open()) { - BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n" << std::endl; + BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed.\nCannot open the file for writing.\n" << std::endl; if (!fs::exists(folder)) { //fs::create_directory(folder); - BOOST_LOG_TRIVIAL(error) << "the parent path " + folder.string() +" is not there!!!" << std::endl; + BOOST_LOG_TRIVIAL(error) << "the parent path " + PathSanitizer::sanitize(folder) + " is not there!!!" << std::endl; } - throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed.\nCannot open the file for writing.\n"); } try { @@ -1494,7 +1566,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu if (file.is_error()) { file.close(); boost::nowide::remove(path_tmp.c_str()); - throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + PathSanitizer::sanitize(path) + " failed\nIs the disk full?\n"); } } catch (std::exception & /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. @@ -1574,17 +1646,19 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu result->filename = path; } + std::string path_safe = PathSanitizer::sanitize(path); + std::string path_tmp_safe = PathSanitizer::sanitize(path_tmp); //QDS: add some log for error output - BOOST_LOG_TRIVIAL(debug) << boost::format("Finished processing gcode to %1% ") % path_tmp; + BOOST_LOG_TRIVIAL(debug) << boost::format("Finished processing gcode to %1% ") % path_tmp_safe; std::error_code ret = rename_file(path_tmp, path); if (ret) { throw Slic3r::RuntimeError( - std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "error code " + ret.message() + '\n' + - "Is " + path_tmp + " locked?" + '\n'); + std::string("Failed to rename the output G-code file from ") + path_tmp_safe + " to " + path_safe + '\n' + "error code " + ret.message() + '\n' + + "Is " + path_tmp_safe + " locked?" + '\n'); } else { - BOOST_LOG_TRIVIAL(info) << boost::format("rename_file from %1% to %2% successfully")% path_tmp % path; + BOOST_LOG_TRIVIAL(info) << boost::format("rename_file from %1% to %2% successfully")% path_tmp_safe % path_safe; } BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); @@ -2350,6 +2424,14 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_placeholder_parser.set("in_head_wrap_detect_zone", !intersection_pl(project_polys, {head_wrap_detect_zone}).empty()); } + { + coordf_t max_print_z = 0; + for (auto& obj : print.objects()) { + max_print_z = std::max(max_print_z, (*std::max_element(obj->layers().begin(), obj->layers().end(), [](Layer* a, Layer* b) { return a->print_z < b->print_z; }))->print_z); + } + m_placeholder_parser.set("max_print_z", new ConfigOptionInt(std::ceil(max_print_z))); + } + // get center without wipe tower BoundingBoxf bbox_wo_wt;// bounding box without wipe tower for (auto& objPtr : print.objects()) { @@ -2400,7 +2482,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato auto used_filaments = print.get_slice_used_filaments(false); m_placeholder_parser.set("is_all_qdt_filament", std::all_of(used_filaments.begin(), used_filaments.end(), [&](auto idx) { - return m_config.filament_vendor.values[idx] == "Bambu Lab"; + return m_config.filament_vendor.values[idx] == "QIDI Tech"; })); //add during_print_exhaust_fan_speed @@ -2433,8 +2515,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //w21 // QDS: chamber temp control for 3rd printers //if (!is_QDT_Printer() && print.config().support_chamber_temp_control.value && max_chamber_temp > 0 ){ - // file.write(m_writer.set_chamber_temperature(max_chamber_temp,true)); - //} + // int temp_out =0; + // if(!custom_gcode_sets_temperature(machine_start_gcode,141,191,false,temp_out)) + // file.write(m_writer.set_chamber_temperature(max_chamber_temp,true)); + // } // adds tag for processor file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); @@ -2452,7 +2536,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //QDS: mark machine start gcode file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::MachineStartGCodeEnd).c_str()); //w21 - if (print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) { + if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) { file.write(m_writer.set_fan(0)); //QDS: disable additional fan file.write(m_writer.set_additional_fan(0)); @@ -2553,6 +2637,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // OrcaSlicer: calib //if (print.calib_params().mode == CalibMode::Calib_PA_Line) { //std::string gcode; + //gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n"; // if ((m_config.default_acceleration.get_at(cur_extruder_index()) > 0 && m_config.outer_wall_acceleration.get_at(cur_extruder_index()) > 0)) { // m_writer.set_acceleration((unsigned int) floor(m_config.outer_wall_acceleration.get_at(cur_extruder_index()) + 0.5)); // } @@ -2774,7 +2859,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write(m_writer.set_fan(0)); //QDS: make sure the additional fan is closed when end - file.write(m_writer.set_additional_fan(0)); + if (m_config.auxiliary_fan.value) + file.write(m_writer.set_additional_fan(0)); //QDS: close spaghetti detector //Note: M981 is also used to tell xcam the last layer is finished, so we need always send it even if spaghetti option is disabled. //if (print.config().spaghetti_detector.value) @@ -2881,7 +2967,33 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //w25 file.write("\n; CONFIG_BLOCK_START\n"); std::string full_config; - append_full_config(print, full_config); + DynamicPrintConfig print_cfg_temp = print.full_print_config(); + + {//correct the flush_volumes_matrix with flush_multiplier values + std::vector temp_cfg_flush_multiplier = print_cfg_temp.option("flush_multiplier")->values; + std::vector temp_flush_volumes_matrix = print_cfg_temp.option("flush_volumes_matrix")->values; + auto temp_filament_color = print_cfg_temp.option("filament_colour")->values; + size_t heads_count_tmp = temp_cfg_flush_multiplier.size(), + matrix_value_count = temp_flush_volumes_matrix.size() / temp_cfg_flush_multiplier.size(), + filament_count_tmp = temp_filament_color.size(); + if (filament_count_tmp * filament_count_tmp * heads_count_tmp == temp_flush_volumes_matrix.size()) { + for (size_t idx = 0; idx < heads_count_tmp; ++idx) { + double temp_cfg_flush_multiplier_idx = temp_cfg_flush_multiplier[idx]; + size_t temp_begin_t = idx * matrix_value_count, temp_end_t = (idx + 1) * matrix_value_count; + std::transform(temp_flush_volumes_matrix.begin() + temp_begin_t, + temp_flush_volumes_matrix.begin() + temp_end_t, + temp_flush_volumes_matrix.begin() + temp_begin_t, + [temp_cfg_flush_multiplier_idx](double inputx) { return inputx * temp_cfg_flush_multiplier_idx; }); + } + print_cfg_temp.option("flush_volumes_matrix")->values = temp_flush_volumes_matrix; + } else if (filament_count_tmp == 1) { + }// Not applicable to flush matrix situations + else + { // flush_volumes_matrix value count error? + throw Slic3r::SlicingError(_(L("Flush volumes matrix do not match to the correct size!"))); + } + } + append_full_config(print_cfg_temp, full_config); if (!full_config.empty()) file.write(full_config); int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type); @@ -3005,6 +3117,15 @@ size_t GCode::get_extruder_id(unsigned int filament_id) const return 0; } +void GCode::set_extrude_acceleration(bool is_first_layer) +{ + if (is_first_layer) { + m_writer.set_acceleration((unsigned int) floor(m_config.initial_layer_acceleration.get_at(cur_extruder_index()) + 0.5)); + } else { + m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.get_at(cur_extruder_index()) + 0.5)); + } +} + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. @@ -3299,69 +3420,6 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std } } -// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code. -// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. -static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out) -{ - temp_out = -1; - if (gcode.empty()) - return false; - - const char *ptr = gcode.data(); - bool temp_set_by_gcode = false; - while (*ptr != 0) { - // Skip whitespaces. - for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); - if (*ptr == 'M' || // Line starts with 'M'. It is a machine command. - (*ptr == 'G' && include_g10)) { // Only check for G10 if requested - bool is_gcode = *ptr == 'G'; - ++ ptr; - // Parse the M or G code value. - char *endptr = nullptr; - int mgcode = int(strtol(ptr, &endptr, 10)); - if (endptr != nullptr && endptr != ptr && - is_gcode ? - // G10 found - mgcode == 10 : - // M104/M109 or M140/M190 found. - (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) { - ptr = endptr; - if (! is_gcode) - // Let the caller know that the custom M-code sets the temperature. - temp_set_by_gcode = true; - // Now try to parse the temperature value. - // While not at the end of the line: - while (strchr(";\r\n\0", *ptr) == nullptr) { - // Skip whitespaces. - for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); - if (*ptr == 'S') { - // Skip whitespaces. - for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); - // Parse an int. - endptr = nullptr; - long temp_parsed = strtol(ptr, &endptr, 10); - if (endptr > ptr) { - ptr = endptr; - temp_out = temp_parsed; - // Let the caller know that the custom G-code sets the temperature - // Only do this after successfully parsing temperature since G10 - // can be used for other reasons - temp_set_by_gcode = true; - } - } else { - // Skip this word. - for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); - } - } - } - } - // Skip the rest of the line. - for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); - // Skip the end of line indicators. - for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); - } - return temp_set_by_gcode; -} // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. @@ -3864,6 +3922,7 @@ GCode::LayerResult GCode::process_layer( bool has_insert_timelapse_gcode = false; bool has_wipe_tower = (layer_tools.has_wipe_tower && m_wipe_tower); + ZHopType z_hope_type = ZHopType(FILAMENT_CONFIG(z_hop_types)); LiftType auto_lift_type = LiftType::NormalLift; if (z_hope_type == ZHopType::zhtAuto || z_hope_type == ZHopType::zhtSpiral || z_hope_type == ZHopType::zhtSlope) @@ -4309,6 +4368,7 @@ GCode::LayerResult GCode::process_layer( if (print.is_QDT_Printer() && (print.num_object_instances() <= g_max_label_object) && // Don't support too many objects on one plate (print.num_object_instances() > 1) && // Don't support skipping single object + (layer_object_label_ids.size() > 0) && (print.calib_params().mode == CalibMode::Calib_None)) { std::ostringstream oss; for (auto it = layer_object_label_ids.begin(); it != layer_object_label_ids.end(); ++it) { @@ -4687,9 +4747,8 @@ void GCode::apply_print_config(const PrintConfig &print_config) m_scaled_resolution = scaled(print_config.resolution.value); } -void GCode::append_full_config(const Print &print, std::string &str) +void GCode::append_full_config(const DynamicPrintConfig &cfg, std::string &str) { - const DynamicPrintConfig &cfg = print.full_print_config(); // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. static const std::set banned_keys({ "compatible_printers"sv, @@ -4910,7 +4969,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou loop.split_at(last_pos, false); // QDS: not apply on fist layer, too small E has stick issue with hotend plate - int filament_scarf_type = FILAMENT_CONFIG(filament_scarf_seam_type); + bool override_filament_scarf_seam_setting = m_config.override_filament_scarf_seam_setting; + int filament_scarf_type = override_filament_scarf_seam_setting ? int(m_config.seam_slope_type.value) : FILAMENT_CONFIG(filament_scarf_seam_type); bool enable_seam_slope = ((filament_scarf_type == int(SeamScarfType::External) && !is_hole) || filament_scarf_type == int(SeamScarfType::All)) && !m_config.spiral_mode && @@ -4962,15 +5022,23 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (enable_seam_slope) { // Create seam slope double start_slope_ratio; - if (FILAMENT_CONFIG(filament_scarf_height).percent) - start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / 100; - else { - start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / paths.front().height; + if (override_filament_scarf_seam_setting) { + if (m_config.seam_slope_start_height.percent) + start_slope_ratio = m_config.seam_slope_start_height.value / 100; + else { + start_slope_ratio = m_config.seam_slope_start_height.value / paths.front().height; + } + } else { + if (FILAMENT_CONFIG(filament_scarf_height).percent) + start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / 100; + else { + start_slope_ratio = FILAMENT_CONFIG(filament_scarf_height).value / paths.front().height; + } } - float slope_gap = FILAMENT_CONFIG(filament_scarf_gap).get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))); - - double scarf_seam_length = FILAMENT_CONFIG(filament_scarf_length); + float slope_gap = override_filament_scarf_seam_setting ? m_config.seam_slope_gap.get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))) : + FILAMENT_CONFIG(filament_scarf_gap).get_abs_value(scale_(EXTRUDER_CONFIG(nozzle_diameter))); + double scarf_seam_length = override_filament_scarf_seam_setting ? m_config.seam_slope_min_length: FILAMENT_CONFIG(filament_scarf_length); double loop_length = 0.; for (const auto &path : paths) { @@ -4994,7 +5062,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou //new_loop.target_speed = get_path_speed(new_loop.starts.back()); //new_loop.slowdown_slope_speed(); // QDS: smooth speed of discontinuity areas - if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && (loop.role() == erExternalPerimeter || loop.role() == erPerimeter)) + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && loop.is_set_speed_discontinuity_area()) smooth_speed_discontinuity_area(new_loop.paths); // Then extrude it for (const auto &p : new_loop.get_all_paths()) { @@ -5022,7 +5090,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (!enable_seam_slope || slope_has_overhang) { //1.9.5 // QDS: smooth speed of discontinuity areas - if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && (loop.role() == erExternalPerimeter || loop.role() == erPerimeter)) + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && loop.is_set_speed_discontinuity_area()) smooth_speed_discontinuity_area(paths); for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { @@ -5382,37 +5450,24 @@ static bool need_smooth_speed(const ExtrusionPath &other_path, const ExtrusionPa return false; } -ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &final_v, ExtrusionPath &this_path, double max_smooth_length, bool split_from_left) +void GCode::split_and_mapping_speed(double other_path_v, double final_v, ExtrusionPaths &this_path, double max_smooth_length, ExtrusionPaths &interpolated_paths, bool split_from_left) { + if (this_path.empty() || max_smooth_length == 0) + return; + ExtrusionPaths splited_path; - if (this_path.length() <= 0 || this_path.polyline.points.size() < 2) { - return splited_path; - } - - // reverse if this slowdown the speed - Polyline input_polyline = this_path.polyline; - if (!split_from_left) - std::reverse(input_polyline.begin(), input_polyline.end()); - - double this_path_x = scale_(get_speed_coor_x(final_v)); - double x_base = scale_(get_speed_coor_x(other_path_v)); - - double smooth_length = this_path_x - x_base; - - // this length not support to get final v, adjust final v - if (smooth_length > max_smooth_length) - final_v = mapping_speed(unscale_(x_base + max_smooth_length)); - - double max_step_length = scale_(1.0); // cut path if the path too long - double min_step_length = scale_(0.4); // cut step + // get params + double this_path_x = scale_(get_speed_coor_x(final_v)); + double x_base = scale_(get_speed_coor_x(other_path_v)); + double smooth_length = this_path_x - x_base; double smooth_length_count = 0; double split_line_speed = 0; - Point line_start_pt = input_polyline.points.front(); - Point line_end_pt = input_polyline.points[1]; - bool get_next_line = false; - size_t end_pt_idx = 1; - + // this length not support to get final v, adjust final v + if( smooth_length > max_smooth_length ) { + smooth_length = max_smooth_length; + final_v = mapping_speed(unscale_(x_base + max_smooth_length)); + } auto insert_speed = [this](double line_lenght, double &pos_x, double &smooth_length_count, double target_v) { pos_x += line_lenght; double pos_x_speed = mapping_speed(unscale_(pos_x)); @@ -5424,139 +5479,258 @@ ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &fina return pos_x_speed; }; - while (split_line_speed < final_v && end_pt_idx < input_polyline.size()) { - // move to next line - if (get_next_line) { - line_start_pt = input_polyline.points[end_pt_idx - 1]; - line_end_pt = input_polyline.points[end_pt_idx]; - } - //This line is cut off as a speed transition area - Polyline cuted_polyline; - Line line(line_start_pt, line_end_pt); + //check line length + auto length_enough = [this](double length) { + if (length < min_step_length || length - min_step_length < min_step_length / 2) + return false; - cuted_polyline.append(line_start_pt); - // split polyline and set speed - if (line.length() < max_step_length || line.length() - min_step_length < min_step_length / 2) { - split_line_speed = insert_speed(line.length(), x_base, smooth_length_count, final_v); - end_pt_idx++; - get_next_line = true; - cuted_polyline.append(line.b); + return true; + }; + + ExtrusionPaths left_paths; + for (int idx = 0; idx < this_path.size(); idx++) { + ExtrusionPath &extrusion = this_path[idx]; + + // just in case, polyline error + if (extrusion.polyline.points.size() < 2) + continue; + + // stop and push the paths back + if (smooth_length_count >= smooth_length) { + left_paths.insert(left_paths.end(), this_path.begin() + idx, this_path.end()); + this_path = std::move(left_paths); + break; + } + + // the path is too short + if (!length_enough(extrusion.length())) { + split_line_speed = insert_speed(extrusion.length(), x_base, smooth_length_count, final_v); + splited_path.emplace_back(extrusion); + splited_path.back().smooth_speed = split_line_speed; + // clear this_path while the extrusion is last one + if (idx < this_path.size() - 1) + continue; + + this_path.clear(); + break; + } + + // reverse if this slowdown the speed + Polyline input_polyline = extrusion.polyline; + if (!split_from_left) + std::reverse(input_polyline.begin(), input_polyline.end()); + + Point line_start_pt = input_polyline.points.front(); + Point line_end_pt = input_polyline.points[1]; + bool get_next_line = false; + size_t end_pt_idx = 1; + + // split long extrusion + Point last_point = line_start_pt; + while (split_line_speed < final_v && end_pt_idx < input_polyline.size()) { + // move to next line + if (get_next_line) { + line_start_pt = input_polyline.points[end_pt_idx - 1]; + line_end_pt = input_polyline.points[end_pt_idx]; + } + // This line is cut off as a speed transition area + Polyline cuted_polyline; + Line line(line_start_pt, line_end_pt); + + cuted_polyline.append(line_start_pt); + // split polyline and set speed + if (!length_enough(line.length())) { + split_line_speed = insert_speed(line.length(), x_base, smooth_length_count, final_v); + end_pt_idx++; + get_next_line = true; + cuted_polyline.append(line.b); + } else { + // path is too long, split it + double rate = min_step_length / line.length(); + Point insert_p = line.a + (line.b - line.a) * rate; + + split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v); + line_start_pt = insert_p; + get_next_line = false; + cuted_polyline.append(insert_p); + } + // reverse back + last_point = cuted_polyline.last_point(); + if (!split_from_left) + std::reverse(cuted_polyline.begin(), cuted_polyline.end()); + ExtrusionPath path_step(cuted_polyline, extrusion); + + path_step.smooth_speed = split_line_speed; + splited_path.push_back(std::move(path_step)); + } + + if (last_point == input_polyline.last_point()){ + if (idx == this_path.size() - 1) + this_path.clear(); + continue; + } + // split polyline + Polyline p1, p2; + extrusion.polyline.split_at(last_point, &p1, &p2); + + if (split_from_left) { + //update split point to avoid travel path + splited_path.back().polyline.points.back() = last_point; + ExtrusionPath polyline_left(p2, extrusion); + left_paths.emplace_back(polyline_left); } else { - // path is too long, split it - double rate = min_step_length / line.length(); - Point insert_p = line.a + (line.b - line.a) * rate; - - split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v); - line_start_pt = insert_p; - get_next_line = false; - cuted_polyline.append(insert_p); + splited_path.back().polyline.points.front() = last_point; + ExtrusionPath polyline_left(p1, extrusion); + left_paths.emplace_back(polyline_left); } - ExtrusionPath path_step(cuted_polyline, this_path); - path_step.smooth_speed = split_line_speed; - splited_path.push_back(std::move(path_step)); + left_paths.insert(left_paths.end(), this_path.begin() + idx + 1, this_path.end()); + + this_path = std::move(left_paths); + break; } - // reverse path back - Polyline p1, p2; - Point & split_point = splited_path.back().polyline.points.back(); - this_path.polyline.split_at(split_point, &p1, &p2); - if (!split_from_left) { - this_path.polyline = p1; - std::reverse(splited_path.begin(), splited_path.end()); - for (ExtrusionPath &path : splited_path) { std::reverse(path.polyline.points.begin(), path.polyline.points.end()); } - } else { - this_path.polyline = p2; - } + // set left path speed + if (!this_path.empty() && final_v != this_path.front().smooth_speed) + for (ExtrusionPath &left : this_path) + left.smooth_speed = final_v; - return splited_path; + interpolated_paths.insert(interpolated_paths.end(), splited_path.begin(), splited_path.end()); + + return; } -ExtrusionPaths GCode::merge_same_speed_paths(const ExtrusionPaths &paths) +std::vector GCode::merge_same_speed_paths(const ExtrusionPaths &paths) { - ExtrusionPaths output_paths; - std::optional merged_path; + std::vector paths_categroy_by_speed; - for(size_t path_idx=0;path_idx path_collection; + + for(size_t path_idx=0; path_idxcan_merge(path)){ - merged_path->polyline.append(path.polyline); - } - else{ - output_paths.push_back(std::move(*merged_path)); - merged_path = path; + if(path_collection->back().can_merge(path)){ + path_collection->emplace_back(path); + } else { + paths_categroy_by_speed.emplace_back(std::move(*path_collection)); + path_collection = std::nullopt; + path_collection = {path}; } } - if(merged_path.has_value()) - output_paths.push_back(std::move(*merged_path)); + if (path_collection.has_value()) + paths_categroy_by_speed.emplace_back(std::move(*path_collection)); - return output_paths; + return paths_categroy_by_speed; } -ExtrusionPaths GCode::set_speed_transition(ExtrusionPaths &paths) +ExtrusionPaths GCode::set_speed_transition(std::vector &paths) { ExtrusionPaths interpolated_paths; for (int path_idx = 0; path_idx < paths.size(); path_idx++) { // update path - ExtrusionPath &path = paths[path_idx]; - - double this_path_speed = 0; + ExtrusionPaths &path = paths[path_idx]; //paths with same speed // 100% overhang speed will not to set smooth speed - if (path.role() == erOverhangPerimeter) { - interpolated_paths.push_back(path); + // overhang path will not be merged to a path collection + if (path.front().role() == erOverhangPerimeter) { + interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); continue; } - bool smooth_left_path = false; - bool smooth_right_path = false; - // first line do not need to smooth speed on left - // prev line speed may change - if (path_idx > 0) - smooth_left_path = need_smooth_speed(paths[path_idx - 1], path); + if (path.empty()) + continue; - // first line do not need to smooth speed on right - if (path_idx < paths.size() - 1) - smooth_right_path = need_smooth_speed(paths[path_idx + 1], path); + bool smooth_left_path = path_idx > 0 && !interpolated_paths.empty() && need_smooth_speed(interpolated_paths.back(), path.front()); + bool smooth_right_path = path_idx < paths.size() - 1 && need_smooth_speed(paths[path_idx + 1].front(), path.front()); + + if (!smooth_left_path && !smooth_right_path) { + interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); + continue; + } // get smooth length - double max_smooth_path_length = path.length(); - if (smooth_right_path && smooth_left_path) max_smooth_path_length /= 2; + auto get_path_length = [this](ExtrusionPaths path) { + return std::accumulate(path.begin(), path.end(), 0.0, [](double sum, const ExtrusionPath &p) { return sum + p.length(); }); + }; + + double max_smooth_path_length = get_path_length(path); + + if (smooth_right_path && smooth_left_path) + max_smooth_path_length /= 2; // smooth left ExtrusionPaths left_split_paths; - if (smooth_left_path) { - left_split_paths = split_and_mapping_speed(paths[path_idx - 1].smooth_speed, path.smooth_speed, path, max_smooth_path_length); - if (!left_split_paths.empty()) interpolated_paths.insert(interpolated_paths.end(), left_split_paths.begin(), left_split_paths.end()); - max_smooth_path_length = path.length(); + if (smooth_left_path ) { + split_and_mapping_speed(interpolated_paths.back().smooth_speed, path.front().smooth_speed, path, max_smooth_path_length, interpolated_paths); + + //update path length + if (path.empty()) + continue; + + max_smooth_path_length = get_path_length(path); } + if (!smooth_right_path) { + interpolated_paths.insert(interpolated_paths.end(), path.begin(), path.end()); + continue; + } + + //QDS: FIX git 4827: if the path is too short, path is not smooth enough // smooth right ExtrusionPaths right_split_paths; - if (smooth_right_path) { - right_split_paths = split_and_mapping_speed(paths[path_idx + 1].smooth_speed, path.smooth_speed, path, max_smooth_path_length, false); } + size_t right_end = path_idx + 1; + // get smoothing window + std::vector paths_cpoy; + paths_cpoy.push_back(&path); - if (!path.empty()) - interpolated_paths.push_back(path); + for (; right_end < paths.size() - 1; right_end++) { + if (paths[right_end].front().role() == erOverhangPerimeter) + break; + + paths_cpoy.push_back(&paths[right_end]); + if (!need_smooth_speed(paths[right_end + 1].front(), paths[right_end].front())) + break; + } + + path_idx += paths_cpoy.size() - 1; + double prev_speed = paths[path_idx + 1].front().smooth_speed; + + // reverse paths + std::reverse(paths_cpoy.begin(), paths_cpoy.end()); + for (ExtrusionPaths *paths_temp : paths_cpoy) { std::reverse(paths_temp->begin(), paths_temp->end()); } + + // smooth right path + ExtrusionPaths transition_right; + for (size_t win_pt = 0; win_pt < paths_cpoy.size(); win_pt++) { + if (win_pt != 0) { + prev_speed = transition_right.back().smooth_speed; + } + + split_and_mapping_speed(prev_speed, paths_cpoy[win_pt]->front().smooth_speed, *paths_cpoy[win_pt], get_path_length(*paths_cpoy[win_pt]), transition_right, false); + + transition_right.insert(transition_right.end(), paths_cpoy[win_pt]->begin(), paths_cpoy[win_pt]->end()); + } + std::reverse(transition_right.begin(), transition_right.end()); + + // reverse paths back + interpolated_paths.insert(interpolated_paths.end(), transition_right.begin(), transition_right.end()); - if (!right_split_paths.empty()) - interpolated_paths.insert(interpolated_paths.end(), right_split_paths.begin(), right_split_paths.end()); } return interpolated_paths; @@ -5569,13 +5743,59 @@ void GCode::smooth_speed_discontinuity_area(ExtrusionPaths &paths) { //step 1 merge same speed path size_t path_tail_pos = 0; - ExtrusionPaths prepare_paths = merge_same_speed_paths(paths); + std::vector prepare_paths = merge_same_speed_paths(paths); //step 2 split path ExtrusionPaths inter_paths; - inter_paths =set_speed_transition(prepare_paths); + inter_paths = set_speed_transition(prepare_paths); paths = std::move(inter_paths); } +bool GCode::slowDownByHeight(double& maxSpeed, double& maxAcc, const ExtrusionPath& path) +{ + double height1, height2, speed1, speed2, acc1, acc2, desiredMaxSpeed = 1000., desiredMaxAcc = 100000; + double currentHeight = this->m_layer->print_z; + bool do_slowdown_by_height = m_config.enable_height_slowdown.get_at(cur_extruder_index()); + + if (path.role() > erNone && path.role() <= erGapFill) {} + else do_slowdown_by_height = false; + + if (do_slowdown_by_height) { + height1 = m_config.slowdown_start_height.get_at(cur_extruder_index()); + height2 = m_config.slowdown_end_height.get_at(cur_extruder_index()); + speed1 = m_config.slowdown_start_speed.get_at(cur_extruder_index()); + speed2 = m_config.slowdown_end_speed.get_at(cur_extruder_index()); + acc1 = m_config.slowdown_start_acc.get_at(cur_extruder_index()); + acc2 = m_config.slowdown_end_acc.get_at(cur_extruder_index()); + + if (height1 >= height2 || currentHeight > height2 || currentHeight < height1) do_slowdown_by_height = false; + else { + // speed should be decreased linearly + desiredMaxSpeed = (currentHeight - height1) / (height2 - height1) * (speed2 - speed1) + speed1; + // acceleration should be decreased linearly + desiredMaxAcc = acc1 - (acc1 - acc2) / (height2 - height1) * (currentHeight - height1); + + // modify travel speed and acceleration + for (auto& av : m_writer.config.travel_speed.values) { + if (!std::isnan(av)) { + av = std::min(av, desiredMaxSpeed); + } + } + for (auto& bv : m_writer.config.travel_speed_z.values) { + if (!std::isnan(bv)) { + bv = std::min(bv, desiredMaxSpeed); + } + } + for (auto& ta : m_writer.get_travel_acceleration()) { + ta = std::min(ta, (unsigned int)desiredMaxAcc); + } + if (!is_QDT_Printer()) + m_config.travel_jerk.value = std::min(m_config.travel_jerk.value, desiredMaxSpeed); + } + } + maxSpeed = desiredMaxSpeed; + maxAcc = desiredMaxAcc; + return do_slowdown_by_height; +} std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed, bool use_seperate_speed, bool is_first_slope) { @@ -5590,6 +5810,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, return lerp(m_nominal_z - height, m_nominal_z, z_ratio); }; + auto temp_travel_speed = m_writer.config.travel_speed; + auto temp_travel_speed_z = m_writer.config.travel_speed_z; + auto temp_travel_jerk = m_config.travel_jerk; + auto temp_travel_acc = m_writer.get_travel_acceleration(); + double desiredMaxSpeed, desiredMaxAcc; + + bool do_slowdown_by_height = slowDownByHeight(desiredMaxSpeed, desiredMaxAcc, path); + // go to first point of extrusion path //QDS: path.first_point is 2D point. But in lazy raise case, lift z is done in travel_to function. //Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance @@ -5603,6 +5831,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, m_need_change_layer_lift_z = false; } + // restore travel speed and acceleration + if (do_slowdown_by_height) { + m_writer.config.travel_speed = temp_travel_speed; + m_writer.config.travel_speed_z = temp_travel_speed_z; + m_writer.set_travel_acceleration(temp_travel_acc); + if (!is_QDT_Printer()) + m_config.travel_jerk = temp_travel_jerk; + } + // if needed, write the gcode_label_objects_end then gcode_label_objects_start // should be already done by travel_to, but just in case m_writer.add_object_change_labels(gcode); @@ -5636,6 +5873,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else { acceleration = m_config.default_acceleration.get_at(cur_extruder_index()); } + if (do_slowdown_by_height) + acceleration = std::min(acceleration, desiredMaxAcc); m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } @@ -5653,6 +5892,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, else if (m_config.top_surface_jerk.value > 0 && is_top_surface(path.role())) jerk = m_config.top_surface_jerk.value; + if (do_slowdown_by_height) + jerk = std::min(jerk, desiredMaxSpeed); gcode += m_writer.set_jerk_xy(jerk); } // calculate extrusion length per distance unit @@ -5765,6 +6006,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min(speed, extrude_speed); } + + if (do_slowdown_by_height) + speed = std::min(speed, desiredMaxSpeed); //w16 //y58 if (m_resonance_avoidance && path.role() == erExternalPerimeter) { if (speed <= m_config.max_resonance_avoidance_speed.get_at(cur_extruder_index())) { @@ -6063,6 +6307,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // } } + // if needed, write the gcode_label_objects_end then gcode_label_objects_start m_writer.add_object_change_labels(gcode); @@ -6113,6 +6358,11 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string return gcode; } +void GCode::reset_last_acceleration() +{ + m_writer.reset_last_acceleration(); +} + //QDS LiftType GCode::to_lift_type(ZHopType z_hop_types) { switch (z_hop_types) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index a84f9bf..2db613c 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -220,7 +220,7 @@ public: std::string set_object_info(Print* print); // append full config to the given string - static void append_full_config(const Print& print, std::string& str); + static void append_full_config(const DynamicPrintConfig &cfg, std::string &str); // QDS: detect lift type in needs_retraction bool needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType &lift_type); @@ -385,6 +385,7 @@ private: void check_placeholder_parser_failed(); size_t cur_extruder_index() const; size_t get_extruder_id(unsigned int filament_id) const; + void set_extrude_acceleration(bool is_first_layer); void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } void set_last_scarf_seam_flag(bool flag) { m_last_scarf_seam_flag = flag; } @@ -400,7 +401,11 @@ private: //smooth speed function void smooth_speed_discontinuity_area(ExtrusionPaths &paths); - ExtrusionPaths merge_same_speed_paths(const ExtrusionPaths &paths); + std::vector merge_same_speed_paths(const ExtrusionPaths &paths); + + // slow down by height + bool slowDownByHeight(double& maxSpeed, double& maxAcc, const ExtrusionPath& path); + // Extruding multiple objects with soluble / non-soluble / combined supports // on a multi-material printer, trying to minimize tool switches. // Following structures sort extrusions by the extruder ID, by an order of objects and object islands. @@ -470,6 +475,8 @@ private: std::string extrude_support(const ExtrusionEntityCollection &support_fills); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment, double z = DBL_MAX); + + void reset_last_acceleration(); // QDS LiftType to_lift_type(ZHopType z_hop_types); @@ -587,8 +594,8 @@ private: int get_highest_bed_temperature(const bool is_first_layer,const Print &print) const; std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1, bool set_holes_and_compensation_speed = false, bool is_first_slope = false); - ExtrusionPaths set_speed_transition(ExtrusionPaths &paths); - ExtrusionPaths split_and_mapping_speed(double &other_path_v, double &final_v, ExtrusionPath &this_path, double max_smooth_length, bool split_from_left = true); + ExtrusionPaths set_speed_transition(std::vector &paths); + void split_and_mapping_speed(double other_path_v, double final_v, ExtrusionPaths &this_path, double max_smooth_length, ExtrusionPaths &interpolated_paths, bool split_from_left = true); double get_path_speed(const ExtrusionPath &path); double get_overhang_degree_corr_speed(float speed, double path_degree); double mapping_speed(double dist); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 99221a5..3aef4fa 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -20,6 +20,8 @@ struct TravelPoint Point point; // Index of the polygon containing this point. A negative value indicates that the point is not on any border. int border_idx; + // simplify_travel() doesn't remove this point. + bool do_not_remove = false; }; struct Intersection @@ -32,6 +34,8 @@ struct Intersection Point point; // Distance from the first point in the corresponding boundary float distance; + // simplify_travel() doesn't remove this point. + bool do_not_remove = false; }; struct ClosestLine @@ -207,8 +211,8 @@ static std::vector extend_for_closest_lines(const std::vector new_intersections; - new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}); - new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}); + new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true}); + new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true}); return new_intersections; } } @@ -259,7 +263,7 @@ static std::vector extend_for_closest_lines(const std::vector::max()) { // If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine. const ClosestLine &cl_start = start_lines[cl_start_idx]; - new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}; + new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true}; } else { // Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the // vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which @@ -267,7 +271,7 @@ static std::vector extend_for_closest_lines(const std::vector::max()) ? start_lines[start_closest_lines_idx] : start_lines.front(); - new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)}); + new_intersections.insert(new_intersections.begin(), {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true}); } } @@ -276,7 +280,7 @@ static std::vector extend_for_closest_lines(const std::vector::max()) { // If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine. const ClosestLine &cl_end = end_lines[cl_end_idx]; - new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}; + new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true}; } else { // Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the // vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which @@ -284,7 +288,7 @@ static std::vector extend_for_closest_lines(const std::vector::max()) ? end_lines[end_closest_lines_idx] : end_lines.front(); - new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)}); + new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true}); } } return new_intersections; @@ -443,21 +447,28 @@ static std::vector simplify_travel(const AvoidCrossingPerimeters::B visitor.pt_current = ¤t_point; - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel[point_idx_2].point == current_point) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - continue; - } + if (!next.do_not_remove) + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + // Workaround for some issue in MSVC 19.29.30037 32-bit compiler. +#if defined(_WIN32) && !defined(_WIN64) + if (bool volatile do_not_remove = travel[point_idx_2].do_not_remove; do_not_remove) break; +#else + if (travel[point_idx_2].do_not_remove) break; +#endif + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + continue; + } - visitor.pt_next = &travel[point_idx_2].point; - boundary.grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel[point_idx_2]; - point_idx = point_idx_2; + visitor.pt_next = &travel[point_idx_2].point; + boundary.grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + } } - } simplified_path.emplace_back(next); } @@ -517,146 +528,22 @@ static float get_perimeter_spacing_external(const Layer &layer) return perimeter_spacing; } - -// Called by avoid_perimeters() and by simplify_travel_heuristics(). -static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &init_boundary, - const AvoidCrossingPerimeters::Boundary &boundary, - const Point &start_point, - const Point &end_point, - const Layer &layer, - std::vector &result_out) +static float get_external_perimeter_width(const Layer &layer) { - const Polygons &boundaries = boundary.boundaries; - const EdgeGrid::Grid &edge_grid = boundary.grid; - Point start = start_point, end = end_point; - // Find all intersections between boundaries and the line segment, sort them along the line segment. - std::vector intersections; - { - intersections.reserve(boundaries.size()); - AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); - edge_grid.visit_cells_intersecting_line(start, end, visitor); - Vec2d dir = (end - start).cast(); - // if do not intersect due to the boundaries inner-offset, try to find the closest point to do intersect again! - if (intersections.empty()) { - // try to find the closest point on boundaries to start/end with distance less than extend_distance, which is noted as new start_point/end_point - auto search_radius = 1.5 * get_perimeter_spacing(layer); - const std::vector closest_line_to_start = get_closest_lines_in_radius(boundary.grid, start, search_radius); - const std::vector closest_line_to_end = get_closest_lines_in_radius(boundary.grid, end, search_radius); - if (!(closest_line_to_start.empty() && closest_line_to_end.empty())) { - auto new_start_point = closest_line_to_start.empty() ? start : closest_line_to_start.front().point; - auto new_end_point = closest_line_to_end.empty() ? end : closest_line_to_end.front().point; - dir = (new_end_point - new_start_point).cast(); - auto unit_direction = dir.normalized(); - // out-offset new_start_point/new_end_point epsilon along the Line(new_start_point, new_end_point) for right intersection! - new_start_point = new_start_point - (unit_direction * double(coord_t(SCALED_EPSILON))).cast(); - new_end_point = new_end_point + (unit_direction * double(coord_t(SCALED_EPSILON))).cast(); - AllIntersectionsVisitor visitor(edge_grid, intersections, Line(new_start_point, new_end_point)); - edge_grid.visit_cells_intersecting_line(new_start_point, new_end_point, visitor); - if (!intersections.empty()) { - start = new_start_point; - end = new_end_point; - } - } + size_t regions_count = 0; + float perimeter_width = 0.f; + for (const LayerRegion *layer_region : layer.regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_width += float(layer_region->flow(frExternalPerimeter).scaled_width()); + ++regions_count; } - for (Intersection &intersection : intersections) { - float dist_from_line_begin = (intersection.point - boundary.boundaries[intersection.border_idx][intersection.line_idx]).cast().norm(); - intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin; - } - std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); - - // Search radius should always be at least equals to the value of offset used for computing boundaries. - const float search_radius = 2.f * get_perimeter_spacing(layer); - // When the offset is too big, then original travel doesn't have to cross created boundaries. - // These cases are fixed by calling extend_for_closest_lines. - intersections = extend_for_closest_lines(intersections, boundary, start, end, search_radius); - } - - std::vector result; - result.push_back({start, -1}); - -#if 0 - auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) { - const Polygon &poly = boundary.boundaries[intersection.border_idx]; - Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast(); - Vec2d intersection_vec = (intersection.point - start).cast(); - return poly_line.normalized().dot(intersection_vec.normalized()) >= 0; - }; -#endif - - for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { - // The entry point to the boundary polygon - const Intersection &intersection_first = *it_first; -// if(!crossing_boundary_from_inside(start, intersection_first)) -// continue; - // Skip the it_first from the search for the farthest exit point from the boundary polygon - auto it_last_item = std::make_reverse_iterator(it_first) - 1; - // Search for the farthest intersection different from it_first but with the same border_idx - auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) { - return intersection_first.border_idx == intersection.border_idx; - }); - - // Append the first intersection into the path - size_t left_idx = intersection_first.line_idx; - size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1; - // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the - // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the - // appended point will be inside the polygon and not on the polygon border. - result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); - - // Check if intersection line also exit the boundary polygon - if (it_second_r != it_last_item) { - // Transform reverse iterator to forward - auto it_second = it_second_r.base() - 1; - // The exit point from the boundary polygon - const Intersection &intersection_second = *it_second; - Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second, - boundary.boundaries_params[intersection_first.border_idx].back()); - // Append the path around the border into the path - if (shortest_direction == Direction::Forward) - for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0) - result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); - else - for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1) - result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); - - // Append the farthest intersection into the path - left_idx = intersection_second.line_idx; - right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)}); - // Skip intersections in between - it_first = it_second; - } - } - - result.push_back({end, -1}); - - -#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT - { - static int iRun = 0; - export_travel_to_svg(boundaries, Line(start, end), result, intersections, debug_out_path("AvoidCrossingPerimetersInner-initial-%d-%d.svg", layer.id(), iRun++)); - } -#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - - if (! intersections.empty()) { - if (!init_boundary.boundaries.empty()) result = simplify_travel(init_boundary, result); - else result = simplify_travel(boundary, result); - } - -#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT - { - static int iRun = 0; - export_travel_to_svg(boundaries, Line(start, end), result, intersections, - debug_out_path("AvoidCrossingPerimetersInner-final-%d-%d.svg", layer.id(), iRun++)); - } -#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - - append(result_out, std::move(result)); - return intersections.size(); + assert(perimeter_width >= 0.f); + if (regions_count != 0) + perimeter_width /= float(regions_count); + else + perimeter_width = get_default_perimeter_spacing(*layer.object()); + return perimeter_width; } static size_t avoid_perimeters_inner( @@ -738,7 +625,7 @@ static size_t avoid_perimeters_inner( // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the // appended point will be inside the polygon and not on the polygon border. result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), - int(intersection_first.border_idx)}); + int(intersection_first.border_idx), intersection_first.do_not_remove}); // Check if intersection line also exit the boundary polygon if (it_second_r != it_last_item) { @@ -766,7 +653,7 @@ static size_t avoid_perimeters_inner( left_idx = intersection_second.line_idx; right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), - int(intersection_second.border_idx)}); + int(intersection_second.border_idx), intersection_second.do_not_remove}); // Skip intersections in between it_first = it_second; } @@ -800,31 +687,6 @@ static size_t avoid_perimeters_inner( return intersections.size(); } - -// Called by AvoidCrossingPerimeters::travel_to() -static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &init_boundary, const AvoidCrossingPerimeters::Boundary &boundary, - const Point &start, - const Point &end, - const Layer &layer, - Polyline &result_out) -{ - // Travel line is completely or partially inside the bounding box. - std::vector path; - size_t num_intersections = avoid_perimeters_inner(init_boundary, boundary, start, end, layer, path); - result_out = to_polyline(path); - - -#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT - { - static int iRun = 0; - export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d-%d.svg", layer.id(), iRun ++)); - } -#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - - return num_intersections; -} - - // Called by AvoidCrossingPerimeters::travel_to() static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, @@ -903,14 +765,14 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec return false; } -static bool need_wipe(const GCode &gcodegen, - const EdgeGrid::Grid &grid_lslice, - const Line &original_travel, - const Polyline &result_travel, - const size_t intersection_count) +static bool need_wipe(const GCode &gcodegen, + const ExPolygons &lslices_offset, + const std::vector &lslices_offset_bboxes, + const EdgeGrid::Grid &grid_lslices_offset, + const Line &original_travel, + const Polyline &result_travel, + const size_t intersection_count) { - const ExPolygons &lslices = gcodegen.layer()->lslices; - const std::vector &lslices_bboxes = gcodegen.layer()->lslices_bboxes; bool z_lift_enabled = gcodegen.config().z_hop.get_at(gcodegen.writer().filament()->id()) > 0.; bool wipe_needed = false; @@ -920,16 +782,16 @@ static bool need_wipe(const GCode &gcodegen, // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. if (z_lift_enabled) { - if (any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, original_travel)) { + if (any_expolygon_contains(lslices_offset, lslices_offset_bboxes, grid_lslices_offset, original_travel)) { // Check if original_travel and result_travel are not same. // If both are the same, then it is possible to skip testing of result_travel wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && - !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + !any_expolygon_contains(lslices_offset, lslices_offset_bboxes, grid_lslices_offset, result_travel); } else { wipe_needed = true; } } else { - wipe_needed = !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + wipe_needed = !any_expolygon_contains(lslices_offset, lslices_offset_bboxes, grid_lslices_offset, result_travel); } } @@ -1149,57 +1011,84 @@ static std::vector contour_distance(const EdgeGrid::Grid &grid, // Polygon offset which ensures that if a polygon breaks up into several separate parts, the original polygon will be used in these places. // ExPolygons are handled one by one so returned ExPolygons could intersect. -static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset) +static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset_dis) { - double min_contour_width = 2. * offset + SCALED_EPSILON; - double search_radius = 2. * (offset + min_contour_width); - ExPolygons ex_poly_result = ex_polygons; - resample_expolygons(ex_poly_result, offset / 2, scaled(0.5)); + // try different offset_dis distances + const std::vector min_contour_width_values = {offset_dis / 2., offset_dis, 2. * offset_dis + SCALED_EPSILON}; + ExPolygons ex_poly_result = ex_polygons; + // remove too small holes from the ex_poly + for (ExPolygon &ex_poly : ex_poly_result) { + for (auto iter = ex_poly.holes.begin(); iter != ex_poly.holes.end();) { + auto out_offset_holes = offset(*iter, scale_(1.0f)); + if (out_offset_holes.empty()) { + iter = ex_poly.holes.erase(iter); + } else { + ++iter; + } + } + } + resample_expolygons(ex_poly_result, offset_dis / 2, scaled(0.5)); for (ExPolygon &ex_poly : ex_poly_result) { BoundingBox bbox(get_extents(ex_poly)); bbox.offset(SCALED_EPSILON); - EdgeGrid::Grid grid; - grid.set_bbox(bbox); - grid.create(ex_poly, coord_t(0.7 * search_radius)); - std::vector> ex_poly_distances; - precompute_expolygon_distances(ex_poly, ex_poly_distances); + // Filter out expolygons smaller than 0.1mm^2 + if (Vec2d bbox_size = bbox.size().cast(); bbox_size.x() * bbox_size.y() < Slic3r::sqr(scale_(0.1f))) continue; - std::vector> offsets; - offsets.reserve(ex_poly.holes.size() + 1); - for (size_t idx_contour = 0; idx_contour <= ex_poly.holes.size(); ++idx_contour) { - const Polygon &poly = (idx_contour == 0) ? ex_poly.contour : ex_poly.holes[idx_contour - 1]; - assert(poly.is_counter_clockwise() == (idx_contour == 0)); - std::vector distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset, search_radius); - for (float &distance : distances) { - if (distance < min_contour_width) - distance = 0.f; - else if (distance > min_contour_width + 2. * offset) - distance = - float(offset); - else - distance = - (distance - float(min_contour_width)) / 2.f; - } - offsets.emplace_back(distances); - } + for (const double &min_contour_width : min_contour_width_values) { + const size_t min_contour_width_idx = &min_contour_width - &min_contour_width_values.front(); + const double search_radius = 2. * (offset_dis + min_contour_width); - ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets); - // If variable_offset_inner_ex produces empty result, then original ex_polygon is used - if (offset_ex_poly.size() == 1) { - ex_poly = std::move(offset_ex_poly.front()); - } else if (offset_ex_poly.size() > 1) { - // fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces - // tiny artefacts polygons, so these artefacts are removed. - double max_area = offset_ex_poly.front().area(); - size_t max_area_idx = 0; - for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) { - double area = offset_ex_poly[poly_idx].area(); - if (max_area < area) { - max_area = area; - max_area_idx = poly_idx; + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(ex_poly, coord_t(0.7 * search_radius)); + + std::vector> ex_poly_distances; + precompute_expolygon_distances(ex_poly, ex_poly_distances); + + std::vector> offsets; + offsets.reserve(ex_poly.holes.size() + 1); + for (size_t idx_contour = 0; idx_contour <= ex_poly.holes.size(); ++idx_contour) { + const Polygon &poly = (idx_contour == 0) ? ex_poly.contour : ex_poly.holes[idx_contour - 1]; + assert(poly.is_counter_clockwise() == (idx_contour == 0)); + std::vector distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset_dis, search_radius); + for (float &distance : distances) { + if (distance < min_contour_width) + distance = 0.0f; + else if (distance > min_contour_width + 2. * offset_dis) + distance = -float(offset_dis); + else + distance = -(distance - float(min_contour_width)) / 2.f; } + offsets.emplace_back(distances); + } + + ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets); + // If variable_offset_inner_ex produces empty result, then original ex_polygon is used + if (offset_ex_poly.size() == 1 && offset_ex_poly.front().holes.size() == ex_poly.holes.size()) { + ex_poly = std::move(offset_ex_poly.front()); + break; + } else if ((min_contour_width_idx + 1) < min_contour_width_values.size()) { + continue; // Try the next round with bigger min_contour_width. + } else if (offset_ex_poly.size() == 1) { + ex_poly = std::move(offset_ex_poly.front()); + break; + } else if (offset_ex_poly.size() > 1) { + // fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces + // tiny artefacts polygons, so these artefacts are removed. + double max_area = offset_ex_poly.front().area(); + size_t max_area_idx = 0; + for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) { + double area = offset_ex_poly[poly_idx].area(); + if (max_area < area) { + max_area = area; + max_area_idx = poly_idx; + } + } + ex_poly = std::move(offset_ex_poly[max_area_idx]); + break; } - ex_poly = std::move(offset_ex_poly[max_area_idx]); } } return ex_poly_result; @@ -1238,49 +1127,12 @@ static ExPolygons get_boundary(const Layer &layer, float perimeter_spacing) if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + return diff_ex(boundary, offset_ex(top_layer_polygons, -1.2 * perimeter_offset)); } return boundary; } -// called by AvoidCrossingPerimeters::travel_to() -static ExPolygons get_slice_boundary_internal(const Layer &layer) -{ - auto const *support_layer = dynamic_cast(&layer); - ExPolygons boundary = layer.lslices; - if(support_layer) { -#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY - append(boundary, support_layer->support_islands); -#endif - auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON); - if (layer_below) - append(boundary, layer_below->lslices); - // After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons - boundary = union_ex(boundary); - } - // Collect all top layers that will not be crossed. - size_t polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) ++polygons_count; - - if (polygons_count > 0) { - ExPolygons top_layer_polygons; - top_layer_polygons.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); - - top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(boundary, top_layer_polygons); - // return diff_ex(boundary, offset_ex(top_layer_polygons, -perimeter_offset)); - } - - return boundary; -} - - // called by AvoidCrossingPerimeters::travel_to() static Polygons get_boundary_external(const Layer &layer) { @@ -1369,7 +1221,7 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons for (const auto& merge_point : merge_poins) { bbox.merge(merge_point); } - bbox.offset(SCALED_EPSILON); + bbox.offset(bbox.radius()); boundary->bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); boundary->grid.set_bbox(bbox); // FIXME 1mm grid? @@ -1377,24 +1229,6 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons init_boundary_distances(boundary); } -void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons, BoundingBox&& ref_bbox, const std::vector& merge_poins) -{ - boundary->clear(); - boundary->boundaries = std::move(boundary_polygons); - - BoundingBox bbox(ref_bbox); - for (const auto& merge_point : merge_poins) { - bbox.merge(merge_point); - } - bbox.offset(SCALED_EPSILON); - boundary->bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); - boundary->grid.set_bbox(bbox); - // FIXME 1mm grid? - boundary->grid.create(boundary->boundaries, coord_t(scale_(1.))); - init_boundary_distances(boundary); -} - - // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { @@ -1413,33 +1247,22 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & const ExPolygons &lslices = gcodegen.layer()->lslices; const std::vector &lslices_bboxes = gcodegen.layer()->lslices_bboxes; bool is_support_layer = (dynamic_cast(gcodegen.layer()) != nullptr); - if (!use_external && (is_support_layer || (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)))) { - if (m_lslice_internal.boundaries.empty()) { - init_boundary(&m_lslice_internal, to_polygons(get_slice_boundary_internal(*gcodegen.layer())), {start, end}); - } else if (!(m_lslice_internal.bbox.contains(startf) && m_lslice_internal.bbox.contains(endf))) { - // check if start and end are in bbox - m_lslice_internal.clear(); - init_boundary(&m_lslice_internal, to_polygons(get_slice_boundary_internal(*gcodegen.layer())), {start, end}); - } - + if (!use_external && (is_support_layer || (!m_lslices_offset.empty() && !any_expolygon_contains(m_lslices_offset, m_lslices_offset_bboxes, m_grid_lslice, travel)))) { // Initialize m_internal only when it is necessary. if (m_internal.boundaries.empty()) { - init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer(), get_perimeter_spacing(*gcodegen.layer()))), get_extents(m_lslice_internal.boundaries), - {start, end}); + init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer(), get_perimeter_spacing(*gcodegen.layer()))), {start, end}); } else if (!(m_internal.bbox.contains(startf) && m_internal.bbox.contains(endf))) { // check if start and end are in bbox, if not, merge start and end points to bbox m_internal.clear(); - init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer(), get_perimeter_spacing(*gcodegen.layer()))), get_extents(m_lslice_internal.boundaries), - {start, end}); + init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer(), get_perimeter_spacing(*gcodegen.layer()))), {start, end}); } - + if (!m_internal.boundaries.empty()) { - travel_intersection_count = avoid_perimeters(m_lslice_internal, m_internal, start, end, *gcodegen.layer(), result_pl); + travel_intersection_count = avoid_perimeters(m_internal, start, end, *gcodegen.layer(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } - } - else if(use_external) { + } else if (use_external) { // Initialize m_external only when exist any external travel for the current layer. if (m_external.boundaries.empty()) { init_boundary(&m_external, get_boundary_external(*gcodegen.layer()), {start, end}); @@ -1449,18 +1272,16 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & init_boundary(&m_external, get_boundary_external(*gcodegen.layer()), {start, end}); } - // Trim the travel line by the bounding box. if (!m_external.boundaries.empty()) { travel_intersection_count = avoid_perimeters(m_external, start, end, *gcodegen.layer(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; - + } } - if(result_pl.empty()) { // Travel line is completely outside the bounding box. result_pl = {start, end}; @@ -1487,7 +1308,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & } else if (max_detour_length_exceeded) { *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, m_lslices_offset, m_lslices_offset_bboxes, m_grid_lslice, travel, result_pl, travel_intersection_count); return result_pl; } @@ -1497,15 +1318,23 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_internal.clear(); - m_lslice_internal.clear(); m_external.clear(); + m_lslices_offset.clear(); + m_lslices_offset_bboxes.clear(); + for (auto coeff : {0.6f, 0.5f, 0.45f}) { + m_lslices_offset = offset_ex(layer.lslices, -get_external_perimeter_width(layer) * coeff); + if (!m_lslices_offset.empty()) break; + } + m_lslices_offset_bboxes.reserve(m_lslices_offset.size()); + for (const auto &ex_polygon : m_lslices_offset) m_lslices_offset_bboxes.emplace_back(get_extents(ex_polygon)); + BoundingBox bbox_slice(get_extents(layer.lslices)); bbox_slice.offset(SCALED_EPSILON); m_grid_lslice.set_bbox(bbox_slice); //FIXME 1mm grid? - m_grid_lslice.create(layer.lslices, coord_t(scale_(1.))); + m_grid_lslice.create(m_lslices_offset, coord_t(scale_(1.))); } #if 0 diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index e4335ac..a348eed 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -17,6 +17,7 @@ class AvoidCrossingPerimeters public: // Routing around the objects vs. inside a single object. void use_external_mp(bool use = true) { m_use_external_mp = use; }; + bool used_external_mp() { return m_use_external_mp; } void use_external_mp_once() { m_use_external_mp_once = true; } bool used_external_mp_once() { return m_use_external_mp_once; } void disable_once() { m_disabled_once = true; } @@ -58,12 +59,13 @@ private: // we enable it by default for the first travel move in print bool m_disabled_once { true }; + // Lslices offseted by half an external perimeter width. Used for detection if line or polyline is inside of any polygon. + ExPolygons m_lslices_offset; + std::vector m_lslices_offset_bboxes; // Used for detection of line or polyline is inside of any polygon. EdgeGrid::Grid m_grid_lslice; // Store all needed data for travels inside object Boundary m_internal; - // Store all needed data for travels inside object without inner offset - Boundary m_lslice_internal; // Store all needed data for travels outside object Boundary m_external; }; diff --git a/src/libslic3r/GCode/GCodeEditor.cpp b/src/libslic3r/GCode/GCodeEditor.cpp index 2a637c1..b610341 100644 --- a/src/libslic3r/GCode/GCodeEditor.cpp +++ b/src/libslic3r/GCode/GCodeEditor.cpp @@ -447,7 +447,7 @@ std::string GCodeEditor::write_layer_gcode( //QDS if (additional_fan_speed_new != m_additional_fan_speed) { m_additional_fan_speed = additional_fan_speed_new; - if (type == SetFanType::sfImmediatelyApply) + if (type == SetFanType::sfImmediatelyApply && m_config.auxiliary_fan.value) new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); else if (type == SetFanType::sfChangingLayer) this->m_set_addition_fan_changing_layer = true; @@ -519,7 +519,7 @@ std::string GCodeEditor::write_layer_gcode( //QDS: force to write a fan speed command again if (m_current_fan_speed != -1) new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); - if (m_additional_fan_speed != -1) + if (m_additional_fan_speed != -1 && m_config.auxiliary_fan.value) new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); } else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) { //QDS: check whether fan speed need to changed when change layer @@ -527,7 +527,7 @@ std::string GCodeEditor::write_layer_gcode( new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); m_set_fan_changing_layer = false; } - if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) { + if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer && m_config.auxiliary_fan.value) { new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); m_set_addition_fan_changing_layer = false; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index b4280ee..119cccb 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -334,10 +334,11 @@ static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodePr // full speed change within the block, we need to adjust the entry speed accordingly. Entry // speeds have already been reset, maximized, and reverse planned by reverse planner. // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck. + + // Èç¹ûǰһ¸öblock²»ÄÜÍê³É¼ÓËÙ£¬ÄÇôҪ¸ù¾ÝÆä¼ÓËÙµ½µÄËÙ¶È£¬ÖØÐ¼ÆË㵱ǰblockµÄentryËÙ¶È if (!prev.flags.nominal_length) { if (prev.feedrate_profile.entry < curr.feedrate_profile.entry) { float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance)); - // Check for junction speed change if (curr.feedrate_profile.entry != entry_speed) { curr.feedrate_profile.entry = entry_speed; @@ -379,7 +380,7 @@ static void recalculate_trapezoids(std::vector& block // Recalculate if current block entry or exit junction speed has changed. if (curr->flags.recalculate || next->flags.recalculate) { // NOTE: Entry and exit factors always > 0 by all previous logic operations. - GCodeProcessor::TimeBlock block = *curr; + GCodeProcessor::TimeBlock& block = *curr; block.feedrate_profile.exit = next->feedrate_profile.entry; block.calculate_trapezoid(); curr->trapezoid = block.trapezoid; @@ -390,7 +391,7 @@ static void recalculate_trapezoids(std::vector& block // Last/newest block in buffer. Always recalculated. if (next != nullptr) { - GCodeProcessor::TimeBlock block = *next; + GCodeProcessor::TimeBlock& block = *next; block.feedrate_profile.exit = next->safe_feedrate; block.calculate_trapezoid(); next->trapezoid = block.trapezoid; @@ -534,7 +535,8 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st if (in.f == nullptr) throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": before process %1%") % filename.c_str(); + std::string filename_safe = PathSanitizer::sanitize(filename); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": before process %1%") % filename_safe; // temporary file to contain modified gcode std::string filename_in = filename; std::string filename_out = filename + ".postprocess"; @@ -675,7 +677,8 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st if (s_IsQDTPrinter) { // Klipper estimator sprintf(buf, "; estimated printing time (normal mode) = %s\n", - get_time_dhms(machine.time).c_str()); + //y68 + get_time_dhms(machine.time + 600.0f).c_str()); ret += buf; } else { @@ -1151,6 +1154,8 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st filename_in = filename_out; // filename_out is opened in read|write mode. During second process ,we ues filename_out as input filename_out = filename + ".postprocessed"; + std::string filename_in_safe = PathSanitizer::sanitize(filename_in); + std::string filename_out_safe = PathSanitizer::sanitize(filename_out); FilePtr new_out = boost::nowide::fopen(filename_out.c_str(), "wb"); std::fseek(out.f, 0, SEEK_SET); // move to start of the file and start reading gcode as in @@ -1164,17 +1169,17 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st in.close(); out.close(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": after process %1%") % filename.c_str(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": after process %1%") % filename_safe; if (boost::nowide::remove(filename_in.c_str()) != 0) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to remove the temporary G-code file %1%") % filename_in.c_str(); - throw Slic3r::RuntimeError(std::string("Failed to remove the temporary G-code file ") + filename_in + '\n' + - "Is " + filename_in + " locked?" + '\n'); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to remove the temporary G-code file %1%") % filename_in_safe; + throw Slic3r::RuntimeError(std::string("Failed to remove the temporary G-code file ") + filename_in_safe + '\n' + + "Is " + filename_in_safe + " locked?" + '\n'); } if (rename_file(filename_out, filename)) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to rename the output G-code file from %1% to %2%") % filename_out.c_str() % filename.c_str(); - throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + filename_out + " to " + filename + '\n' + - "Is " + filename_out + " locked?" + '\n'); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": Failed to rename the output G-code file from %1% to %2%") % filename_out_safe % filename_safe; + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + filename_out_safe + " to " + filename_safe + '\n' + + "Is " + filename_out_safe + " locked?" + '\n'); } } @@ -1607,7 +1612,9 @@ void GCodeProcessor::register_commands() {"VG1", [this](const GCodeReader::GCodeLine& line) { process_VG1(line); }}, {"VM104", [this](const GCodeReader::GCodeLine& line) { process_VM104(line); }}, - {"VM109", [this](const GCodeReader::GCodeLine& line) { process_VM109(line); }} + {"VM109", [this](const GCodeReader::GCodeLine& line) { process_VM109(line); }}, + {"M622", [this](const GCodeReader::GCodeLine& line) { process_M622(line);}}, + {"M623", [this](const GCodeReader::GCodeLine& line) { process_M623(line);}} }; std::unordered_setearly_quit_commands = { @@ -1795,6 +1802,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_time_processor.filament_load_times = static_cast(config.machine_load_filament_time.value); m_time_processor.filament_unload_times = static_cast(config.machine_unload_filament_time.value); m_time_processor.extruder_change_times = static_cast(config.machine_switch_extruder_time.value); + m_time_processor.prepare_compensation_time = static_cast(config.machine_prepare_compensation_time.value); for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); @@ -2058,6 +2066,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_switch_extruder_time != nullptr) m_time_processor.extruder_change_times = static_cast(machine_switch_extruder_time->value); + const ConfigOptionFloat* machine_prepare_compensation_time = config.option("machine_prepare_compensation_time"); + if (machine_prepare_compensation_time != nullptr) + m_time_processor.prepare_compensation_time = static_cast(machine_prepare_compensation_time->value); + if (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfKlipper) { const ConfigOptionFloatsNullable* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); if (machine_max_acceleration_x != nullptr) @@ -2099,7 +2111,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_max_jerk_y != nullptr) m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values; - const ConfigOptionFloatsNullable* machine_max_jerk_z = config.option("machine_max_jerkz"); + const ConfigOptionFloatsNullable* machine_max_jerk_z = config.option("machine_max_jerk_z"); if (machine_max_jerk_z != nullptr) m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values; @@ -2275,6 +2287,16 @@ static inline const char* remove_eols(const char *begin, const char *end) { return end; } +DynamicConfig GCodeProcessor::export_config_for_render() const +{ + DynamicConfig config; + config.set_key_value("filament_colour", new ConfigOptionStrings(m_parser.get_config().filament_colour.values)); + config.set_key_value("filament_is_support", new ConfigOptionBools(m_parser.get_config().filament_is_support.values)); + config.set_key_value("filament_type", new ConfigOptionStrings(m_parser.get_config().filament_type.values)); + config.set_key_value("filament_map", new ConfigOptionInts(m_parser.get_config().filament_map.values)); + return config; +} + // Load a G-code into a stand-alone G-code viewer. // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) @@ -3783,6 +3805,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) TimeMachine::State& prev = machine.prev; std::vector& blocks = machine.blocks; + // m_feedrate ´ÓgcodeÖнâÎö»ñµÃ£¬ÔÚ±£Ö¤×îСËÙ¶ÈÏÞÖÆµÄÇé¿öϸ³¸øfeedrate curr.feedrate = (delta_pos[E] == 0.0f) ? minimum_travel_feedrate(static_cast(i), m_feedrate) : minimum_feedrate(static_cast(i), m_feedrate); @@ -3800,15 +3823,23 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) //QDS: don't calculate travel time into extrusion path, except travel inside start and end gcode. block.role = (type != EMoveType::Travel || m_extrusion_role == erCustom) ? m_extrusion_role : erNone; block.distance = distance; - block.move_id = m_result.moves.size(); + block.move_id = m_result.moves.size(); // new move will be pushed back at the end of the func, so use size of move as idx block.g1_line_id = m_g1_line_id; block.layer_id = std::max(1, m_layer_id); block.flags.prepare_stage = m_processing_start_custom_gcode; + // calculates block acceleration£¬¼ÆË㵱ǰblockµÄ×î¸ßÄܵ½´ïµÄ¼ÓËÙ¶È + 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))); + //QDS: limite the cruise according to centripetal acceleration //Only need to handle when both prev and curr segment has movement in x-y plane if ((prev.exit_direction(0) != 0.0f || prev.exit_direction(1) != 0.0f) && - (curr.enter_direction(0) != 0.0f || curr.enter_direction(1) != 0.0f)) { + (curr.enter_direction(0) != 0.0f || curr.enter_direction(1) != 0.0f) && + !is_extrusion_only_move(delta_pos)) { Vec3f v1 = prev.exit_direction; v1(2, 0) = 0.0f; v1.normalize(); @@ -3826,12 +3857,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) float angle = float(atan2(double(cross), double(dot))); float sin_theta_2 = sqrt((1.0f - cos(angle)) * 0.5f); float r = sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y])) * 0.5 / sin_theta_2; - float acc = get_acceleration(static_cast(i)); - curr.feedrate = std::min(curr.feedrate, sqrt(acc * r)); + curr.feedrate = std::min(curr.feedrate, sqrt(acceleration * r)); } } // calculates block cruise feedrate + // ÅÙ³ýǰºó¹ØÏµ£¬µ¥´¿¼ÆË㵱ǰblock×î¸ßÄܵ½´ïµÄËÙ¶È float min_feedrate_factor = 1.0f; for (unsigned char a = X; a <= E; ++a) { curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance; @@ -3840,7 +3871,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), m_extruder_id); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), get_extruder_id()); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -3855,16 +3886,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))); - //QDS for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), m_extruder_id); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), get_extruder_id()); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance); } @@ -3887,75 +3911,36 @@ 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); - // 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; + vmax_junction = std::min(prev.feedrate, block.feedrate_profile.cruise); - float v_factor = 1.0f; bool limited = false; + Vec3f exit_direction_unit = prev.exit_direction.normalized(); + Vec3f enter_direction_unit = curr.enter_direction.normalized(); + float k, k_min = 10000.f; - for (unsigned char a = X; a <= E; ++a) { + for (unsigned char a = X; a <= Z; ++a) { // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. if (a == X) { - Vec3f exit_v = prev.feedrate * (prev.exit_direction); - if (prev_speed_larger) - exit_v *= smaller_speed_factor; - Vec3f entry_v = block.feedrate_profile.cruise * (curr.enter_direction); - Vec3f jerk_v = entry_v - exit_v; + Vec3f jerk_v = enter_direction_unit - exit_direction_unit; jerk_v = Vec3f(abs(jerk_v.x()), abs(jerk_v.y()), abs(jerk_v.z())); Vec3f max_xyz_jerk_v = get_xyz_max_jerk(static_cast(i)); - for (size_t i = 0; i < 3; i++) - { - if (jerk_v[i] > max_xyz_jerk_v[i]) { - v_factor *= max_xyz_jerk_v[i] / jerk_v[i]; - jerk_v *= v_factor; + for (size_t i = 0; i < 3; i++){ + if (jerk_v[i] > 0) { limited = true; + k = max_xyz_jerk_v[i] / jerk_v[i]; + if (k < k_min) + k_min = k; } } } else if (a == Y || a == Z) { continue; } - else { - float v_exit = prev.axis_feedrate[a]; - float v_entry = curr.axis_feedrate[a]; - - if (prev_speed_larger) - v_exit *= smaller_speed_factor; - - if (limited) { - v_exit *= v_factor; - v_entry *= v_factor; - } - - // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. - 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)); - - - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); - if (jerk > axis_max_jerk) { - v_factor *= axis_max_jerk / jerk; - limited = true; - } - } } if (limited) - vmax_junction *= v_factor; + vmax_junction = k_min; // Now the transition velocity is known, which maximizes the shared exit / entry velocity while // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints. @@ -4279,7 +4264,7 @@ void GCodeProcessor::process_VG1(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), m_extruder_id); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), get_extruder_id()); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -4303,7 +4288,7 @@ void GCodeProcessor::process_VG1(const GCodeReader::GCodeLine& line) //QDS for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), m_extruder_id); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), get_extruder_id()); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration / (std::abs(delta_pos[a]) * inv_distance); } @@ -4665,7 +4650,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), m_extruder_id); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a), get_extruder_id()); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } } @@ -4692,7 +4677,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) axis_acc[a] = acceleration * std::abs(delta_pos[a]) * inv_distance; if (axis_acc[a] != 0.0f) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), m_extruder_id); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a), get_extruder_id()); if (axis_max_acceleration != 0.0f && axis_acc[a] > axis_max_acceleration) min_acc_factor = std::min(min_acc_factor, axis_max_acceleration / axis_acc[a]); } } @@ -4710,74 +4695,39 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; 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); //QDS: 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; + vmax_junction = std::min(prev.feedrate, block.feedrate_profile.cruise); - float v_factor = 1.0f; bool limited = false; - for (unsigned char a = X; a <= E; ++a) { + Vec3f exit_direction_unit = prev.exit_direction.normalized(); + Vec3f entry_direction_unit = curr.enter_direction.normalized(); + float k=0, k_min = 10000.f; + + for (unsigned char a = X; a <= Z; ++a) { //QDS: Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. if (a == X) { - Vec3f exit_v = prev.feedrate * (prev.exit_direction); - if (prev_speed_larger) - exit_v *= smaller_speed_factor; - Vec3f entry_v = block.feedrate_profile.cruise * (curr.enter_direction); - Vec3f jerk_v = entry_v - exit_v; + Vec3f jerk_v = entry_direction_unit - exit_direction_unit; jerk_v = Vec3f(abs(jerk_v.x()), abs(jerk_v.y()), abs(jerk_v.z())); Vec3f max_xyz_jerk_v = get_xyz_max_jerk(static_cast(i)); for (size_t i = 0; i < 3; i++) { - if (jerk_v[i] > max_xyz_jerk_v[i]) { - v_factor *= max_xyz_jerk_v[i] / jerk_v[i]; - jerk_v *= v_factor; + if(jerk_v[i] > 0){ limited = true; + k = max_xyz_jerk_v[i] / jerk_v[i]; + if(k < k_min) + k_min = k; } } } else if (a == Y || a == Z) { continue; } - else { - float v_exit = prev.axis_feedrate[a]; - float v_entry = curr.axis_feedrate[a]; - - if (prev_speed_larger) - v_exit *= smaller_speed_factor; - - if (limited) { - v_exit *= v_factor; - v_entry *= v_factor; - } - - //QDS: Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. - float jerk = - (v_exit > v_entry) ? - (((v_entry > 0.0f) || (v_exit < 0.0f)) ? - //QDS: coasting - (v_exit - v_entry) : - //QDS: axis reversal - std::max(v_exit, -v_entry)) : - (((v_entry < 0.0f) || (v_exit > 0.0f)) ? - //QDS: coasting - (v_entry - v_exit) : - //QDS: axis reversal - std::max(-v_exit, v_entry)); - - - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); - if (jerk > axis_max_jerk) { - v_factor *= axis_max_jerk / jerk; - limited = true; - } - } } if (limited) - vmax_junction *= v_factor; + vmax_junction = k_min; //QDS: Now the transition velocity is known, which maximizes the shared exit / entry velocity while // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints. @@ -5303,6 +5253,24 @@ void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) } } +void GCodeProcessor::process_M622(const GCodeReader::GCodeLine& line) +{ + float value_j; + if(line.has_value('J',value_j)){ + int interger_j = (int)(std::round(value_j)); + if(interger_j == 1 && !m_measure_g29_time) + m_measure_g29_time = true; + } + +} + +void GCodeProcessor::process_M623(const GCodeReader::GCodeLine& line) +{ + if(m_measure_g29_time) + m_measure_g29_time = false; +} + + void GCodeProcessor::process_M400(const GCodeReader::GCodeLine& line) { float value_s = 0.0; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 76fdcff..6817f71 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -393,6 +393,7 @@ namespace Slic3r { std::unique_ptr root; }; + class GCodeProcessor { static const std::vector ReservedTags; @@ -415,7 +416,6 @@ namespace Slic3r { Total_Layer_Number_Placeholder, Wipe_Tower_Start, Wipe_Tower_End, - //1.9.7.52 Used_Filament_Weight_Placeholder, Used_Filament_Volume_Placeholder, Used_Filament_Length_Placeholder, @@ -443,10 +443,10 @@ namespace Slic3r { // checks the given gcode for reserved tags and returns true when finding any // (the first max_count found tags are returned into found_tag) static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector& found_tag); - static bool get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos); static int get_gcode_last_filament(const std::string &gcode_str); static bool get_last_z_from_gcode(const std::string& gcode_str, double& z); + static bool get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos); static const float Wipe_Width; static const float Wipe_Height; @@ -686,7 +686,6 @@ namespace Slic3r { friend class GCodeProcessor; }; - //1.9.7.52 struct TimeProcessContext { UsedFilaments used_filaments; // stores the accurate filament usage info @@ -770,6 +769,7 @@ namespace Slic3r { float filament_load_times; float filament_unload_times; float extruder_change_times; + float prepare_compensation_time; std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; @@ -778,7 +778,7 @@ namespace Slic3r { // post process the file with the given filename to add remaining time lines M73 // and updates moves' gcode ids accordingly void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends, const TimeProcessContext& context); - private: + private: void handle_offsets_of_first_process( const std::vector>& offsets, std::vector& moves, @@ -1020,7 +1020,6 @@ namespace Slic3r { int m_object_label_id{-1}; float m_print_z{0.0f}; std::vector m_remaining_volume; - //1.9.7.52 std::vector m_filament_lists; std::vector m_filament_nozzle_temp; std::vector m_filament_types; @@ -1067,6 +1066,7 @@ namespace Slic3r { size_t m_last_default_color_id; bool m_detect_layer_based_on_tag {false}; int m_seams_count; + bool m_measure_g29_time {false}; #if ENABLE_GCODE_VIEWER_STATISTICS std::chrono::time_point m_start_time; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1111,7 +1111,8 @@ namespace Slic3r { const std::vector &filament_map, const std::vector>& unprintable_filament_types ); void apply_config(const PrintConfig& config); - //1.9.7.52 + + DynamicConfig export_config_for_render() const; void set_filaments(const std::vector&filament_lists) { m_filament_lists=filament_lists;} void enable_stealth_time_estimator(bool enabled); @@ -1296,9 +1297,11 @@ namespace Slic3r { // Processes T line (Select Tool) void process_T(const GCodeReader::GCodeLine& line); void process_T(const std::string_view command); - void process_M1020(const GCodeReader::GCodeLine &line); + void process_M622(const GCodeReader::GCodeLine &line); + void process_M623(const GCodeReader::GCodeLine &line); + void process_filament_change(int id); //QDS: different path_type is only used for arc move void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 2edaf71..0ef783d 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -30,6 +30,8 @@ #endif static const int average_filter_window_size = 5; +static const float overhang_filter = 0.0f; +static const float lensLimit = 1.0f; namespace Slic3r { namespace SeamPlacerImpl { @@ -698,9 +700,12 @@ struct SeamComparator distance_penalty_b = 1.0f - gauss((b.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); } + double a_overhang_around_penalty = a.extra_overhang_point < overhang_filter ? 0 : a.extra_overhang_point; + double b_overhang_around_penalty = b.extra_overhang_point < overhang_filter ? 0 : b.extra_overhang_point; + // the penalites are kept close to range [0-1.x] however, it should not be relied upon - float penalty_a = a.overhang + a.visibility + angle_importance * compute_angle_penalty(a.local_ccw_angle) + distance_penalty_a; - float penalty_b = b.overhang + b.visibility + angle_importance * compute_angle_penalty(b.local_ccw_angle) + distance_penalty_b; + float penalty_a = a.overhang + a.visibility + angle_importance * compute_angle_penalty(a.local_ccw_angle) + distance_penalty_a + a_overhang_around_penalty; + float penalty_b = b.overhang + b.visibility + angle_importance * compute_angle_penalty(b.local_ccw_angle) + distance_penalty_b + b_overhang_around_penalty; return penalty_a < penalty_b; } @@ -733,9 +738,11 @@ struct SeamComparator if (setup == SeamPosition::spRear) { return a.position.y() + SeamPlacer::seam_align_score_tolerance * 5.0f > b.position.y(); } - float penalty_a = a.overhang + a.visibility + angle_importance * compute_angle_penalty(a.local_ccw_angle); - float penalty_b = b.overhang + b.visibility + angle_importance * compute_angle_penalty(b.local_ccw_angle); + double a_overhang_around_penalty = a.extra_overhang_point < overhang_filter ? 0 : a.extra_overhang_point; + double b_overhang_around_penalty = b.extra_overhang_point < overhang_filter ? 0 : b.extra_overhang_point; + float penalty_a = a.overhang + a.visibility + angle_importance * compute_angle_penalty(a.local_ccw_angle) + a_overhang_around_penalty; + float penalty_b = b.overhang + b.visibility + angle_importance * compute_angle_penalty(b.local_ccw_angle) + b_overhang_around_penalty; return penalty_a <= penalty_b || penalty_a - penalty_b < SeamPlacer::seam_align_score_tolerance; } @@ -954,7 +961,6 @@ void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, const Se void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) { using namespace SeamPlacerImpl; - std::vector &layers = m_seam_per_object[po].layers; tbb::parallel_for(tbb::blocked_range(0, layers.size()), [po, &layers](tbb::blocked_range r) { std::unique_ptr prev_layer_distancer; @@ -970,17 +976,65 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) bool should_compute_layer_embedding = regions_with_perimeter > 1; std::unique_ptr current_layer_distancer = std::make_unique(po->layers()[layer_idx]); - for (SeamCandidate &perimeter_point : layers[layer_idx].points) { + int points_size = layers[layer_idx].points.size(); + for (size_t i = 0; i < points_size; i++) { + SeamCandidate &perimeter_point = layers[layer_idx].points[i]; Vec2f point = Vec2f{perimeter_point.position.head<2>()}; if (prev_layer_distancer.get() != nullptr) { - perimeter_point.overhang = prev_layer_distancer->distance_from_perimeter(point) + 0.6f * perimeter_point.perimeter.flow_width - - tan(SeamPlacer::overhang_angle_threshold) * po->layers()[layer_idx]->height; - perimeter_point.overhang = perimeter_point.overhang < 0.0f ? 0.0f : perimeter_point.overhang; + double dist_temp = prev_layer_distancer->distance_from_perimeter(point); + perimeter_point.overhang = dist_temp + 0.6f * perimeter_point.perimeter.flow_width - tan(SeamPlacer::overhang_angle_threshold) * po->layers()[layer_idx]->height; + perimeter_point.overhang = perimeter_point.overhang < 0.0f ? 0.0f : perimeter_point.overhang; + + perimeter_point.overhang_degree = (dist_temp + 0.6f * perimeter_point.perimeter.flow_width) / perimeter_point.perimeter.flow_width; + perimeter_point.overhang_degree = perimeter_point.overhang_degree < 0.0f ? 0.0f : perimeter_point.overhang_degree; } if (should_compute_layer_embedding) { // search for embedded perimeter points (points hidden inside the print ,e.g. multimaterial join, best position for seam) perimeter_point.embedded_distance = current_layer_distancer->distance_from_perimeter(point) + 0.6f * perimeter_point.perimeter.flow_width; } + + size_t start_index = perimeter_point.perimeter.start_index; + size_t end_index = perimeter_point.perimeter.end_index; + if (po->config().seam_placement_away_from_overhangs.value && perimeter_point.overhang_degree > 0.0f && end_index - start_index > 1) { + // QDS. extend overhang range + float dist = 0.0f; + size_t idx = i; + double gauss_value = gauss(0.0f, 0.0f, 1.0f, 10.0f); + perimeter_point.extra_overhang_point = perimeter_point.overhang_degree * gauss_value; + // check left + while (true) { + int prev = idx; + idx = idx == start_index ? end_index - 1 : idx - 1; + if (idx == i) + break; + dist += sqrt((layers[layer_idx].points[idx].position.head<2>() - layers[layer_idx].points[prev].position.head<2>()).squaredNorm()); + if (dist > lensLimit) + break; + double gauss_value_dist = gauss(dist, 0.0f, 1.0f, 10.0f); + + if (layers[layer_idx].points[idx].extra_overhang_point > perimeter_point.overhang_degree * gauss_value_dist) + continue; + layers[layer_idx].points[idx].extra_overhang_point = perimeter_point.overhang_degree * gauss_value_dist; + } + + //check right + dist = 0.0f; + idx = i; + while (true) { + int prev = idx; + idx = idx == end_index - 1 ? start_index : idx + 1; + if (idx == i) + break; + dist += sqrt((layers[layer_idx].points[idx].position.head<2>() - layers[layer_idx].points[prev].position.head<2>()).squaredNorm()); + if (dist > lensLimit) + break; + double gauss_value_dist = gauss(dist, 0.0f, 1.0f, 10.0f); + + if (layers[layer_idx].points[idx].extra_overhang_point > perimeter_point.overhang_degree * gauss_value_dist) + continue; + layers[layer_idx].points[idx].extra_overhang_point = perimeter_point.overhang_degree * gauss_value_dist; + } + } } prev_layer_distancer.swap(current_layer_distancer); @@ -1220,6 +1274,7 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: // interpolate between current and fitted position, prefer current pos for large weights. Vec3f final_position = t * current_pos + (1.0f - t) * to_3d(fitted_pos, current_pos.z()); + Perimeter &perimeter = layers[pair.first].points[pair.second].perimeter; perimeter.seam_index = pair.second; perimeter.final_seam_position = final_position; @@ -1249,7 +1304,6 @@ void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl:: fclose(aligns); #endif } - std::vector> SeamPlacer::gather_all_seams_of_object(const std::vector &layers) { // gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 2e84eeb..61726e8 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -69,7 +69,18 @@ struct Perimeter struct SeamCandidate { SeamCandidate(const Vec3f &pos, Perimeter &perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type) - : position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle(local_ccw_angle), type(type), central_enforcer(false), enable_scarf_seam(false),is_grouped(false) + : position(pos) + , perimeter(perimeter) + , visibility(0.0f) + , overhang(0.0f) + , embedded_distance(0.0f) + , local_ccw_angle(local_ccw_angle) + , type(type) + , central_enforcer(false) + , enable_scarf_seam(false) + , is_grouped(false) + , extra_overhang_point(0.0f) + , overhang_degree(0.0f) {} const Vec3f position; // pointer to Perimeter loop of this point. It is shared across all points of the loop @@ -84,6 +95,8 @@ struct SeamCandidate bool central_enforcer; // marks this candidate as central point of enforced segment on the perimeter - important for alignment bool enable_scarf_seam; // marks this candidate as a candidate for scarf seam bool is_grouped; + float extra_overhang_point; + float overhang_degree; }; struct SeamCandidateCoordinateFunctor @@ -133,6 +146,8 @@ public: // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size static constexpr float enforcer_oversampling_distance = 0.2f; + static constexpr float end_point_oversampling_threshold = 4.0f; + static constexpr float end_point_oversampling_distance = 1.5f; // When searching for seam clusters for alignment: // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer diff --git a/src/libslic3r/GCode/TimelapsePosPicker.cpp b/src/libslic3r/GCode/TimelapsePosPicker.cpp index cee5fef..db54c98 100644 --- a/src/libslic3r/GCode/TimelapsePosPicker.cpp +++ b/src/libslic3r/GCode/TimelapsePosPicker.cpp @@ -1,3 +1,4 @@ +#include "ClipperUtils.hpp" #include "TimelapsePosPicker.hpp" #include "Layer.hpp" diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index d25f2b1..d3cdcb4 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -28,12 +28,12 @@ namespace Slic3r { //! macro used to mark string used at localization, //! return same string - #ifndef _L - #define _L(s) Slic3r::I18N::translate(s) - #endif +#ifndef _L +#define _L(s) Slic3r::I18N::translate(s) +#endif - const static bool g_wipe_into_objects = false; - constexpr double similar_color_threshold_de2000 = 20.0; +const static bool g_wipe_into_objects = false; +constexpr double similar_color_threshold_de2000 = 20.0; static std::setget_filament_by_type(const std::vector& used_filaments, const PrintConfig* print_config, const std::string& type) { @@ -435,8 +435,26 @@ std::vector ToolOrdering::generate_first_layer_tool_order(const Pr std::map min_areas_per_extruder; for (auto object : print.objects()) { - auto first_layer = object->get_layer(0); - for (auto layerm : first_layer->regions()) { + const Layer* target_layer = nullptr; + for(auto layer : object->layers()){ + for(auto layerm : layer->regions()){ + for(auto& expoly : layerm->raw_slices){ + if (!offset_ex(expoly, -0.2 * scale_(print.config().initial_layer_line_width)).empty()) { + target_layer = layer; + break; + } + } + if(target_layer) + break; + } + if(target_layer) + break; + } + + if(!target_layer) + return tool_order; + + for (auto layerm : target_layer->regions()) { int extruder_id = layerm->region().config().option("wall_filament")->getInt(); for (auto expoly : layerm->raw_slices) { @@ -492,8 +510,26 @@ std::vector ToolOrdering::generate_first_layer_tool_order(const Pr std::vector tool_order; int initial_extruder_id = -1; std::map min_areas_per_extruder; - auto first_layer = object.get_layer(0); - for (auto layerm : first_layer->regions()) { + const Layer* target_layer = nullptr; + for(auto layer : object.layers()){ + for(auto layerm : layer->regions()){ + for(auto& expoly : layerm->raw_slices){ + if (!offset_ex(expoly, -0.2 * scale_(object.config().line_width)).empty()) { + target_layer = layer; + break; + } + } + if(target_layer) + break; + } + if(target_layer) + break; + } + + if(!target_layer) + return tool_order; + + for (auto layerm : target_layer->regions()) { int extruder_id = layerm->region().config().option("wall_filament")->getInt(); for (auto expoly : layerm->raw_slices) { if (offset_ex(expoly, -0.2 * scale_(object.config().line_width)).empty()) @@ -560,30 +596,6 @@ void ToolOrdering::initialize_layers(std::vector &zs) // Collect extruders reuqired to print layers. void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches) { - // Collect the support extruders. - for (auto support_layer : object.support_layers()) { - LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); - ExtrusionRole role = support_layer->support_fills.role(); - bool has_support = false; - bool has_interface = false; - for (const ExtrusionEntity *ee : support_layer->support_fills.entities) { - ExtrusionRole er = ee->role(); - if (er == erSupportMaterial || er == erSupportTransition) has_support = true; - if (er == erSupportMaterialInterface) has_interface = true; - if (has_support && has_interface) break; - } - unsigned int extruder_support = object.config().support_filament.value; - unsigned int extruder_interface = object.config().support_interface_filament.value; - if (has_support) - layer_tools.extruders.push_back(extruder_support); - if (has_interface) - layer_tools.extruders.push_back(extruder_interface); - if (has_support || has_interface) { - layer_tools.has_support = true; - layer_tools.wiping_extrusions().is_support_overriddable_and_mark(role, object); - } - } - // Extruder overrides are ordered by print_z. std::vector>::const_iterator it_per_layer_extruder_override; it_per_layer_extruder_override = per_layer_extruder_switches.begin(); @@ -598,7 +610,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto for (auto layer : object.layers()) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); - // Override extruder with the next + // Override extruder with the next for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override) extruder_override = (int)it_per_layer_extruder_override->second; @@ -664,7 +676,56 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto sort_remove_duplicates(firstLayerExtruders); const_cast(object).object_first_layer_wall_extruders = firstLayerExtruders; - + + // Collect the support extruders. + for (auto support_layer : object.support_layers()) { + LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); + ExtrusionRole role = support_layer->support_fills.role(); + bool has_support = false; + bool has_interface = false; + for (const ExtrusionEntity *ee : support_layer->support_fills.entities) { + ExtrusionRole er = ee->role(); + if (er == erSupportMaterial || er == erSupportTransition) has_support = true; + if (er == erSupportMaterialInterface) has_interface = true; + if (has_support && has_interface) break; + } + unsigned int extruder_support = object.config().support_filament.value; + unsigned int extruder_interface = object.config().support_interface_filament.value; + if (has_support) { + if (extruder_support > 0 || !has_interface || extruder_interface == 0 || layer_tools.has_object) + layer_tools.extruders.push_back(extruder_support); + else { + auto all_extruders = object.print()->extruders(); + auto get_next_extruder = [&](int current_extruder, const std::vector &extruders) { + std::vector flush_matrix( + cast(get_flush_volumes_matrix(object.print()->config().flush_volumes_matrix.values, 0, object.print()->config().nozzle_diameter.values.size()))); + const unsigned int number_of_extruders = (unsigned int) (sqrt(flush_matrix.size()) + EPSILON); + // Extract purging volumes for each extruder pair: + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + int next_extruder = current_extruder; + float min_flush = std::numeric_limits::max(); + for (auto extruder_id : extruders) { + if (object.print()->config().filament_soluble.get_at(extruder_id) || extruder_id == current_extruder) continue; + if (wipe_volumes[extruder_interface - 1][extruder_id] < min_flush) { + next_extruder = extruder_id; + min_flush = wipe_volumes[extruder_interface - 1][extruder_id]; + } + } + return next_extruder; + }; + bool interface_not_for_body = object.config().support_interface_not_for_body; + layer_tools.extruders.push_back(get_next_extruder(interface_not_for_body ? extruder_interface - 1 : -1, all_extruders) + 1); + } + } + if (has_interface) layer_tools.extruders.push_back(extruder_interface); + if (has_support || has_interface) { + layer_tools.has_support = true; + layer_tools.wiping_extrusions().is_support_overriddable_and_mark(role, object); + } + } + for (auto& layer : m_layer_tools) { // Sort and remove duplicates sort_remove_duplicates(layer.extruders); @@ -675,6 +736,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } } + void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height) { if (m_layer_tools.empty()) @@ -1072,64 +1134,63 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first if (!print_config || m_layer_tools.empty()) return; - const unsigned int number_of_extruders = (unsigned int)(print_config->filament_colour.values.size() + EPSILON); + const unsigned int number_of_extruders = (unsigned int)(print_config->filament_colour.values.size() + EPSILON); - using FlushMatrix = std::vector>; - size_t nozzle_nums = print_config->nozzle_diameter.values.size(); - - std::vector nozzle_flush_mtx; - for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { - std::vector flush_matrix(cast(get_flush_volumes_matrix(print_config->flush_volumes_matrix.values, nozzle_id, nozzle_nums))); - std::vector> wipe_volumes; - for (unsigned int i = 0; i < number_of_extruders; ++i) - wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); - - nozzle_flush_mtx.emplace_back(wipe_volumes); - } + using FlushMatrix = std::vector>; + size_t nozzle_nums = print_config->nozzle_diameter.values.size(); - auto flush_multiplies = print_config->flush_multiplier.values; - flush_multiplies.resize(nozzle_nums, 1); - for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { - for (auto& vec : nozzle_flush_mtx[nozzle_id]) { - for (auto& v : vec) - v *= flush_multiplies[nozzle_id]; - } + std::vector nozzle_flush_mtx; + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + std::vector flush_matrix(cast(get_flush_volumes_matrix(print_config->flush_volumes_matrix.values, nozzle_id, nozzle_nums))); + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + + nozzle_flush_mtx.emplace_back(wipe_volumes); + } + + auto flush_multiplies = print_config->flush_multiplier.values; + flush_multiplies.resize(nozzle_nums, 1); + for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { + for (auto& vec : nozzle_flush_mtx[nozzle_id]) { + for (auto& v : vec) + v *= flush_multiplies[nozzle_id]; } - - - std::vectorfilament_maps(number_of_extruders, 0); - FilamentMapMode map_mode = FilamentMapMode::fmmAutoForFlush; - - std::vector> layer_filaments; - for (auto& lt : m_layer_tools) { - layer_filaments.emplace_back(lt.extruders); - } - - std::vector used_filaments = collect_sorted_used_filaments(layer_filaments); - - std::vector>geometric_unprintables = m_print->get_geometric_unprintable_filaments(); - std::vector>physical_unprintables = m_print->get_physical_unprintable_filaments(used_filaments); - - filament_maps = m_print->get_filament_maps(); - map_mode = m_print->get_filament_map_mode(); - // only check and map in sequence mode, in by object mode, we check the map in print.cpp - if (print_config->print_sequence != PrintSequence::ByObject || m_print->objects().size() == 1) { - if (map_mode < FilamentMapMode::fmmManual) { - const PrintConfig* print_config = m_print_config_ptr; - if (!print_config && m_print_object_ptr) { - print_config = &(m_print_object_ptr->print()->config()); - } - - filament_maps = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables); - - if (filament_maps.empty()) - return; - std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value + 1; }); - m_print->update_filament_maps_to_config(filament_maps); + } + + std::vectorfilament_maps(number_of_extruders, 0); + FilamentMapMode map_mode = FilamentMapMode::fmmAutoForFlush; + + std::vector> layer_filaments; + for (auto& lt : m_layer_tools) { + layer_filaments.emplace_back(lt.extruders); + } + + std::vector used_filaments = collect_sorted_used_filaments(layer_filaments); + + std::vector>geometric_unprintables = m_print->get_geometric_unprintable_filaments(); + std::vector>physical_unprintables = m_print->get_physical_unprintable_filaments(used_filaments); + + filament_maps = m_print->get_filament_maps(); + map_mode = m_print->get_filament_map_mode(); + // only check and map in sequence mode, in by object mode, we check the map in print.cpp + if (print_config->print_sequence != PrintSequence::ByObject || m_print->objects().size() == 1) { + if (map_mode < FilamentMapMode::fmmManual) { + const PrintConfig* print_config = m_print_config_ptr; + if (!print_config && m_print_object_ptr) { + print_config = &(m_print_object_ptr->print()->config()); } - std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; }); - - check_filament_printable_after_group(used_filaments, filament_maps, print_config); + + filament_maps = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables); + + if (filament_maps.empty()) + return; + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value + 1; }); + m_print->update_filament_maps_to_config(filament_maps); + } + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; }); + + check_filament_printable_after_group(used_filaments, filament_maps, print_config); } else { // we just need to change the map to 0 based @@ -1168,7 +1229,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first } } return false; - }; + }; reorder_filaments_for_minimum_flush_volume( filament_lists, @@ -1276,12 +1337,12 @@ void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_lay static CustomGCode::Info custom_gcode_per_print_z; void ToolOrdering::assign_custom_gcodes(const Print& print) { - // Only valid for non-sequential print. - assert(print.config().print_sequence == PrintSequence::ByLayer); + // Only valid for non-sequential print. + assert(print.config().print_sequence == PrintSequence::ByLayer); custom_gcode_per_print_z = print.model().get_curr_plate_custom_gcodes(); - if (custom_gcode_per_print_z.gcodes.empty()) - return; + if (custom_gcode_per_print_z.gcodes.empty()) + return; // QDS auto num_filaments = unsigned(print.config().filament_diameter.size()); @@ -1325,6 +1386,7 @@ void ToolOrdering::assign_custom_gcodes(const Print& print) extruder_print_above_by_layer.emplace(layer_idx, extruder_printing_above); } } + for (auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin(); custom_gcode_it != custom_gcode_per_print_z.gcodes.rend(); ++custom_gcode_it) { if (custom_gcode_it->type == CustomGCode::ToolChange) continue; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 0310c33..f7fb5e6 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -418,7 +418,7 @@ Polylines remove_points_from_polygon(const Polygon &polygon, const std::vector 2); Polylines result; - std::vector new_pl;// add intersection points for gaps, where bool indicates whether it's a gap point. + std::vector new_pl; // add intersection points for gaps, where bool indicates whether it's a gap point. std::vector inter_info; Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0); auto polygon_box = get_extents(polygon); @@ -632,7 +632,7 @@ public: // Extrude with an explicitely provided amount of extrusion. WipeTowerWriter &extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false ,LimitFlow limit_flow = LimitFlow::LimitPrintFlow) { - if ((std::abs(x - m_current_pos.x()) <= (float)EPSILON) && (std::abs(y - m_current_pos.y()) < (float)EPSILON) && e == 0.f && (f == 0.f || f == m_current_feedrate)) + if ((std::abs(x - m_current_pos.x()) <= (float)EPSILON) && (std::abs(y - m_current_pos.y()) < (float)EPSILON) && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. return *this; @@ -823,41 +823,7 @@ public: } while (i != index_of_closest); return (*this); } - WipeTowerWriter &line(const WipeTower *wipe_tower, Vec2f p0, Vec2f p1,const float f = 0.f) - { - bool need_change_flow = wipe_tower->need_thick_bridge_flow(p0.y()); - if (need_change_flow) { - set_extrusion_flow(wipe_tower->extrusion_flow(0.2)); - append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(0.2) + "\n"); - } - if (abs(x() - p0.x()) > abs(x() - p1.x())) std::swap(p0, p1); - travel(p0.x(), y()); - travel(x(), p0.y()); - extrude(p1, f); - if (need_change_flow) { - set_extrusion_flow(wipe_tower->get_extrusion_flow()); - append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(m_layer_height) + "\n"); - } - return (*this); - } - WipeTowerWriter &rectangle_fill_box(const WipeTower *wipe_tower, const WipeTower::box_coordinates &fill_box, std::vector &finish_rect_wipe_path, const float f = 0.f) - { - float width = fill_box.rd.x() - fill_box.ld.x(); - float height = fill_box.ru.y() - fill_box.rd.y(); - if (height > wipe_tower->m_perimeter_width - wipe_tower->WT_EPSILON) { - rectangle_fill_box(wipe_tower, fill_box.ld, width, height, f); - Vec2f target = (pos() == fill_box.ld ? fill_box.rd : (pos() == fill_box.rd ? fill_box.ru : (pos() == fill_box.ru ? fill_box.lu : fill_box.ld))); - finish_rect_wipe_path.emplace_back(pos()); - finish_rect_wipe_path.emplace_back(target); - } else if (height > wipe_tower->WT_EPSILON) { - line(wipe_tower, fill_box.ld, fill_box.rd); - Vec2f target = (pos() == fill_box.ld ? fill_box.rd : fill_box.ld); - finish_rect_wipe_path.emplace_back(pos()); - finish_rect_wipe_path.emplace_back(target); - } - return (*this); - } WipeTowerWriter &rectangle_fill_box(const WipeTower* wipe_tower, const Vec2f &ld, float width, float height, const float f = 0.f) { bool need_change_flow = wipe_tower->need_thick_bridge_flow(ld.y()); @@ -896,7 +862,41 @@ public: } while (i != index_of_closest); return (*this); } + WipeTowerWriter &line(const WipeTower *wipe_tower, Vec2f p0, Vec2f p1,const float f = 0.f) + { + bool need_change_flow = wipe_tower->need_thick_bridge_flow(p0.y()); + if (need_change_flow) { + set_extrusion_flow(wipe_tower->extrusion_flow(0.2)); + append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(0.2) + "\n"); + } + if (abs(x() - p0.x()) > abs(x() - p1.x())) std::swap(p0, p1); + travel(p0.x(), y()); + travel(x(), p0.y()); + extrude(p1, f); + if (need_change_flow) { + set_extrusion_flow(wipe_tower->get_extrusion_flow()); + append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + std::to_string(m_layer_height) + "\n"); + } + return (*this); + } + WipeTowerWriter &rectangle_fill_box(const WipeTower *wipe_tower, const WipeTower::box_coordinates &fill_box, std::vector &finish_rect_wipe_path, const float f = 0.f) + { + float width = fill_box.rd.x() - fill_box.ld.x(); + float height = fill_box.ru.y() - fill_box.rd.y(); + if (height > wipe_tower->m_perimeter_width - wipe_tower->WT_EPSILON) { + rectangle_fill_box(wipe_tower, fill_box.ld, width, height, f); + Vec2f target = (pos() == fill_box.ld ? fill_box.rd : (pos() == fill_box.rd ? fill_box.ru : (pos() == fill_box.ru ? fill_box.lu : fill_box.ld))); + finish_rect_wipe_path.emplace_back(pos()); + finish_rect_wipe_path.emplace_back(target); + } else if (height > wipe_tower->WT_EPSILON) { + line(wipe_tower, fill_box.ld, fill_box.rd); + Vec2f target = (pos() == fill_box.ld ? fill_box.rd : fill_box.ld); + finish_rect_wipe_path.emplace_back(pos()); + finish_rect_wipe_path.emplace_back(target); + } + return (*this); + } WipeTowerWriter& rectangle(const WipeTower::box_coordinates& box, const float f = 0.f) { rectangle(Vec2f(box.ld.x(), box.ld.y()), @@ -904,7 +904,6 @@ public: box.ru.y() - box.rd.y(), f); return (*this); } - WipeTowerWriter &polygon(const Polygon &wall_polygon, const float f = 0.f) { Polyline pl = to_polyline(wall_polygon); @@ -1079,7 +1078,7 @@ public: WipeTowerWriter& flush_planner_queue() { - m_gcode += "G4 S0\n"; + m_gcode += "G4 S0\n"; return *this; } @@ -1521,9 +1520,10 @@ TriangleMesh WipeTower::its_make_rib_tower(float width, float depth, float heigh Polygon bottom = rib_section(width, depth, rib_length, rib_width, fillet_wall); Polygon top = rib_section(width, depth, std::sqrt(width * width + depth * depth), rib_width, fillet_wall); if (fillet_wall) - assert(bottom.points.size() == top.points.size()); + assert(bottom.points.size() == top.points.size()); int offset = bottom.points.size(); res.its.vertices.reserve(offset * 2); + if (bottom.area() < scaled(EPSILON) || top.area() < scaled(EPSILON) || bottom.points.size() != top.points.size()) return res; auto faces_bottom = Triangulation::triangulate(bottom); auto faces_top = Triangulation::triangulate(top); res.its.indices.reserve(offset * 2 + faces_bottom.size() + faces_top.size()); @@ -1546,6 +1546,7 @@ TriangleMesh WipeTower::its_make_rib_tower(float width, float depth, float heigh TriangleMesh WipeTower::its_make_rib_brim(const Polygon& brim, float layer_height) { TriangleMesh res; + if (brim.area() < scaled(EPSILON))return res; int offset = brim.size(); res.its.vertices.reserve(brim.size() * 2); auto faces= Triangulation::triangulate(brim); @@ -2844,7 +2845,7 @@ WipeTower::ToolChangeResult WipeTower::merge_tcr(ToolChangeResult &first, ToolCh WipeTower::ToolChangeResult out = first; if ((first.end_pos - second.start_pos).norm() > (float)EPSILON) { std::string travel_gcode = "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + " Y" + - Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + "F" + std::to_string(m_max_speed) + "\n"; + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + " F" + std::to_string(m_max_speed) + "\n"; bool need_insert_travel = true; if (second.is_tool_change && is_approx(second.start_pos.x(), second.tool_change_start_pos.x()) @@ -3885,7 +3886,6 @@ void WipeTower::generate_wipe_tower_blocks() } } } - void WipeTower::calc_block_infill_gap() { //1.calc block infill gap width @@ -3966,6 +3966,7 @@ void WipeTower::calc_block_infill_gap() } m_extra_spacing = 1.f; } + void WipeTower::plan_tower_new() { if (m_wipe_tower_brim_width < 0) m_wipe_tower_brim_width = get_auto_brim_by_height(m_wipe_tower_height); @@ -4436,7 +4437,7 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode) .set_initial_tool(m_current_tool) .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); - set_for_wipe_tower_writer(writer); + set_for_wipe_tower_writer(writer); // Slow down on the 1st layer. bool first_layer = is_first_layer(); @@ -4717,9 +4718,11 @@ bool WipeTower::is_valid_last_layer(int tool) const } float WipeTower::get_block_gap_width(int tool,bool is_nozzlechangle) { - assert(m_block_infill_gap_width.count(m_filpar[tool].category)); + //assert(m_block_infill_gap_width.count(m_filpar[tool].category));//The code contains logic that attempts to access non-existent blocks, + // such as in case of involving two extruders with only a single head and a single layer, + // some code will attempt to access the block's nozzle_change_gap_width, even though the block does not exist. if (!m_block_infill_gap_width.count(m_filpar[tool].category)) { - return m_perimeter_width; + return is_nozzlechangle ? m_nozzle_change_perimeter_width : m_perimeter_width; } return is_nozzlechangle ? m_block_infill_gap_width[m_filpar[tool].category].second : m_block_infill_gap_width[m_filpar[tool].category].first; diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 7fd1c36..41a34a2 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -171,6 +171,10 @@ public: return c; } + GCodeConfig get_config() const + { + return m_config; + } private: template bool parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index b54639c..60766b1 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -175,6 +175,11 @@ void GCodeWriter::set_travel_acceleration(const std::vector& accel m_travel_accelerations = accelerations; } +void GCodeWriter::reset_last_acceleration() +{ + m_last_acceleration = 0; +} + void GCodeWriter::set_first_layer_travel_acceleration(const std::vector &travel_accelerations) { m_first_layer_travel_accelerations = travel_accelerations; diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index e1f8463..cc5dc7a 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -56,6 +56,8 @@ public: std::string set_chamber_temperature(int temperature, bool wait = false); void set_acceleration(unsigned int acceleration); void set_travel_acceleration(const std::vector& travel_accelerations); + void reset_last_acceleration(); + std::vector &get_travel_acceleration() { return m_travel_accelerations; } void set_first_layer_travel_acceleration(const std::vector& travel_accelerations); void set_first_layer(bool is_first_layer); std::string set_pressure_advance(double pa) const; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 249cc69..9e773b7 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -682,7 +682,8 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation // General anisotropic scaling, general rotation. // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. // Scale it to get the required size. - out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse()); + auto scling_facor = instance_transformation.get_scaling_factor(); + out.set_scaling_factor(scling_facor.cwiseInverse()); } return out; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 8fbb188..636014c 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -7,6 +7,7 @@ #include "BoundingBox.hpp" #include "libslic3r/AABBTreeLines.hpp" #include +#include "PerimeterGenerator.hpp" static const int Continuitious_length = scale_(0.01); static const int dist_scale_threshold = 1.2; @@ -55,19 +56,19 @@ void Layer::make_slices() polygons_append(slices_p, to_polygons(layerm->slices.surfaces)); slices = union_safety_offset_ex(slices_p); } - + this->lslices.clear(); this->lslices.reserve(slices.size()); - + // prepare ordering points Points ordering_points; ordering_points.reserve(slices.size()); for (const ExPolygon &ex : slices) ordering_points.push_back(ex.contour.first_point()); - + // sort slices std::vector order = chain_points(ordering_points); - + // populate slices vector for (size_t i : order) this->lslices.emplace_back(std::move(slices[i])); @@ -144,17 +145,41 @@ ExPolygons Layer::merged(float offset_scaled) const return out; } +// If there is any incompatibility, separate LayerRegions have to be created. +bool Layer::has_compatible_layer_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + return config.wall_filament == other_config.wall_filament + && config.wall_loops == other_config.wall_loops + && config.inner_wall_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.inner_wall_speed.get_at(get_extruder_id(config.wall_filament)) + && config.outer_wall_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.outer_wall_speed.get_at(get_extruder_id(config.wall_filament)) + && config.gap_infill_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.gap_infill_speed.get_at(get_extruder_id(config.wall_filament)) + && config.detect_overhang_wall == other_config.detect_overhang_wall + && config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value + && config.opt_serialize("inner_wall_line_width") == other_config.opt_serialize("inner_wall_line_width") + && config.detect_thin_wall == other_config.detect_thin_wall + && config.infill_wall_overlap == other_config.infill_wall_overlap + && config.seam_slope_conditional == other_config.seam_slope_conditional + && config.override_filament_scarf_seam_setting == other_config.override_filament_scarf_seam_setting + && config.seam_slope_type == other_config.seam_slope_type + && config.seam_slope_start_height == other_config.seam_slope_start_height + && config.seam_slope_gap == other_config.seam_slope_gap + && config.seam_slope_min_length == other_config.seam_slope_min_length + //&& config.scarf_angle_threshold == other_config.scarf_angle_threshold + && config.seam_slope_entire_loop == other_config.seam_slope_entire_loop + && config.seam_slope_steps == other_config.seam_slope_steps + && config.seam_slope_inner_walls == other_config.seam_slope_inner_walls; +} + // Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters. // The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region. // The resulting fill surface is split back among the originating regions. void Layer::make_perimeters() { BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id(); - // keep track of regions whose perimeters we have already generated std::vector done(m_regions.size(), false); - - for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) + + for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) if ((*layerm)->slices.empty()) { (*layerm)->perimeters.clear(); (*layerm)->fills.clear(); @@ -166,44 +191,37 @@ void Layer::make_perimeters() BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; done[region_id] = true; const PrintRegionConfig &config = (*layerm)->region().config(); - + // find compatible regions - LayerRegionPtrs layerms; - layerms.push_back(*layerm); - 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.wall_filament == other_config.wall_filament - && config.wall_loops == other_config.wall_loops - && config.inner_wall_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.inner_wall_speed.get_at(get_extruder_id(config.wall_filament)) - && config.outer_wall_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.outer_wall_speed.get_at(get_extruder_id(config.wall_filament)) - && config.gap_infill_speed.get_at(get_extruder_id(config.wall_filament)) == other_config.gap_infill_speed.get_at(get_extruder_id(config.wall_filament)) - && config.detect_overhang_wall == other_config.detect_overhang_wall - && config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value - && config.opt_serialize("inner_wall_line_width") == other_config.opt_serialize("inner_wall_line_width") - && config.detect_thin_wall == other_config.detect_thin_wall - && config.infill_wall_overlap == other_config.infill_wall_overlap - && config.fuzzy_skin == other_config.fuzzy_skin - && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance - && config.seam_slope_conditional == other_config.seam_slope_conditional - //&& config.scarf_angle_threshold == other_config.scarf_angle_threshold - && config.seam_slope_entire_loop == other_config.seam_slope_entire_loop - && config.seam_slope_steps == other_config.seam_slope_steps - && config.seam_slope_inner_walls == other_config.seam_slope_inner_walls) - { - other_layerm->perimeters.clear(); - other_layerm->fills.clear(); - other_layerm->thin_fills.clear(); - layerms.push_back(other_layerm); - done[it - m_regions.begin()] = true; - } - } - + LayerRegionPtrs layerms; + layerms.push_back(*layerm); + + PerimeterRegions perimeter_regions; + for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) { + if ((*it)->slices.empty()) + continue; + + LayerRegion* other_layerm = *it; + const size_t next_region_id = std::distance(m_regions.cbegin(), it); + const PrintRegionConfig &other_config = other_layerm->region().config(); + if (!has_compatible_layer_regions(config, other_config)) + continue; + + other_layerm->perimeters.clear(); + other_layerm->fills.clear(); + other_layerm->thin_fills.clear(); + // layer_region_ids.push_back(next_region_id); + layerms.push_back(other_layerm); + done[it - m_regions.begin()] = true; + + if (!PerimeterRegion::has_compatible_perimeter_regions(config, other_config)) { + perimeter_regions.emplace_back(*other_layerm); + } + } + if (layerms.size() == 1) { // optimization (*layerm)->fill_surfaces.surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons, this->loop_nodes); + (*layerm)->make_perimeters((*layerm)->slices, perimeter_regions, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons, this->loop_nodes); (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); } else { @@ -223,15 +241,20 @@ void Layer::make_perimeters() for (std::pair &surfaces_with_extra_perimeters : slices) new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); } - + + // Try to merge compatible PerimeterRegions. + if (perimeter_regions.size() > 1) { + PerimeterRegion::merge_compatible_perimeter_regions(perimeter_regions); + } + // make perimeters SurfaceCollection fill_surfaces; //QDS ExPolygons fill_no_overlap; - layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap, this->loop_nodes); + layerm_config->make_perimeters(new_slices, perimeter_regions, &fill_surfaces, &fill_no_overlap, this->loop_nodes); // assign fill_surfaces to each layer - if (!fill_surfaces.surfaces.empty()) { + if (!fill_surfaces.surfaces.empty()) { for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) { // Separate the fill surfaces. ExPolygons expp = intersection_ex(fill_surfaces.surfaces, (*l)->slices.surfaces); @@ -243,6 +266,7 @@ void Layer::make_perimeters() } } } + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } @@ -426,7 +450,7 @@ void Layer::export_region_slices_to_svg(const char *path) const for (const auto &surface : region->slices.surfaces) svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); - svg.Close(); + svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 20bf313..4b40eb9 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -19,6 +19,8 @@ class LayerRegion; using LayerRegionPtrs = std::vector; class PrintRegion; class PrintObject; +struct PerimeterRegion; +using PerimeterRegions = std::vector; namespace FillAdaptive { struct Octree; @@ -81,7 +83,12 @@ public: void prepare_fill_surfaces(); //QDS void auto_circle_compensation(SurfaceCollection &slices, const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params, float manual_offset = 0.0f); - void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap, std::vector &loop_nodes); + void make_perimeters(const SurfaceCollection &slices, + // Configuration regions that will be applied to parts of created perimeters. + const PerimeterRegions &perimeter_regions, + SurfaceCollection *fill_surfaces, + ExPolygons *fill_no_overlap, + std::vector &loop_nodes); void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); double infill_area_threshold() const; // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. @@ -264,6 +271,8 @@ protected: void simplify_support_multi_path(ExtrusionMultiPath* multipath); void simplify_support_loop(ExtrusionLoop* loop); + bool has_compatible_layer_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config); + private: // Sequential index of layer, 0-based, offsetted by number of raft layers. size_t m_id; @@ -318,6 +327,7 @@ protected: coordf_t dist_to_top; // mm dist to top bool need_infill = false; bool need_extra_wall = false; + bool need_cooling = false; AreaGroup(ExPolygon *a, int t, coordf_t d) : area(a), type(t), dist_to_top(d) {} }; std::vector area_groups; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 25ff35c..ea26984 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -49,7 +49,7 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. void LayerRegion::slices_to_fill_surfaces_clipped() { - // Note: this method should be idempotent, but fill_surfaces gets modified + // Note: this method should be idempotent, but fill_surfaces gets modified // in place. However we're now only using its boundaries (which are invariant) // so we're safe. This guarantees idempotence of prepare_infill() also in case // that combine_infill() turns some fill_surface into VOID surfaces. @@ -128,7 +128,7 @@ void LayerRegion::auto_circle_compensation(SurfaceCollection& slices, const Auto } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap, std::vector &loop_nodes) +void LayerRegion::make_perimeters(const SurfaceCollection &slices, const PerimeterRegions &perimeter_regions, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap, std::vector &loop_nodes) { this->perimeters.clear(); this->thin_fills.clear(); @@ -151,7 +151,6 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec &this->layer()->object()->config(), &print_config, spiral_mode, - // output: &this->perimeters, &this->thin_fills, @@ -160,17 +159,18 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec fill_no_overlap, &loop_nodes ); - + if (this->layer()->lower_layer != nullptr) // Cummulative sum of polygons over all the regions. g.lower_slices = &this->layer()->lower_layer->lslices; if (this->layer()->upper_layer != NULL) g.upper_slices = &this->layer()->upper_layer->lslices; - + g.layer_id = (int)this->layer()->id(); g.ext_perimeter_flow = this->flow(frExternalPerimeter); g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges); g.solid_infill_flow = this->flow(frSolidInfill); + g.perimeter_regions = &perimeter_regions; if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) g.process_arachne(); @@ -612,6 +612,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly shells = union_ex(shells, areas_to_be_solid); } + // m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); fill_surfaces.clear(); unsigned zones_expolygons_count = 0; @@ -639,7 +640,6 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly #else - #endif void LayerRegion::prepare_fill_surfaces() @@ -652,7 +652,7 @@ void LayerRegion::prepare_fill_surfaces() /* Note: in order to make the psPrepareInfill step idempotent, we should never alter fill_surfaces boundaries on which our idempotency relies since that's the only meaningful information returned by psPerimeters. */ - + bool spiral_mode = this->layer()->object()->print()->config().spiral_mode; #if 0 @@ -752,7 +752,7 @@ void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const const float transparency = 0.5f; for (const Surface &surface : this->fill_surfaces.surfaces) { svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); - svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); + svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); } export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); @@ -841,4 +841,3 @@ void LayerRegion::simplify_loop(ExtrusionLoop* loop) } } - diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 513087e..1a8ba33 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -250,12 +250,27 @@ public: Vec3d vector() const { return this->b - this->a; } Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); } double length() const { return vector().norm(); } - + void reverse() { std::swap(this->a, this->b); } Vec3d a; Vec3d b; static const constexpr int Dim = 3; using Scalar = Vec3d::Scalar; + + static void get_point_projection_to_line(const Vec3d &pt, const Vec3d &line_start, const Vec3d &line_dir, Vec3d &intersection_pt, float &proj_length) + { + auto line_dir_ = line_dir.normalized(); + auto BA = pt - line_start; + proj_length = BA.dot(line_dir_); + intersection_pt = line_start + proj_length * line_dir_; + } + float get_distance_of_point_to_line(const Vec3d &pt) + { + Vec3d intersection_pt; + float proj_length; + get_point_projection_to_line(pt, a, vector(), intersection_pt, proj_length); + return (intersection_pt - pt).norm(); + } }; class Line3float @@ -277,6 +292,26 @@ public: typedef std::vector Line3floats; BoundingBox get_extents(const Lines &lines); +using Line_3D = Linef3; + +class Polygon_3D +{ +public: + Polygon_3D(const std::vector &points) : m_points(points) {} + + std::vector get_lines() + { + std::vector lines; + lines.reserve(m_points.size()); + if (m_points.size() > 2) { + for (int i = 0; i < m_points.size() - 1; ++i) { lines.push_back(Line_3D(m_points[i], m_points[i + 1])); } + lines.push_back(Line_3D(m_points.back(), m_points.front())); + } + return lines; + } + std::vector m_points; +}; + } // namespace Slic3r // start Boost @@ -289,7 +324,7 @@ namespace boost { namespace polygon { struct segment_traits { typedef coord_t coordinate_type; typedef Slic3r::Point point_type; - + static inline point_type get(const Slic3r::Line& line, direction_1d dir) { return dir.to_int() ? line.b : line.a; } diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 1f49648..6e5139d 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -5,6 +5,7 @@ #include "libslic3r/format.hpp" #undef PI +#include // Include igl first. It defines "L" macro which then clashes with our localization #include #undef L diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 266f8fa..5514090 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -975,28 +974,30 @@ std::string Model::get_backup_path() buf << this->id().id; backup_path = parent_path.string() + buf.str(); - BOOST_LOG_TRIVIAL(info) << boost::format("model %1%, id %2%, backup_path empty, set to %3%")%this%this->id().id%backup_path; + std::string backup_path_safe = PathSanitizer::sanitize(backup_path); + BOOST_LOG_TRIVIAL(info) << boost::format("model %1%, id %2%, backup_path empty, set to %3%")%this%this->id().id % backup_path_safe; boost::filesystem::path temp_path(backup_path); if (boost::filesystem::exists(temp_path)) { - BOOST_LOG_TRIVIAL(info) << boost::format("model %1%, id %2%, remove previous %3%")%this%this->id().id%backup_path; + BOOST_LOG_TRIVIAL(info) << boost::format("model %1%, id %2%, remove previous %3%")%this%this->id().id % backup_path_safe; boost::filesystem::remove_all(temp_path); } } boost::filesystem::path temp_path(backup_path); - try { + std::string temp_path_safe = PathSanitizer::sanitize(temp_path); + try { if (!boost::filesystem::exists(temp_path)) { - BOOST_LOG_TRIVIAL(info) << "create /3D/Objects in " << temp_path; + BOOST_LOG_TRIVIAL(info) << "create /3D/Objects in " << temp_path_safe; boost::filesystem::create_directories(backup_path + "/3D/Objects"); - BOOST_LOG_TRIVIAL(info) << "create /Metadata in " << temp_path; + BOOST_LOG_TRIVIAL(info) << "create /Metadata in " << temp_path_safe; boost::filesystem::create_directories(backup_path + "/Metadata"); - BOOST_LOG_TRIVIAL(info) << "create /lock.txt in " << temp_path; - boost::filesystem::save_string_file(backup_path + "/lock.txt", + BOOST_LOG_TRIVIAL(info) << "create /lock.txt in " << temp_path_safe; + save_string_file(backup_path + "/lock.txt", boost::lexical_cast(get_current_pid())); } } catch (std::exception &ex) { - BOOST_LOG_TRIVIAL(error) << "Failed to create backup path" << temp_path << ": " << ex.what(); + BOOST_LOG_TRIVIAL(error) << "Failed to create backup path" << temp_path_safe << ": " << ex.what(); } return backup_path; @@ -1008,7 +1009,7 @@ void Model::remove_backup_path_if_exist() boost::filesystem::path temp_path(backup_path); if (boost::filesystem::exists(temp_path)) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("model %1%, id %2% remove backup_path %3%")%this%this->id().id%backup_path; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("model %1%, id %2% remove backup_path %3%") % this % this->id().id % PathSanitizer::sanitize(backup_path); boost::filesystem::remove_all(temp_path); } backup_path.clear(); @@ -1020,11 +1021,11 @@ std::string Model::get_backup_path(const std::string &sub_path) auto path = get_backup_path() + "/" + sub_path; try { if (!boost::filesystem::exists(path)) { - BOOST_LOG_TRIVIAL(info) << "create missing sub_path" << path; + BOOST_LOG_TRIVIAL(info) << "create missing sub_path" << PathSanitizer::sanitize(path); boost::filesystem::create_directories(path); } } catch (std::exception &ex) { - BOOST_LOG_TRIVIAL(error) << "Failed to create missing sub_path" << path << ": " << ex.what(); + BOOST_LOG_TRIVIAL(error) << "Failed to create missing sub_path" << PathSanitizer::sanitize(path) << ": " << ex.what(); } return path; } @@ -1038,11 +1039,11 @@ void Model::set_backup_path(std::string const& path) return; } if (!backup_path.empty()) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<id().id%backup_path; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", model %1%, id %2%, remove previous backup %3%") % this % this->id().id % PathSanitizer::sanitize(backup_path); Slic3r::remove_backup(*this, true); } backup_path = path; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<id().id%backup_path; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", model %1%, id %2%, set backup to %3%") % this % this->id().id % PathSanitizer::sanitize(backup_path); } void Model::load_from(Model& model) @@ -1372,6 +1373,11 @@ bool ModelObject::is_mm_painted() const return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); } +bool ModelObject::is_fuzzy_skin_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fuzzy_skin_facets_painted(); }); +} + void ModelObject::sort_volumes(bool full_sort) { // sort volumes inside the object to order "Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " @@ -1805,6 +1811,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con vol->source.is_from_builtin_objects = volume->source.is_from_builtin_objects; vol->supported_facets.assign(volume->supported_facets); + vol->fuzzy_skin_facets.assign(volume->fuzzy_skin_facets); vol->seam_facets.assign(volume->seam_facets); vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets); @@ -2006,7 +2013,6 @@ bool ModelVolume::is_the_only_one_part() const return true; } - Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array& plane_points) { Vec3d mid_point = {0.0, 0.0, 0.0}; @@ -2326,6 +2332,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array plane_poi const auto volume_matrix = volume->get_matrix(); volume->supported_facets.reset(); + volume->fuzzy_skin_facets.reset(); volume->seam_facets.reset(); volume->mmu_segmentation_facets.reset(); @@ -2416,6 +2423,7 @@ ModelObjectPtrs ModelObject::segment(size_t instance, unsigned int max_extruders const auto volume_matrix = volume->get_matrix(); volume->supported_facets.reset(); + volume->fuzzy_skin_facets.reset(); volume->seam_facets.reset(); if (!volume->is_model_part()) { @@ -2993,6 +3001,7 @@ void ModelVolume::set_material_id(t_model_material_id material_id) void ModelVolume::reset_extra_facets() { this->supported_facets.reset(); + this->fuzzy_skin_facets.reset(); this->seam_facets.reset(); this->mmu_segmentation_facets.reset(); } @@ -3230,6 +3239,14 @@ const Polygon& ModelVolume::get_convex_hull_2d(const Transform3d &trafo_instance return m_convex_hull_2d; } +void ModelVolume::set_transformation(const Geometry::Transformation &transformation) { + m_transformation = transformation; +} + +void ModelVolume::set_transformation(const Transform3d &trafo) { + m_transformation.set_from_transform(trafo); +} + int ModelVolume::get_repaired_errors_count() const { const RepairedMeshErrors &stats = this->mesh().stats().repaired_errors; @@ -3320,6 +3337,7 @@ size_t ModelVolume::split(unsigned int max_extruders, float scale_det) this->mmu_segmentation_facets.reset(); this->exterior_facets.reset(); this->supported_facets.reset(); + this->fuzzy_skin_facets.reset(); this->seam_facets.reset(); for (size_t i = 0; i < cur_face_count; i++) { if (ships[idx].find(i) != ships[idx].end()) { @@ -3341,7 +3359,6 @@ size_t ModelVolume::split(unsigned int max_extruders, float scale_det) } } } - this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); this->object->volumes[ivolume]->center_geometry_after_creation(); this->object->volumes[ivolume]->translate(offset); @@ -3350,6 +3367,9 @@ size_t ModelVolume::split(unsigned int max_extruders, float scale_det) this->object->volumes[ivolume]->config.set("extruder", this->extruder_id()); //this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); this->object->volumes[ivolume]->m_is_splittable = 0; + if (this->is_text()) { + this->object->volumes[ivolume]->clear_text_info(); + } ++ idx; last_all_mesh_face_count += cur_face_count; } @@ -3397,6 +3417,7 @@ void ModelVolume::assign_new_unique_ids_recursive() ObjectBase::set_new_unique_id(); config.set_new_unique_id(); supported_facets.set_new_unique_id(); + fuzzy_skin_facets.set_new_unique_id(); seam_facets.set_new_unique_id(); mmu_segmentation_facets.set_new_unique_id(); } @@ -3481,11 +3502,61 @@ void ModelVolume::convert_from_meters() this->source.is_converted_from_meters = true; } +void ModelVolume::set_text_configuration(const TextConfiguration text_configuration) { + m_text_info.text_configuration = text_configuration; +} + const Transform3d &ModelVolume::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } +void ModelInstance::set_transformation(const Geometry::Transformation& transformation) +{ + m_transformation = transformation; + m_assemble_scalling_factor_dirty = true; +} + +const Geometry::Transformation& ModelInstance::get_assemble_transformation() const +{ + if (m_assemble_scalling_factor_dirty) + { + m_assemble_transformation.set_scaling_factor(m_transformation.get_scaling_factor()); + m_assemble_scalling_factor_dirty = false; + } + return m_assemble_transformation; +} + +void ModelInstance::set_assemble_transformation(const Geometry::Transformation &transformation) +{ + m_assemble_initialized = true; + m_assemble_transformation = transformation; +} + +void ModelInstance::set_assemble_from_transform(const Transform3d &transform) +{ + m_assemble_initialized = true; + m_assemble_transformation.set_from_transform(transform); +} + +void ModelInstance::set_assemble_offset(const Vec3d &offset) +{ + m_assemble_initialized = true; + m_assemble_transformation.set_offset(offset); +} + +void ModelInstance::set_scaling_factor(const Vec3d& scaling_factor) +{ + m_transformation.set_scaling_factor(scaling_factor); + m_assemble_scalling_factor_dirty = true; +} + +void ModelInstance::set_scaling_factor(Axis axis, double scaling_factor) +{ + m_transformation.set_scaling_factor(axis, scaling_factor); + m_assemble_scalling_factor_dirty = true; +} + void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->transform(get_matrix(dont_translate)); @@ -4050,9 +4121,9 @@ void FacetsAnnotation::get_facets(const ModelVolume& mv, std::vector void serialize(Archive &ar) { - ar(m_font_name, m_font_version, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_embeded_depth, m_rotate_angle, m_text_gap, m_is_surface_text, - m_keep_horizontal, m_text, m_rr); - } + ar(m_font_name, m_font_version, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_embeded_depth, m_rotate_angle, m_text_gap, m_surface_type, m_text, + m_rr,text_configuration); + } + TextConfiguration text_configuration; }; // An object STL, or a modifier volume, over which a different set of parameters shall be applied. @@ -939,6 +950,8 @@ public: // List of mesh facets painted for MMU segmentation. FacetsAnnotation mmu_segmentation_facets; + // List of fuzzy skin + FacetsAnnotation fuzzy_skin_facets; // QDS: quick access for volume extruders, 1 based mutable std::vector mmuseg_extruders; mutable Timestamp mmuseg_ts; @@ -950,7 +963,6 @@ public: // Is set only when volume is Embossed Shape // Contain 2d information about embossed shape to be editabled std::optional emboss_shape; - // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; } ModelVolumeType type() const { return m_type; } @@ -1020,8 +1032,8 @@ public: static std::string type_to_string(const ModelVolumeType t); const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } - void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } + void set_transformation(const Geometry::Transformation &transformation); + void set_transformation(const Transform3d &trafo); const Vec3d& get_offset() const { return m_transformation.get_offset(); } double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } @@ -1049,7 +1061,10 @@ public: void convert_from_imperial_units(); void convert_from_meters(); + void set_text_configuration(const TextConfiguration text_configuration); + TextConfiguration& get_text_configuration() { return m_text_info.text_configuration; } void set_text_info(const TextInfo& text_info) { m_text_info = text_info; } + void clear_text_info() { m_text_info.m_text = ""; } const TextInfo& get_text_info() const { return m_text_info; } bool is_text() const { return !m_text_info.m_text.empty(); } const Transform3d &get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; @@ -1057,11 +1072,13 @@ public: ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); this->supported_facets.set_new_unique_id(); + this->fuzzy_skin_facets.set_new_unique_id(); this->seam_facets.set_new_unique_id(); this->mmu_segmentation_facets.set_new_unique_id(); } bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } + bool is_fuzzy_skin_facets_painted() const { return !this->fuzzy_skin_facets.empty(); } bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } @@ -1096,7 +1113,9 @@ private: Geometry::Transformation m_transformation; TextInfo m_text_info; - + // Is set only when volume is Embossed Text type + // Contain information how to re-create volume + //std::optional text_configuration; //QDS: add convex_hell_2d related logic void calculate_convex_hull_2d(const Geometry::Transformation &transformation) const; @@ -1111,10 +1130,12 @@ private: assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mmu_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mmu_segmentation_facets.id()); if (mesh.facets_count() > 1) @@ -1125,10 +1146,12 @@ private: assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mmu_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mmu_segmentation_facets.id()); } @@ -1137,10 +1160,12 @@ private: assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mmu_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mmu_segmentation_facets.id()); } @@ -1149,22 +1174,28 @@ private: ModelVolume(ModelObject *object, const ModelVolume &other) : ObjectBase(other), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), - config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), - m_text_info(other.m_text_info), emboss_shape(other.emboss_shape) + config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + , supported_facets(other.supported_facets) + , fuzzy_skin_facets(other.fuzzy_skin_facets) + , seam_facets(other.seam_facets) + , mmu_segmentation_facets(other.mmu_segmentation_facets) + , m_text_info(other.m_text_info), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mmu_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mmu_segmentation_facets.id()); assert(this->id() == other.id()); assert(this->config.id() == other.config.id()); assert(this->supported_facets.id() == other.supported_facets.id()); + assert(this->fuzzy_skin_facets.id() == other.fuzzy_skin_facets.id()); assert(this->seam_facets.id() == other.seam_facets.id()); assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); this->set_material_id(other.material_id()); @@ -1177,10 +1208,12 @@ private: assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mmu_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mmu_segmentation_facets.id()); assert(this->id() != other.id()); @@ -1192,10 +1225,12 @@ private: assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->supported_facets.id() != other.supported_facets.id()); + assert(this->fuzzy_skin_facets.id() != other.fuzzy_skin_facets.id()); assert(this->seam_facets.id() != other.seam_facets.id()); assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); assert(this->id() != this->config.id()); assert(this->supported_facets.empty()); + assert(this->fuzzy_skin_facets.empty()); assert(this->seam_facets.empty()); assert(this->mmu_segmentation_facets.empty()); } @@ -1205,10 +1240,12 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; // Used for deserialization, therefore no IDs are allocated. - ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { + ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), fuzzy_skin_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) + { assert(this->id().invalid()); assert(this->config.id().invalid()); assert(this->supported_facets.id().invalid()); + assert(this->fuzzy_skin_facets.id().invalid()); assert(this->seam_facets.id().invalid()); assert(this->mmu_segmentation_facets.id().invalid()); } @@ -1222,6 +1259,9 @@ private: auto t = supported_facets.timestamp(); cereal::load_by_value(ar, supported_facets); mesh_changed |= t != supported_facets.timestamp(); + t = fuzzy_skin_facets.timestamp(); + cereal::load_by_value(ar, fuzzy_skin_facets); + mesh_changed |= t != fuzzy_skin_facets.timestamp(); t = seam_facets.timestamp(); cereal::load_by_value(ar, seam_facets); mesh_changed |= t != seam_facets.timestamp(); @@ -1245,6 +1285,7 @@ private: bool has_convex_hull = m_convex_hull.get() != nullptr; ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, m_text_info, cut_info); cereal::save_by_value(ar, supported_facets); + cereal::save_by_value(ar, fuzzy_skin_facets); cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, mmu_segmentation_facets); cereal::save_by_value(ar, config); @@ -1280,9 +1321,10 @@ class ModelInstance final : public ObjectBase { private: Geometry::Transformation m_transformation; - Geometry::Transformation m_assemble_transformation; + mutable Geometry::Transformation m_assemble_transformation; Vec3d m_offset_to_assembly{ 0.0, 0.0, 0.0 }; bool m_assemble_initialized; + mutable bool m_assemble_scalling_factor_dirty{ true }; public: // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) @@ -1304,19 +1346,14 @@ public: ModelObject* get_object() const { return this->object; } const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } + void set_transformation(const Geometry::Transformation& transformation); - const Geometry::Transformation& get_assemble_transformation() const { return m_assemble_transformation; } - void set_assemble_transformation(const Geometry::Transformation& transformation) { - m_assemble_initialized = true; - m_assemble_transformation = transformation; - } - void set_assemble_from_transform(const Transform3d& transform) { - m_assemble_initialized = true; - m_assemble_transformation.set_from_transform(transform); - } + const Geometry::Transformation& get_assemble_transformation() const; + void set_assemble_transformation(const Geometry::Transformation &transformation); + void set_assemble_from_transform(const Transform3d &transform); const Vec3d& get_assemble_offset() {return m_assemble_transformation.get_offset(); } - void set_assemble_offset(const Vec3d &offset){ m_assemble_initialized = true;m_assemble_transformation.set_offset(offset);} + void set_assemble_offset(const Vec3d &offset); + void set_assemble_rotation(const Vec3d &rotation) { m_assemble_transformation.set_rotation(rotation); } void rotate_assemble(double angle, const Vec3d& axis) { m_assemble_transformation.set_rotation(m_assemble_transformation.get_rotation() + Geometry::extract_euler_angles(Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)).toRotationMatrix())); @@ -1343,8 +1380,8 @@ public: const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } - void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } - void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + void set_scaling_factor(const Vec3d& scaling_factor); + void set_scaling_factor(Axis axis, double scaling_factor); const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } @@ -1758,8 +1795,8 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO // Test whether the now ModelObject has newer custom supports data than the old one. // The function assumes that volumes list is synchronized. bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new); - -// Test whether the now ModelObject has newer custom seam data than the old one. +bool model_custom_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new); + // Test whether the now ModelObject has newer custom seam data than the old one. // The function assumes that volumes list is synchronized. bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index e8defab..350f786 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -2335,4 +2335,245 @@ std::vector> multi_material_segmentation_by_painting(con return segmented_regions_merged; } +std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) +{ + const size_t num_extruders = 1; + const size_t num_layers = print_object.layers().size(); + std::vector> segmented_regions(num_layers); + segmented_regions.assign(num_layers, std::vector(num_extruders + 1)); + std::vector> painted_lines(num_layers); + std::array painted_lines_mutex; + std::vector edge_grids(num_layers); + const ConstLayerPtrsAdaptor layers = print_object.layers(); + std::vector input_expolygons(num_layers); + + throw_on_cancel_callback(); + +#ifdef MM_SEGMENTATION_DEBUG + static int iRun = 0; +#endif // MM_SEGMENTATION_DEBUG + + // Merge all regions and remove small holes + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + throw_on_cancel_callback(); + ExPolygons ex_polygons; + for (LayerRegion *region : layers[layer_idx]->regions()) + for (const Surface &surface : region->slices.surfaces) Slic3r::append(ex_polygons, offset_ex(surface.expolygon, float(10 * SCALED_EPSILON))); + // All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON + // to ensure that very close polygons will be merged. + ex_polygons = union_ex(ex_polygons); + // Remove all expolygons and holes with an area less than 0.1mm^2 + remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f))); + // Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams + // and consequently with the extraction of colored segments by function extract_colored_segments. + // Calling simplify_polygons removes these self-intersections. + // Also, occasionally input polygons contained several points very close together (distance between points is 1 or so). + // Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices. + // This consequently leads to issues with the extraction of colored segments by function extract_colored_segments. + // Calling expolygons_simplify fixed these issues. + input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), + PI / 6); + +#ifdef MM_SEGMENTATION_DEBUG_INPUT + export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun), layers[layer_idx]->regions(), input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_INPUT + } + }); // end of parallel_for + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - end"; + + std::vector layer_bboxes(num_layers); + for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { + throw_on_cancel_callback(); + layer_bboxes[layer_idx] = get_extents(layers[layer_idx]->regions()); + layer_bboxes[layer_idx].merge(get_extents(input_expolygons[layer_idx])); + } + + for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { + throw_on_cancel_callback(); + BoundingBox bbox = layer_bboxes[layer_idx]; + // Projected triangles could, in rare cases (as in GH issue #7299), belongs to polygons printed in the previous or the next layer. + // Let's merge the bounding box of the current layer with bounding boxes of the previous and the next layer to ensure that + // every projected triangle will be inside the resulting bounding box. + if (layer_idx > 1) bbox.merge(layer_bboxes[layer_idx - 1]); + if (layer_idx < num_layers - 1) bbox.merge(layer_bboxes[layer_idx + 1]); + // Projected triangles may slightly exceed the input polygons. + bbox.offset(30 * SCALED_EPSILON); + edge_grids[layer_idx].set_bbox(bbox); + edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); + } + + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - begin"; + for (const ModelVolume *mv : print_object.model_object()->volumes) { +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE + tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, + &throw_on_cancel_callback](const tbb::blocked_range &range) { + for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { +#else + for (size_t extruder_idx = 1; extruder_idx < num_extruders + 1; ++extruder_idx) { +#endif + throw_on_cancel_callback(); + const indexed_triangle_set custom_facets = mv->fuzzy_skin_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); + if (!mv->is_model_part() || custom_facets.indices.empty()) continue; + + const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE + tbb::parallel_for(tbb::blocked_range(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &layers, &edge_grids, &input_expolygons, + &painted_lines, &painted_lines_mutex, + &extruder_idx](const tbb::blocked_range &range) { + for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) { +#else + for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) { +#endif + float min_z = std::numeric_limits::max(); + float max_z = std::numeric_limits::lowest(); + + std::array facet; + for (int p_idx = 0; p_idx < 3; ++p_idx) { + facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)]; + max_z = std::max(max_z, facet[p_idx].z()); + min_z = std::min(min_z, facet[p_idx].z()); + } + + if (is_equal(min_z, max_z)) continue; + + // Sort the vertices by z-axis for simplification of projected_facet on slices + std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); }); + + // Find lowest slice not below the triangle. + auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON), [](float z, const Layer *l1) { return z < l1->slice_z; }); + auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z + EPSILON), [](float z, const Layer *l1) { return z < l1->slice_z; }); + --last_layer; + + for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) { + const Layer *layer = *layer_it; + size_t layer_idx = layer_it - layers.begin(); + if (input_expolygons[layer_idx].empty() || is_less(layer->slice_z, facet[0].z()) || is_less(facet[2].z(), layer->slice_z)) continue; + + // https://kandepet.com/3d-printing-slicing-3d-objects/ + float t = (float(layer->slice_z) - facet[0].z()) / (facet[2].z() - facet[0].z()); + Vec3f line_start_f = facet[0] + t * (facet[2] - facet[0]); + Vec3f line_end_f; + + // QDS: When one side of a triangle coincides with the slice_z. + if ((is_equal(facet[0].z(), facet[1].z()) && is_equal(facet[1].z(), layer->slice_z)) || + (is_equal(facet[1].z(), facet[2].z()) && is_equal(facet[1].z(), layer->slice_z))) { + line_end_f = facet[1]; + } else if (facet[1].z() > layer->slice_z) { + // [P0, P2] and [P0, P1] + float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z()); + line_end_f = facet[0] + t1 * (facet[1] - facet[0]); + } else { + // [P0, P2] and [P1, P2] + float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z()); + line_end_f = facet[1] + t2 * (facet[2] - facet[1]); + } + + Line line_to_test(Point(scale_(line_start_f.x()), scale_(line_start_f.y())), Point(scale_(line_end_f.x()), scale_(line_end_f.y()))); + line_to_test.translate(-print_object.center_offset()); + + // BoundingBoxes for EdgeGrids are computed from printable regions. It is possible that the painted line (line_to_test) could + // be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618). + // To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases + // when any of the endpoints of the line are outside the EdgeGrid's BoundingBox. + const BoundingBox &edge_grid_bbox = edge_grids[layer_idx].bbox(); + if (!edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) { + // If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line. + if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) || !line_to_test.clip_with_bbox(edge_grid_bbox)) continue; + } + + size_t mutex_idx = layer_idx & 0x3F; + assert(mutex_idx < painted_lines_mutex.size()); + + PaintedLineVisitor visitor(edge_grids[layer_idx], painted_lines[layer_idx], painted_lines_mutex[mutex_idx], 16); + visitor.line_to_test = line_to_test; + visitor.color = int(extruder_idx); + edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor, true); + } + } +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE + }); // end of parallel_for +#endif + } +#ifndef MM_SEGMENTATION_DEBUG_PAINT_LINE + }); // end of parallel_for +#endif + } + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - painted layers count: " + << std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector &pl) { return !pl.empty(); }); + + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, + &throw_on_cancel_callback](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + throw_on_cancel_callback(); + if (!painted_lines[layer_idx].empty()) { +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES + export_painted_lines_to_svg(debug_out_path("0-mm-painted-lines-%d-%d.svg", layer_idx, iRun), {painted_lines[layer_idx]}, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES + + std::vector> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), + std::move(painted_lines[layer_idx])); + +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES + export_painted_lines_to_svg(debug_out_path("1-mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun), post_processed_painted_lines, + input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES + + std::vector color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines); + +#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS + export_colorized_polygons_to_svg(debug_out_path("2-mm-colorized_polygons-%d-%d.svg", layer_idx, iRun), color_poly, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS + + assert(!color_poly.empty()); + assert(!color_poly.front().empty()); + if (has_layer_only_one_color(color_poly)) { + // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. + segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx]; + } else { + MMU_Graph graph = build_graph(layer_idx, color_poly); + remove_multiple_edges_in_vertices(graph, color_poly); + graph.remove_nodes_with_one_arc(); + segmented_regions[layer_idx] = extract_colored_segments(graph, num_extruders); + // segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx); + } + +#ifdef MM_SEGMENTATION_DEBUG_REGIONS + export_regions_to_svg(debug_out_path("3-mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_REGIONS + } + } + }); // end of parallel_for + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end"; + throw_on_cancel_callback(); + + float max_width = 0.f; + for (size_t region_idx = 0; region_idx < print_object.num_printing_regions(); ++region_idx) { + const PrintRegion ®ion = print_object.printing_region(region_idx); + max_width = std::max(max_width, region.flow(print_object, frExternalPerimeter, print_object.config().layer_height).width()); + } + + if (max_width > 0.f) { + cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(0)), throw_on_cancel_callback); + throw_on_cancel_callback(); + } + + std::vector> segmented_regions_merged = segmented_regions; + throw_on_cancel_callback(); + +#ifdef MM_SEGMENTATION_DEBUG_REGIONS + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) + export_regions_to_svg(debug_out_path("4-mm-regions-merged-%d-%d.svg", layer_idx, iRun), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_REGIONS + +#ifdef MM_SEGMENTATION_DEBUG + ++iRun; +#endif // MM_SEGMENTATION_DEBUG + + return segmented_regions_merged; +} + } // namespace Slic3r diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index 91d0f29..3988e78 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -23,6 +23,9 @@ using ColoredLines = std::vector; // Returns MMU segmentation based on painting in MMU segmentation gizmo std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); +// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo +std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); + } // namespace Slic3r namespace boost::polygon { diff --git a/src/libslic3r/OverhangDetector.hpp b/src/libslic3r/OverhangDetector.hpp index 5ff0724..22185cc 100644 --- a/src/libslic3r/OverhangDetector.hpp +++ b/src/libslic3r/OverhangDetector.hpp @@ -2,6 +2,7 @@ #define OVERHANG_DETECTOR_HPP #include "ExtrusionEntityCollection.hpp" +#include #include "ClipperUtils.hpp" #include "Flow.hpp" #include "AABBTreeLines.hpp" diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index bc9b85a..a3366fd 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -14,6 +14,7 @@ #include #include #include "OverhangDetector.hpp" +#include "FuzzySkin.hpp" static const double narrow_loop_length_threshold = 10; //QDS: when the width of expolygon is smaller than @@ -44,21 +45,20 @@ public: bool is_smaller_width_perimeter; // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. unsigned short depth; - // Should this contur be fuzzyfied on path generation? - bool fuzzify; // Slow down speed for circle bool need_circle_compensation = false; // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector children; - PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify, bool is_small_width_perimeter = false, bool need_circle_compensation_ = false) : - polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), fuzzify(fuzzify), need_circle_compensation(need_circle_compensation_) {} + PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool is_small_width_perimeter = false, bool need_circle_compensation_ = false) : + polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), need_circle_compensation(need_circle_compensation_) {} // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. bool is_internal_contour() const; }; +#if 0 // Thanks Cura developers for this function. static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_distance) { @@ -138,7 +138,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy if (out.size() >= 3) ext_lines.junctions = std::move(out); } - +#endif using PerimeterGeneratorLoops = std::vector; static void lowpass_filter_by_paths_overhang_degree(ExtrusionPaths& paths) { @@ -454,7 +454,6 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object ExtrusionEntityCollection coll; - Polygon fuzzified; for (const PerimeterGeneratorLoop &loop : loops) { bool is_external = loop.is_external(); bool is_small_width = loop.is_smaller_width_perimeter; @@ -509,12 +508,10 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime extrusion_width = perimeter_generator.perimeter_flow.width(); } + // Apply fuzzy skin if it is enabled for at least some part of the polygon. + const Polygon polygon = apply_fuzzy_skin(loop.polygon, *(perimeter_generator.config), *(perimeter_generator.perimeter_regions), + perimeter_generator.layer_id, loop.depth, loop.is_contour); - const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon; - if (loop.fuzzify) { - fuzzified = loop.polygon; - fuzzy_polygon(fuzzified, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_distance.value)); - } if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { // get non 100% overhang paths by intersecting this loop with the grown lower slices // prepare grown lower layer slices for overhang detection @@ -534,22 +531,6 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime bool detect_overhang_degree = perimeter_generator.config->enable_overhang_speed.get_at(get_extruder_index(*(perimeter_generator.print_config), perimeter_generator.config->wall_filament - 1)) && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None; - //QDS: fuzziy skin may generate a line that approximates a point, which can cause the clipper to get empty results - if (loop.fuzzify && remain_polines.empty() && inside_polines.empty()) { - bool inside_contour = false; - for (const Polygon cliped_polygon : lower_polygons_series_clipped) { - if (cliped_polygon.contains(polygon.first_point())) { - inside_contour = true; - break; - } - } - - if (inside_contour) - inside_polines.push_back(to_polyline(polygon)); - else - remain_polines.push_back(to_polyline(polygon)); - } - if (!detect_overhang_degree) { if (!inside_polines.empty()) extrusion_paths_append( @@ -597,12 +578,14 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } } + // get 100% overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter if (remain_polines.size() != 0) { - if (!((perimeter_generator.object_config->enable_support || perimeter_generator.object_config->enforce_support_layers > 0) - && perimeter_generator.object_config->support_top_z_distance.value == 0)) { + if (!((perimeter_generator.object_config->enable_support || perimeter_generator.object_config->enforce_support_layers > 0) && + perimeter_generator.object_config->support_top_z_distance.value == 0) && + detect_overhang_degree) { //detect if the overhang perimeter is bridge detect_bridge_wall(perimeter_generator, paths, @@ -623,6 +606,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } + if (paths.empty()) + continue; + // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); @@ -847,8 +833,9 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p perimeter_generator.loop_nodes->push_back(std::move(node)); } - if (pg_extrusion.fuzzify) - fuzzy_extrusion_line(*extrusion, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_distance.value)); + // Apply fuzzy skin if it is enabled for at least some part of the ExtrusionLine. + *extrusion = apply_fuzzy_skin(*extrusion, *(perimeter_generator.config), *(perimeter_generator.perimeter_regions), perimeter_generator.layer_id, + pg_extrusion.extrusion->inset_idx, !pg_extrusion.extrusion->is_closed || pg_extrusion.is_contour); ExtrusionPaths paths; // detect overhanging/bridging perimeters @@ -1245,19 +1232,17 @@ void PerimeterGenerator::process_classic() break; } { - const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && ((i == 0 && this->layer_id > 0) || this->config->fuzzy_skin == FuzzySkinType::AllWalls); - const bool fuzzify_holes = fuzzify_contours && (this->config->fuzzy_skin == FuzzySkinType::All || this->config->fuzzy_skin == FuzzySkinType::AllWalls); for (const ExPolygon& expolygon : offsets) { // Outer contour may overlap with an inner contour, // inner contour may overlap with another inner contour, // outer contour may overlap with itself. //FIXME evaluate the overlaps, annotate each point with an overlap depth, // compensate for the depth of intersection. - contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, false, counter_circle_compensation)); + contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, false, counter_circle_compensation)); if (!expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); for (const Polygon &hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, fuzzify_holes, false, is_compensation_hole(hole)); + holes[i].emplace_back(hole, i, false, false, is_compensation_hole(hole)); } } @@ -1293,11 +1278,11 @@ void PerimeterGenerator::process_classic() } for (const ExPolygon& expolygon : offsets_with_smaller_width) { - contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true, counter_circle_compensation)); + contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, true, counter_circle_compensation)); if (!expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); for (const Polygon& hole : expolygon.holes) - holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, fuzzify_contours, true, is_compensation_hole(hole))); + holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, true, is_compensation_hole(hole))); } } } @@ -1934,46 +1919,6 @@ void PerimeterGenerator::process_arachne() } } - if (this->layer_id > 0 && this->config->fuzzy_skin != FuzzySkinType::None) { - std::vector closed_loop_extrusions; - for (PerimeterGeneratorArachneExtrusion& extrusion : ordered_extrusions) - if (extrusion.extrusion->inset_idx == 0) { - if (extrusion.extrusion->is_closed && this->config->fuzzy_skin == FuzzySkinType::External) { - closed_loop_extrusions.emplace_back(&extrusion); - } - else { - extrusion.fuzzify = true; - } - } - - if (this->config->fuzzy_skin == FuzzySkinType::External) { - ClipperLib_Z::Paths loops_paths; - loops_paths.reserve(closed_loop_extrusions.size()); - for (const auto& cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); - size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); - ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) - loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); - loops_paths.emplace_back(loop_path); - } - - ClipperLib_Z::Clipper clipper; - clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); - ClipperLib_Z::PolyTree loops_polytree; - clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); - - for (const ClipperLib_Z::PolyNode* child_node : loops_polytree.Childs) { - // The whole contour must have the same index. - coord_t polygon_idx = child_node->Contour.front().z(); - bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), - [&polygon_idx](const ClipperLib_Z::IntPoint& point) -> bool { return polygon_idx == point.z(); }); - if (has_same_idx) - closed_loop_extrusions[polygon_idx]->fuzzify = true; - } - } - } // QDS. adjust wall generate seq if (this->object_config->wall_sequence == WallSequence::InnerOuterInner) { if (ordered_extrusions.size() > 2) { // 3 walls minimum needed to do inner outer inner ordering @@ -2092,4 +2037,38 @@ std::vector PerimeterGenerator::generate_lower_polygons_series(float w return lower_polygons_series; } +PerimeterRegion::PerimeterRegion(const LayerRegion &layer_region) : region(&layer_region.region()) +{ + this->expolygons = to_expolygons(layer_region.slices.surfaces); + this->bbox = get_extents(this->expolygons); +} + +bool PerimeterRegion::has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + return config.fuzzy_skin == other_config.fuzzy_skin && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness + && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance; +} + +void PerimeterRegion::merge_compatible_perimeter_regions(PerimeterRegions &perimeter_regions) +{ + if (perimeter_regions.size() <= 1) { return; } + + PerimeterRegions perimeter_regions_merged; + for (auto it_curr_region = perimeter_regions.begin(); it_curr_region != perimeter_regions.end();) { + PerimeterRegion current_merge = *it_curr_region; + auto it_next_region = std::next(it_curr_region); + for (; it_next_region != perimeter_regions.end() && has_compatible_perimeter_regions(it_next_region->region->config(), it_curr_region->region->config()); + ++it_next_region) { + Slic3r::append(current_merge.expolygons, std::move(it_next_region->expolygons)); + current_merge.bbox.merge(it_next_region->bbox); + } + + if (std::distance(it_curr_region, it_next_region) > 1) { current_merge.expolygons = union_ex(current_merge.expolygons); } + + perimeter_regions_merged.emplace_back(std::move(current_merge)); + it_curr_region = it_next_region; + } + + perimeter_regions = perimeter_regions_merged; +} } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 25cd195..ea70148 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -9,6 +9,25 @@ #include "SurfaceCollection.hpp" namespace Slic3r { +class LayerRegion; +class PrintRegion; + +struct PerimeterRegion +{ + const PrintRegion *region; + ExPolygons expolygons; + BoundingBox bbox; + + explicit PerimeterRegion(const LayerRegion &layer_region); + + // If there is any incompatibility, we don't need to create separate LayerRegions. + // Because it is enough to split perimeters by PerimeterRegions. + static bool has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config); + + static void merge_compatible_perimeter_regions(std::vector &perimeter_regions); +}; + +using PerimeterRegions = std::vector; class PerimeterGenerator { public: @@ -25,6 +44,7 @@ public: const PrintRegionConfig *config; const PrintObjectConfig *object_config; const PrintConfig *print_config; + const PerimeterRegions *perimeter_regions; // Outputs: ExtrusionEntityCollection *loops; ExtrusionEntityCollection *gap_fill; @@ -41,10 +61,10 @@ public: std::pair m_external_overhang_dist_boundary; std::pair m_smaller_external_overhang_dist_boundary; std::vector *loop_nodes; - + PerimeterGenerator( // Input: - const SurfaceCollection* slices, + const SurfaceCollection* slices, double layer_height, Flow flow, const PrintRegionConfig* config, diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 43c3344..fb619d1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -481,7 +481,7 @@ void Preset::load_info(const std::string& file) } else if (v.first.compare("base_id") == 0) { this->base_id = v.second.get_value(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << file << " and base_id: " << this->base_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load info from: " << PathSanitizer::sanitize(file) << " and base_id: " << this->base_id; if (this->base_id.compare("null") == 0) this->base_id.clear(); } @@ -892,11 +892,11 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle) static std::vector s_Preset_print_options { "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "spiral_mode_max_xy_smoothing", "slicing_mode", - //1.9.5 - "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", + "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "top_color_penetration_layers", "bottom_color_penetration_layers", - "smooth_speed_discontinuity_area","smooth_coefficient", - "seam_position", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", + "smooth_speed_discontinuity_area","smooth_coefficient", "seam_position", "seam_placement_away_from_overhangs", + "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", + "locked_skin_infill_pattern", "locked_skeleton_infill_pattern", "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle", "infill_shift_step", "skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", "infill_rotate_step", "symmetric_infill_y_axis", @@ -940,9 +940,9 @@ static std::vector s_Preset_print_options { "tree_support_branch_diameter_angle", "detect_narrow_internal_solid_infill", "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination", /*"adaptive_layer_height",*/ - //1.9.5 "support_bottom_interface_spacing", "enable_overhang_speed", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "overhang_totally_speed", - "initial_layer_infill_speed", "top_one_wall_type", "top_area_threshold", "only_one_wall_first_layer", + "enable_height_slowdown","slowdown_start_height", "slowdown_start_speed", "slowdown_start_acc", "slowdown_end_height", "slowdown_end_speed", "slowdown_end_acc", + "initial_layer_infill_speed", "top_one_wall_type", "top_area_threshold", "only_one_wall_first_layer", "timelapse_type", "internal_bridge_support_thickness", "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", @@ -954,8 +954,8 @@ static std::vector s_Preset_print_options { // calib "print_flow_ratio", //Orca - "exclude_object", /*"seam_slope_type",*/ "seam_slope_conditional", "scarf_angle_threshold", /*"seam_slope_start_height", */"seam_slope_entire_loop",/* "seam_slope_min_length",*/ - "seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed" /*, "seam_slope_gap"*/, "precise_outer_wall", + "exclude_object", "override_filament_scarf_seam_setting", "seam_slope_type", "seam_slope_conditional", "scarf_angle_threshold", "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", + "seam_slope_steps", "seam_slope_inner_walls", "role_base_wipe_speed" , "seam_slope_gap", "precise_outer_wall", "interlocking_beam", "interlocking_orientation", "interlocking_beam_layer_count", "interlocking_depth", "interlocking_boundary_avoidance", "interlocking_beam_width" //w16 ,"resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed" @@ -1035,7 +1035,7 @@ static std::vector s_Preset_printer_options { "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "use_relative_e_distances", "extruder_type","use_firmware_retraction", "grab_length","machine_switch_extruder_time","hotend_cooling_rate","hotend_heating_rate","enable_pre_heating", "physical_extruder_map", - "bed_temperature_formula" + "bed_temperature_formula","machine_prepare_compensation_time", "nozzle_flush_dataset","apply_top_surface_compensation" //w34 ,"support_multi_bed_types" //y58 @@ -1235,7 +1235,7 @@ void PresetCollection::load_presets( } //QDS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, load presets from %1%, current type %2%")%dir %Preset::get_type_string(m_type); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, load presets from %1%, current type %2%") % PathSanitizer::sanitize(dir) % Preset::get_type_string(m_type); //QDS do not parse folder if not exists m_dir_path = dir.string(); if (!fs::exists(dir)) { @@ -1292,7 +1292,7 @@ void PresetCollection::load_presets( file_path.replace_extension(".info"); if (fs::exists(file_path)) fs::remove(file_path); - BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed")%preset.file; + BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed") % PathSanitizer::sanitize(preset.file); continue; } @@ -1300,7 +1300,6 @@ void PresetCollection::load_presets( boost::optional version = Semver::parse(version_str); if (!version) continue; Semver app_version = *(Semver::parse(SLIC3R_VERSION)); - //1.9.5 if ( version->maj() > app_version.maj()) { BOOST_LOG_TRIVIAL(warning) << "Preset incompatibla, not loading: " << name; continue; @@ -1336,7 +1335,7 @@ void PresetCollection::load_presets( else { auto inherits_config2 = dynamic_cast(inherits_config); if ((inherits_config2 && !inherits_config2->value.empty())) { - BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!")%inherits_config2->value %preset.file; + BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent %1% for config %2%!") % inherits_config2->value % PathSanitizer::sanitize(preset.file); continue; } // We support custom root preset now @@ -1351,18 +1350,18 @@ void PresetCollection::load_presets( // Report configuration fields, which are misplaced into a wrong group. std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config); if (!incorrect_keys.empty()) - BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << - preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << PathSanitizer::sanitize(preset.file) + << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; preset.loaded = true; //QDS: add some workaround for previous incorrect settings if ((!preset.setting_id.empty())&&(preset.setting_id == preset.base_id)) preset.setting_id.clear(); //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, is_system %3%, is_default %4%, is_visible %5%")%Preset::get_type_string(m_type) %preset.name %preset.is_system %preset.is_default %preset.is_visible; // add alias for custom filament preset set_custom_preset_alias(preset); } catch (const std::ifstream::failure &err) { - BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%")%preset.file %err.what(); + BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%") % PathSanitizer::sanitize(preset.file) % err.what(); fs::path file_path(preset.file); if (fs::exists(file_path)) fs::remove(file_path); @@ -1371,7 +1370,7 @@ void PresetCollection::load_presets( fs::remove(file_path); //throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { - BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%")%preset.file %err.what(); + BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%") % PathSanitizer::sanitize(preset.file) % err.what(); //throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); fs::path file_path(preset.file); if (fs::exists(file_path)) @@ -1392,7 +1391,8 @@ void PresetCollection::load_presets( m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); //QDS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": loaded %1% presets from %2%, type %3%")%presets_loaded.size() %dir %Preset::get_type_string(m_type); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ + << boost::format(": loaded %1% presets from %2%, type %3%") % presets_loaded.size() % PathSanitizer::sanitize(dir) % Preset::get_type_string(m_type); //this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative); @@ -1802,7 +1802,6 @@ bool PresetCollection::load_user_preset(std::string name, std::mapmaj() > app_version.maj()) { BOOST_LOG_TRIVIAL(warning)<< __FUNCTION__ << boost::format("version %1% mismatch with app version %2%, not loading for user preset %3%")%version_str %SLIC3R_VERSION %name; return false; @@ -2101,7 +2100,6 @@ std::pair PresetCollection::load_external_preset( std::set *key_set1 = nullptr, *key_set2 = nullptr; Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); - if (!inherits.empty() && (different_settings_list.size() > 0)) { auto iter = this->find_preset_internal(inherits); if (iter != m_presets.end() && iter->name == inherits) { @@ -2296,7 +2294,7 @@ std::pair PresetCollection::load_external_preset( this->get_edited_preset().is_external = true; //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", type %1% added a preset, name %2%, path %3%, is_system %4%, is_default %5% is_external %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_external; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", type %1% added a preset, name %2%, is_system %3%, is_default %4%, is_external %5%")%Preset::get_type_string(m_type) %preset.name %preset.is_system %preset.is_default %preset.is_external; return std::make_pair(&preset, false); } @@ -2323,7 +2321,7 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string this->select_preset_by_name(name, true); unlock(); //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, is_system %3%, is_default %4%, is_visible %5%")%Preset::get_type_string(m_type) %preset.name %preset.is_system %preset.is_default %preset.is_visible; return preset; } @@ -2412,6 +2410,11 @@ bool PresetCollection::clone_presets_for_filament(Preset const *const & pres preset.config.apply_only(dynamic_config, {"filament_vendor", "compatible_printers", "filament_type"},true); preset.filament_id = filament_id; + auto compatible = dynamic_cast(preset.config.option("compatible_printers")); + if (compatible->values.empty()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " " << __LINE__ << preset.name << " apply compatible_printer failed"; + compatible->values.push_back(compatible_printers); + } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << preset.name << " is cloned and filament_id: " << filament_id; } }, @@ -2600,7 +2603,9 @@ bool PresetCollection::delete_preset(const std::string& name) } //QDS: add lock logic for sync preset in background lock(); - m_presets.erase(it); + it = m_presets.erase(it); + if (std::distance(m_presets.begin(), it) < m_idx_selected) + --m_idx_selected; unlock(); return true; @@ -2663,7 +2668,6 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const const Preset *PresetCollection::get_preset_base(const Preset &child) const { - //y60 if (child.is_system || child.is_default) return &child; // Handle user preset @@ -2701,7 +2705,7 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string& it_preset->is_visible && (it_preset->is_compatible || size_t(it_preset - m_presets.begin()) == m_idx_selected)) return it_preset->name; } - + return alias; } @@ -2801,7 +2805,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name)); const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter"); if (opt) - config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); int some_compatible = 0; if (active_print) @@ -3258,7 +3262,6 @@ std::string PresetCollection::path_from_name(const std::string &new_name, bool d return (boost::filesystem::path(m_dir_path) / "base" / file_name).make_preferred().string(); else return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); - } std::string PresetCollection::path_for_preset(const Preset &preset) const diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index c8126c6..a781dab 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -60,8 +60,14 @@ #define QDT_JSON_KEY_FAMILY "family" #define QDT_JSON_KEY_BED_MODEL "bed_model" #define QDT_JSON_KEY_BED_TEXTURE "bed_texture" +#define QDT_JSON_KEY_USE_RECT_GRID "use_rect_grid" #define QDT_JSON_KEY_IMAGE_BED_TYPE "image_bed_type" #define QDT_JSON_KEY_DEFAULT_BED_TYPE "default_bed_type" +#define QDT_JSON_KEY_BOTTOM_TEXTURE_END_NAME "bottom_texture_end_name" +#define QDT_JSON_KEY_BOTTOM_TEXTURE_RECT "bottom_texture_rect" +#define QDT_JSON_KEY_MIDDLE_TEXTURE_RECT "middle_texture_rect" +#define QDT_JSON_KEY_RIGHT_ICON_OFFSET_BED "right_icon_offset_bed" + #define QDT_JSON_KEY_HOTEND_MODEL "hotend_model" #define QDT_JSON_KEY_DEFAULT_MATERIALS "default_materials" #define QDT_JSON_KEY_NOT_SUPPORT_BED_TYPE "not_support_bed_type" @@ -91,6 +97,7 @@ extern int get_values_from_json(std::string file_path, std::vector& extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree); extern void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults); + class VendorProfile { public: @@ -120,8 +127,13 @@ public: // Vendor & Printer Model specific print bed model & texture. std::string bed_model; std::string bed_texture; + std::string use_rect_grid; std::string image_bed_type; std::string default_bed_type; + std::string bottom_texture_end_name; + std::string bottom_texture_rect; + std::string middle_texture_rect; + std::string right_icon_offset_bed; std::string hotend_model; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7a1ba5d..1fb4b23 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -39,6 +39,8 @@ static std::vector s_project_options { "flush_volumes_matrix", // QDS "filament_colour", + "filament_colour_type", + "filament_multi_colour", "wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle", @@ -88,7 +90,7 @@ PresetBundle::PresetBundle() for(const std::string& opt_key : default_config.keys()){ ConfigOption* opt = default_config.optptr(opt_key, false); bool is_override_key = std::find(filament_extruder_override_keys.begin(),filament_extruder_override_keys.end(), opt_key) != filament_extruder_override_keys.end(); - if(!is_override_key || !opt->nullable()) + if(!is_override_key || !opt->nullable()) continue; opt->deserialize("nil",ForwardCompatibilitySubstitutionRule::Disable); } @@ -917,7 +919,7 @@ void PresetBundle::save_user_presets(AppConfig& config, std::vector //QDS: change directory by design const std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/"+ user_sub_folder; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, save to %1%")%dir_user_presets; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, save to %1%") % PathSanitizer::sanitize(dir_user_presets); fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR); if (!fs::exists(user_folder)) @@ -939,7 +941,7 @@ void PresetBundle::update_user_presets_directory(const std::string preset_folder //QDS: change directory by design const std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/"+ preset_folder; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, update directory to %1%")%dir_user_presets; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, update directory to %1%") % PathSanitizer::sanitize(dir_user_presets); fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR); if (!fs::exists(user_folder)) @@ -1698,6 +1700,8 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p break; this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); } + + // Load data from AppConfig to ProjectConfig when Studio is initialized. std::vector filament_colors; if (config.has("presets", "filament_colors")) { boost::algorithm::split(filament_colors, config.get("presets", "filament_colors"), boost::algorithm::is_any_of(",")); @@ -1705,6 +1709,20 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p filament_colors.resize(filament_presets.size(), "#4479FB"); // y6 project_config.option("filament_colour")->values = filament_colors; + std::vector multi_filament_colors; + if (config.has("presets", "filament_multi_colors")) { + boost::algorithm::split(multi_filament_colors, config.get("presets", "filament_multi_colors"), boost::algorithm::is_any_of(",")); + } + if (multi_filament_colors.size() == 0) project_config.option("filament_multi_colour")->values = filament_colors; + else project_config.option("filament_multi_colour")->values = multi_filament_colors; + + std::vector filament_color_types; + if (config.has("presets", "filament_color_types")) { + boost::algorithm::split(filament_color_types, config.get("presets", "filament_color_types"), boost::algorithm::is_any_of(",")); + } + filament_color_types.resize(filament_presets.size(), "1"); + project_config.option("filament_colour_type")->values = filament_color_types; + std::vector filament_maps(filament_colors.size(), 1); project_config.option("filament_map")->values = filament_maps; @@ -1808,10 +1826,19 @@ void PresetBundle::export_selections(AppConfig &config) sprintf(name, "filament_%02u", i); config.set("presets", name, filament_presets[i]); } + // Load project config data into app config CNumericLocalesSetter locales_setter; std::string filament_colors = boost::algorithm::join(project_config.option("filament_colour")->values, ","); config.set("presets", "filament_colors", filament_colors); + // Load filament multi color data into app config + std::string filament_multi_colors = boost::algorithm::join(project_config.option("filament_multi_colour")->values, ","); + config.set("presets", "filament_multi_colors", filament_multi_colors); + + // Load filament color type data into app config + std::string filament_color_types = boost::algorithm::join(project_config.option("filament_colour_type")->values, ","); + config.set("presets", "filament_color_types", filament_color_types); + std::string flush_volumes_matrix = boost::algorithm::join(project_config.option("flush_volumes_matrix")->values | boost::adaptors::transformed(static_cast(std::to_string)), "|"); @@ -1845,10 +1872,13 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) else { filament_presets.resize(n); } - ConfigOptionStrings* filament_color = project_config.option("filament_colour"); + ConfigOptionStrings *filament_multi_color = project_config.option("filament_multi_colour"); + ConfigOptionStrings* filament_color_type = project_config.option("filament_colour_type"); ConfigOptionInts* filament_map = project_config.option("filament_map"); filament_color->resize(n); + filament_multi_color->resize(n); + filament_color_type->resize(n); filament_map->values.resize(n, 1); ams_multi_color_filment.resize(n); @@ -1857,6 +1887,8 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color) if (!new_color.empty()) { for (unsigned i = old_filament_count; i < n; i++) { filament_color->values[i] = new_color; + filament_multi_color->values[i] = new_color; + filament_color_type->values[i] = "1"; // default color type } } } @@ -1870,9 +1902,24 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id) assert(to_del_flament_id < old_filament_count); filament_presets.erase(filament_presets.begin() + to_del_flament_id); - ConfigOptionStrings *filament_color = project_config.option("filament_colour"); - ConfigOptionInts* filament_map = project_config.option("filament_map"); + // update edited_preset + { + Preset& edited_preset = filaments.get_edited_preset(); + bool edited_preset_deleted = true; + for (std::string filament_preset_name : filament_presets) { + if (filament_preset_name == edited_preset.name) { + edited_preset_deleted = false; + } + } + if (edited_preset_deleted) { + filaments.select_preset_by_name(filament_presets.front(), false); + } + } + ConfigOptionStrings *filament_color = project_config.option("filament_colour"); + ConfigOptionStrings *filament_multi_color = project_config.option("filament_multi_colour"); + ConfigOptionStrings *filament_color_type = project_config.option("filament_colour_type"); + ConfigOptionInts* filament_map = project_config.option("filament_map"); if (filament_color->values.size() > to_del_flament_id) { filament_color->values.erase(filament_color->values.begin() + to_del_flament_id); if (filament_map->values.size() > to_del_flament_id) { @@ -1884,12 +1931,18 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id) filament_map->values.resize(to_del_flament_id, 1); } - if (ams_multi_color_filment.size() > to_del_flament_id){ - ams_multi_color_filment.erase(ams_multi_color_filment.begin() + to_del_flament_id); - } - else { - ams_multi_color_filment.resize(to_del_flament_id); - } + // lambda function to erase or resize the container + auto erase_or_resize = [to_del_flament_id](auto& container) { + if (container.size() > to_del_flament_id) { + container.erase(container.begin() + to_del_flament_id); + } else { + container.resize(to_del_flament_id); + } + }; + + erase_or_resize(filament_multi_color->values); + erase_or_resize(filament_color_type->values); + erase_or_resize(ams_multi_color_filment); update_multi_material_filament_presets(to_del_flament_id); } @@ -1904,7 +1957,7 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info) auto filament_color = ams.opt_string("filament_colour", 0u); auto ams_name = ams.opt_string("tray_name", 0u); auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed"); - auto filament_multi_color = ams.opt("filament_multi_colors")->values; + auto filament_multi_color = ams.opt("filament_multi_colour")->values; if (filament_id.empty()) { continue; } @@ -1952,6 +2005,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector ams_filament_presets; std::vector ams_filament_colors; + std::vector ams_filament_color_types; std::vector ams_array_maps; ams_multi_color_filment.clear(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_ams_list size: %1%") % filament_ams_list.size(); @@ -1960,6 +2014,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector mutli_filament_color; }; @@ -1970,8 +2025,9 @@ unsigned int PresetBundle::sync_ams_list(std::vector("filament_multi_colors")->values; + auto filament_multi_color = ams.opt("filament_multi_colour")->values; //y59 auto ams_id = std::to_string(std::stoi(ams.opt_string("slot_id", 0u)) / 4 + 1); @@ -1990,6 +2046,7 @@ unsigned int PresetBundle::sync_ams_list(std::vectorfilament_presets.size() > ams_filament_presets.size()) { ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]); ams_filament_colors.push_back(filament_color); + ams_filament_color_types.push_back(filament_color_type); ams_multi_color_filment.push_back(filament_multi_color); continue; } @@ -2022,6 +2080,7 @@ unsigned int PresetBundle::sync_ams_list(std::vectorfilament_presets.size()) { ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]); ams_filament_colors.push_back(filament_color); + ams_filament_color_types.push_back(filament_color_type); ams_multi_color_filment.push_back(filament_multi_color); unknowns.emplace_back(&ams, has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") : L("The filament model is unknown. Still using the previous filament preset.")); @@ -2042,29 +2101,31 @@ unsigned int PresetBundle::sync_ams_list(std::vectorname); ams_filament_colors.push_back(filament_color); + ams_filament_color_types.push_back(filament_color_type); ams_multi_color_filment.push_back(filament_multi_color); } if (ams_filament_presets.empty()) return 0; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "get filament_colour and from config"; ConfigOptionStrings *filament_color = project_config.option("filament_colour"); + ConfigOptionStrings *filament_color_type = project_config.option("filament_colour_type"); ConfigOptionInts * filament_map = project_config.option("filament_map"); if (use_map) { auto check_has_merge_info = [](std::map &maps, MergeFilamentInfo &merge_info, int exist_colors_size) { - std::map done; - for (int i = 0; i < maps.size(); i++) { + std::set done; + for (auto it_i = maps.begin(); it_i != maps.end(); ++it_i) { std::vector same_ams; - same_ams.emplace_back(i); - for (size_t j = i + 1; j < maps.size(); j++) { - if (done.find(j) != done.end()) { + same_ams.emplace_back(it_i->first); + for (auto it_j = std::next(it_i); it_j != maps.end(); ++it_j) { + if (done.find(it_j->first) != done.end()) { continue; } - if (maps[i].slot_id == "" || maps[i].ams_id == ""){//reserve + if (it_i->second.slot_id == "" || it_i->second.ams_id == ""){ continue; } - if (maps[i].slot_id == maps[j].slot_id && maps[i].ams_id == maps[j].ams_id) { - same_ams.emplace_back(j); - done[j] =true; + if (it_i->second.slot_id == it_j->second.slot_id && it_i->second.ams_id == it_j->second.ams_id) { + same_ams.emplace_back(it_j->first); + done.insert(it_j->first); } } if (same_ams.size() > 1) { @@ -2084,6 +2145,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector need_append_colors; auto exist_colors = filament_color->values; + auto exist_color_types = filament_color_type->values; auto exist_filament_presets = this->filament_presets; std::vector> exist_multi_color_filment; exist_multi_color_filment.resize(exist_colors.size()); @@ -2095,6 +2157,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector= 0 && valid_index < ams_filament_presets.size()) { exist_colors[i] = ams_filament_colors[valid_index]; + exist_color_types[i] = ams_filament_color_types[valid_index]; exist_filament_presets[i] = ams_filament_presets[valid_index]; exist_multi_color_filment[i] = ams_multi_color_filment[valid_index]; } else { @@ -2113,12 +2176,14 @@ unsigned int PresetBundle::sync_ams_list(std::vector(); } } else { ams_filament_colors[i] = ""; + ams_filament_color_types[i] = ""; ams_filament_presets[i] = ""; ams_multi_color_filment[i] = std::vector(); } @@ -2126,6 +2191,8 @@ unsigned int PresetBundle::sync_ams_list(std::vectorresize(exist_colors.size()); filament_color->values = exist_colors; + filament_color_type->resize(exist_colors.size()); + filament_color_type->values = exist_color_types; ams_multi_color_filment = exist_multi_color_filment; this->filament_presets = exist_filament_presets; filament_map->values.resize(exist_filament_presets.size(), 1); @@ -2163,15 +2233,39 @@ unsigned int PresetBundle::sync_ams_list(std::vectorresize(ams_filament_presets.size()); filament_color->values = ams_filament_colors; + filament_color_type->resize(ams_filament_presets.size()); + filament_color_type->values = ams_filament_color_types; this->filament_presets = ams_filament_presets; filament_map->values.resize(ams_filament_colors.size(), 1); } - + // Update ams_multi_color_filment + update_filament_multi_color(); update_multi_material_filament_presets(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "finish sync ams list"; return this->filament_presets.size(); } +void PresetBundle::update_filament_multi_color() +{ + std::vector exsit_multi_colors; + for (auto &fil_item : ams_multi_color_filment){ + if (fil_item.empty()) break; + if (fil_item.size() == 1) + exsit_multi_colors.push_back(fil_item[0]); + else { + std::string colors = ""; + for (auto &color : fil_item){ + colors += color + " "; + } + colors.erase(colors.size() - 1); // remove last space + exsit_multi_colors.push_back(colors); + } + } + ConfigOptionStrings *filament_multi_colour = project_config.option("filament_multi_colour"); + filament_multi_colour->resize(exsit_multi_colors.size()); + filament_multi_colour->values = exsit_multi_colors; +} + std::vector PresetBundle::get_used_tpu_filaments(const std::vector &used_filaments) { std::vector tpu_filaments; @@ -2493,7 +2587,7 @@ bool PresetBundle::support_different_extruders() DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional>filament_maps) const { return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? - this->full_fff_config(apply_extruder, filament_maps) : + this->full_fff_config(apply_extruder, filament_maps) : this->full_sla_config(); } @@ -2907,6 +3001,32 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw return ConfigSubstitutions{}; } + +//some filament presets split from one to sperate ones +//following map recording these filament presets +//for example: previously ''Bambu PLA Basic @BBL H2D 0.6 nozzle' was saved in ''Bambu PLA Basic @BBL H2D' with 0.4 +static std::map> filament_preset_convert = { +{"Bambu Lab H2D 0.6 nozzle", {{"Bambu PLA Basic @BBL H2D", "Bambu PLA Basic @BBL H2D 0.6 nozzle"}, + {"Bambu PLA Matte @BBL H2D", "Bambu PLA Matte @BBL H2D 0.6 nozzle"}, + {"Bambu ABS @BBL H2D", "Bambu ABS @BBL H2D 0.6 nozzle"}}}, +{"Bambu Lab H2D 0.8 nozzle", {{"Bambu PETG HF @BBL H2D 0.6 nozzle", "Bambu PETG HF @BBL H2D 0.8 nozzle"}, + {"Bambu ASA @BBL H2D 0.6 nozzle", "Bambu ASA @BBL H2D 0.8 nozzle"}}} +}; + +//convert the old filament preset to new one after split +static void convert_filament_preset_name(std::string& machine_name, std::string& filament_name) +{ + auto machine_iter = filament_preset_convert.find(machine_name); + if (machine_iter != filament_preset_convert.end()) + { + std::map& filament_maps = machine_iter->second; + auto filament_iter = filament_maps.find(filament_name); + if (filament_iter != filament_maps.end()) + { + filament_name = filament_iter->second; + } + } +} // Load a config file from a boost property_tree. This is a private method called from load_config_file. // is_external == false on if called from ConfigWizard void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version, bool selected) @@ -3080,6 +3200,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool auto old_filament_profile_names = config.option("filament_settings_id", true); old_filament_profile_names->values.resize(num_filaments, std::string()); + auto old_machine_profile_name = config.option("printer_settings_id", true); + if (num_filaments <= 1) { // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. inherits = inherits_values[1]; @@ -3101,8 +3223,13 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool std::string filament_id = filament_ids[0]; //QDS: add config related logs BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load single filament preset from filament_settings_id"); - if (is_external) + if (is_external) { + if (inherits.empty()) + convert_filament_preset_name(old_machine_profile_name->value, old_filament_profile_names->values.front()); + else + convert_filament_preset_name(old_machine_profile_name->value, inherits); loaded = this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config, filament_different_keys_set, PresetCollection::LoadAndSelect::Always, file_version, filament_id).first; + } else { // called from Config Wizard. loaded= &this->filaments.load_preset(this->filaments.path_from_name(name, inherits.empty()), name, config, true, file_version); @@ -3167,6 +3294,11 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool std::string filament_id = filament_ids[i]; // Load all filament presets, but only select the first one in the preset dialog. + std::string& filament_inherit = cfg.opt_string("inherits", true); + if (filament_inherit.empty() && (i < int(old_filament_profile_names->values.size()))) + convert_filament_preset_name(old_machine_profile_name->value, old_filament_profile_names->values[i]); + else + convert_filament_preset_name(old_machine_profile_name->value, filament_inherit); auto [loaded, modified] = this->filaments.load_external_preset(name_or_path, name, (i < int(old_filament_profile_names->values.size())) ? old_filament_profile_names->values[i] : "", std::move(cfg), @@ -3792,7 +3924,7 @@ std::pair PresetBundle::load_vendor_configs_ PresetsConfigSubstitutions substitutions; //QDS: add config related logs - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, path %1%, compatibility_rule %2%")%path.c_str()%compatibility_rule; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, path %1%, compatibility_rule %2%") % PathSanitizer::sanitize(path) % compatibility_rule; if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem)) // Reset this bundle, delete user profile files if SaveImported. this->reset(flags.has(LoadConfigBundleAttribute::SaveImported)); @@ -3854,7 +3986,7 @@ std::pair PresetBundle::load_vendor_configs_ } else if (boost::iequals(it.key(), QDT_JSON_KEY_DESCRIPTION)) { //get description - BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": parse "< PresetBundle::load_vendor_configs_ } else if (boost::iequals(it.key(), QDT_JSON_KEY_DEFAULT_BED_TYPE)) { // get bed type model.default_bed_type = it.value(); - } else if (boost::iequals(it.key(), QDT_JSON_KEY_IMAGE_BED_TYPE)) { + } else if (boost::iequals(it.key(), QDT_JSON_KEY_RIGHT_ICON_OFFSET_BED)) { + model.right_icon_offset_bed = it.value(); + } else if (boost::iequals(it.key(), QDT_JSON_KEY_BOTTOM_TEXTURE_END_NAME)) { + model.bottom_texture_end_name = it.value(); + } else if (boost::iequals(it.key(), QDT_JSON_KEY_BOTTOM_TEXTURE_RECT)) { + model.bottom_texture_rect = it.value(); + } else if (boost::iequals(it.key(), QDT_JSON_KEY_MIDDLE_TEXTURE_RECT)) { + model.middle_texture_rect = it.value(); + } else if (boost::iequals(it.key(), QDT_JSON_KEY_USE_RECT_GRID)) { + model.use_rect_grid = it.value(); + } + else if (boost::iequals(it.key(), QDT_JSON_KEY_IMAGE_BED_TYPE)) { model.image_bed_type = it.value(); } else if (boost::iequals(it.key(), QDT_JSON_KEY_BED_TEXTURE)) { @@ -4164,7 +4307,7 @@ std::pair PresetBundle::load_vendor_configs_ loaded.description = description; loaded.setting_id = setting_id; loaded.filament_id = filament_id; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << __LINE__ << ", " << loaded.name << " load filament_id: " << filament_id; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " " << __LINE__ << ", " << loaded.name << " load filament_id: " << filament_id; if (presets_collection->type() == Preset::TYPE_FILAMENT) { if (filament_id.empty() && "Template" != vendor_name) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": can not find filament_id for " << preset_name; @@ -4203,7 +4346,7 @@ std::pair PresetBundle::load_vendor_configs_ config_maps.emplace(preset_name, loaded.config); ++count; //QDS: add config related logs - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", got preset %1%, from %2%")%loaded.name %subfile; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << boost::format(", got preset %1%")%loaded.name; return reason; }; diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 2819986..4cd282c 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -325,6 +325,8 @@ private: std::pair load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule); // Merge one vendor's presets with the other vendor's presets, report duplicates. std::vector merge_presets(PresetBundle &&other); + // Update the multicolor information for filaments. + void update_filament_multi_color(); // Update renamed_from and alias maps of system profiles. void update_system_maps(); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 97f5d49..e3f43f8 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -347,7 +347,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n //QDS: when enable arc fitting, we must re-generate perimeter || opt_key == "enable_arc_fitting" || opt_key == "wall_sequence" - || opt_key == "z_direction_outwall_speed_continuous") { + || opt_key == "z_direction_outwall_speed_continuous" + || opt_key == "override_filament_scarf_seam_setting" + || opt_key == "seam_slope_type" + || opt_key == "seam_slope_start_height" + || opt_key == "seam_slope_gap" + || opt_key == "seam_slope_min_length") { osteps.emplace_back(posPerimeters); osteps.emplace_back(posInfill); osteps.emplace_back(posSupportMaterial); @@ -810,12 +815,6 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print //print_instance_with_bounding_box.pop_back(); /*bool has_interlaced_objects = false; for (int k = 0; k < print_instance_count; k++) - { - // åªéœ€è¦è€ƒè™‘喷嘴到滑æ†çš„åç§»é‡ï¼Œè¿™ä¸ªæ¯”整个工具头的碰撞åŠå¾„è¦å°å¾—多 - BoundingBox& bbox = print_instance_with_bounding_box[k].bounding_box; - bbox.offset( scale_(print_config.extruder_clearance_dist_to_rod.value*0.5) - obj_distance); - } - for (int k = 0; k < print_instance_count; k++) { auto inst = print_instance_with_bounding_box[k].print_instance; auto bbox = print_instance_with_bounding_box[k].bounding_box; @@ -843,6 +842,12 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print int print_instance_count = print_instance_with_bounding_box.size(); std::map> too_tall_instances; for (int k = 0; k < print_instance_count; k++) + { + // åªéœ€è¦è€ƒè™‘喷嘴到滑æ†çš„åç§»é‡ï¼Œè¿™ä¸ªæ¯”整个工具头的碰撞åŠå¾„è¦å°å¾—多 + BoundingBox& bbox = print_instance_with_bounding_box[k].bounding_box; + bbox.offset( scale_(print_config.extruder_clearance_dist_to_rod.value*0.5) - obj_distance); + } + for (int k = 0; k < print_instance_count; k++) { auto inst = print_instance_with_bounding_box[k].print_instance; auto bbox = print_instance_with_bounding_box[k].bounding_box; @@ -1081,7 +1086,6 @@ StringObjectException Print::check_multi_filament_valid(const Print& print) std::vector extruders = print.extruders(); std::vector filament_types; filament_types.reserve(extruders.size()); - for (const auto& extruder_idx : extruders) filament_types.push_back(print_config.filament_type.get_at(extruder_idx)); @@ -1191,21 +1195,20 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // Custom layering is not allowed for tree supports as of now. - //1.9.5 for (size_t print_object_idx = 0; print_object_idx < m_objects.size(); ++print_object_idx) { PrintObject &print_object = *m_objects[print_object_idx]; print_object.has_variable_layer_heights = false; if (print_object.has_support_material() && is_tree(print_object.config().support_type.value) && print_object.model_object()->has_custom_layering()) { - if (const std::vector &layers = layer_height_profile(print_object_idx); !layers.empty()) + if (const std::vector &layers = layer_height_profile(print_object_idx); !layers.empty()) if (!check_object_layers_fixed(print_object.slicing_parameters(), layers)) { print_object.has_variable_layer_heights = true; BOOST_LOG_TRIVIAL(warning) << "print_object: " << print_object.model_object()->name << " has_variable_layer_heights: " << print_object.has_variable_layer_heights; if (print_object.config().support_style.value == smsTreeOrganic) return {L("Variable layer height is not supported with Organic supports.")}; } - } } + } if (this->has_wipe_tower() && ! m_objects.empty()) { // Make sure all extruders use same diameter filament and have the same nozzle diameter @@ -1288,7 +1291,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (has_custom_layering) { std::vector> layer_z_series; layer_z_series.assign(m_objects.size(), std::vector()); - + for (size_t idx_object = 0; idx_object < m_objects.size(); ++idx_object) { layer_z_series[idx_object] = generate_object_layers(m_objects[idx_object]->slicing_parameters(), layer_height_profiles[idx_object], m_objects[idx_object]->config().precise_z_height.value); } @@ -1686,7 +1689,6 @@ std::map getObjectExtruderMap(const Print& print) { } // Slicing process, running at a background thread. -//1.9.5 void Print::process(std::unordered_map* slice_time, bool use_cache) { long long start_time = 0, end_time = 0; @@ -1697,6 +1699,7 @@ void Print::process(std::unordered_map* slice_time, bool (*slice_time)[TIME_GENERATE_SUPPORT] = 0; } + name_tbb_thread_pool_threads_set_locale(); //compute the PrintObject with the same geometries @@ -1736,6 +1739,8 @@ void Print::process(std::unordered_map* slice_time, bool return false; if (!model_volume1.supported_facets.equals(model_volume2.supported_facets)) return false; + if (!model_volume1.fuzzy_skin_facets.equals(model_volume2.fuzzy_skin_facets)) + return false; if (!model_volume1.seam_facets.equals(model_volume2.seam_facets)) return false; if (!model_volume1.mmu_segmentation_facets.equals(model_volume2.mmu_segmentation_facets)) @@ -1801,13 +1806,15 @@ void Print::process(std::unordered_map* slice_time, bool BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total object counts %1% in current print, need to slice %2%")%m_objects.size()%need_slicing_objects.size(); BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); + const AutoContourHolesCompensationParams &auto_contour_holes_compensation_params = AutoContourHolesCompensationParams(m_config); if (!use_cache) { if (slice_time) { start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); } - + + for (PrintObject* obj : m_objects) { if (need_slicing_objects.count(obj) != 0) { obj->set_auto_circle_compenstaion_params(auto_contour_holes_compensation_params); @@ -1820,7 +1827,7 @@ void Print::process(std::unordered_map* slice_time, bool obj->set_done(posPerimeters); } } - //1.9.5 + if (slice_time) { end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); (*slice_time)[TIME_MAKE_PERIMETERS] = (*slice_time)[TIME_MAKE_PERIMETERS] + end_time - start_time; @@ -1838,7 +1845,7 @@ void Print::process(std::unordered_map* slice_time, bool obj->set_done(posInfill); } } - //1.9.5 + if (slice_time) { end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); (*slice_time)[TIME_INFILL] = (*slice_time)[TIME_INFILL] + end_time - start_time; @@ -1853,7 +1860,7 @@ void Print::process(std::unordered_map* slice_time, bool obj->set_done(posIroning); } } - //1.9.5 + if (slice_time) { start_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); } @@ -1872,7 +1879,7 @@ void Print::process(std::unordered_map* slice_time, bool } } ); - //1.9.5 + if (slice_time) { end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); (*slice_time)[TIME_GENERATE_SUPPORT] = (*slice_time)[TIME_GENERATE_SUPPORT] + end_time - start_time; @@ -1924,6 +1931,8 @@ void Print::process(std::unordered_map* slice_time, bool } } + + if (this->set_started(psWipeTower)) { { std::vector> geometric_unprintables(m_config.nozzle_diameter.size()); @@ -2084,7 +2093,7 @@ void Print::process(std::unordered_map* slice_time, bool this->finalize_first_layer_convex_hull(); this->set_done(psSkirtBrim); - //1.9.5 + if (slice_time) { end_time = (long long)Slic3r::Utils::get_current_milliseconds_time_utc(); (*slice_time)[TIME_USING_CACHE] = (*slice_time)[TIME_USING_CACHE] + end_time - start_time; @@ -2476,6 +2485,47 @@ int Print::get_hrc_by_nozzle_type(const NozzleType&type) return 0; } +std::vector Print::get_incompatible_filaments_by_nozzle(const float nozzle_diameter, const std::optional nozzle_volume_type) +{ + static std::map>> incompatible_filaments; + if(incompatible_filaments.empty()){ + fs::path file_path = fs::path(resources_dir()) / "info" / "nozzle_incompatibles.json"; + boost::nowide::ifstream in(file_path.string()); + json j; + try { + j = json::parse(in); + for(auto& [volume_type, diameter_list] : j["incompatible_nozzles"].items()) { + for(auto& [diameter, filaments]: diameter_list.items()){ + incompatible_filaments[volume_type][diameter] = filaments.get>(); + } + } + } + catch(const json::parse_error& err){ + in.close(); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what(); + + incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow)] = {}; + incompatible_filaments[get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard)] = {}; + } + } + std::ostringstream oss; + oss << std::fixed << std::setprecision(1) << nozzle_diameter; + std::string diameter_str = oss.str(); + + if(nozzle_volume_type.has_value()){ + return incompatible_filaments[get_nozzle_volume_type_string(nozzle_volume_type.value())][diameter_str]; + } + + std::vector incompatible_filaments_list; + for(auto& [volume_type, diameter_list] : incompatible_filaments){ + auto iter = diameter_list.find(diameter_str); + if(iter != diameter_list.end()){ + append(incompatible_filaments_list, iter->second); + } + } + return incompatible_filaments_list; +} + void Print::finalize_first_layer_convex_hull() { append(m_first_layer_convex_hull.points, m_skirt_convex_hull); @@ -2523,6 +2573,10 @@ void Print::update_filament_maps_to_config(std::vector f_maps) m_has_auto_filament_map_result = true; } +void Print::apply_config_for_render(const DynamicConfig &config) +{ + m_config.apply(config); +} std::vector Print::get_filament_maps() const { @@ -4233,6 +4287,7 @@ void WipeTowerData::construct_mesh(float width, float depth, float height, float { wipe_tower_mesh_data = WipeTowerMeshData{}; float first_layer_height=0.08; //brim height + if (width < EPSILON || depth < EPSILON || height < EPSILON) return; if (!is_rib_wipe_tower) { wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height); wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height); @@ -4242,7 +4297,9 @@ void WipeTowerData::construct_mesh(float width, float depth, float height, float } else { wipe_tower_mesh_data->real_wipe_tower_mesh = WipeTower::its_make_rib_tower(width, depth, height, rib_length, rib_width, fillet_wall); wipe_tower_mesh_data->bottom = WipeTower::rib_section(width, depth, rib_length, rib_width, fillet_wall); - wipe_tower_mesh_data->bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width)).front(); + auto brim_bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width)); + if (!brim_bottom.empty()) + wipe_tower_mesh_data->bottom = brim_bottom.front(); wipe_tower_mesh_data->real_brim_mesh = WipeTower::its_make_rib_brim(wipe_tower_mesh_data->bottom, first_layer_height); wipe_tower_mesh_data->real_wipe_tower_mesh.translate(Vec3f(rib_offset[0], rib_offset[1],0)); wipe_tower_mesh_data->real_brim_mesh.translate(Vec3f(rib_offset[0], rib_offset[1], 0)); @@ -4250,4 +4307,21 @@ void WipeTowerData::construct_mesh(float width, float depth, float height, float } } +PrintRegion *PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region(const LayerRangeRegions &layer_range) const +{ + using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; + + if (this->parent_type == FuzzySkinParentType::PaintedRegion) { + return layer_range.painted_regions[this->parent].region; + } + + assert(this->parent_type == FuzzySkinParentType::VolumeRegion); + return layer_range.volume_regions[this->parent].region; +} + +int PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region_id(const LayerRangeRegions &layer_range) const +{ + return this->parent_print_object_region(layer_range)->print_object_region_id(); +} + } // namespace Slic3r diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 926dbf0..86bf19c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -40,7 +40,6 @@ class ExtrusionLayers; #define MARGIN_HEIGHT 1.5 #define MAX_OUTER_NOZZLE_RADIUS 4 -//1.9.5 #define TIME_USING_CACHE "time_using_cache" #define TIME_MAKE_PERIMETERS "make_perimeters_time" #define TIME_INFILL "infill_time" @@ -192,8 +191,6 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor(data) {} }; -class BoundingBoxf3; // TODO: for temporary constructor parameter - // Single instance of a PrintObject. // As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z), // ModelObject's instancess will be distributed among these multiple PrintObjects. @@ -256,6 +253,26 @@ public: PrintRegion *region { nullptr }; }; + struct LayerRangeRegions; + + struct FuzzySkinPaintedRegion + { + enum class ParentType + { + VolumeRegion, + PaintedRegion + }; + + ParentType parent_type{ParentType::VolumeRegion}; + // Index of a parent VolumeRegion or PaintedRegion. + int parent{-1}; + // Pointer to PrintObjectRegions::all_regions. + PrintRegion *region{nullptr}; + + PrintRegion *parent_print_object_region(const LayerRangeRegions &layer_range) const; + int parent_print_object_region_id(const LayerRangeRegions &layer_range) const; + }; + // One slice over the PrintObject (possibly the whole PrintObject) and a list of ModelVolumes and their bounding boxes // possibly clipped by the layer_height_range. struct LayerRangeRegions @@ -270,6 +287,7 @@ public: // Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc. std::vector volume_regions; std::vector painted_regions; + std::vector fuzzy_skin_painted_regions; bool has_volume(const ObjectID id) const { auto it = lower_bound_by_predicate(this->volumes.begin(), this->volumes.end(), [id](const VolumeExtents &l) { return l.volume_id < id; }); @@ -458,6 +476,8 @@ public: bool has_support_material() const { return this->has_support() || this->has_raft(); } // Checks if the model object is painted using the multi-material painting gizmo. bool is_mm_painted() const { return this->model_object()->is_mm_painted(); } + // Checks if the model object is painted using the fuzzy skin painting gizmo. + bool is_fuzzy_skin_painted() const { return this->model_object()->is_fuzzy_skin_painted(); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) std::vector object_extruders() const; @@ -488,7 +508,6 @@ public: // QDS: returns 1-based indices of extruders used to print the first layer wall of objects std::vector object_first_layer_wall_extruders; - //1.9.5 bool has_variable_layer_heights = false; // OrcaSlicer @@ -622,7 +641,7 @@ struct FakeWipeTower Vec2f pos; float width; float height; - float layer_height; + float layer_height;// Due to variable layer height, this parameter may be not right. float depth; float brim_width; Vec2d plate_origin; @@ -820,7 +839,6 @@ public: ApplyStatus apply(const Model &model, DynamicPrintConfig config, bool extruder_applied = false) override; - //1.9.5 void process(std::unordered_map* slice_time = nullptr, bool use_cache = false) override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). @@ -903,6 +921,8 @@ public: const ToolOrdering& tool_ordering() const { return m_tool_ordering; } void update_filament_maps_to_config(std::vector f_maps); + void apply_config_for_render(const DynamicConfig &config); + // 1 based group ids std::vector get_filament_maps() const; FilamentMapMode get_filament_map_mode() const; @@ -991,6 +1011,7 @@ public: Vec2d translate_to_print_space(const Point& point) const; static FilamentTempType get_filament_temp_type(const std::string& filament_type); static int get_hrc_by_nozzle_type(const NozzleType& type); + static std::vector get_incompatible_filaments_by_nozzle(const float nozzle_diameter, const std::optional nozzle_volume_type = std::nullopt); static FilamentCompatibilityType check_multi_filaments_compatibility(const std::vector& filament_types); // similar to check_multi_filaments_compatibility, but the input is int, and may be negative (means unset) static bool is_filaments_compatible(const std::vector& types); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index cb2cc08..38935ff 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1,3 +1,4 @@ +#include "ClipperUtils.hpp" #include "Model.hpp" #include "Print.hpp" @@ -74,6 +75,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, mv_dst.config.assign_config(mv_src.config); assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id()); mv_dst.supported_facets.assign(mv_src.supported_facets); + assert(mv_dst.fuzzy_skin_facets.id() == mv_src.fuzzy_skin_facets.id()); + mv_dst.fuzzy_skin_facets.assign(mv_src.fuzzy_skin_facets); assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); mv_dst.seam_facets.assign(mv_src.seam_facets); assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); @@ -830,6 +833,29 @@ bool verify_update_print_object_regions( print_region_ref_inc(*region.region); } + // Verify and / or update PrintRegions produced by fuzzy skin painting. + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { + for (const PrintObjectRegions::FuzzySkinPaintedRegion ®ion : layer_range.fuzzy_skin_painted_regions) { + const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range); + PrintRegionConfig cfg = parent_print_region.config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + if (cfg != region.region->config()) { + // Region configuration changed. + if (print_region_ref_cnt(*region.region) == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.region->config().diff(cfg); + callback_invalidate(region.region->config(), cfg, diff); + region.region->config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + print_region_ref_inc(*region.region); + } + } + // Lastly verify, whether some regions were not merged. { std::vector regions; @@ -934,7 +960,8 @@ static PrintObjectRegions* generate_print_object_regions( size_t num_extruders, const float xy_contour_compensation, const std::vector & painting_extruders, - std::vector & variant_index) + std::vector & variant_index, + const bool has_painted_fuzzy_skin) { // Reuse the old object or generate a new one. auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); @@ -956,6 +983,7 @@ static PrintObjectRegions* generate_print_object_regions( r.config = range.config; r.volume_regions.clear(); r.painted_regions.clear(); + r.fuzzy_skin_painted_regions.clear(); } } else { out->trafo_bboxes = trafo; @@ -1044,6 +1072,35 @@ static PrintObjectRegions* generate_print_object_regions( return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); }); } + if (has_painted_fuzzy_skin) { + using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; + + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) { + // FuzzySkinPaintedRegion can override different parts of the Layer than PaintedRegions, + // so FuzzySkinPaintedRegion has to point to both VolumeRegion and PaintedRegion. + for (int parent_volume_region_id = 0; parent_volume_region_id < int(layer_range.volume_regions.size()); ++parent_volume_region_id) { + if (const PrintObjectRegions::VolumeRegion &parent_volume_region = layer_range.volume_regions[parent_volume_region_id]; parent_volume_region.model_volume->is_model_part() || parent_volume_region.model_volume->is_modifier()) { + PrintRegionConfig cfg = parent_volume_region.region->config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::VolumeRegion, parent_volume_region_id, get_create_region(std::move(cfg))}); + } + } + + for (int parent_painted_regions_id = 0; parent_painted_regions_id < int(layer_range.painted_regions.size()); ++parent_painted_regions_id) { + const PrintObjectRegions::PaintedRegion &parent_painted_region = layer_range.painted_regions[parent_painted_regions_id]; + + PrintRegionConfig cfg = parent_painted_region.region->config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::PaintedRegion, parent_painted_regions_id, get_create_region(std::move(cfg))}); + } + + // Sort the regions by parent region::print_object_region_id() to help the slicing algorithm when applying fuzzy skin segmentation. + std::sort(layer_range.fuzzy_skin_painted_regions.begin(), layer_range.fuzzy_skin_painted_regions.end(), [&layer_range](auto &l, auto &r) { + return l.parent_print_object_region_id(layer_range) < r.parent_print_object_region_id(layer_range); + }); + } + } + return out.release(); } @@ -1080,6 +1137,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_support_used = false; // check if filament ebnable scarf seam + bool has_scarf_joint_seam = false; { std::vector scarf_seam_type = new_full_config.option("filament_scarf_seam_type")->values; auto check_object_scarf_seam_type = [](const std::vector scarf_seam_type, const ModelObject *mo) { @@ -1125,7 +1183,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ }; // check custon_gcode - bool has_scarf_joint_seam = check_gcode_scarf_seam_type(scarf_seam_type, model) || + has_scarf_joint_seam = check_gcode_scarf_seam_type(scarf_seam_type, model) || std::any_of(model.objects.begin(), model.objects.end(), [scarf_seam_type, &check_object_scarf_seam_type](const ModelObject *obj) { return check_object_scarf_seam_type(scarf_seam_type, obj); }); @@ -1137,10 +1195,35 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } } - { //check scarf seam setting + if (!has_scarf_joint_seam) { // check scarf seam setting const auto &o = model.objects; - const bool has_scarf_joint_seam = std::any_of(o.begin(), o.end(), [&new_full_config](ModelObject *obj) { - return obj->get_config_value(new_full_config, "apply_scarf_seam_on_circles")->value; + + has_scarf_joint_seam = std::any_of(o.begin(), o.end(), [&new_full_config](ModelObject *obj) { + return obj->get_config_value(new_full_config, "enable_circle_compensation") + ->value && obj->get_config_value(new_full_config, "apply_scarf_seam_on_circles") + ->value; + }); + + if (has_scarf_joint_seam) { + new_full_config.set("has_scarf_joint_seam", true); + } + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", has_scarf_joint_seam:" << has_scarf_joint_seam; + } + + if (!has_scarf_joint_seam) { // check scarf seam setting + const auto &o = model.objects; + const auto opt_has_scarf_joint_seam = [](const DynamicConfig &c) { + return c.has("override_filament_scarf_seam_setting") && c.opt_bool("override_filament_scarf_seam_setting") && c.has("seam_slope_type") && + c.opt_enum("seam_slope_type") != SeamScarfType::None; + }; + + has_scarf_joint_seam = std::any_of(o.begin(), o.end(), [&new_full_config, &opt_has_scarf_joint_seam](ModelObject *obj) { + return obj->get_config_value>(new_full_config, "seam_slope_type")->value != SeamScarfType::None || + std::any_of(obj->volumes.begin(), obj->volumes.end(), + [&opt_has_scarf_joint_seam](const ModelVolume *v) { return opt_has_scarf_joint_seam(v->config.get()); }) || + std::any_of(obj->layer_config_ranges.begin(), obj->layer_config_ranges.end(), + [&opt_has_scarf_joint_seam](const auto &r) { return opt_has_scarf_joint_seam(r.second.get()); }); }); if (has_scarf_joint_seam) { @@ -1395,8 +1478,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Check whether a model part volume was added or removed, their transformations or order changed. // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) || - model_mmu_segmentation_data_changed(model_object, model_object_new) || - (model_object_new.is_mm_painted() && num_extruders_changed ); + model_mmu_segmentation_data_changed(model_object, model_object_new) || (model_object_new.is_mm_painted() && num_extruders_changed) || + model_custom_fuzzy_skin_data_changed(model_object, model_object_new); bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); @@ -1709,7 +1792,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ num_extruders , print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value), painting_extruders, - print_variant_index); + print_variant_index, + print_object.is_fuzzy_skin_painted()); } for (auto it = it_print_object; it != it_print_object_end; ++it) if ((*it)->m_shared_regions) { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 10480f8..f71bf5b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -61,7 +61,7 @@ namespace Slic3r { const std::vector filament_extruder_override_keys = { // floats - "filament_retraction_length", + "filament_retraction_length", "filament_z_hop", "filament_z_hop_types", "filament_retract_lift_above", @@ -70,7 +70,7 @@ const std::vector filament_extruder_override_keys = { "filament_deretraction_speed", "filament_retract_restart_extra", "filament_retraction_minimum_travel", - // BBS: floats + // QDS: floats "filament_wipe_distance", // bools "filament_retract_when_changing_layer", @@ -616,6 +616,7 @@ void PrintConfigDef::init_common_params() def->min = 0; def->max = 1000; def->mode = comAdvanced; + def->nullable = true; def->set_default_value(new ConfigOptionFloatsNullable{0}); // Options used by physical printers @@ -1129,7 +1130,6 @@ void PrintConfigDef::init_fff_params() def->nullable = true; def->set_default_value(new ConfigOptionFloatsNullable{0}); - //1.9.5 def = this->add("overhang_totally_speed", coFloats); def->label = L("100%"); def->category = L("Speed"); @@ -1141,6 +1141,71 @@ void PrintConfigDef::init_fff_params() def->nullable = true; def->set_default_value(new ConfigOptionFloatsNullable{ 10 }); + def = this->add("enable_height_slowdown", coBools); + def->label = L("Slow down by height"); + def->category = L("Speed"); + def->tooltip = L("Enable this option to slow printing down by height"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable{ false }); + + def = this->add("slowdown_start_height", coFloats); + def->label = L("Starting height"); + def->category = L("Speed"); + def->tooltip = L("The height starts to slow down"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 0 }); + + def = this->add("slowdown_start_speed", coFloats); + def->label = L("Speed at starting height"); + def->category = L("Speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 1000. }); + + def = this->add("slowdown_start_acc", coFloats); + def->label = L("Acceleration at starting height"); + def->category = L("Speed"); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 100000. }); + + def = this->add("slowdown_end_height", coFloats); + def->label = L("Ending height"); + def->category = L("Speed"); + def->tooltip = L("The height finishes slowing down, " + "Ending height should be larger than Starting height, or the slowing down will not work!"); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 400 }); + + def = this->add("slowdown_end_speed", coFloats); + def->label = L("Speed at ending height"); + def->category = L("Speed"); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 1000. }); + + def = this->add("slowdown_end_acc", coFloats); + def->label = L("Acceleration at ending height"); + def->category = L("Speed"); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionFloatsNullable{ 100000. }); + def = this->add("bridge_speed", coFloats); def->label = L("Bridge"); def->category = L("Speed"); @@ -1158,7 +1223,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->min = 0; def->max = 100; - def->mode = comAdvanced; + def->mode = comSimple; def->set_default_value(new ConfigOptionFloat(0.)); def = this->add("brim_type", coEnum); @@ -1200,6 +1265,7 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("compatible_printers", coStrings); def->label = L("Compatible machine"); def->mode = comDevelop; @@ -1431,22 +1497,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool{true}); - //1.9.5 - def = this->add("smooth_speed_discontinuity_area", coBool); - def->label = L("Smooth speed discontinuity area"); - def->category = L("Quality"); - def->tooltip = L("Add the speed transition between discontinuity area."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - - def = this->add("smooth_coefficient", coFloat); - def->label = L("Smooth coefficient"); - def->category = L("Quality"); - def->tooltip = L("The smaller the number, the longer the speed transition path. 0 means not apply."); - def->mode = comAdvanced; - def->min = 0; - def->set_default_value(new ConfigOptionFloat(80)); - def = this->add("internal_bridge_support_thickness", coFloat); def->label = L("Internal bridge support thickness"); def->category = L("Strength"); @@ -1535,7 +1585,6 @@ void PrintConfigDef::init_fff_params() def->nullable = true; def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{FloatOrPercent(50, true)}); - def = this->add("small_perimeter_threshold", coFloats); def->label = L("Small perimter threshold"); def->category = L("Speed"); @@ -1683,6 +1732,7 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 0.02 }); + def = this->add("filament_notes",coString); def->label= L("Filament notes"); def->tooltip = L("You can put your notes regarding the filament here."); @@ -1767,6 +1817,14 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionStrings{ "#4479FB" }); // y96 + + def = this->add("filament_multi_colour", coStrings); + def->set_default_value(new ConfigOptionStrings{""}); + + // 0: gradient color, 1: default color(single or multi color) + def = this->add("filament_colour_type", coStrings); + def->set_default_value(new ConfigOptionStrings{"1"}); // Init as default color + //qds def = this->add("required_nozzle_HRC", coInts); def->label = L("Required nozzle HRC"); @@ -1845,6 +1903,7 @@ void PrintConfigDef::init_fff_params() def->nullable = true; def->set_default_value(new ConfigOptionFloatsNullable{-1}); + def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); def->label = L("Minimal purge on wipe tower"); //def->tooltip = L("After a tool change, the exact position of the newly loaded filament inside " @@ -1880,6 +1939,10 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(5)); + def = this->add("machine_prepare_compensation_time", coFloat); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionFloat(260)); + def = this->add("hotend_cooling_rate", coFloats); def->nullable = true; def->set_default_value(new ConfigOptionFloatsNullable{2}); @@ -1902,6 +1965,10 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("By Highest Temp")); def->set_default_value(new ConfigOptionEnum(BedTempFormula::btfFirstFilament)); + def = this->add("nozzle_flush_dataset", coInts); + def->nullable = true; + def->set_default_value(new ConfigOptionIntsNullable{0}); + def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); def->tooltip = L("Filament diameter is used to calculate extrusion in gcode, so it's important and should be accurate"); @@ -2177,6 +2244,88 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Locked Zag")); def->set_default_value(new ConfigOptionEnum(ipCubic)); + def = this->add("locked_skin_infill_pattern", coEnum); + def->label = L("Skin infill pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern for skin"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("zig-zag"); + def->enum_values.push_back("grid"); + def->enum_values.push_back("line"); + def->enum_values.push_back("cubic"); + def->enum_values.push_back("triangles"); + def->enum_values.push_back("tri-hexagon"); + def->enum_values.push_back("gyroid"); + def->enum_values.push_back("honeycomb"); + def->enum_values.push_back("alignedrectilinear"); + def->enum_values.push_back("3dhoneycomb"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("crosshatch"); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("crosszag"); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Grid")); + def->enum_labels.push_back(L("Line")); + def->enum_labels.push_back(L("Cubic")); + def->enum_labels.push_back(L("Triangles")); + def->enum_labels.push_back(L("Tri-hexagon")); + def->enum_labels.push_back(L("Gyroid")); + def->enum_labels.push_back(L("Honeycomb")); + def->enum_labels.push_back(L("Aligned Rectilinear")); + def->enum_labels.push_back(L("3D Honeycomb")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Cross Hatch")); + def->enum_labels.push_back(L("Zig Zag")); + def->enum_labels.push_back(L("Cross Zag")); + def->set_default_value(new ConfigOptionEnum(ipCrossZag)); + + def = this->add("locked_skeleton_infill_pattern", coEnum); + def->label = L("Skeleton infill pattern"); + def->category = L("Strength"); + def->tooltip = L("Line pattern for skeleton"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("concentric"); + def->enum_values.push_back("zig-zag"); + def->enum_values.push_back("grid"); + def->enum_values.push_back("line"); + def->enum_values.push_back("cubic"); + def->enum_values.push_back("triangles"); + def->enum_values.push_back("tri-hexagon"); + def->enum_values.push_back("gyroid"); + def->enum_values.push_back("honeycomb"); + def->enum_values.push_back("alignedrectilinear"); + def->enum_values.push_back("3dhoneycomb"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("crosshatch"); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("crosszag"); + def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Grid")); + def->enum_labels.push_back(L("Line")); + def->enum_labels.push_back(L("Cubic")); + def->enum_labels.push_back(L("Triangles")); + def->enum_labels.push_back(L("Tri-hexagon")); + def->enum_labels.push_back(L("Gyroid")); + def->enum_labels.push_back(L("Honeycomb")); + def->enum_labels.push_back(L("Aligned Rectilinear")); + def->enum_labels.push_back(L("3D Honeycomb")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Cross Hatch")); + def->enum_labels.push_back(L("Zig Zag")); + def->enum_labels.push_back(L("Cross Zag")); + def->set_default_value(new ConfigOptionEnum(ipZigZag)); + def = this->add("top_surface_acceleration", coFloats); def->label = L("Top surface"); def->tooltip = L("Acceleration of top surface infill. Using a lower value may improve top surface quality"); @@ -2227,7 +2376,7 @@ void PrintConfigDef::init_fff_params() def = this->add("accel_to_decel_enable", coBool); def->label = L("Enable accel_to_decel"); def->tooltip = L("Klipper's max_accel_to_decel will be adjusted automatically"); - def->mode = comDevelop; + def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); //w20 @@ -2237,7 +2386,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = "%"; def->min = 1; def->max = 100; - def->mode = comDevelop; + def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercent(50)); def = this->add("default_jerk", coFloat); @@ -2558,6 +2707,10 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def->readonly=false; + def = this->add("apply_top_surface_compensation", coBool); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool(false)); + //y58 def =this->add("support_box_temp_control",coBool); def->label=L("Support control box temperature"); @@ -3339,6 +3492,21 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("smooth_speed_discontinuity_area", coBool); + def->label = L("Smooth speed discontinuity area"); + def->category = L("Quality"); + def->tooltip = L("Add the speed transition between discontinuity area."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("smooth_coefficient", coFloat); + def->label = L("Smooth coefficient"); + def->category = L("Quality"); + def->tooltip = L("The smaller the number, the longer the speed transition path. 0 means not apply."); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(80)); + def = this->add("wall_filament", coInt); //def->label = L("Walls"); //def->category = L("Extruders"); @@ -3461,12 +3629,12 @@ void PrintConfigDef::init_fff_params() def = this->add("raft_first_layer_expansion", coFloat); def->label = L("Initial layer expansion"); def->category = L("Support"); - def->tooltip = L("Expand the first raft or support layer to improve bed plate adhesion"); + def->tooltip = L("Expand the first raft or support layer to improve bed plate adhesion, -1 means auto"); def->sidetext = L("mm"); - def->min = 0; + def->min = -1; def->mode = comAdvanced; //QDS: change from 3.0 to 2.0 - def->set_default_value(new ConfigOptionFloat(2.0)); + def->set_default_value(new ConfigOptionFloat(-1)); def = this->add("raft_layers", coInt); def->label = L("Raft layers"); @@ -3761,8 +3929,16 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(spAligned)); + def = this->add("seam_placement_away_from_overhangs", coBool); + def->label = L("Seam placement away from overhangs(experimental)"); + def->category = L("Quality"); + def->tooltip = L("Ensure seam placement away from overhangs for alignment and backing modes."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("seam_gap", coPercent); def->label = L("Seam gap"); + def->category = L("Quality"); def->tooltip = L("In order to reduce the visibility of the seam in a closed loop extrusion, the loop is interrupted and shortened by a specified amount.\n" "This amount as a percentage of the current extruder diameter. The default value for this parameter is 15"); def->sidetext = "%"; def->min = 0; @@ -3771,12 +3947,14 @@ void PrintConfigDef::init_fff_params() def = this->add("seam_slope_conditional", coBool); def->label = L("Smart scarf seam application"); + def->category = L("Quality"); def->tooltip = L("Apply scarf joints only to smooth perimeters where traditional seams do not conceal the seams at sharp corners effectively."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); def = this->add("scarf_angle_threshold", coInt); def->label = L("Scarf application angle threshold"); + def->category = L("Quality"); def->tooltip = L("This option sets the threshold angle for applying a conditional scarf joint seam.\nIf the seam angle within the perimeter loop " "exceeds this value (indicating the absence of sharp corners), a scarf joint seam will be used. The default value is 155°."); def->mode = comAdvanced; def->sidetext = L("°"); @@ -3786,12 +3964,14 @@ void PrintConfigDef::init_fff_params() def = this->add("seam_slope_entire_loop", coBool); def->label = L("Scarf around entire wall"); + def->category = L("Quality"); def->tooltip = L("The scarf extends to the entire length of the wall."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); def = this->add("seam_slope_steps", coInt); def->label = L("Scarf steps"); + def->category = L("Quality"); def->tooltip = L("Minimum number of segments of each scarf."); def->min = 1; def->mode = comAdvanced; @@ -3799,12 +3979,64 @@ void PrintConfigDef::init_fff_params() def = this->add("seam_slope_inner_walls", coBool); def->label = L("Scarf joint for inner walls"); + def->category = L("Quality"); def->tooltip = L("Use scarf joint for inner walls as well."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("override_filament_scarf_seam_setting", coBool); + def->label = L("Override filament scarf seam setting"); + def->category = L("Quality"); + def->tooltip = L("Overrider filament scarf seam setting and could control settings by modifier."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("seam_slope_type", coEnum); + def->label = L("Scarf seam type"); + def->category = L("Quality"); + def->tooltip = L("Set scarf seam type for this filament. This setting could minimize seam visibiliy."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("none"); + def->enum_values.push_back("external"); + def->enum_values.push_back("all"); + def->enum_labels.push_back(L("None")); + def->enum_labels.push_back(L("Contour")); + def->enum_labels.push_back(L("Contour and hole")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(SeamScarfType::None)); + + def = this->add("seam_slope_start_height", coFloatOrPercent); + def->label = L("Scarf start height"); + def->category = L("Quality"); + def->tooltip = L("This amount can be specified in millimeters or as a percentage of the current layer height."); + def->min = 0; + def->ratio_over = "layer_height"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent{10, true}); + + def = this->add("seam_slope_gap", coFloatOrPercent); + def->label = L("Scarf slope gap"); + def->category = L("Quality"); + def->tooltip = L("In order to reduce the visiblity of the seam in closed loop, the inner wall and outer wall are shortened by a specified amount."); + def->min = 0; + def->ratio_over = "nozzle_diameter"; + def->sidetext = L("mm/%"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent{0, 0}); + + def = this->add("seam_slope_min_length", coFloat); + def->label = L("Scarf length"); + def->category = L("Quality"); + def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); + def->min = 0; + def->sidetext = "mm"; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat{10}); + def = this->add("wipe_speed", coPercent); def->label = L("Wipe speed"); + def->category = L("Quality"); def->tooltip = L("The wipe speed is determined by the speed setting specified in this configuration." "If the value is expressed as a percentage (e.g. 80%), it will be calculated based on the travel speed setting above." "The default value for this parameter is 80%"); def->sidetext = "%"; def->min = 0.01; @@ -3813,6 +4045,7 @@ void PrintConfigDef::init_fff_params() def = this->add("role_base_wipe_speed", coBool); def->label = L("Role-based wipe speed"); + def->category = L("Quality"); def->tooltip = L("The wipe speed is determined by speed of current extrusion role. " "e.g if a wipe action is executed immediately following an outer wall extrusion, the speed of the outer wall extrusion will be utilized for the wipe action."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); @@ -4365,7 +4598,7 @@ void PrintConfigDef::init_fff_params() "If the angle is increased, the branches can be printed more horizontally, allowing them to reach farther."); def->sidetext = L("°"); def->min = 0; - def->max = 80; + def->max = 60; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(40.)); @@ -4404,11 +4637,12 @@ void PrintConfigDef::init_fff_params() def = this->add("tree_support_wall_count", coInt); def->label = L("Support wall loops"); def->category = L("Support"); - def->tooltip = L("This setting specifies the count of support walls in the range of [0,2]. 0 means auto."); - def->min = 0; + def->tooltip = L("This setting specifies the count of support walls in the range of [-1,2]. -1 means auto, " + "and 0 means allowing infill-only mode where support is thick enough."); + def->min = -1; def->max = 2; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(0)); + def->set_default_value(new ConfigOptionInt(-1)); //w24 def = this->add("chamber_temperatures", coInts); @@ -4426,7 +4660,7 @@ void PrintConfigDef::init_fff_params() //y58 def = this->add("box_temperature", coInts); def->label = L("Box temperature"); - def->tooltip = L("The Tips!!"); + def->tooltip = L("Set the temperature of the box during printing, set to 0 (representing off)."); def->sidetext = "°C"; def->full_label = L("Box temperature"); def->min = 0; @@ -4617,7 +4851,7 @@ void PrintConfigDef::init_fff_params() def = this->add("prime_tower_enable_framework", coBool); def->label = L("Internal ribs"); def->tooltip = L("Enable internal ribs to increase the stability of the prime tower."); - def->mode = comSimple; + def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); def = this->add("enable_circle_compensation", coBool); @@ -5863,7 +6097,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va if (token.size() >= 2 && token.front() == '"' && token.back() == '"') token = token.substr(1, token.size() - 2); if (token == "ASA-Aero") { - token = "ASA-Aero"; + token = "ASA-AERO"; rebuild_value = true; } type_list.emplace_back(token); @@ -5889,7 +6123,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va // QDS , "support_sharp_tails","support_remove_small_overhangs", "support_with_sheath", "tree_support_collision_resolution", "tree_support_with_infill", - "tree_support_brim_width", //1.9.5 + "tree_support_brim_width", "max_volumetric_speed", "max_print_speed", "support_closing_radius", "remove_freq_sweep", "remove_bed_leveling", "remove_extrusion_calibration", @@ -5897,7 +6131,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "can_switch_nozzle_type", "can_add_auxiliary_fan", "extra_flush_volume", "spaghetti_detector", "adaptive_layer_height", "z_hop_type","nozzle_hrc","chamber_temperature","only_one_wall_top","bed_temperature_difference","long_retraction_when_cut", "retraction_distance_when_cut", - "seam_slope_type","seam_slope_start_height","seam_slope_gap", "seam_slope_min_length", "prime_volume" }; @@ -5964,6 +6197,13 @@ std::set print_options_with_variant = { "overhang_3_4_speed", "overhang_4_4_speed", "overhang_totally_speed", + "enable_height_slowdown", + "slowdown_start_height", + "slowdown_start_speed", + "slowdown_start_acc", + "slowdown_end_height", + "slowdown_end_speed", + "slowdown_end_acc", "bridge_speed", "gap_infill_speed", "support_speed", @@ -6009,7 +6249,6 @@ std::set filament_options_with_variant = { "nozzle_temperature", "filament_flush_volumetric_speed", "filament_flush_temp" - }; // Parameters that are the same as the number of extruders @@ -6047,7 +6286,8 @@ std::set printer_options_with_variant_1 = { "printer_extruder_id", "printer_extruder_variant", "hotend_cooling_rate", - "hotend_heating_rate" + "hotend_heating_rate", + "nozzle_flush_dataset" }; //options with silient mode @@ -6307,27 +6547,29 @@ void handle_legacy_sla(DynamicPrintConfig &config) size_t DynamicPrintConfig::get_parameter_size(const std::string& param_name, size_t extruder_nums) { - if (extruder_nums > 1) { - size_t volume_type_size = 2; - auto nozzle_volume_type_opt = dynamic_cast(this->option("nozzle_volume_type")); - if (nozzle_volume_type_opt) { - volume_type_size = nozzle_volume_type_opt->values.size(); - } - if (printer_options_with_variant_1.count(param_name) > 0) { - return extruder_nums * volume_type_size; - } - else if (printer_options_with_variant_2.count(param_name) > 0) { - return extruder_nums * volume_type_size * 2; - } - else if (filament_options_with_variant.count(param_name) > 0) { - return extruder_nums * volume_type_size; - } - else if (print_options_with_variant.count(param_name) > 0) { - return extruder_nums * volume_type_size; - } - else { - return extruder_nums; - } + constexpr size_t default_param_length = 1; + size_t filament_variant_length = default_param_length; + size_t process_variant_length = default_param_length; + size_t machine_variant_length = default_param_length; + + if (this->has("filament_extruder_variant")) + filament_variant_length = this->option("filament_extruder_variant")->size(); + if (this->has("print_extruder_variant")) + process_variant_length = this->option("print_extruder_variant")->size(); + if (this->has("printer_extruder_variant")) + machine_variant_length = this->option("printer_extruder_variant")->size(); + + if (printer_options_with_variant_1.count(param_name) > 0) { + return machine_variant_length; + } + else if (printer_options_with_variant_2.count(param_name) > 0) { + return machine_variant_length * 2; + } + else if (filament_options_with_variant.count(param_name) > 0) { + return filament_variant_length; + } + else if (print_options_with_variant.count(param_name) > 0) { + return process_variant_length; } return extruder_nums; } @@ -7763,12 +8005,6 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->cli_params = "filename.3mf"; def->set_default_value(new ConfigOptionString("output.3mf")); - def = this->add("export_gcode", coString); - def->label = "Export G-code"; - def->tooltip = "Export project as G-code."; - def->cli_params = "filename.gcode"; - def->set_default_value(new ConfigOptionString("output.gcode")); - def = this->add("export_slicedata", coString); def->label = "Export slicing data"; def->tooltip = "Export slicing data to a folder."; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 605a97a..5575427 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -817,6 +817,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, raft_first_layer_expansion)) ((ConfigOptionInt, raft_layers)) ((ConfigOptionEnum, seam_position)) + ((ConfigOptionBool, seam_placement_away_from_overhangs)) ((ConfigOptionFloat, slice_closing_radius)) ((ConfigOptionEnum, slicing_mode)) ((ConfigOptionBool, enable_support)) @@ -930,6 +931,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, infill_lock_depth)) ((ConfigOptionFloat, skin_infill_depth)) ((ConfigOptionEnum, sparse_infill_pattern)) + ((ConfigOptionEnum, locked_skin_infill_pattern)) + ((ConfigOptionEnum, locked_skeleton_infill_pattern)) ((ConfigOptionEnum, fuzzy_skin)) ((ConfigOptionFloat, fuzzy_skin_thickness)) ((ConfigOptionFloat, fuzzy_skin_point_distance)) @@ -952,8 +955,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, ironing_speed)) // Detect bridging perimeters ((ConfigOptionBool, detect_overhang_wall)) - ((ConfigOptionBool, smooth_speed_discontinuity_area)) //1.9.5 - ((ConfigOptionFloat, smooth_coefficient)) //1.9.5 + ((ConfigOptionBool, smooth_speed_discontinuity_area)) + ((ConfigOptionFloat, smooth_coefficient)) ((ConfigOptionInt, wall_filament)) ((ConfigOptionFloat, inner_wall_line_width)) ((ConfigOptionFloatsNullable, inner_wall_speed)) @@ -980,6 +983,13 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsNullable, overhang_2_4_speed)) ((ConfigOptionFloatsNullable, overhang_3_4_speed)) ((ConfigOptionFloatsNullable, overhang_4_4_speed)) + ((ConfigOptionBoolsNullable, enable_height_slowdown)) + ((ConfigOptionFloatsNullable, slowdown_start_height)) + ((ConfigOptionFloatsNullable, slowdown_start_speed)) + ((ConfigOptionFloatsNullable, slowdown_start_acc)) + ((ConfigOptionFloatsNullable, slowdown_end_height)) + ((ConfigOptionFloatsNullable, slowdown_end_speed)) + ((ConfigOptionFloatsNullable, slowdown_end_acc)) ((ConfigOptionFloatOrPercent, sparse_infill_anchor)) ((ConfigOptionFloatOrPercent, sparse_infill_anchor_max)) //OrcaSlicer @@ -990,13 +1000,13 @@ PRINT_CONFIG_CLASS_DEFINE( //calib ((ConfigOptionFloat, print_flow_ratio)) // Orca: seam slopes - // ((ConfigOptionEnum, seam_slope_type)) + ((ConfigOptionBool, override_filament_scarf_seam_setting)) + ((ConfigOptionEnum, seam_slope_type)) ((ConfigOptionBool, seam_slope_conditional)) - //((ConfigOptionInt, scarf_angle_threshold)) - // ((ConfigOptionFloatOrPercent, seam_slope_start_height)) - //((ConfigOptionFloatOrPercent, seam_slope_gap)) + ((ConfigOptionFloatOrPercent, seam_slope_start_height)) + ((ConfigOptionFloatOrPercent, seam_slope_gap)) ((ConfigOptionBool, seam_slope_entire_loop)) - // ((ConfigOptionFloat, seam_slope_min_length)) + ((ConfigOptionFloat, seam_slope_min_length)) ((ConfigOptionInt, seam_slope_steps)) ((ConfigOptionBool, seam_slope_inner_walls)) //w16 //y58 @@ -1055,6 +1065,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionStrings, filament_type)) ((ConfigOptionBools, filament_soluble)) ((ConfigOptionStrings, filament_ids)) + ((ConfigOptionStrings, filament_colour)) ((ConfigOptionStrings, filament_vendor)) ((ConfigOptionBools, filament_is_support)) ((ConfigOptionInts, filament_printable)) @@ -1082,11 +1093,13 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, machine_load_filament_time)) ((ConfigOptionFloat, machine_unload_filament_time)) ((ConfigOptionFloat, machine_switch_extruder_time)) + ((ConfigOptionFloat, machine_prepare_compensation_time)) ((ConfigOptionBool, enable_pre_heating)) ((ConfigOptionEnum, bed_temperature_formula)) ((ConfigOptionInts, physical_extruder_map)) ((ConfigOptionFloatsNullable, hotend_cooling_rate)) ((ConfigOptionFloatsNullable, hotend_heating_rate)) + ((ConfigOptionIntsNullable, nozzle_flush_dataset)) ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) ((ConfigOptionFloatsNullable, filament_flush_volumetric_speed)) ((ConfigOptionIntsNullable, filament_flush_temp)) @@ -1137,6 +1150,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionEnum,printer_structure)) ((ConfigOptionBool, auxiliary_fan)) ((ConfigOptionBool, support_chamber_temp_control)) + ((ConfigOptionBool, apply_top_surface_compensation)) //y58 ((ConfigOptionBool, support_box_temp_control)) ((ConfigOptionBool, support_air_filtration)) @@ -1216,7 +1230,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionPoints, extruder_offset)) ((ConfigOptionBools, reduce_fan_stop_start_freq)) ((ConfigOptionInts, fan_cooling_layer_time)) - ((ConfigOptionStrings, filament_colour)) ((ConfigOptionFloatsNullable, top_surface_acceleration)) ((ConfigOptionFloatsNullable, outer_wall_acceleration)) ((ConfigOptionFloatsNullable, initial_layer_acceleration)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 2de07ff..89bc8b5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -75,7 +75,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor // snug height and an approximate bounding box in XY. BoundingBoxf3 bbox = model_object->raw_bounding_box(); Vec3d bbox_center = bbox.center(); - + // We may need to rotate the bbox / bbox_center from the original instance to the current instance. double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_rotation(), instances.front().model_instance->get_rotation()); if (std::abs(z_diff) > EPSILON) { @@ -523,6 +523,7 @@ void PrintObject::prepare_infill() this->discover_vertical_shells(); m_print->throw_if_canceled(); + // this will detect bridges and reverse bridges // and rearrange top/bottom/internal surfaces // It produces enlarged overlapping bridging areas. @@ -627,13 +628,13 @@ void PrintObject::infill() //BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( - tbb::blocked_range(0, m_layers.size()), - [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get()); - } - } + tbb::blocked_range(0, m_layers.size()), + [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + m_print->throw_if_canceled(); + m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get()); + } + } ); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end"; @@ -1110,7 +1111,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "skeleton_infill_density" || opt_key == "skin_infill_density" || opt_key == "infill_lock_depth" - || opt_key == "skin_infill_depth") { + || opt_key == "skin_infill_depth" + || opt_key == "locked_skin_infill_pattern" + || opt_key == "locked_skeleton_infill_pattern") { steps.emplace_back(posPrepareInfill); } else if (opt_key == "sparse_infill_density") { // One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes), @@ -1159,6 +1162,7 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" + || opt_key == "seam_placement_away_from_overhangs" || opt_key == "seam_slope_conditional" || opt_key == "scarf_angle_threshold" || opt_key == "seam_slope_entire_loop" @@ -1169,15 +1173,13 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "role_base_wipe_speed" || opt_key == "support_speed" || opt_key == "support_interface_speed" - //1.9.5 || opt_key == "smooth_speed_discontinuity_area" || opt_key == "smooth_coefficient" || opt_key == "overhang_1_4_speed" || opt_key == "overhang_2_4_speed" || opt_key == "overhang_3_4_speed" || opt_key == "overhang_4_4_speed" - //1.9.5 - || opt_key == "overhang_totally_speed" + || opt_key == "overhang_totally_speed" || opt_key == "bridge_speed" || opt_key == "outer_wall_speed" || opt_key == "small_perimeter_speed" @@ -1186,7 +1188,14 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "inner_wall_speed" || opt_key == "internal_solid_infill_speed" || opt_key == "top_surface_speed" - || opt_key == "vertical_shell_speed") { + || opt_key == "vertical_shell_speed" + || opt_key == "enable_height_slowdown" + || opt_key == "slowdown_start_height" + || opt_key == "slowdown_start_speed" + || opt_key == "slowdown_start_acc" + || opt_key == "slowdown_end_height" + || opt_key == "slowdown_end_speed" + || opt_key == "slowdown_end_acc" ) { invalidated |= m_print->invalidate_step(psGCodeExport); } else if ( opt_key == "flush_into_infill" @@ -1908,6 +1917,8 @@ void PrintObject::discover_vertical_shells() // PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str()); } + + void PrintObject::discover_shell_for_perimeters() { const size_t num_regions = this->num_printing_regions(); @@ -2056,14 +2067,14 @@ void PrintObject::bridge_over_infill() #endif #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "_candidate_processing_" + std::to_string(area(unsupported)), - to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))), - to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))), + to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))), + to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))), to_lines(unsupported_area)); #endif } } } - } + } #if USE_TBB_IN_INFILL }); #endif @@ -2771,7 +2782,7 @@ void PrintObject::bridge_over_infill() SurfacesPtr internal_infills = region->fill_surfaces.filter_by_type(stInternal); ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); // 新的稀ç–填充区域,去掉生æˆçš„æ¡¥æŽ¥åŒºåŸŸ - new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); + new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); for (const ExPolygon &ep : new_internal_infills) { new_surfaces.emplace_back(stInternal, ep); } @@ -2784,7 +2795,7 @@ void PrintObject::bridge_over_infill() Surface tmp{*surface, {}}; tmp.surface_type = stInternalBridge; tmp.bridge_angle = cs.bridge_angle; - for (const ExPolygon &ep : union_ex(cs.new_polys)) { + for (const ExPolygon &ep : intersection_ex(union_ex(cs.new_polys),region->fill_expolygons)) { new_surfaces.emplace_back(tmp, ep); } break; @@ -2814,7 +2825,7 @@ void PrintObject::bridge_over_infill() region->fill_surfaces.append(new_surfaces); } } - }); +}); BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); @@ -3600,6 +3611,7 @@ template void PrintObject::remove_bridges_from_contacts( SupportNecessaryType PrintObject::is_support_necessary() { const double cantilevel_dist_thresh = scale_(6); + TreeSupport tree_support(*this, m_slicing_params); tree_support.support_type = SupportType::stTreeAuto; // need to set support type to fully utilize the power of feature detection tree_support.detect_overhangs(true); @@ -3805,7 +3817,7 @@ void PrintObject::project_and_append_custom_facets( if (mv->is_model_part()) { const indexed_triangle_set custom_facets = seam ? mv->seam_facets.get_facets_strict(*mv, type) - : mv->supported_facets.get_facets_strict(*mv, type); + : mv->supported_facets.get_facets_strict(*mv, type);//just for support if (! custom_facets.indices.empty()) { if (seam) project_triangles_to_slabs(this->layers(), custom_facets, diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 6f08383..747249c 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -968,6 +968,112 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance }); } +template void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) +{ + // Returns fuzzy skin segmentation based on painting in the fuzzy skin painting gizmo. + std::vector> segmentation = fuzzy_skin_segmentation_by_painting(print_object, throw_on_cancel); + assert(segmentation.size() == print_object.layer_count()); + + struct ByRegion + { + ExPolygons expolygons; + bool needs_merge{false}; + }; + + tbb::parallel_for(tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), + [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { + const auto &layer_ranges = print_object.shared_regions()->layer_ranges; + auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(int(range.begin()))->slice_z); + + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + throw_on_cancel(); + + Layer &layer = *print_object.get_layer(int(layer_idx)); + it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); + const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; + + //assert(segmentation[layer_idx].size() == 1); + const ExPolygons &fuzzy_skin_segmentation = segmentation[layer_idx][1]; + const BoundingBox fuzzy_skin_segmentation_bbox = get_extents(fuzzy_skin_segmentation); + if (fuzzy_skin_segmentation.empty()) + continue; + + // Split LayerRegions by painted fuzzy skin regions. + // layer_range.fuzzy_skin_painted_regions are sorted by parent PrintObject region ID. + std::vector by_region(layer.region_count()); + auto it_fuzzy_skin_region_begin = layer_range.fuzzy_skin_painted_regions.cbegin(); + for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) { + if (it_fuzzy_skin_region_begin == layer_range.fuzzy_skin_painted_regions.cend()) continue; + + const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx); + const PrintRegion &parent_print_region = parent_layer_region.region(); + assert(parent_print_region.print_object_region_id() == parent_layer_region_idx); + if (parent_layer_region.slices.empty()) + continue; + + // Find the first FuzzySkinPaintedRegion, which overrides the parent PrintRegion. + auto it_fuzzy_skin_region = std::find_if(it_fuzzy_skin_region_begin, layer_range.fuzzy_skin_painted_regions.cend(), + [&layer_range, &parent_print_region](const auto &fuzzy_skin_region) { + return fuzzy_skin_region.parent_print_object_region_id(layer_range) == parent_print_region.print_object_region_id(); + }); + + if (it_fuzzy_skin_region == layer_range.fuzzy_skin_painted_regions.cend()) continue; // This LayerRegion isn't overrides by any FuzzySkinPaintedRegion. + + assert(it_fuzzy_skin_region->parent_print_object_region(layer_range) == &parent_print_region); + + // Update the beginning FuzzySkinPaintedRegion iterator for the next iteration. + it_fuzzy_skin_region_begin = std::next(it_fuzzy_skin_region); + + const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces); + Polygons layer_region_remaining_polygons = to_polygons(parent_layer_region.slices.surfaces); + // Don't trim by self, it is not reliable. + if (parent_layer_region_bbox.overlap(fuzzy_skin_segmentation_bbox) && it_fuzzy_skin_region->region != &parent_print_region) { + // Steal from this region. + const int target_region_id = it_fuzzy_skin_region->region->print_object_region_id(); + ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, fuzzy_skin_segmentation); + if (!stolen.empty()) { + ByRegion &dst = by_region[target_region_id]; + if (dst.expolygons.empty()) { + dst.expolygons = std::move(stolen); + } else { + append(dst.expolygons, std::move(stolen)); + dst.needs_merge = true; + } + } + + // Trim slices of this LayerRegion by the fuzzy skin region. + layer_region_remaining_polygons = diff(layer_region_remaining_polygons, fuzzy_skin_segmentation); + + // Filter out unprintable polygons. Detailed explanation is inside apply_mm_segmentation. + if (!layer_region_remaining_polygons.empty()) { + layer_region_remaining_polygons = opening(union_ex(layer_region_remaining_polygons), scaled(5. * EPSILON), scaled(5. * EPSILON)); + } + } + + if (!layer_region_remaining_polygons.empty()) { + ByRegion &dst = by_region[parent_print_region.print_object_region_id()]; + if (dst.expolygons.empty()) { + dst.expolygons = union_ex(layer_region_remaining_polygons); + } else { + append(dst.expolygons, union_ex(layer_region_remaining_polygons)); + dst.needs_merge = true; + } + } + } + + // Re-create Surfaces of LayerRegions. + for (int region_id = 0; region_id < layer.region_count(); ++region_id) { + ByRegion &src = by_region[region_id]; + if (src.needs_merge) { + // Multiple regions were merged into one. + src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON)); + } + + layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); + } + } + }); // end of parallel_for +} // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions @@ -1055,6 +1161,22 @@ void PrintObject::slice_volumes() apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } + // Is any ModelVolume fuzzy skin painted? + if (this->model_object()->is_fuzzy_skin_painted()) { + // If XY Size compensation is also enabled, notify the user that XY Size compensation + // would not be used because the object has custom fuzzy skin painted. + if (m_config.xy_hole_compensation.value != 0.f || m_config.xy_contour_compensation.value != 0.f) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, + _u8L("An object has enabled XY Size compensation which will not be used because it is also fuzzy skin painted.\nXY Size " + "compensation cannot be combined with fuzzy skin painting.") + + "\n" + (_u8L("Object name")) + ": " + this->model_object()->name); + } + + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - Fuzzy skin segmentation"; + apply_fuzzy_skin_segmentation(*this, [print]() { print->throw_if_canceled(); }); + } + InterlockingGenerator::generate_interlocking_structure(this); m_print->throw_if_canceled(); @@ -1191,7 +1313,7 @@ void PrintObject::slice_volumes() // Merge all regions' slices to get islands, chain them by a shortest path. if (this->config().enable_circle_compensation) layer->apply_auto_circle_compensation(); - layer->make_slices(); + layer->make_slices(); } }); if (elephant_foot_compensation_scaled > 0.f && ! m_layers.empty()) { diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index 2243a3c..a9438c3 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -42,85 +42,85 @@ public: using TValue = typename TColor::value_type; using TPixel = typename PixelRenderer::pixel_type; using TRawBuffer = agg::rendering_buffer; - + protected: - + Resolution m_resolution; PixelDim m_pxdim_scaled; // used for scaled coordinate polygons - + std::vector m_buf; agg::rendering_buffer m_rbuf; - + PixelRenderer m_pixrenderer; - + agg::renderer_base m_raw_renderer; Renderer> m_renderer; - + Trafo m_trafo; Scanline m_scanlines; Rasterizer m_rasterizer; - + void flipy(agg::path_storage &path) const { path.flip_y(0, double(m_resolution.height_px)); } - + void flipx(agg::path_storage &path) const { path.flip_x(0, double(m_resolution.width_px)); } - + double getPx(const Point &p) { return p(0) * m_pxdim_scaled.w_mm; } double getPy(const Point &p) { return p(1) * m_pxdim_scaled.h_mm; } agg::path_storage to_path(const Polygon &poly) { return to_path(poly.points); } - + template agg::path_storage _to_path(const PointVec& v) { agg::path_storage path; - + auto it = v.begin(); path.move_to(getPx(*it), getPy(*it)); while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); path.line_to(getPx(v.front()), getPy(v.front())); - + return path; } - + template agg::path_storage _to_path_flpxy(const PointVec& v) { agg::path_storage path; - + auto it = v.begin(); path.move_to(getPy(*it), getPx(*it)); while(++it != v.end()) path.line_to(getPy(*it), getPx(*it)); path.line_to(getPy(v.front()), getPx(v.front())); - + return path; } - + template agg::path_storage to_path(const PointVec &v) { auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v); - + path.translate_all_paths(m_trafo.center_x * m_pxdim_scaled.w_mm, m_trafo.center_y * m_pxdim_scaled.h_mm); - + if(m_trafo.mirror_x) flipx(path); if(m_trafo.mirror_y) flipy(path); - + return path; } - + template void _draw(const P &poly) { m_rasterizer.reset(); - + m_rasterizer.add_path(to_path(contour(poly))); for(auto& h : holes(poly)) m_rasterizer.add_path(to_path(h)); - + agg::render_scanlines(m_rasterizer, m_scanlines, m_renderer); } - + public: template AGGRaster(const Resolution &res, @@ -149,25 +149,25 @@ public: } m_renderer.color(foreground); clear(background); - + m_rasterizer.gamma(gammafn); } - + Trafo trafo() const override { return m_trafo; } - Resolution resolution() const override { return m_resolution; } - PixelDim pixel_dimensions() const override + Resolution resolution() const { return m_resolution; } + PixelDim pixel_dimensions() const { return {SCALING_FACTOR / m_pxdim_scaled.w_mm, SCALING_FACTOR / m_pxdim_scaled.h_mm}; } - + void draw(const ExPolygon &poly) override { _draw(poly); } - + EncodedRaster encode(RasterEncoder encoder) const override { - return encoder(m_buf.data(), m_resolution.width_px, m_resolution.height_px, 1); + return encoder(m_buf.data(), m_resolution.width_px, m_resolution.height_px, 1); } - + void clear(const TColor color) { m_raw_renderer.clear(color); } }; @@ -175,7 +175,7 @@ public: * Captures an anti-aliased monochrome canvas where vectorial * polygons can be rasterized. Fill color is always white and the background is * black. Contours are anti-aliased. - * + * * A gamma function can be specified at compile time to make it more flexible. */ using _RasterGrayscaleAA = @@ -187,30 +187,30 @@ class RasterGrayscaleAA : public _RasterGrayscaleAA { using typename Base::TValue; public: template - RasterGrayscaleAA(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, + RasterGrayscaleAA(const Resolution &res, + const PixelDim & pd, const RasterBase::Trafo & trafo, GammaFn && fn) : Base(res, pd, trafo, Colors::White, Colors::Black, std::forward(fn)) {} - + uint8_t read_pixel(size_t col, size_t row) const { static_assert(std::is_same::value, "Not grayscale pix"); - + uint8_t px; Base::m_buf[row * Base::resolution().width_px + col].get(px); return px; } - + void clear() { Base::clear(Colors::Black); } }; class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA { public: - RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, + RasterGrayscaleAAGammaPower(const Resolution &res, + const PixelDim & pd, const RasterBase::Trafo & trafo, double gamma = 1.) : RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma)) diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index 581e848..fb61007 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -11,29 +11,24 @@ namespace Slic3r { namespace sla { -const RasterBase::TMirroring RasterBase::NoMirror = {false, false}; -const RasterBase::TMirroring RasterBase::MirrorX = {true, false}; -const RasterBase::TMirroring RasterBase::MirrorY = {false, true}; -const RasterBase::TMirroring RasterBase::MirrorXY = {true, true}; - EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h, size_t num_components) { std::vector buf; size_t s = 0; - + void *rawdata = tdefl_write_image_to_png_file_in_memory( ptr, int(w), int(h), int(num_components), &s); - + // On error, data() will return an empty vector. No other info can be // retrieved from miniz anyway... if (rawdata == nullptr) return EncodedRaster({}, "png"); - + auto pptr = static_cast(rawdata); - + buf.reserve(s); std::copy(pptr, pptr + s, std::back_inserter(buf)); - + MZ_FREE(rawdata); return EncodedRaster(std::move(buf), "png"); } @@ -42,7 +37,7 @@ std::ostream &operator<<(std::ostream &stream, const EncodedRaster &bytes) { stream.write(reinterpret_cast(bytes.data()), std::streamsize(bytes.size())); - + return stream; } @@ -50,36 +45,38 @@ EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h, size_t num_components) { std::vector buf; - + auto header = std::string("P5 ") + std::to_string(w) + " " + std::to_string(h) + " " + "255 "; - + auto sz = w * h * num_components; size_t s = sz + header.size(); - + buf.reserve(s); auto buff = reinterpret_cast(ptr); std::copy(header.begin(), header.end(), std::back_inserter(buf)); std::copy(buff, buff+sz, std::back_inserter(buf)); - + return EncodedRaster(std::move(buf), "ppm"); } std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, + const Resolution &res, + const PixelDim & pxdim, double gamma, const RasterBase::Trafo & tr) { std::unique_ptr rst; - + if (gamma > 0) rst = std::make_unique(res, pxdim, tr, gamma); + else if (std::abs(gamma - 1.) < 1e-6) + rst = std::make_unique(res, pxdim, tr, agg::gamma_none()); else rst = std::make_unique(res, pxdim, tr, agg::gamma_threshold(.5)); - + return rst; } diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 1eba360..10d26f7 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -25,30 +25,51 @@ public: explicit EncodedRaster(std::vector &&buf, std::string ext) : m_buffer(std::move(buf)), m_ext(std::move(ext)) {} - + size_t size() const { return m_buffer.size(); } const void * data() const { return m_buffer.data(); } const char * extension() const { return m_ext.c_str(); } }; +/// Type that represents a resolution in pixels. +struct Resolution +{ + size_t width_px = 0; + size_t height_px = 0; + + Resolution() = default; + Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} + size_t pixels() const { return width_px * height_px; } +}; + +/// Types that represents the dimension of a pixel in millimeters. +struct PixelDim +{ + double w_mm = 1.; + double h_mm = 1.; + + PixelDim() = default; + PixelDim(double px_width_mm, double px_height_mm) : w_mm(px_width_mm), h_mm(px_height_mm) {} +}; + using RasterEncoder = std::function; class RasterBase { public: - + enum Orientation { roLandscape, roPortrait }; - + using TMirroring = std::array; - static const TMirroring NoMirror; - static const TMirroring MirrorX; - static const TMirroring MirrorY; - static const TMirroring MirrorXY; - + static const constexpr TMirroring NoMirror = {false, false}; + static const constexpr TMirroring MirrorX = {true, false}; + static const constexpr TMirroring MirrorY = {false, true}; + static const constexpr TMirroring MirrorXY = {true, true}; + struct Trafo { bool mirror_x = false, mirror_y = false, flipXY = false; coord_t center_x = 0, center_y = 0; - + // Portrait orientation will make sure the drawed polygons are rotated // by 90 degrees. Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) @@ -57,43 +78,20 @@ public: , mirror_y(!mirror[1]) // Makes raster origin to be top left corner , flipXY(o == roPortrait) {} - + TMirroring get_mirror() const { return { (roPortrait ? !mirror_x : mirror_x), mirror_y}; } Orientation get_orientation() const { return flipXY ? roPortrait : roLandscape; } Point get_center() const { return {center_x, center_y}; } }; - - /// Type that represents a resolution in pixels. - struct Resolution { - size_t width_px = 0; - size_t height_px = 0; - - Resolution() = default; - Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} - size_t pixels() const { return width_px * height_px; } - }; - - /// Types that represents the dimension of a pixel in millimeters. - struct PixelDim { - double w_mm = 1.; - double h_mm = 1.; - - PixelDim() = default; - PixelDim(double px_width_mm, double px_height_mm) - : w_mm(px_width_mm), h_mm(px_height_mm) - {} - }; - + virtual ~RasterBase() = default; - + /// Draw a polygon with holes. virtual void draw(const ExPolygon& poly) = 0; - + /// Get the resolution of the raster. - virtual Resolution resolution() const = 0; - virtual PixelDim pixel_dimensions() const = 0; virtual Trafo trafo() const = 0; - + virtual EncodedRaster encode(RasterEncoder encoder) const = 0; }; @@ -109,8 +107,8 @@ std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); // If gamma is zero, thresholding will be performed which disables AA. std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, + const Resolution &res, + const PixelDim & pxdim, double gamma = 1.0, const RasterBase::Trafo & tr = {}); diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 23e6388..6bbe4f7 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -328,8 +328,8 @@ SupportGeneratorLayersPtr generate_raft_base( } // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. - const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); - const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine); + const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); + float inflate_factor_1st_layer = float(scale_(object.config().raft_first_layer_expansion)); SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front(); SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front(); @@ -389,7 +389,11 @@ SupportGeneratorLayersPtr generate_raft_base( new_layer.height = slicing_params.first_print_layer_height; new_layer.bottom_z = 0.; first_layer = union_(std::move(first_layer), base); - new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(first_layer, inflate_factor_1st_layer) : first_layer; + if (inflate_factor_1st_layer < 0) { //means auto + inflate_factor_1st_layer = scale_(2.); + new_layer.polygons = expand(first_layer, inflate_factor_1st_layer - inflate_factor_fine); + } else + new_layer.polygons = inflate_factor_1st_layer > inflate_factor_fine ? expand(first_layer, inflate_factor_1st_layer - inflate_factor_fine) : first_layer; } // Insert the base layers. for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) { @@ -423,8 +427,17 @@ SupportGeneratorLayersPtr generate_raft_base( //if (object.has_brim()) // trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); //else - trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (inflate_factor_1st_layer > SCALED_EPSILON) { + trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (inflate_factor_1st_layer < 0) { //means auto + //for (const auto &expoly : to_expolygons(raft)) { + // //TODO: it's hard to get the exact support height here + // inflate_factor_1st_layer = std::max(0., + // scale_(expoly.map_moment_to_expansion(max_speed, contacts_with_height.empty() ? 0 : contacts_with_height[0]->print_z))); + //} + inflate_factor_1st_layer = scale_(2.); + } + if (inflate_factor_1st_layer > inflate_factor_fine + SCALED_EPSILON) { + inflate_factor_1st_layer = inflate_factor_1st_layer - inflate_factor_fine; // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); float step = inflate_factor_1st_layer / nsteps; @@ -1463,7 +1476,8 @@ void generate_support_toolpaths( const SupportGeneratorLayersPtr &top_contacts, const SupportGeneratorLayersPtr &intermediate_layers, const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers) + const SupportGeneratorLayersPtr &base_interface_layers, + const std::vector &cooldown_areas) { // loop_interface_processor with a given circle radius. LoopInterfaceProcessor loop_interface_processor(1.5 * support_params.support_material_interface_flow.scaled_width()); @@ -1595,7 +1609,7 @@ void generate_support_toolpaths( std::vector layer_caches(support_layers.size()); tbb::parallel_for(tbb::blocked_range(n_raft_layers, support_layers.size()), - [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, + [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &cooldown_areas, &layer_caches, &loop_interface_processor, &bbox_object, &angles, &interface_angles, n_raft_layers, link_max_length_factor] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. @@ -1810,6 +1824,29 @@ void generate_support_toolpaths( support_params, sheath, no_sort); } + if (!cooldown_areas.empty() && !cooldown_areas[support_layer_id].empty()) { + std::function setOverhangDegreeImpl = [&](ExtrusionEntitiesPtr &entities) { + for (auto entityPtr : entities) { + if (overlaps(to_expolygons(entityPtr->polygons_covered_by_width()), cooldown_areas[support_layer_id])) { + if (ExtrusionEntityCollection *collection = dynamic_cast(entityPtr)) { + setOverhangDegreeImpl(collection->entities); + } else if (ExtrusionPath *path = dynamic_cast(entityPtr)) { + path->set_overhang_degree(10); + } else if (ExtrusionMultiPath *multipath = dynamic_cast(entityPtr)) { + for (ExtrusionPath &path : multipath->paths) { path.set_overhang_degree(10); } + } else if (ExtrusionLoop *loop = dynamic_cast(entityPtr)) { + for (ExtrusionPath &path : loop->paths) { path.set_overhang_degree(10); } + } + } + } + }; + setOverhangDegreeImpl(base_layer.extrusions); + setOverhangDegreeImpl(top_contact_layer.extrusions); + setOverhangDegreeImpl(bottom_contact_layer.extrusions); + setOverhangDegreeImpl(interface_layer.extrusions); + setOverhangDegreeImpl(base_interface_layer.extrusions); + } + // Merge base_interface_layers to base_layers to avoid unneccessary retractions if (! base_layer.empty() && ! base_interface_layer.empty() && ! base_layer.polygons_to_extrude().empty() && ! base_interface_layer.polygons_to_extrude().empty() && base_layer.could_merge(base_interface_layer)) @@ -1998,6 +2035,7 @@ sub clip_with_shape { } } */ + /*! * \brief Unions two Polygons. Ensures that if the input is non empty that the output also will be non empty. * \param first[in] The first Polygon. diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp index 8cca96a..f88dc1f 100644 --- a/src/libslic3r/Support/SupportCommon.hpp +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -72,7 +72,8 @@ void generate_support_toolpaths( const SupportGeneratorLayersPtr &top_contacts, const SupportGeneratorLayersPtr &intermediate_layers, const SupportGeneratorLayersPtr &interface_layers, - const SupportGeneratorLayersPtr &base_interface_layers); + const SupportGeneratorLayersPtr &base_interface_layers, + const std::vector &cooldown_areas = {}); // FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold. // Find the first item with Z value >= of an internal threshold of fn_higher_equal. diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index a17f467..c451b3d 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1775,7 +1775,7 @@ static inline std::pair new_cont // Contact layer will be printed with a normal flow, but // it will support layers printed with a bridging flow. - if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer) && print_config.independent_support_layer_height) { + if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)/* && print_config.independent_support_layer_height*/) { coordf_t bridging_height = 0.; for (const LayerRegion* region : layer.regions()) bridging_height += region->region().bridging_height_avg(print_config); @@ -2928,7 +2928,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp for (size_t i = 0; i < top_contacts.size(); ++i) assert(top_contacts[i]->height > 0.); #endif /* _DEBUG */ - + return intermediate_layers; } diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 811ce05..e5c9ef4 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -160,13 +160,13 @@ struct SupportParameters { this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase; this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); - this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; + this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase; if (object_config.support_interface_pattern == smipGrid) this->contact_fill_pattern = ipGrid; else if (object_config.support_interface_pattern == smipRectilinearInterlaced || object_config.support_interface_pattern == smipAuto) this->contact_fill_pattern = ipRectilinear; else - this->contact_fill_pattern = object_config.support_interface_pattern == smipConcentric ? + this->contact_fill_pattern = object_config.support_interface_pattern == smipConcentric ? ipConcentric : (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); @@ -220,7 +220,7 @@ struct SupportParameters { } } - if(object_config.tree_support_wall_count.value==0){ + if(object_config.tree_support_wall_count.value==-1){ tree_support_branch_diameter_double_wall = support_filament_strength; this->tree_branch_diameter_double_wall_area_scaled = 0.25*sqr(scaled(tree_support_branch_diameter_double_wall))*M_PI; }else{ @@ -258,7 +258,7 @@ struct SupportParameters { Flow support_material_flow; Flow support_material_interface_flow; Flow support_material_bottom_interface_flow; - // Flow at raft inteface & contact layers. + // Flow at raft inteface & contact layers. Flow raft_interface_flow; coordf_t support_extrusion_width; // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 0ed0f88..ef375ab 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -945,7 +945,10 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) blocker = offset_ex(union_(blockers[layer_nr]), scale_(radius_sample_resolution)); if (!blocker.empty()) overhangs_all_layers[layer_nr] = diff_ex(overhangs_all_layers[layer_nr], blocker); } - if (!enforced_overhangs.empty()) overhangs_all_layers[layer_nr] = union_ex(overhangs_all_layers[layer_nr], enforced_overhangs); + if (!enforced_overhangs.empty()) { + enforced_overhangs = diff_ex(offset_ex(enforced_overhangs, enforcer_overhang_offset), lower_layer->lslices_extrudable); + overhangs_all_layers[layer_nr] = union_ex(overhangs_all_layers[layer_nr], enforced_overhangs); + } } else if (layer_nr < enforcers.size() && lower_layer) { if (!enforced_overhangs.empty()) { @@ -1210,7 +1213,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) layer->loverhangs_with_type.clear(); for (const auto &cantilever : layer->cantilevers) add_overhang(layer, cantilever, OverhangType::Cantilever); } else { - ExPolygons enforced_overhangs = to_expolygons(enforcers[layer_nr]); + ExPolygons enforced_overhangs = offset_ex(to_expolygons(enforcers[layer_nr]), enforcer_overhang_offset); ExPolygons loverhangs_new; std::vector> loverhangs_with_type_new; ExPolygons bigflat; @@ -1507,7 +1510,7 @@ void TreeSupport::generate_toolpaths() coordf_t support_extrusion_width = m_support_params.support_extrusion_width; coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(object_config.support_filament - 1); coordf_t layer_height = object_config.layer_height.value; - const size_t wall_count = object_config.tree_support_wall_count.value; + const int wall_count = object_config.tree_support_wall_count.value; // coconut: use same intensity settings as SupportMaterial.cpp auto m_support_material_interface_flow = support_material_interface_flow(m_object, float(m_slicing_params.layer_height)); @@ -1616,7 +1619,7 @@ void TreeSupport::generate_toolpaths() filler_raft->angle = PI / 2; filler_raft->spacing = support_flow.spacing(); for (auto& poly : first_non_raft_base) - make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::min(size_t(1), wall_count), support_flow, erSupportMaterial, filler_raft, interface_density, false); + make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::min(1, wall_count), support_flow, erSupportMaterial, filler_raft, interface_density, false); } if (m_object->support_layer_count() <= m_raft_layers) @@ -1747,8 +1750,16 @@ void TreeSupport::generate_toolpaths() filler_interface->layer_id = 0; } - fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface, - interface_flow); + ExtrusionEntityCollection *temp_support_fills = new ExtrusionEntityCollection(); + for (auto &single_poly : union_ex(polys)) + make_perimeter_and_infill(temp_support_fills->entities, single_poly, 1, interface_flow, erSupportMaterialInterface, filler_interface.get(), + interface_density, false); + temp_support_fills->no_sort = true; // make sure loops are first + if (!temp_support_fills->entities.empty()) + ts_layer->support_fills.entities.push_back(temp_support_fills); + else + delete temp_support_fills; + } else { // base_areas @@ -1773,17 +1784,35 @@ void TreeSupport::generate_toolpaths() // allow infill-only mode if support is thick enough (so min_wall_count is 0); // otherwise must draw 1 wall // Don't need extra walls if we have infill. Extra walls may overlap with the infills. - size_t min_wall_count = 1; - make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(min_wall_count, wall_count), flow, + size_t min_wall_count = wall_count == 0 ? 0 : 1; + make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(int(min_wall_count), wall_count), flow, erSupportMaterial, filler_support.get(), support_density); } else { SupportParameters support_params = m_support_params; - if (area_group.need_extra_wall && object_config.tree_support_wall_count.value == 0) + if (area_group.need_extra_wall && object_config.tree_support_wall_count.value == -1) support_params.tree_branch_diameter_double_wall_area_scaled = 0.1; tree_supports_generate_paths(ts_layer->support_fills.entities, loops, flow, support_params); } } + + if (area_group.need_cooling) + { + std::function setOverhangDegreeImpl = [&](ExtrusionEntityCollection *entity) { + for (auto entityPtr : entity->entities) { + if (ExtrusionEntityCollection *collection = dynamic_cast(entityPtr)) { + setOverhangDegreeImpl(collection); + } else if (ExtrusionPath *path = dynamic_cast(entityPtr)) { + path->set_overhang_degree(10); + } else if (ExtrusionMultiPath *multipath = dynamic_cast(entityPtr)) { + for (ExtrusionPath &path : multipath->paths) { path.set_overhang_degree(10); } + } else if (ExtrusionLoop *loop = dynamic_cast(entityPtr)) { + for (ExtrusionPath &path : loop->paths) { path.set_overhang_degree(10); } + } + } + }; + setOverhangDegreeImpl(&ts_layer->support_fills); + } } } if (m_support_params.base_fill_pattern == ipLightning) @@ -2304,6 +2333,7 @@ void TreeSupport::draw_circles() ts_layer->support_islands.reserve(curr_layer_nodes.size()); ExPolygons area_poly; // the polygon node area which will be printed as normal support ExPolygons extra_wall_area; //where nodes would have extra walls + ExPolygons cooldown_area; for (const SupportNode* p_node : curr_layer_nodes) { if (print->canceled()) @@ -2327,7 +2357,6 @@ void TreeSupport::draw_circles() append(area_poly, area); if (node.need_extra_wall) append(extra_wall_area, area); } - if (tree_brim_width <= 0) brim_width = node.type == ePolygon ? 1 : 3; } else { Polygon circle(branch_circle); @@ -2352,15 +2381,16 @@ void TreeSupport::draw_circles() circle.points[i] = circle.points[i] * scale + node.position; } } - brim_width = tree_brim_width > 0 ? - tree_brim_width : - std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, - std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius); + // brim_width = tree_brim_width > 0 ? + // tree_brim_width : + // std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, + // std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius); area = avoid_object_remove_extra_small_parts(ExPolygon(circle), get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); // area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0)); if (!area.empty()) has_circle_node = true; if (node.need_extra_wall) append(extra_wall_area, area); + if (node.overhang_degree >= 2) append(cooldown_area, area); // merge overhang to get a smoother interface surface // Do not merge when buildplate_only is on, because some underneath nodes may have been deleted. @@ -2374,10 +2404,17 @@ void TreeSupport::draw_circles() } append(area, overhang_expanded); } + } - if (layer_nr == 0 && m_raft_layers == 0) + if (layer_nr == 0 && m_raft_layers == 0) { + if (tree_brim_width >= 0) brim_width = tree_brim_width; + else { + for (const auto &expoly : area) + brim_width = std::max(brim_width, expoly.map_moment_to_expansion(m_object_config->support_speed.values[0], node.dist_mm_to_top)); + } area = safe_offset_inc(area, scale_(brim_width), get_collision(false), scale_(MIN_BRANCH_RADIUS * 0.5), 0, 1); + } if (obj_layer_nr>0 && node.distance_to_top < 0) append(roof_gap_areas, area); else if (m_support_params.num_top_interface_layers > 0 && obj_layer_nr > 0 && (node.support_roof_layers_below == 0 || node.support_roof_layers_below == 1) && @@ -2461,6 +2498,7 @@ void TreeSupport::draw_circles() area_groups.back().need_infill = overlaps({ expoly }, area_poly); bool need_extra_wall = overlaps({expoly},extra_wall_area); area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill; + area_groups.back().need_cooling = overlaps({ expoly }, cooldown_area); } for (auto& expoly : ts_layer->roof_areas) { //if (area(expoly) < SQ(scale_(1))) continue; @@ -2880,6 +2918,18 @@ void TreeSupport::drop_nodes() if (!overlap_with_circle.empty() && is_inside_ex(overlap_with_circle, node.position)) { continue; } + Polygon circle = make_circle(scale_(node.radius), 0.00789 * scale_(node.radius)); + circle.translate(node.position); + ExPolygons area = avoid_object_remove_extra_small_parts(ExPolygon(circle), get_collision(0, obj_layer_nr)); + if (!area.empty()) { + p_node->overhang = area[0]; + if (area[0].area() < SQ(scale_(1.)) || (!node.parent->to_buildplate && !overlaps({node.overhang}, {node.parent->overhang}))) { + p_node->valid = false; + p_node->is_processed = true; + continue; + } + } else + continue; } if (node.to_buildplate || parts.empty()) //It's outside, so make it go towards the build plate. { @@ -3362,8 +3412,10 @@ void TreeSupport::drop_nodes() } if (i_node->child) { i_node->child->parent = i_node->parent; - i_node->child->parents.erase(std::find(i_node->child->parents.begin(), i_node->child->parents.end(), i_node)); - append(i_node->child->parents, i_node->parents); + auto it = std::find(i_node->child->parents.begin(), i_node->child->parents.end(), i_node); + if (it != i_node->child->parents.end()) i_node->child->parents.erase(it); + if (!i_node->parents.empty()) + append(i_node->child->parents, i_node->parents); } i_node->is_processed = true; // mark to be deleted later @@ -3441,6 +3493,7 @@ void TreeSupport::smooth_nodes() branch[i]->radius = radii1[i]; branch[i]->movement = (pts[i + 1] - pts[i - 1]) / 2; branch[i]->is_processed = true; + branch[i]->overhang_degree = std::min(10.0, unscale_((branch[i]->movement).cast().norm()) / m_object_config->support_line_width * 10); if (branch[i]->parents.size() > 1 || (branch[i]->movement.x() > max_move || branch[i]->movement.y() > max_move) || (total_height > thresh_tall_branch && branch[i]->dist_mm_to_top < thresh_dist_to_top)) branch[i]->need_extra_wall = true; diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index dc7bc79..966d50f 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -125,6 +125,7 @@ struct SupportNode bool is_sharp_tail = false; bool valid = true; bool fading = false; + double overhang_degree = 0.0; // overhang degree for cooling just like perimeter ExPolygon overhang; // when type==ePolygon, set this value to get original overhang area coordf_t origin_area; @@ -432,7 +433,6 @@ private: SlicingParameters m_slicing_params; // Various precomputed support parameters to be shared with external functions. SupportParameters m_support_params; - //1.9.5 size_t m_raft_layers = 0; // number of raft layers, including raft base, raft interface, raft gap size_t m_highest_overhang_layer = 0; std::vector> m_spanning_trees; diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index 1db55f0..a30d032 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -4140,7 +4140,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // Outputs layer_storage, top_contacts, interface_layers, base_interface_layers }; - + std::vector cooldown_areas(num_support_layers); if (has_support) { auto t_precalc = std::chrono::high_resolution_clock::now(); // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas @@ -4185,7 +4185,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // this new function give correct result when raft is also enabled organic_draw_branches( *print.get_object(processing.second.front()), volumes, config, move_bounds, - bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, + bottom_contacts, top_contacts, interface_placer, intermediate_layers, layer_storage, cooldown_areas, throw_on_cancel); #endif @@ -4232,7 +4232,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons // Don't fill in the tree supports, make them hollow with just a single sheath line. print.set_status(69, _L("Generating support")); generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), - raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers, cooldown_areas); auto t_end = std::chrono::high_resolution_clock::now(); BOOST_LOG_TRIVIAL(info) << "Total time of organic tree support: " << 0.001 * std::chrono::duration_cast(t_end - t_start).count() << " ms"; @@ -4287,6 +4287,7 @@ void organic_draw_branches( // Output: SupportGeneratorLayersPtr& intermediate_layers, SupportGeneratorLayerStorage& layer_storage, + std::vector& cooldown_areas, std::function throw_on_cancel) { @@ -4336,6 +4337,30 @@ void organic_draw_branches( organic_smooth_branches_avoid_collisions(print_object, volumes, config, elements_with_link_down, linear_data_layers, throw_on_cancel); + //save the cooling area + for (LayerIndex layer_idx = 0; layer_idx + 1 < LayerIndex(move_bounds.size()); ++layer_idx) { + auto &layer_elements = move_bounds[layer_idx]; + for (auto &elem : layer_elements) { + if (!elem.parents.empty()) { + SupportElements *layer_above = layer_idx + 1 < LayerIndex(move_bounds.size()) ? &move_bounds[layer_idx + 1] : nullptr; + if (!layer_above) break; + for (auto &parent_elem : elem.parents) { + auto &parent = (*layer_above)[parent_elem]; + double overhang_degree = std::min(10.0, (parent.state.result_on_layer - elem.state.result_on_layer).cast().norm() / + config.support_line_width * 10); + + if (overhang_degree >= 2) { + coord_t radius = support_element_radius(config, parent); + Polygon circle = make_circle(radius, radius / 100); + circle.translate(parent.state.result_on_layer); + cooldown_areas[layer_idx + 1].emplace_back(ExPolygon(circle)); + } + } + } + } + } + + // Reduce memory footprint. After this point only finalize_interface_and_support_areas() will use volumes and from that only collisions with zero radius will be used. volumes.clear_all_but_object_collision(); diff --git a/src/libslic3r/Support/TreeSupport3D.hpp b/src/libslic3r/Support/TreeSupport3D.hpp index 96eea2c..07b0f13 100644 --- a/src/libslic3r/Support/TreeSupport3D.hpp +++ b/src/libslic3r/Support/TreeSupport3D.hpp @@ -318,6 +318,7 @@ void organic_draw_branches( // Output: SupportGeneratorLayersPtr& intermediate_layers, SupportGeneratorLayerStorage& layer_storage, + std::vector& cooldown_areas, std::function throw_on_cancel); diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index d33b67b..3739520 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -11,7 +11,6 @@ #include "Point.hpp" // Transform3d namespace Slic3r { - /// /// User modifiable property of text style /// NOTE: OnEdit fix serializations: EmbossStylesSerializable, TextConfigurationSerialization @@ -72,18 +71,17 @@ struct FontProp /// /// Y size of text [in mm] /// Z size of text [in mm] - FontProp(float line_height = 10.f) : size_in_mm(line_height), per_glyph(false) - {} + FontProp(float line_height = 10.f) : size_in_mm(line_height), per_glyph(false) { + } bool operator==(const FontProp& other) const { - return - char_gap == other.char_gap && - line_gap == other.line_gap && - per_glyph == other.per_glyph && - align == other.align && - is_approx(size_in_mm, other.size_in_mm) && - is_approx(boldness, other.boldness) && - is_approx(skew, other.skew); + auto case0 = is_approx(boldness.value_or(0), other.boldness.value_or(0)); + auto case1 = is_approx(skew.value_or(0), other.skew.value_or(0)); + auto case2 = line_gap.value_or(0) == other.line_gap.value_or(0); + auto case3 = char_gap.value_or(0) == other.char_gap.value_or(0); + return per_glyph == other.per_glyph && + align == other.align && is_approx(size_in_mm, other.size_in_mm) + && case0 && case1 && case2 &&case3; } // undo / redo stack recovery @@ -148,12 +146,8 @@ struct EmbossStyle bool operator==(const EmbossStyle &other) const { - return - type == other.type && - prop == other.prop && - name == other.name && - path == other.path - ; + auto case0 = prop == other.prop; + return type == other.type && case0 && name == other.name; } // undo / redo stack recovery diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index b08810b..c0c4006 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -166,6 +166,24 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si this->special_side_idx = char(special_side_idx); } +void TriangleSelector::Triangle::select_by_seed_fill() +{ + assert(!is_split()); + m_selected_by_seed_fill = true; +} + +void TriangleSelector::Triangle::unselect_by_seed_fill() +{ + assert(!is_split()); + m_selected_by_seed_fill = false; +} + +bool TriangleSelector::Triangle::is_selected_by_seed_fill() const +{ + assert(!is_split()); + return m_selected_by_seed_fill; +} + inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) { // Real-time collision detection, Ericson, Chapter 3.4 @@ -476,6 +494,20 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver process_subtriangle(touching.second, Partition::Second); } +void TriangleSelector::append_touching_its(int itriangle, indexed_triangle_set &its) const +{ + if (itriangle == -1) + return; + auto& idxs= m_triangles[itriangle].verts_idxs; + its.indices.emplace_back(stl_triangle_vertex_indices(idxs[0], idxs[1], idxs[2])); + if (its.vertices.empty()) { + its.vertices.reserve(m_vertices.size()); + for (int i = 0; i < m_vertices.size(); i++) { + its.vertices.emplace_back(m_vertices[i].v); + } + } +} + // QDS: add seed_fill_angle parameter void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, const ClippingPlane &clp, float seed_fill_angle, bool propagate, bool force_reselection) { @@ -1600,7 +1632,61 @@ std::vector TriangleSelector::get_seed_fill_contour() const { return edges_out; } -void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &edges_out) const { +indexed_triangle_set TriangleSelector::get_seed_fill_mesh(int &state) const +{ + indexed_triangle_set its; + std::set face_idx_set; + for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) { + const Vec3i neighbors = m_neighbors[facet_idx]; + this->get_seed_fill_its_recursive(facet_idx, neighbors, neighbors, face_idx_set, its, state); + } + return its; +} + +void TriangleSelector::get_seed_fill_its_recursive( + int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::set &idx_set, indexed_triangle_set &its, int &state) const +{ + assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); + assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); + const Triangle *tr = &m_triangles[facet_idx]; + if (!tr->valid()) + return; + + if (tr->is_split()) { + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i = 0; i < num_of_children; ++i) { + assert(i < int(tr->children.size())); + assert(tr->children[i] < int(m_triangles.size())); + // Recursion, deep first search over the children of this triangle. + // All children of this triangle were created by splitting a single source triangle of the original mesh. + const Vec3i child_neighbors = this->child_neighbors(*tr, neighbors, i); + this->get_seed_fill_its_recursive(tr->children[i], child_neighbors, this->child_neighbors_propagated(*tr, neighbors_propagated, i, child_neighbors), idx_set, its, + state); + } + } + } else if (tr->is_selected_by_seed_fill()) { + auto select_state = m_triangles[facet_idx].get_state(); + if (state < 0) { + state = (int) select_state; + } + if (idx_set.find(facet_idx) == idx_set.end()) { + idx_set.insert(facet_idx); + append_touching_its(facet_idx, its); + for (int i = 0; i < 3; i++) { + if (select_state == m_triangles[neighbors(i)].get_state()) { + if (idx_set.find(neighbors(i)) == idx_set.end()) { + idx_set.insert(neighbors(i)); + append_touching_its(neighbors(i), its); + } + } + } + } + } +} + +void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &edges_out) const +{ assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); const Triangle *tr = &m_triangles[facet_idx]; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 54b2b1e..696dc4c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -257,7 +257,7 @@ public: indexed_triangle_set get_facets_strict(EnforcerBlockerType state) const; // Get edges around the selected area by seed fill. std::vector get_seed_fill_contour() const; - + indexed_triangle_set get_seed_fill_mesh(int& state) const; // QDS void get_facets(std::vector& facets_per_type) const; @@ -320,10 +320,10 @@ protected: EnforcerBlockerType get_state() const { assert(! is_split()); return state; } // Set if the triangle has been selected or unselected by seed fill. - void select_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = true; } - void unselect_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = false; } + void select_by_seed_fill(); + void unselect_by_seed_fill(); // Get if the triangle has been selected or not by seed fill. - bool is_selected_by_seed_fill() const { assert(! is_split()); return m_selected_by_seed_fill; } + bool is_selected_by_seed_fill() const; // Is this triangle valid or marked to be removed? bool valid() const noexcept { return m_valid; } @@ -413,7 +413,7 @@ private: //void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector &touching_subtriangles_out) const; void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector &touching_edges_out) const; - + void append_touching_its(int itriangle, indexed_triangle_set &its) const; #ifndef NDEBUG //bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const; bool verify_triangle_midpoints(const Triangle& tr) const; @@ -426,6 +426,7 @@ private: std::vector &out_triangles) const; void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector &out_triangles) const; + void get_seed_fill_its_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::set &idx_set, indexed_triangle_set &its, int &state) const; void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &edges_out) const; int m_free_triangles_head { -1 }; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index af19b13..4423700 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -10,9 +10,11 @@ #include #include +#include #include #include "libslic3r.h" +#include "libslic3r_version.h" //define CLI errors @@ -69,6 +71,7 @@ #define CLI_GCODE_PATH_IN_UNPRINTABLE_AREA -102 #define CLI_FILAMENT_UNPRINTABLE_ON_FIRST_LAYER -103 + namespace boost { namespace filesystem { class directory_entry; }} namespace Slic3r { @@ -122,6 +125,146 @@ inline std::string convert_to_full_version(std::string short_version) } return result; } + +class PathSanitizer +{ +public: + static std::string sanitize(const std::string &path) { + return sanitize_impl(path); + } + + static std::string sanitize(std::string &&path) { + return sanitize_impl(path); + } + + static std::string sanitize(const char *path) { + return path ? sanitize_impl(std::string(path)) : ""; + } + + static std::string sanitize(const boost::filesystem::path &path) { + return sanitize_impl(path.string()); + } + +private: + inline static size_t start_pos = std::string::npos; + inline static size_t id_start_pos = std::string::npos; + inline static size_t name_size = 0; + + static bool init_usrname_range() + { + if (start_pos != std::string::npos) { + return true; + } +#ifdef _WIN32 + const char *env = std::getenv("USERPROFILE"); + #if QDT_RELEASE_TO_PUBLIC + const size_t len = strlen("\\AppData\\Roaming\\QIDIStudio\\user"); + #else + const size_t len = QDT_INTERNAL_TESTING == 1 ? strlen("\\AppData\\Roaming\\QIDIStudioInternal\\user") : strlen("\\AppData\\Roaming\\QIDIStudioBeta\\user"); + #endif +#elif __APPLE__ + const char *env = std::getenv("HOME"); + #if QDT_RELEASE_TO_PUBLIC + const size_t len = strlen("/Library/Application Support/QIDIStudio/user"); + #else + const size_t len = QDT_INTERNAL_TESTING == 1 ? strlen("/Library/Application Support/QIDIStudioInternal/user") : strlen("/Library/Application Support/QIDIStudioBeta/user"); + #endif +#elif __linux__ + const char *env = std::getenv("HOME"); + #if QDT_RELEASE_TO_PUBLIC + const size_t len = strlen("/.config/QIDIStudio/user"); + #else + const size_t len = QDT_INTERNAL_TESTING == 1 ? strlen("/.config/QIDIStudioInternal/user") : strlen("/.config/QIDIStudioBeta/user"); + #endif +#else + // Unsupported platform, return raw input + return false; +#endif + if (!env) { + return false; + } + std::string full(env); + size_t sep_pos = full.find_last_of("\\/"); + if (sep_pos == std::string::npos) { + return false; + } + start_pos = sep_pos + 1; + name_size = full.length() - start_pos; + id_start_pos = full.length() + len + 1; + + if (name_size == 0) { + return false; + } + if (start_pos + name_size > full.length()) { + return false; + } + return true; + } + + static std::string sanitize_impl(const std::string &raw) + { + if (!init_usrname_range()) { + return raw; + } + + if (raw.length() < start_pos + name_size) { + return raw; + } + + std::string sanitized = raw; + if (raw[start_pos + name_size] == '\\' || raw[start_pos + name_size] == '/') { + sanitized.replace(start_pos, name_size, std::string(name_size, '*')); + } else if (std::isupper(raw[start_pos])) { + sanitized.replace(start_pos, 12, std::string(12, '*')); + } else { + return raw; + } + + if (id_start_pos != std::string::npos && id_start_pos < sanitized.length() && (sanitized[id_start_pos - 1] == '\\' || sanitized[id_start_pos - 1] == '/') && + std::isdigit(sanitized[id_start_pos])) { + // If the ID part is present, sanitize it as well + size_t id_end_pos = sanitized.find_first_of("\\/", id_start_pos); + if (id_end_pos == std::string::npos) { + id_end_pos = sanitized.length(); + } + sanitized.replace(id_start_pos, id_end_pos - id_start_pos, std::string(id_end_pos - id_start_pos, '*')); + } + + return sanitized; + } + + static std::string sanitize_impl(std::string &&raw) + { + if (!init_usrname_range()) { + return raw; + } + + if (raw.length() < start_pos + name_size) { + return raw; + } + + if (raw[start_pos + name_size] == '\\' || raw[start_pos + name_size] == '/') { + raw.replace(start_pos, name_size, std::string(name_size, '*')); + } else if (std::isupper(raw[start_pos])) { + raw.replace(start_pos, 12, std::string(12, '*')); + } else { + return raw; + } + + if (id_start_pos != std::string::npos && id_start_pos < raw.length() && (raw[id_start_pos - 1] == '\\' || raw[id_start_pos - 1] == '/') && + std::isdigit(raw[id_start_pos])) { + // If the ID part is present, sanitize it as well + size_t id_end_pos = raw.find_first_of("\\/", id_start_pos); + if (id_end_pos == std::string::npos) { + id_end_pos = raw.length(); + } + raw.replace(id_start_pos, id_end_pos - id_start_pos, std::string(id_end_pos - id_start_pos, '*')); + } + + return std::move(raw); + } +}; + template inline DataType round_divide(DataType dividend, DataType divisor) //!< Return dividend divided by divisor rounded to the nearest integer { @@ -692,6 +835,9 @@ inline std::string filter_characters(const std::string& str, const std::string& return filteredStr; } +void save_string_file(const boost::filesystem::path& p, const std::string& str); +void load_string_file(const boost::filesystem::path& p, std::string& str); + } // namespace Slic3r #if WIN32 diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 127afda..c991caf 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -67,7 +67,7 @@ static constexpr double SPARSE_INFILL_RESOLUTION = 0.04; static constexpr double SUPPORT_RESOLUTION = 0.0375; #define SCALED_SUPPORT_RESOLUTION (SUPPORT_RESOLUTION / SCALING_FACTOR) -// Maximum perimeter length for the loop to apply the small perimeter speed. +// Maximum perimeter length for the loop to apply the small perimeter speed. #define SMALL_PERIMETER_LENGTH(LENGTH) (((LENGTH)/SCALING_FACTOR)*2*PI) static constexpr double INSET_OVERLAP_TOLERANCE = 0.4; // 3mm ring around the top / bottom / bridging areas. @@ -111,7 +111,7 @@ using deque = template inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } -enum Axis { +enum Axis { X=0, Y, Z, @@ -184,7 +184,7 @@ inline void append_reversed(std::vector& dest, std::vector&& src) // Casting an std::vector<> from one type to another type without warnings about a loss of accuracy. template -std::vector cast(const std::vector &src) +std::vector cast(const std::vector &src) { std::vector dst; dst.reserve(src.size()); @@ -222,7 +222,7 @@ ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey ForwardIt it; typename std::iterator_traits::difference_type count, step; count = std::distance(first, last); - + while (count > 0) { it = first; step = count / 2; @@ -241,10 +241,10 @@ ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey template> ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={}) { - // Note: BOTH type T and the type after ForwardIt is dereferenced - // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. + // Note: BOTH type T and the type after ForwardIt is dereferenced + // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. // This is stricter than lower_bound requirement (see above) - + first = std::lower_bound(first, last, value, comp); return first != last && !comp(value, *first) ? first : last; } @@ -253,10 +253,10 @@ ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare c template ForwardIt binary_find_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key, EqualToKeyPredicate equal_to_key) { - // Note: BOTH type T and the type after ForwardIt is dereferenced - // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. + // Note: BOTH type T and the type after ForwardIt is dereferenced + // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. // This is stricter than lower_bound requirement (see above) - + first = lower_bound_by_predicate(first, last, lower_thank_key); return first != last && equal_to_key(*first) ? first : last; } @@ -317,7 +317,7 @@ template struct is_scaled_coord // return type will be bool. // For more info how to use, see docs for std::enable_if // -template +template using FloatingOnly = std::enable_if_t::value, O>; template diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in index 84812a4..bb4baac 100644 --- a/src/libslic3r/libslic3r_version.h.in +++ b/src/libslic3r/libslic3r_version.h.in @@ -6,6 +6,7 @@ #define SLIC3R_VERSION "@SLIC3R_VERSION@" #define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@" #define SLIC3R_BUILD_TIME "@SLIC3R_BUILD_TIME@" +#define SLIC3R_COMPILE_VERSION "@SLIC3R_COMPILE_VERSION@" //#define SLIC3R_RC_VERSION "@SLIC3R_VERSION@" #define QDT_RELEASE_TO_PUBLIC @QDT_RELEASE_TO_PUBLIC@ #define QDT_INTERNAL_TESTING @QDT_INTERNAL_TESTING@ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 7456d2d..e63b4ff 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1517,5 +1517,23 @@ bool qdt_calc_md5(std::string &filename, std::string &md5_out) return true; } +void save_string_file(const boost::filesystem::path& p, const std::string& str) +{ + boost::nowide::ofstream file; + file.exceptions(std::ios_base::failbit | std::ios_base::badbit); + file.open(p.generic_string(), std::ios_base::binary); + file.write(str.c_str(), str.size()); +} + +void load_string_file(const boost::filesystem::path& p, std::string& str) +{ + boost::nowide::ifstream file; + file.exceptions(std::ios_base::failbit | std::ios_base::badbit); + file.open(p.generic_string(), std::ios_base::binary); + std::size_t sz = static_cast(boost::filesystem::file_size(p)); + str.resize(sz, '\0'); + file.read(&str[0], sz); +} + }; // namespace Slic3r