From e0d447172ceab9e7549a49768408a0d8202ae787 Mon Sep 17 00:00:00 2001 From: QIDI TECH <893239786@qq.com> Date: Tue, 8 Jul 2025 20:00:47 +0800 Subject: [PATCH] update libslic3r --- .../include/libnest2d/placers/nfpplacer.hpp | 8 +- src/libslic3r/AppConfig.cpp | 10 +- src/libslic3r/Arachne/utils/ExtrusionLine.cpp | 16 +- src/libslic3r/Arachne/utils/ExtrusionLine.hpp | 7 +- src/libslic3r/Arrange.hpp | 6 +- src/libslic3r/Brim.cpp | 14 +- src/libslic3r/BrimEarsPoint.hpp | 12 +- src/libslic3r/CMakeLists.txt | 4 + src/libslic3r/Config.cpp | 45 ++ src/libslic3r/Config.hpp | 3 + src/libslic3r/Extruder.cpp | 4 +- src/libslic3r/Fill/Fill.cpp | 115 +++- src/libslic3r/Fill/FillBase.cpp | 49 +- src/libslic3r/Fill/FillBase.hpp | 16 +- src/libslic3r/Fill/FillFloatingConcentric.cpp | 108 +++- src/libslic3r/Fill/FillHoneycomb.hpp | 2 +- src/libslic3r/Fill/FillLine.hpp | 2 +- src/libslic3r/Fill/FillRectilinear.cpp | 134 +++- src/libslic3r/Fill/FillRectilinear.hpp | 15 + src/libslic3r/Flow.hpp | 7 + src/libslic3r/Format/qds_3mf.cpp | 4 +- src/libslic3r/GCode.cpp | 166 ++++- src/libslic3r/GCode.hpp | 6 + .../GCode/AvoidCrossingPerimeters.cpp | 313 +++++++++- .../GCode/AvoidCrossingPerimeters.hpp | 2 + src/libslic3r/GCode/GCodeProcessor.cpp | 14 +- src/libslic3r/GCode/GCodeProcessor.hpp | 2 +- src/libslic3r/GCode/TimelapsePosPicker.cpp | 583 ++++++++++++++++++ src/libslic3r/GCode/TimelapsePosPicker.hpp | 81 +++ src/libslic3r/GCode/ToolOrderUtils.cpp | 23 +- src/libslic3r/GCode/ToolOrdering.cpp | 50 +- src/libslic3r/GCode/ToolOrdering.hpp | 2 - src/libslic3r/GCode/WipeTower.cpp | 241 +++++--- src/libslic3r/GCode/WipeTower.hpp | 20 +- src/libslic3r/GCodeWriter.cpp | 52 +- src/libslic3r/Model.cpp | 3 + src/libslic3r/MultiPoint.hpp | 4 +- src/libslic3r/Orient.cpp | 5 + src/libslic3r/Orient.hpp | 4 +- src/libslic3r/OverhangDetector.cpp | 332 ++++++++++ src/libslic3r/OverhangDetector.hpp | 48 ++ src/libslic3r/PNGReadWrite.cpp | 11 +- src/libslic3r/PNGReadWrite.hpp | 1 + src/libslic3r/PerimeterGenerator.cpp | 297 +++------ src/libslic3r/Polyline.hpp | 4 +- src/libslic3r/Preset.cpp | 81 ++- src/libslic3r/Preset.hpp | 1 + src/libslic3r/PresetBundle.cpp | 36 +- src/libslic3r/PresetBundle.hpp | 4 +- src/libslic3r/Print.cpp | 33 +- src/libslic3r/Print.hpp | 4 +- src/libslic3r/PrintApply.cpp | 53 +- src/libslic3r/PrintConfig.cpp | 225 ++++++- src/libslic3r/PrintConfig.hpp | 26 +- src/libslic3r/PrintObject.cpp | 23 +- src/libslic3r/ShortestPath.cpp | 2 +- src/libslic3r/Slicing.cpp | 2 +- src/libslic3r/Support/SupportCommon.cpp | 110 ++-- src/libslic3r/Support/SupportParameters.hpp | 43 +- src/libslic3r/Support/TreeSupport.cpp | 175 ++++-- src/libslic3r/Support/TreeSupportCommon.hpp | 2 +- src/libslic3r/VariableWidth.cpp | 2 +- src/libslic3r/VariableWidth.hpp | 2 +- src/libslic3r/libslic3r.h | 1 + 64 files changed, 2958 insertions(+), 712 deletions(-) create mode 100644 src/libslic3r/GCode/TimelapsePosPicker.cpp create mode 100644 src/libslic3r/GCode/TimelapsePosPicker.hpp create mode 100644 src/libslic3r/OverhangDetector.cpp create mode 100644 src/libslic3r/OverhangDetector.hpp diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 2d84f29..afaa85a 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -693,7 +693,7 @@ private: // item won't overlap with virtual objects if it's inside or touches NFP // @return 1 if current item overlaps with virtual objects, 0 otherwise bool overlapWithVirtObject(const Item& item, const Box& binbb){ - if (items_.empty()) return 0; + if (items_.empty()) return false; Shapes nfps = calcnfp(item, binbb, Lvl()); auto v = item.referenceVertex(); for (const RawShape &nfp : nfps) { @@ -1263,9 +1263,9 @@ private: Vertex ci, cb; Box bbin = sl::boundingBox(bin_); - Vertex shrink(10, 10); - bbin.maxCorner() -= shrink; - bbin.minCorner() += shrink; + //Vertex shrink(10, 10); + //bbin.maxCorner() -= shrink; + //bbin.minCorner() += shrink; switch(config_.starting_point) { case Config::Alignment::CENTER: { diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index b8d44c0..52a6ccd 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -176,6 +176,8 @@ void AppConfig::set_defaults() set_bool("enable_merge_color_by_sync_ams", false); 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); if (get("zoom_to_mouse").empty()) set_bool("zoom_to_mouse", false); @@ -193,6 +195,8 @@ void AppConfig::set_defaults() set_bool("user_bed_type", true); if (get("grabber_size_factor").empty()) set("grabber_size_factor", "1.0"); + if (get("cancel_glmultidraw").empty()) + set_bool("cancel_glmultidraw", false); //#ifdef SUPPORT_SHOW_HINTS if (get("show_hints").empty()) set_bool("show_hints", false); @@ -1284,11 +1288,11 @@ std::string AppConfig::get_region() std::string sel = get("iot_environment"); std::string region; if (sel == ENV_DEV_HOST) - region = "ENV_CN_DEV"; + region = "NEW_ENV_DEV_HOST"; else if (sel == ENV_QAT_HOST) - region = "ENV_CN_QA"; + region = "NEW_ENV_QAT_HOST"; else if (sel == ENV_PRE_HOST) - region = "ENV_CN_PRE"; + region = "NEW_ENV_PRE_HOST"; if (region.empty()) return this->get("region"); return region; diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index 797e8d3..4c6677f 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -279,7 +279,17 @@ double ExtrusionLine::area() const namespace Slic3r { // 1.9.5 -void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow, int overhang) + +void extrusion_paths_append(std::list &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow, double overhang) +{ + for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) { + ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path); + ExtrusionPaths path = thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON), overhang).paths; + dst.insert(dst.end(), std::make_move_iterator(path.begin()), std::make_move_iterator(path.end())); + } +} + +void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow, double overhang) { for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path); @@ -289,7 +299,7 @@ void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extr } //1.9.5 -void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow, int overhang) +void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow, double overhang) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); //1.9.5 @@ -297,7 +307,7 @@ void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &e } //1.9.5 -void extrusion_path_append(ExtrusionPaths &dst, const ThickPolyline &thick_polyline, const ExtrusionRole role, const Flow &flow, int overhang) +void extrusion_path_append(ExtrusionPaths &dst, const ThickPolyline &thick_polyline, const ExtrusionRole role, const Flow &flow, double overhang) { Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON), overhang).paths); } diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index 0e3002c..2c094a9 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -302,9 +302,10 @@ using VariableWidthLines = std::vector; // &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow, double overhang = 0); +void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow, double overhang = 0); +void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow, double overhang = 0); +void extrusion_path_append(ExtrusionPaths &dst, const ThickPolyline &thick_polyline, const ExtrusionRole role, const Flow &flow, double overhang = 0); } // namespace Slic3r #endif // UTILS_EXTRUSION_LINE_H diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 20fd64f..f63674c 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -4,7 +4,7 @@ #include "ExPolygon.hpp" #include "PrintConfig.hpp" -#define BED_SHRINK_SEQ_PRINT 5 +#define BED_SHRINK_SEQ_PRINT 0 namespace Slic3r { @@ -127,8 +127,8 @@ struct ArrangeParams { bool is_seq_print = false; bool align_to_y_axis = false; bool save_svg = false; - float bed_shrink_x = 0.1; - float bed_shrink_y = 0.1; + float bed_shrink_x = 0.0; + float bed_shrink_y = 0.0; float brim_skirt_distance = 0; float clearance_height_to_rod = 0; float clearance_height_to_lid = 0; diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 1c160b9..4e44ede 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -768,7 +768,10 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi const Point ¢er_offset = object->center_offset(); model_trsf = model_trsf.pretranslate(Vec3d(- unscale(center_offset.x()), - unscale(center_offset.y()), 0)); for (auto &pt : brim_ear_points) { - Vec3f world_pos = pt.transform(trsf.get_matrix()); + Transform3d v_trsf = Transform3d::Identity(); + if (pt.volume_idx > -1) + v_trsf = object->model_object()->volumes[pt.volume_idx]->get_matrix(); + Vec3f world_pos = pt.transform(trsf.get_matrix() * v_trsf); if ( world_pos.z() > 0) continue; Polygon point_round; float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2; @@ -783,7 +786,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi } mouse_ears_ex.emplace_back(); mouse_ears_ex.back().contour = point_round; - Vec3f pos = pt.transform(model_trsf); + Vec3f pos = pt.transform(model_trsf * v_trsf); int32_t pt_x = scale_(pos.x()); int32_t pt_y = scale_(pos.y()); mouse_ears_ex.back().contour.translate(Point(pt_x, pt_y)); @@ -1020,6 +1023,11 @@ 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) @@ -1136,6 +1144,8 @@ 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/BrimEarsPoint.hpp b/src/libslic3r/BrimEarsPoint.hpp index a859d54..4592a7f 100644 --- a/src/libslic3r/BrimEarsPoint.hpp +++ b/src/libslic3r/BrimEarsPoint.hpp @@ -18,22 +18,26 @@ struct BrimPoint { Vec3f pos; float head_front_radius; + int volume_idx; BrimPoint() - : pos(Vec3f::Zero()), head_front_radius(0.f) + : pos(Vec3f::Zero()), head_front_radius(0.f), volume_idx(-1) {} BrimPoint(float pos_x, float pos_y, float pos_z, - float head_radius) + float head_radius, + int volume_idx = -1) : pos(pos_x, pos_y, pos_z) , head_front_radius(head_radius) + , volume_idx(volume_idx) {} - BrimPoint(Vec3f position, float head_radius) + BrimPoint(Vec3f position, float head_radius, int volume_idx = -1) : pos(position) , head_front_radius(head_radius) + , volume_idx(volume_idx) {} Vec3f transform(const Transform3d &trsf) @@ -56,7 +60,7 @@ struct BrimPoint bool operator!=(const BrimPoint &sp) const { return !(sp == (*this)); } template void serialize(Archive &ar) { - ar(pos, head_front_radius); + ar(pos, head_front_radius, volume_idx); } }; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 84a5cb7..494ddaa 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -169,6 +169,8 @@ set(lisbslic3r_sources GCode/Smoothing.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp + GCode/TimelapsePosPicker.cpp + GCode/TimelapsePosPicker.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -242,6 +244,8 @@ set(lisbslic3r_sources ParameterUtils.hpp PerimeterGenerator.cpp PerimeterGenerator.hpp + OverhangDetector.hpp + OverhangDetector.cpp PlaceholderParser.cpp PlaceholderParser.hpp Platform.cpp diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 73dc62a..a1e0504 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -1477,6 +1477,51 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name, BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; } +//y66 +void ConfigBase::save_to_temp_json(const std::string &file, std::map key_values) const +{ + json j; + //record the headers + for(auto key_value : key_values) + { + j[key_value.first] = key_value.second; + } + + //record all the key-values + for (const std::string &opt_key : this->keys()) + { + const ConfigOption* opt = this->option(opt_key); + if ( opt->is_scalar() ) { + if (opt->type() == coString) + //keep \n, \r, \t + j[opt_key] = (dynamic_cast(opt))->value; + else + j[opt_key] = opt->serialize(); + } + else { + const ConfigOptionVectorBase* vec = static_cast(opt); + //if (!vec->empty()) + std::vector string_values = vec->vserialize(); + + /*for (int i = 0; i < string_values.size(); i++) + { + std::string string_value = escape_string_cstyle(string_values[i]); + j[opt_key][i] = string_value; + }*/ + + json j_array(string_values); + j[opt_key] = j_array; + } + } + + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + c << std::setw(4) << j << std::endl; + c.close(); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; +} + void ConfigBase::save(const std::string &file) const { boost::nowide::ofstream c; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 7422f37..9666bad 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2570,6 +2570,9 @@ public: static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions); + //y66 + void save_to_temp_json(const std::string &file, std::map key_values) const; + private: // Set a configuration value from a string. bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append); diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index a9725c8..9edb116 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -179,12 +179,12 @@ double Extruder::retract_restart_extra() const double Extruder::retract_length_toolchange() const { - return m_config->retract_length_toolchange.get_at(m_id); + return m_config->retract_length_toolchange.get_at(extruder_id()); } double Extruder::retract_restart_extra_toolchange() const { - return m_config->retract_restart_extra_toolchange.get_at(m_id); + return m_config->retract_restart_extra_toolchange.get_at(extruder_id()); } } diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 8566608..59dd3fb 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -65,7 +65,8 @@ struct SurfaceFillParams float solid_infill_speed = 0; float infill_shift_step = 0;// param for cross zag float infill_rotate_step = 0; // param for zig zag to get cross texture - + float infill_lock_depth = 0; + float skin_infill_depth = 0; bool symmetric_infill_y_axis = false; bool operator<(const SurfaceFillParams &rhs) const { @@ -95,7 +96,9 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(solid_infill_speed); RETURN_COMPARE_NON_EQUAL(infill_shift_step); RETURN_COMPARE_NON_EQUAL(infill_rotate_step); - RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis); + RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis); + RETURN_COMPARE_NON_EQUAL(infill_lock_depth); + RETURN_COMPARE_NON_EQUAL(skin_infill_depth); return false; } @@ -119,7 +122,9 @@ struct SurfaceFillParams this->solid_infill_speed == rhs.solid_infill_speed && this->infill_shift_step == rhs.infill_shift_step && this->infill_rotate_step == rhs.infill_rotate_step && - this->symmetric_infill_y_axis == rhs.symmetric_infill_y_axis; + 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; } }; @@ -135,16 +140,44 @@ struct SurfaceFill { ExPolygons no_overlap_expolygons; }; -std::vector group_fills(const Layer &layer) +// QDS: used to judge whether the internal solid infill area is narrow +static bool is_narrow_infill_area(const ExPolygon& expolygon) +{ + ExPolygons offsets = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD)); + if (offsets.empty()) + return true; + + return false; +} + +std::vector group_fills(const Layer &layer, LockRegionParam &lock_param) { std::vector surface_fills; - // Fill in a map of a region & surface to SurfaceFillParams. std::set set_surface_params; std::vector> region_to_surface_params(layer.regions().size(), std::vector()); SurfaceFillParams params; bool has_internal_voids = false; const PrintObjectConfig& object_config = layer.object()->config(); + + auto append_flow_param = [](std::map &flow_params, Flow flow, const ExPolygon &exp) { + auto it = flow_params.find(flow); + if (it == flow_params.end()) + 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) { + auto it = density_params.find(density); + if (it == density_params.end()) + 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) { const LayerRegion &layerm = *layer.regions()[region_id]; region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr); @@ -158,7 +191,12 @@ std::vector group_fills(const Layer &layer) params.extruder = layerm.region().extruder(extrusion_role); params.pattern = region_config.sparse_infill_pattern.value; params.density = float(region_config.sparse_infill_density); - if (params.pattern == ipCrossZag){ + if (params.pattern == ipLockedZag) { + params.infill_lock_depth = scale_(region_config.infill_lock_depth); + params.skin_infill_depth = scale_(region_config.skin_infill_depth); + + } + if (params.pattern == ipCrossZag || params.pattern == ipLockedZag){ params.infill_shift_step = scale_(region_config.infill_shift_step); params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis; }else if (params.pattern == ipZigZag){ @@ -185,8 +223,8 @@ std::vector group_fills(const Layer &layer) is_bridge ? erBridgeInfill : (surface.is_solid() ? - (surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : surface.is_floating_vertical_shell()?erFloatingVerticalShell:erSolidInfill)) : - erInternalInfill); + (surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : surface.is_floating_vertical_shell()?erFloatingVerticalShell:erSolidInfill)) : + erInternalInfill); params.bridge_angle = float(surface.bridge_angle); params.angle = float(Geometry::deg2rad(region_config.infill_direction.value)); @@ -199,9 +237,9 @@ std::vector group_fills(const Layer &layer) //QDS: record speed params if (!params.bridge) { if (params.extrusion_role == erInternalInfill) - params.sparse_infill_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder)); + params.sparse_infill_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder)); else if (params.extrusion_role == erTopSolidInfill) - params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder)); + params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder)); else if (params.extrusion_role == erSolidInfill) params.solid_infill_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder)); else if (params.extrusion_role == erFloatingVerticalShell) @@ -227,7 +265,28 @@ std::vector group_fills(const Layer &layer) params.anchor_length = std::min(params.anchor_length, params.anchor_length_max); } - auto it_params = set_surface_params.find(params); + //get locked region param + if (params.pattern == ipLockedZag){ + const PrintObject *object = layerm.layer()->object(); + auto nozzle_diameter = float(object->print()->config().nozzle_diameter.get_at(layerm.region().extruder(extrusion_role) - 1)); + Flow skin_flow = params.bridge ? params.flow : Flow::new_from_config_width(extrusion_role, region_config.skin_infill_line_width, nozzle_diameter, float((surface.thickness == -1) ? layer.height : surface.thickness)); + //add skin flow + append_flow_param(lock_param.skin_flow_params, skin_flow, surface.expolygon); + + Flow skeleton_flow = params.bridge ? params.flow : Flow::new_from_config_width(extrusion_role, region_config.skeleton_infill_line_width, nozzle_diameter, float((surface.thickness == -1) ? layer.height : surface.thickness)) ; + // add skeleton flow + append_flow_param(lock_param.skeleton_flow_params, skeleton_flow, surface.expolygon); + + // add skin density + append_density_param(lock_param.skin_density_params, float(0.01 * region_config.skin_infill_density), surface.expolygon); + + // add skin density + append_density_param(lock_param.skeleton_density_params, float(0.01 * region_config.skeleton_infill_density), surface.expolygon); + + } + + auto it_params = set_surface_params.find(params); + if (it_params == set_surface_params.end()) it_params = set_surface_params.insert(it_params, params); region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params); @@ -384,7 +443,7 @@ std::vector group_fills(const Layer &layer) auto bbox = get_extents(surface_fills[i].expolygons[j]); auto clipped_internals = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_internal_areas, bbox.inflated(scale_(2))); // expand a little auto clipped_internal_bbox = get_extents(clipped_internals); - if (is_narrow_expolygon(surface_fills[i].expolygons[j], narrow_threshold)) { + if (is_narrow_infill_area(surface_fills[i].expolygons[j])) { if (!clipped_internals.empty() && bbox.overlap(clipped_internal_bbox) && !intersection_ex(offset_ex(surface_fills[i].expolygons[j],SCALED_EPSILON), clipped_internals).empty()) { narrow_floating_expoly_idx.emplace_back(j); } @@ -484,8 +543,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #ifdef SLIC3R_DEBUG_SLICE_PROCESSING // this->export_region_fill_surfaces_to_svg_debug("10_fill-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - std::vector surface_fills = group_fills(*this); + LockRegionParam lock_param; + std::vector surface_fills = group_fills(*this, lock_param); const Slic3r::BoundingBox bbox = this->object()->bounding_box(); const auto resolution = this->object()->print()->config().resolution.value; @@ -539,8 +598,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: lower_unsuporrt_expolys = union_ex(lower_unsuporrt_expolys, sexpolys); } lower_unsuporrt_expolys = shrink_ex(lower_unsuporrt_expolys, SCALED_EPSILON); - - std::vector lower_fills = group_fills(*lower_layer); + LockRegionParam temp_skin_inner_param; + std::vector lower_fills = group_fills(*lower_layer, temp_skin_inner_param); bool detect_lower_sparse_lines = true; for (auto& fill : lower_fills) { if (fill.params.pattern == ipAdaptiveCubic || fill.params.pattern == ipLightning || fill.params.pattern == ipSupportCubic) { @@ -596,7 +655,13 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.extrusion_role = surface_fill.params.extrusion_role; params.using_internal_flow = using_internal_flow; params.no_extrusion_overlap = surface_fill.params.overlap; - if (surface_fill.params.pattern == ipCrossZag) { + if( surface_fill.params.pattern == ipLockedZag ) { + params.locked_zag = true; + 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); + } + if (surface_fill.params.pattern == ipCrossZag || surface_fill.params.pattern == ipLockedZag) { if (f->layer_id % 2 == 0) { params.horiz_move -= surface_fill.params.infill_shift_step * (f->layer_id / 2); } else { @@ -614,7 +679,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: LayerRegion* layerm = this->m_regions[surface_fill.region_id]; for (ExPolygon& expoly : surface_fill.expolygons) { - f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); + f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); if (params.symmetric_infill_y_axis) { params.symmetric_y_axis = f->extended_object_bounding_box().center().x(); expoly.symmetric_y(params.symmetric_y_axis); @@ -624,9 +689,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->spacing = surface_fill.params.spacing; surface_fill.surface.expolygon = std::move(expoly); // QDS: make fill - f->fill_surface_extrusion(&surface_fill.surface, - params, - m_regions[surface_fill.region_id]->fills.entities); + f->fill_surface_extrusion(&surface_fill.surface, params, m_regions[surface_fill.region_id]->fills.entities); } } @@ -650,7 +713,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const { - std::vector surface_fills = group_fills(*this); + LockRegionParam skin_inner_param; + std::vector surface_fills = group_fills(*this, skin_inner_param); const Slic3r::BoundingBox bbox = this->object()->bounding_box(); const auto resolution = this->object()->print()->config().resolution.value; @@ -684,7 +748,8 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc case ipArchimedeanChords: case ipOctagramSpiral: case ipZigZag: - case ipCrossZag: break; + case ipCrossZag: + case ipLockedZag: break; } // Create the filler object. @@ -807,7 +872,7 @@ void Layer::make_ironing() // ironing flowrate (5% percent) // ironing speed (10 mm/sec) - // Kisslicer: + // Kisslicer: // iron off, Sweep, Group // ironing speed: 15 mm/sec @@ -827,7 +892,7 @@ void Layer::make_ironing() const PrintRegionConfig &config = layerm->region().config(); if (config.ironing_type != IroningType::NoIroning && (config.ironing_type == IroningType::AllSolid || - (config.top_shell_layers > 0 && + (config.top_shell_layers > 0 && (config.ironing_type == IroningType::TopSurfaces || (config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) { if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) { diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index bf692be..cf5de4c 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -60,6 +60,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipZigZag: return new FillZigZag(); case ipCrossZag: return new FillCrossZag(); case ipFloatingConcentric: return new FillFloatingConcentric(); + case ipLockedZag: return new FillLockedZag(); default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -171,7 +172,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para // Calculate a new spacing to fill width with possibly integer number of lines, // the first and last line being centered at the interval ends. -// This function possibly increases the spacing, never decreases, +// This function possibly increases the spacing, never decreases, // and for a narrow width the increase in spacing may become severe, // therefore the adjustment is limited to 20% increase. coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance) @@ -282,10 +283,10 @@ struct ContourIntersectionPoint { bool could_take_next() const throw() { return ! this->consumed && this->contour_not_taken_length_next > SCALED_EPSILON; } // Could extrude a complete segment from this to this->prev_on_contour. - bool could_connect_prev() const throw() + bool could_connect_prev() const throw() { return ! this->consumed && this->prev_on_contour != this && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; } // Could extrude a complete segment from this to this->next_on_contour. - bool could_connect_next() const throw() + bool could_connect_next() const throw() { return ! this->consumed && this->next_on_contour != this && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; } }; @@ -494,7 +495,7 @@ static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, Cont } static void take_limited( - Polyline &pl1, const Points &contour, const std::vector ¶ms, + Polyline &pl1, const Points &contour, const std::vector ¶ms, ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, double take_max_length, double line_half_width) { #ifndef NDEBUG @@ -658,8 +659,8 @@ static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, do // Calculate intersection of a line with a thick segment. // Returns Eucledian parameters of the line / thick segment overlap. static inline bool line_rounded_thick_segment_collision( - const Vec2d &line_a, const Vec2d &line_b, - const Vec2d &segment_a, const Vec2d &segment_b, const double offset, + const Vec2d &line_a, const Vec2d &line_b, + const Vec2d &segment_a, const Vec2d &segment_b, const double offset, std::pair &out_interval) { const Vec2d line_v0 = line_b - line_a; @@ -722,8 +723,8 @@ static inline bool line_rounded_thick_segment_collision( std::pair interval; if (Geometry::liang_barsky_line_clipping_interval( Vec2d(line_p0.dot(dir_x), line_p0.dot(dir_y)), - Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), - BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), + Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), + BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), interval)) extend_interval(interval.first, interval.second); } else @@ -1083,7 +1084,7 @@ void mark_boundary_segments_touching_infill( // Clip the infill polyline by the Eucledian distance along the polyline. SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance); SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance); - if (start_point.valid() && end_point.valid() && + if (start_point.valid() && end_point.valid() && (start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) { // The clipped polyline is non-empty. #ifdef INFILL_DEBUG_OUTPUT @@ -1223,21 +1224,21 @@ struct BoundaryInfillGraph }; static Direction dir(const Point &p1, const Point &p2) { - return p1.x() == p2.x() ? + return p1.x() == p2.x() ? (p1.y() < p2.y() ? Up : Down) : (p1.x() < p2.x() ? Right : Left); } const Direction dir_prev(const ContourIntersectionPoint &cp) const { assert(cp.prev_on_contour); - return cp.could_take_prev() ? + return cp.could_take_prev() ? dir(this->point(cp), this->point(*cp.prev_on_contour)) : Taken; } const Direction dir_next(const ContourIntersectionPoint &cp) const { assert(cp.next_on_contour); - return cp.could_take_next() ? + return cp.could_take_next() ? dir(this->point(cp), this->point(*cp.next_on_contour)) : Taken; } @@ -1295,7 +1296,7 @@ static inline void mark_boundary_segments_overlapping_infill( assert(interval.first == 0.); double len_out = closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; if (len_out < cp.contour_not_taken_length_next) { - // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, // thus at least some of the contour is outside and we will extrude this segment. inside = false; break; @@ -1327,7 +1328,7 @@ static inline void mark_boundary_segments_overlapping_infill( assert(interval.first == 0.); double len_out = closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; if (len_out < cp.contour_not_taken_length_prev) { - // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, // thus at least some of the contour is outside and we will extrude this segment. inside = false; break; @@ -1424,7 +1425,7 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second]; if (pprev) { pprev->next_on_contour = pthis; - pthis->prev_on_contour = pprev; + pthis->prev_on_contour = pprev; } else pfirst = pthis; contour_intersection_points.emplace_back(pthis); @@ -1449,7 +1450,7 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered ip->param = contour_params[ip->point_idx]; // and measure distance to the previous and next intersection point. const double contour_length = contour_params.back(); - for (ContourIntersectionPoint *ip : contour_intersection_points) + for (ContourIntersectionPoint *ip : contour_intersection_points) if (ip->next_on_contour == ip) { assert(ip->prev_on_contour == ip); ip->contour_not_taken_length_prev = ip->contour_not_taken_length_next = contour_length; @@ -1855,14 +1856,14 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI // The contour is supposed to enter the "forbidden" zone outside of the (left, right) band at tbegin and also at tend. static inline void emit_loops_in_band( // Vertical band, which will trim the contour between tbegin and tend. - coord_t left, + coord_t left, coord_t right, // Contour and its parametrization. const Points &contour, const std::vector &contour_params, // Span of the parameters of an arch to trim with the vertical band. double tbegin, - double tend, + double tend, // Minimum arch length to put into polylines_out. Shorter arches are not necessary to support a dense support infill. double min_length, Polylines &polylines_out) @@ -1918,13 +1919,13 @@ static inline void emit_loops_in_band( }; enum InOutBand { - Entering, + Entering, Leaving, }; class State { public: - State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : + State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : m_left(left), m_right(right), m_min_length(min_length), m_polylines_out(polylines_out) {} void add_inner_point(const Point* p) @@ -2225,7 +2226,7 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector SCALED_EPSILON && + if (cp.contour_not_taken_length_prev > SCALED_EPSILON && (cost_prev.self_loop ? cost_prev.cost > cap_cost : cost_prev.cost > cost_veryhigh)) { @@ -2571,7 +2572,7 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector SCALED_EPSILON && + if (cp.contour_not_taken_length_next > SCALED_EPSILON && (cost_next.self_loop ? cost_next.cost > cap_cost : cost_next.cost > cost_veryhigh)) { diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 7582ad2..60314ed 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -35,6 +35,15 @@ public: InfillFailedException() : Slic3r::RuntimeError("Infill failed") {} }; +struct LockRegionParam +{ + LockRegionParam() {} + std::map skin_density_params; + std::map skeleton_density_params; + std::map skin_flow_params; + std::map skeleton_flow_params; +}; + struct FillParams { bool full_infill() const { return density > 0.9999f; } @@ -80,6 +89,9 @@ struct FillParams float horiz_move{0.0}; //move infill to get cross zag pattern bool symmetric_infill_y_axis{false}; coord_t symmetric_y_axis{0}; + bool locked_zag{false}; + float infill_lock_depth{0.0}; + float skin_infill_depth{0.0}; }; static_assert(IsTriviallyCopyable::value, "FillParams class is not POD (and it should be - see constructor)."); @@ -135,10 +147,10 @@ public: // Perform the fill. 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){}; // 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& params, ExtrusionEntitiesPtr& out); + virtual void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out); protected: Fill() : diff --git a/src/libslic3r/Fill/FillFloatingConcentric.cpp b/src/libslic3r/Fill/FillFloatingConcentric.cpp index 738af19..f59f1f5 100644 --- a/src/libslic3r/Fill/FillFloatingConcentric.cpp +++ b/src/libslic3r/Fill/FillFloatingConcentric.cpp @@ -5,8 +5,10 @@ #include "../Surface.hpp" #include "../VariableWidth.hpp" #include "../format.hpp" +#include "../EdgeGrid.hpp" #include "FillFloatingConcentric.hpp" #include +#include namespace Slic3r { @@ -685,7 +687,19 @@ FloatingThickPolylines FillFloatingConcentric::resplit_order_loops(Point curr_po if (all_extrusions[idx]->empty()) continue; ThickPolyline thick_polyline = Arachne::to_thick_polyline(*all_extrusions[idx]); - FloatingThickPolyline thick_line_with_floating = detect_floating_line(thick_polyline, floating_areas, default_width, !print_object_config->detect_floating_vertical_shell.value); + bool is_self_intersect = false; + if(print_object_config->detect_floating_vertical_shell.value) + { + Polyline polyline = thick_polyline; + auto bbox_line = get_extents(polyline); + + EdgeGrid::Grid grid; + grid.set_bbox(bbox_line); + grid.create({ polyline.points }, scaled(10.), !all_extrusions[idx]->is_closed); + if (grid.has_intersecting_edges()) + is_self_intersect = true; + } + FloatingThickPolyline thick_line_with_floating = detect_floating_line(thick_polyline, floating_areas, default_width, !print_object_config->detect_floating_vertical_shell.value || is_self_intersect); smooth_floating_line(thick_line_with_floating, scale_(2), scale_(2)); int split_idx = 0; if (!floating_areas.empty() && all_extrusions[idx]->is_closed && thick_line_with_floating.points.front() == thick_line_with_floating.points.back()) { @@ -789,6 +803,79 @@ void FillFloatingConcentric::_fill_surface_single( } #endif +static std::vector toplogic_sort_extruisons(const std::vector& all_extrusions) +{ + std::vector ordered_extrusions; + // Find topological order with constraints from extrusions_constrains. + std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. + std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. + std::unordered_map map_extrusion_to_idx; + for (size_t idx = 0; idx < all_extrusions.size(); idx++) + map_extrusion_to_idx.emplace(all_extrusions[idx], idx); + + auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, true); + for (auto [before, after] : extrusions_constrains) { + auto after_it = map_extrusion_to_idx.find(after); + ++blocked[after_it->second]; + blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); + } + + std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. + Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. + while (ordered_extrusions.size() < all_extrusions.size()) { + size_t best_candidate = 0; + double best_distance_sqr = std::numeric_limits::max(); + bool is_best_closed = false; + + std::vector available_candidates; + for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { + if (processed[candidate] || blocked[candidate]) + continue; // Not a valid candidate. + available_candidates.push_back(candidate); + } + + std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { + return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; + }); + + for (const size_t candidate_path_idx : available_candidates) { + auto& path = all_extrusions[candidate_path_idx]; + + if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (best_distance_sqr == std::numeric_limits::max()) { + best_candidate = candidate_path_idx; + is_best_closed = path->is_closed; + } + continue; + } + + const Point candidate_position = path->junctions.front().p; + double distance_sqr = (current_position - candidate_position).cast().norm(); + if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. + if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { + best_candidate = candidate_path_idx; + best_distance_sqr = distance_sqr; + is_best_closed = path->is_closed; + } + } + } + + auto& best_path = all_extrusions[best_candidate]; + ordered_extrusions.push_back(best_path); + processed[best_candidate] = true; + for (size_t unlocked_idx : blocking[best_candidate]) + blocked[unlocked_idx]--; + + if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. + if (best_path->is_closed) + current_position = best_path->junctions[0].p; //We end where we started. + else + current_position = best_path->junctions.back().p; //Pick the other end from where we started. + } + } + return ordered_extrusions; +} + void FillFloatingConcentric::_fill_surface_single(const FillParams& params, unsigned int thickness_layers, const std::pair& direction, @@ -813,17 +900,21 @@ void FillFloatingConcentric::_fill_surface_single(const FillParams& params, Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params); std::vector loops = wallToolPaths.getToolPaths(); - std::vector all_extrusions; - for (Arachne::VariableWidthLines& loop : loops) { - if (loop.empty()) - continue; - for (const Arachne::ExtrusionLine& wall : loop) - all_extrusions.emplace_back(&wall); + std::vector ordered_extrusions; + { + std::vector all_extrusions; + for (Arachne::VariableWidthLines& loop : loops) { + if (loop.empty()) + continue; + for (Arachne::ExtrusionLine& wall : loop) + all_extrusions.emplace_back(&wall); + } + ordered_extrusions = toplogic_sort_extruisons(all_extrusions); } // Split paths using a nearest neighbor search. size_t firts_poly_idx = thick_polylines_out.size(); - auto thick_polylines = resplit_order_loops({ 0,0 }, all_extrusions, this->lower_layer_unsupport_areas, this->lower_sparse_polys,min_spacing); + auto thick_polylines = resplit_order_loops({ 0,0 }, ordered_extrusions, this->lower_layer_unsupport_areas, this->lower_sparse_polys,min_spacing); append(thick_polylines_out, thick_polylines); @@ -855,6 +946,7 @@ FloatingThickPolylines FillFloatingConcentric::fill_surface_arachne_floating(con void FillFloatingConcentric::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out) { FloatingThickPolylines floating_lines = this->fill_surface_arachne_floating(surface, params); + //reorder_by_shortest_traverse(floating_lines); if (floating_lines.empty()) return; Flow new_flow = params.flow.with_spacing(this->spacing); diff --git a/src/libslic3r/Fill/FillHoneycomb.hpp b/src/libslic3r/Fill/FillHoneycomb.hpp index e8f806a..55be423 100644 --- a/src/libslic3r/Fill/FillHoneycomb.hpp +++ b/src/libslic3r/Fill/FillHoneycomb.hpp @@ -13,7 +13,7 @@ class FillHoneycomb : public Fill { public: ~FillHoneycomb() override {} - bool is_self_crossing() override { return false; } + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillHoneycomb(*this); }; diff --git a/src/libslic3r/Fill/FillLine.hpp b/src/libslic3r/Fill/FillLine.hpp index b1ddc73..4ad9ca4 100644 --- a/src/libslic3r/Fill/FillLine.hpp +++ b/src/libslic3r/Fill/FillLine.hpp @@ -14,7 +14,7 @@ class FillLine : public Fill public: Fill* clone() const override { return new FillLine(*this); }; ~FillLine() override = default; - bool is_self_crossing() override { return false; } + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 7e746de..2d98539 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -1423,10 +1423,10 @@ static SegmentIntersection& end_of_vertical_run(SegmentedIntersectionLine &il, S } static void traverse_graph_generate_polylines(const ExPolygonWithOffset &poly_with_offset, - const FillParams ¶ms, - std::vector &segs, - const bool consistent_pattern, - Polylines &polylines_out) + const FillParams ¶ms, + std::vector &segs, + const bool consistent_pattern, + Polylines &polylines_out) { // For each outer only chords, measure their maximum distance to the bow of the outer contour. // Mark an outer only chord as consumed, if the distance is low. @@ -1597,7 +1597,7 @@ static void traverse_graph_generate_polylines(const ExPolygonWithOffset int i_vertical = it->vertical_outside(); auto vertical_link_quality = (i_vertical == -1 || vline.intersections[i_vertical + (going_up ? 0 : -1)].consumed_vertical_up) ? SegmentIntersection::LinkQuality::Invalid : it->vertical_outside_quality(); -#if 0 +#if 0 if (vertical_link_quality == SegmentIntersection::LinkQuality::Valid || // Follow the link if there is no horizontal link available. (! intersection_horizontal_valid && vertical_link_quality != SegmentIntersection::LinkQuality::Invalid)) { @@ -2585,7 +2585,7 @@ static std::vector chain_monotonic_regions( path.emplace_back(MonotonicRegionLink{ next_region, next_dir }); assert(left_neighbors_unprocessed[next_region - regions.data()] == 1); left_neighbors_unprocessed[next_region - regions.data()] = 0; - print_ant("\tRegion (%1%:%2%,%3%) (%4%:%5%,%6%) length to prev %7%", + print_ant("\tRegion (%1%:%2%,%3%) (%4%:%5%,%6%) length to prev %7%", next_region->left.vline, next_dir ? next_region->left.high : next_region->left.low, next_dir ? next_region->left.low : next_region->left.high, @@ -2833,6 +2833,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa // Rotate polygons so that we can work with vertical lines here std::pair rotate_vector = this->_infill_direction(surface); + if (params.locked_zag) + rotate_vector.first += float(M_PI/2.); rotate_vector.first += angleBase; assert(params.density > 0.0001f && params.density <= 1.f); @@ -2886,7 +2888,6 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa x0 += params.horiz_move - (gap_line - 1) * line_spacing; n_vlines += 1; } - #ifdef SLIC3R_DEBUG static int iRun = 0; BoundingBox bbox_svg = poly_with_offset.bounding_box_outer(); @@ -2972,7 +2973,7 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa //assert(! it->has_duplicate_points()); it->remove_duplicate_points(); - //get origin direction infill + //get origin direction infill if (params.symmetric_infill_y_axis) { it->symmetric_y(params.symmetric_y_axis); } @@ -2984,6 +2985,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa assert(! polyline.has_duplicate_points()); #endif /* SLIC3R_DEBUG */ + + return true; } @@ -3371,5 +3374,120 @@ void FillMonotonicLineWGapFill::fill_surface_by_lines(const Surface* surface, co } } +void FillLockedZag::fill_surface_locked_zag (const Surface * surface, + const FillParams & params, + std::vector> &multi_width_polyline) +{ + // merge different part exps + // diff skin flow + Polylines skin_lines; + Polylines skeloton_lines; + double offset_threshold = params.skin_infill_depth; + double overlap_threshold = params.infill_lock_depth; + Surface cross_surface = *surface; + Surface zig_surface = *surface; + // inner exps + // inner union exps + ExPolygons zig_expas = offset_ex({surface->expolygon}, -offset_threshold); + ExPolygons cross_expas = diff_ex(surface->expolygon, zig_expas); + + 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(); + while (it != flow_params.end()) { + ExPolygons region_exp = union_safety_offset_ex(it->second); + + Polylines polys = intersection_pl(polylines, region_exp); + multi_width_polyline.emplace_back(polys, it->first); + it++; + } + }; + + 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); + ExPolygons exps = intersection_ex(region_exp, zig_expas); + zig_params.density = it->first; + exps = intersection_ex(offset_ex(exps, overlap_threshold), surface->expolygon); + for (ExPolygon &exp : exps) { + zig_surface.expolygon = exp; + + Polylines zig_polylines_out = this->fill_surface(&zig_surface, zig_params); + skeloton_lines.insert(skeloton_lines.end(), zig_polylines_out.begin(), zig_polylines_out.end()); + } + it++; + } + + // set skeleton flow + generate_for_different_flow(this->lock_param.skeleton_flow_params, skeloton_lines); + + // skin exps + bool cross_get = false; + FillParams cross_params = params; + cross_params.locked_zag = false; + auto skin_density = this->lock_param.skin_density_params.begin(); + 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; + for (ExPolygon &exp : exps) { + cross_surface.expolygon = exp; + Polylines cross_polylines_out = this->fill_surface(&cross_surface, cross_params); + skin_lines.insert(skin_lines.end(), cross_polylines_out.begin(), cross_polylines_out.end()); + } + skin_density++; + } + + generate_for_different_flow(this->lock_param.skin_flow_params, skin_lines); +} + +void FillLockedZag::fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) +{ + Polylines polylines; + ThickPolylines thick_polylines; + std::vector> multi_width_polyline; + try { + this->fill_surface_locked_zag(surface, params, multi_width_polyline); + } + catch (InfillFailedException&) {} + + if (!thick_polylines.empty() || !multi_width_polyline.empty()) { + // Save into layer. + ExtrusionEntityCollection* eec = nullptr; + out.push_back(eec = new ExtrusionEntityCollection()); + // Only concentric fills are not sorted. + eec->no_sort = this->no_sort(); + size_t idx = eec->entities.size(); + { + for (std::pair &poly_with_flow: multi_width_polyline) { + // calculate actual flow from spacing (which might have been adjusted by the infill + // pattern generator) + double flow_mm3_per_mm = poly_with_flow.second.mm3_per_mm(); + double flow_width = poly_with_flow.second.width(); + if (params.using_internal_flow) { + // if we used the internal flow we're not doing a solid infill + // so we can safely ignore the slight variation that might have + // been applied to f->spacing + } else { + Flow new_flow = poly_with_flow.second.with_spacing(this->spacing); + flow_mm3_per_mm = new_flow.mm3_per_mm(); + flow_width = new_flow.width(); + } + extrusion_entities_append_paths( + eec->entities, std::move(poly_with_flow.first), + params.extrusion_role, + flow_mm3_per_mm, float(flow_width), poly_with_flow.second.height()); + } + } + if (!params.can_reverse) { + for (size_t i = idx; i < eec->entities.size(); i++) + eec->entities[i]->set_reverse(); + } + } +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index c7d6da9..69aef8b 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -159,6 +159,21 @@ public: bool has_consistent_pattern() const override { return true; } }; +class FillLockedZag : public FillRectilinear +{ +public: + Fill *clone() const override { return new FillLockedZag(*this); } + ~FillLockedZag() override = default; + LockRegionParam lock_param; + + void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + + bool has_consistent_pattern() const override { return true; } + void set_lock_region_param(const LockRegionParam &lock_param) override { this->lock_param = lock_param;}; + void fill_surface_locked_zag(const Surface * surface, + const FillParams & params, + std::vector> &multi_width_polyline); +}; Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box); diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 71e346c..05e1f48 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -81,6 +81,13 @@ public: bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; } + bool operator!=(const Flow &rhs) const{ + return m_width != rhs.m_width || m_height != rhs.m_height || m_nozzle_diameter != rhs.m_nozzle_diameter || m_bridge != rhs.m_bridge; + } + + bool operator <(const Flow &rhs) const { + return this->mm3_per_mm() < rhs.mm3_per_mm(); + } Flow with_width (float width) const { assert(! m_bridge); return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge); diff --git a/src/libslic3r/Format/qds_3mf.cpp b/src/libslic3r/Format/qds_3mf.cpp index d007e65..7d8f550 100644 --- a/src/libslic3r/Format/qds_3mf.cpp +++ b/src/libslic3r/Format/qds_3mf.cpp @@ -601,7 +601,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) std::vector m_filament_densities = result->filament_densities; auto get_used_filament_from_volume = [m_filament_diameters, m_filament_densities](double volume, int extruder_id) { double koef = 0.001; - std::pair ret = {koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])), volume * m_filament_densities[extruder_id] * 0.001}; + double section_area = PI * sqr(0.5 * m_filament_diameters[extruder_id]); + std::pair ret = {section_area < EPSILON ? 0 : (koef * volume / section_area), volume * m_filament_densities[extruder_id] * 0.001}; return ret; }; @@ -1760,7 +1761,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) /*if (m_qidislicer_generator_version) { Semver app_version = *(Semver::parse(SLIC3R_VERSION)); Semver file_version = *m_qidislicer_generator_version; - //1.9.5 if (file_version.maj() > app_version.maj()) dont_load_config = true; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 412de2c..a5a205e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -581,6 +581,9 @@ static std::vector get_path_of_change_filament(const Print& print) throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); int new_extruder_id = get_extruder_index(*m_print_config, new_filament_id); + + bool is_nozzle_change = !tcr.nozzle_change_result.gcode.empty() && (gcodegen.config().nozzle_diameter.size() > 1); + std::string gcode; // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) @@ -664,7 +667,7 @@ static std::vector get_path_of_change_filament(const Print& print) auto_lift_type = LiftType::SpiralLift; // QDS: should be placed before toolchange parsing - std::string toolchange_retract_str = gcodegen.retract(true, false, auto_lift_type, true); + std::string toolchange_retract_str = gcodegen.retract(tcr.is_tool_change && !is_nozzle_change, false, auto_lift_type, true); check_add_eol(toolchange_retract_str); // Process the custom change_filament_gcode. If it is empty, provide a simple Tn command to change the filament. @@ -675,7 +678,7 @@ static std::vector get_path_of_change_filament(const Print& print) // add nozzle change gcode into change filament gcode std::string nozzle_change_gcode_trans; - if (!tcr.nozzle_change_result.gcode.empty() && (gcodegen.config().nozzle_diameter.size() > 1)) { + if (is_nozzle_change) { // move to start_pos before nozzle change std::string start_pos_str; start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(tcr.nozzle_change_result.start_pos) + plate_origin_2d), erMixed, @@ -688,7 +691,7 @@ static std::vector get_path_of_change_filament(const Print& print) gcodegen.m_wipe.reset_path(); for (const Vec2f& wipe_pt : tcr.nozzle_change_result.wipe_path) gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d)); - nozzle_change_gcode_trans += gcodegen.retract(true, false, auto_lift_type, true); + nozzle_change_gcode_trans += gcodegen.retract(tcr.is_tool_change, false, auto_lift_type, true); end_filament_gcode_str = nozzle_change_gcode_trans + end_filament_gcode_str; } @@ -764,6 +767,18 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); + auto flush_v_speed = m_print_config->filament_flush_volumetric_speed.values; + auto flush_temps = m_print_config->filament_flush_temp.values; + for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { + if (flush_v_speed[idx] == 0) + flush_v_speed[idx] = m_print_config->filament_max_volumetric_speed.get_at(idx); + } + for (size_t idx = 0; idx < flush_temps.size(); ++idx) { + if (flush_temps[idx] == 0) + flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(idx); + } + config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed)); + config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps)); config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter)); config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); @@ -864,6 +879,8 @@ static std::vector get_path_of_change_filament(const Print& print) gcodegen.placeholder_parser().set("current_extruder", new_filament_id); gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(new_filament_id)); gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_filament_id)); + gcodegen.placeholder_parser().set("retraction_distance_when_ec", gcodegen.m_config.retraction_distances_when_ec.get_at(new_filament_id)); + gcodegen.placeholder_parser().set("long_retraction_when_ec", gcodegen.m_config.long_retractions_when_ec.get_at(new_filament_id)); // Process the start filament gcode. std::string start_filament_gcode_str; @@ -907,7 +924,7 @@ static std::vector get_path_of_change_filament(const Print& print) gcodegen.m_wipe.reset_path(); for (const Vec2f& wipe_pt : tcr.wipe_path) gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d)); - gcode += gcodegen.retract(true, false, auto_lift_type, true); + gcode += gcodegen.retract(false, false, auto_lift_type, true); } // Let the planner know we are traveling between objects. @@ -1173,12 +1190,12 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // raft contact distance should not trigger any warning if (last_extrusion_layer && last_extrusion_layer->support_layer) { - double raft_gap = object.config().raft_contact_distance.value; + double raft_gap = top_cd == 0 ? 0 : object.config().raft_contact_distance.value; //if (!object.print()->config().independent_support_layer_height) { raft_gap = std::ceil(raft_gap / object.config().layer_height) * object.config().layer_height; } - extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); + extra_gap = std::max(extra_gap, top_cd == 0 ? 0 :object.config().raft_contact_distance.value); } double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height @@ -1869,6 +1886,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato PROFILE_FUNC(); m_print = &print; + m_timelapse_pos_picker.init(&print,m_writer.get_xy_offset().cast()); // modifies m_silent_time_estimator_enabled DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, m_writer.extruders()); @@ -1928,6 +1946,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); m_enable_cooling_markers = true; +//yzy1 + m_overhang_fan_can_start = true; this->apply_print_config(print.config()); //m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); @@ -1944,7 +1964,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_enable_extrusion_role_markers = false; #endif /* HAS_PRESSURE_EQUALIZER */ //w12 - const GCodeThumbnailsFormat m_gcode_thumbnail_format = print.full_print_config().opt_enum("thumbnails_formats"); + //const GCodeThumbnailsFormat m_gcode_thumbnail_format = print.full_print_config().opt_enum("thumbnails_formats"); file.write_format("; HEADER_BLOCK_START\n"); // Write information on the generator. @@ -2004,6 +2024,18 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.writeln(max_height_z_tip.str()); } + { + auto used_filaments = print.get_slice_used_filaments(false); + std::ostringstream out; + out << "; filament: "; + for (size_t idx = 0; idx < used_filaments.size(); ++idx) { + if (idx != 0) + out << ','; + out << used_filaments[idx] + 1; + } + file.writeln(out.str()); + } + file.write_format("; HEADER_BLOCK_END\n\n"); //QDS: write global config at the beginning of gcode file because printer need these config information @@ -2124,7 +2156,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. tool_ordering = print.tool_ordering(); - tool_ordering.cal_most_used_extruder(print.config()); tool_ordering.assign_custom_gcodes(print); if (tool_ordering.all_extruders().empty()) // No object to print was found, cancel the G-code export. @@ -2208,9 +2239,26 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato //set the key for compatibilty m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); + m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(initial_extruder_id)); + m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(initial_extruder_id)); m_placeholder_parser.set("retraction_distances_when_cut", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_cut)); m_placeholder_parser.set("long_retractions_when_cut",new ConfigOptionBoolsNullable(m_config.long_retractions_when_cut)); + m_placeholder_parser.set("retraction_distances_when_ec", new ConfigOptionFloatsNullable(m_config.retraction_distances_when_ec)); + m_placeholder_parser.set("long_retractions_when_ec",new ConfigOptionBoolsNullable(m_config.long_retractions_when_ec)); + + auto flush_v_speed = m_config.filament_flush_volumetric_speed.values; + auto flush_temps = m_config.filament_flush_temp.values; + for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { + if (flush_v_speed[idx] == 0) + flush_v_speed[idx] = m_config.filament_max_volumetric_speed.get_at(idx); + } + for (size_t idx = 0; idx < flush_temps.size(); ++idx) { + if (flush_temps[idx] == 0) + flush_temps[idx] = m_config.nozzle_temperature_range_high.get_at(idx); + } + m_placeholder_parser.set("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed)); + m_placeholder_parser.set("flush_temperatures", new ConfigOptionInts(flush_temps)); //Set variable for total layer count so it can be used in custom gcode. m_placeholder_parser.set("total_layer_count", m_layer_count); // Useful for sequential prints. @@ -2350,6 +2398,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_placeholder_parser.set("z_offset", new ConfigOptionFloat(0.0f)); m_placeholder_parser.set("plate_name", new ConfigOptionString(print.get_plate_name())); + 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"; + })); + //add during_print_exhaust_fan_speed std::vector during_print_exhaust_fan_speed_num; during_print_exhaust_fan_speed_num.reserve(m_config.during_print_exhaust_fan_speed.size()); @@ -2588,6 +2641,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. tool_ordering.cal_most_used_extruder(print.config()); + m_printed_objects.emplace_back(&object); this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file, prime_extruder); { @@ -2672,6 +2726,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato #endif print.throw_if_canceled(); } + + tool_ordering.cal_most_used_extruder(print.config()); + // 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. @@ -3807,8 +3864,6 @@ GCode::LayerResult GCode::process_layer( bool has_insert_timelapse_gcode = false; bool has_wipe_tower = (layer_tools.has_wipe_tower && m_wipe_tower); - int physical_extruder_id = print.config().physical_extruder_map.get_at(most_used_extruder); - 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) @@ -3820,7 +3875,7 @@ GCode::LayerResult GCode::process_layer( m_object_layer_over_raft = false; if (! print.config().layer_change_gcode.value.empty()) { DynamicConfig config; - config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(physical_extruder_id)); + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); gcode += this->placeholder_parser_process("layer_change_gcode", @@ -4217,14 +4272,28 @@ GCode::LayerResult GCode::process_layer( } } - auto insert_timelapse_gcode = [this, print_z, &print, &physical_extruder_id, &layer_object_label_ids]() -> std::string { + auto insert_timelapse_gcode = [this, print_z, &print, &most_used_extruder, &layer_object_label_ids,&printed_objects = std::as_const(m_printed_objects)]() -> std::string { + PosPickCtx ctx; + ctx.curr_pos = { (coord_t)(scale_(m_writer.get_position().x())),(coord_t)(scale_(m_writer.get_position().y())) }; + ctx.curr_layer = this->layer(); + ctx.curr_extruder_id = m_writer.filament()->extruder_id(); + ctx.picture_extruder_id = most_used_extruder; + if (m_config.print_sequence == PrintSequence::ByObject) + ctx.printed_objects = printed_objects; + + auto timelapse_pos=m_timelapse_pos_picker.pick_pos(ctx); + std::string timepals_gcode; if (!print.config().time_lapse_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); - config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(physical_extruder_id)); + config.set_key_value("most_used_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(most_used_extruder))); + config.set_key_value("curr_physical_extruder_id", new ConfigOptionInt(m_config.physical_extruder_map.get_at(ctx.curr_extruder_id))); + config.set_key_value("timelapse_pos_x", new ConfigOptionInt(timelapse_pos.x())); + config.set_key_value("timelapse_pos_y", new ConfigOptionInt(timelapse_pos.y())); + config.set_key_value("has_timelapse_safe_pos", new ConfigOptionBool(timelapse_pos != DefaultTimelapsePos)); timepals_gcode = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.filament()->id(), &config) + "\n"; } m_writer.set_current_position_clear(false); @@ -5080,8 +5149,20 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou bool flag = path.get_customize_flag() == CustomizeFlag::cfFloatingVerticalShell; std::string gcode = this->_extrude(path, description, speed,flag); if (m_wipe.enable && FILAMENT_CONFIG(wipe)) { - m_wipe.path = std::move(path.polyline); - m_wipe.path.reverse(); + m_wipe.path = path.polyline; + if (is_tree(this->config().support_type) && (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface || path.role() == erSupportTransition)) { + if ((m_wipe.path.first_point() - m_wipe.path.last_point()).cast().norm() > scale_(0.2)) { + double min_dist = scale_(0.2); + int i = 0; + for (; i < path.polyline.points.size(); i++) { + double dist = (path.polyline.points[i] - path.last_point()).cast().norm(); + if (dist < min_dist) min_dist = dist; + if (min_dist < scale_(0.2) && dist > min_dist) break; + } + m_wipe.path = Polyline(Points(path.polyline.points.begin() + i - 1, path.polyline.points.end())); + } + } else + m_wipe.path.reverse(); } //QDS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. if (!this->on_first_layer()) { @@ -5575,9 +5656,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += m_writer.set_jerk_xy(jerk); } // calculate extrusion length per distance unit - auto _mm3_per_mm = path.mm3_per_mm * double(m_curr_print->calib_mode() == CalibMode::Calib_Flow_Rate ? this->config().print_flow_ratio.value : 1); - - // calculate extrusion length per distance unit + auto _mm3_per_mm = path.mm3_per_mm * double(this->config().print_flow_ratio.value); if( path.role() == erTopSolidInfill ) _mm3_per_mm *= m_config.top_solid_infill_flow_ratio.value; else if (this->on_first_layer()) @@ -5756,7 +5835,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || is_bridge(path.role()))) { - gcode += ";_OVERHANG_FAN_START\n"; + //yzy1 + if (m_overhang_fan_can_start){ + gcode += ";_OVERHANG_FAN_START\n"; + m_overhang_fan_can_start = false; + } } } @@ -5867,9 +5950,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, //QDS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter int overhang_threshold = FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? Overhang_threshold_none : FILAMENT_CONFIG(overhang_fan_threshold) - 1; - if ((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || - is_bridge(path.role()))) + //yzy1 + if (!m_overhang_fan_can_start && !((FILAMENT_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter) || (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role())))){ + m_overhang_fan_can_start = true; gcode += ";_OVERHANG_FAN_END\n"; + } } } @@ -5930,11 +6016,12 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // if a retraction would be needed, try to use reduce_crossing_wall to plan a // multi-hop travel path inside the configuration space - if (needs_retraction - && m_config.reduce_crossing_wall - && ! m_avoid_crossing_perimeters.disabled_once() - //QDS: don't generate detour travel paths when current position is unclear - && m_writer.is_current_position_clear()) { + // if ( + if (m_config.reduce_crossing_wall + && !m_avoid_crossing_perimeters.disabled_once() + && m_writer.is_current_position_clear()) + //QDS: don't generate detour travel paths when current position is unclea + { travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role, lift_type); @@ -5964,9 +6051,17 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string if (used_external_mp_once) m_avoid_crossing_perimeters.reset_once_modifiers(); } - } else + } else { // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); + // if (m_config.reduce_crossing_wall) { + // // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. + // if (used_external_mp_once) m_avoid_crossing_perimeters.use_external_mp_once(); + // travel = m_avoid_crossing_perimeters.travel_to(*this, point); + // // If state of use_external_mp_once was changed reset it to right value. + // if (used_external_mp_once) m_avoid_crossing_perimeters.reset_once_modifiers(); + // } + } // if needed, write the gcode_label_objects_end then gcode_label_objects_start m_writer.add_object_change_labels(gcode); @@ -6197,6 +6292,8 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo m_placeholder_parser.set("current_extruder", new_filament_id); m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); + m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id)); + m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(new_filament_id)); std::string gcode; // Append the filament start G-code. @@ -6349,6 +6446,18 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo dyn_config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(false)); dyn_config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); + auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values; + auto flush_temps =m_print->config().filament_flush_temp.values; + for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) { + if (flush_v_speed[idx] == 0) + flush_v_speed[idx] = m_print->config().filament_max_volumetric_speed.get_at(idx); + } + for (size_t idx = 0; idx < flush_temps.size(); ++idx) { + if (flush_temps[idx] == 0) + flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(idx); + } + dyn_config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed)); + dyn_config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps)); dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length)); int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time)); @@ -6423,6 +6532,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo m_placeholder_parser.set("current_extruder", new_filament_id); m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id)); m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id)); + m_placeholder_parser.set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id)); + m_placeholder_parser.set("long_retraction_when_ec", m_config.long_retractions_when_ec.get_at(new_filament_id)); + // Append the filament start G-code. const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(new_filament_id); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index c400a25..a84f9bf 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -19,6 +19,7 @@ #include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" #include "libslic3r/ObjectID.hpp" +#include "GCode/TimelapsePosPicker.hpp" #include #include @@ -496,11 +497,14 @@ private: Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; + TimelapsePosPicker m_timelapse_pos_picker; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _OVERHANG_FAN_START, _OVERHANG_FAN_END // Those comments are received and consumed (removed from the G-code) by the CoolingBuffer.pm Perl module. bool m_enable_cooling_markers; +//yzy1 + bool m_overhang_fan_can_start; // Markers for the Pressure Equalizer to recognize the extrusion type. // The Pressure Equalizer removes the markers from the final G-code. bool m_enable_extrusion_role_markers; @@ -561,6 +565,8 @@ private: Print *m_print{nullptr}; + std::vector m_printed_objects; + // Processor GCodeProcessor m_processor; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 51e5e30..99221a5 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -416,6 +416,17 @@ static Direction get_shortest_direction(const AvoidCrossingPerimeters::Boundary return Direction::Backward; } +Polyline ConvertBBoxToPolyline(const BoundingBoxf &bbox) +{ + Point left_bottom = bbox.min.cast(); + Point left_up(bbox.min.cast()[0], bbox.max.cast()[1]); + Point right_up = bbox.max.cast(); + Point right_bottom(bbox.max.cast()[0], bbox.min.cast()[1]); + + return Polyline({left_bottom, right_bottom, right_up, left_up, left_bottom}); +} + + // Straighten the travel path as long as it does not collide with the contours stored in edge_grid. static std::vector simplify_travel(const AvoidCrossingPerimeters::Boundary &boundary, const std::vector &travel) { @@ -423,7 +434,6 @@ static std::vector simplify_travel(const AvoidCrossingPerimeters::B std::vector simplified_path; simplified_path.reserve(travel.size()); simplified_path.emplace_back(travel.front()); - // Try to skip some points in the path. //FIXME maybe use a binary search to trim the line? //FIXME how about searching tangent point at long segments? @@ -507,8 +517,10 @@ 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 &boundary, +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, @@ -630,8 +642,10 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - if (! intersections.empty()) - result = simplify_travel(boundary, result); + 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 { @@ -645,6 +659,172 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo return intersections.size(); } +static size_t avoid_perimeters_inner( + const AvoidCrossingPerimeters::Boundary &boundary, const Point &start_point, const Point &end_point, const Layer &layer, std::vector &result_out) +{ + 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; + } + } + } + + 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}); + + auto result_polyline = to_polyline(result); + (void) result_polyline; + +#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()) result = simplify_travel(boundary, result); + + auto simplified_result_polyline = to_polyline(result); + (void) simplified_result_polyline; + +#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(); +} + + +// 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, @@ -1028,9 +1208,9 @@ static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset) //#define INCLUDE_SUPPORTS_IN_BOUNDARY // called by AvoidCrossingPerimeters::travel_to() -static ExPolygons get_boundary(const Layer &layer) +static ExPolygons get_boundary(const Layer &layer, float perimeter_spacing) { - const float perimeter_spacing = get_perimeter_spacing(layer); + // const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; auto const *support_layer = dynamic_cast(&layer); ExPolygons boundary = union_ex(inner_offset(layer.lslices, 1.5 * perimeter_spacing)); @@ -1064,6 +1244,43 @@ static ExPolygons get_boundary(const Layer &layer) 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) { @@ -1129,7 +1346,7 @@ static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]); } -static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons) +void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons) { boundary->clear(); boundary->boundaries = std::move(boundary_polygons); @@ -1143,6 +1360,41 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons init_boundary_distances(boundary); } +static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons, const std::vector& merge_poins) +{ + boundary->clear(); + boundary->boundaries = std::move(boundary_polygons); + + BoundingBox bbox(get_extents(boundary->boundaries)); + 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); +} + +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) { @@ -1162,29 +1414,53 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & 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)))) { - // Initialize m_internal only when it is necessary. - if (m_internal.boundaries.empty()) - init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer()))); + 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}); + } - // Trim the travel line by the bounding box. - if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { - travel_intersection_count = avoid_perimeters(m_internal, startf.cast(), endf.cast(), *gcodegen.layer(), result_pl); + // 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}); + } 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}); + } + + if (!m_internal.boundaries.empty()) { + travel_intersection_count = avoid_perimeters(m_lslice_internal, 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())); + if (m_external.boundaries.empty()) { + init_boundary(&m_external, get_boundary_external(*gcodegen.layer()), {start, end}); + } else if (!(m_external.bbox.contains(startf) && m_external.bbox.contains(endf))) { + // check if start and end are in bbox + m_external.clear(); + init_boundary(&m_external, get_boundary_external(*gcodegen.layer()), {start, end}); + } + // Trim the travel line by the bounding box. - if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { - travel_intersection_count = avoid_perimeters(m_external, startf.cast(), endf.cast(), *gcodegen.layer(), result_pl); + 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}; @@ -1221,6 +1497,7 @@ 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(); BoundingBox bbox_slice(get_extents(layer.lslices)); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index b67642c..e4335ac 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -62,6 +62,8 @@ private: 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/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index cf528e4..b4280ee 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2778,7 +2778,7 @@ bool GCodeProcessor::get_last_z_from_gcode(const std::string& gcode_str, double& line_str.erase(line_str.find_last_not_of(" ") + 1); //command which may have z movement - if (line_str.size() > 5 && (line_str.find("G0 ") == 0 + if (line_str.size() > 4 && (line_str.find("G0 ") == 0 || line_str.find("G1 ") == 0 || line_str.find("G2 ") == 0 || line_str.find("G3 ") == 0)) @@ -5450,6 +5450,16 @@ void GCodeProcessor::process_T(const std::string_view command) } +void GCodeProcessor::init_filament_maps_and_nozzle_type_when_import_only_gcode() +{ + if (m_filament_maps.empty()) { + m_filament_maps.assign((int) EnforcerBlockerType::ExtruderMax, 1); + } + if (m_result.nozzle_type.empty()) { + m_result.nozzle_type.assign((int) EnforcerBlockerType::ExtruderMax, NozzleType::ntUndefine); + } +} + void GCodeProcessor::process_filament_change(int id) { assert(id < m_result.filaments_count); @@ -5872,7 +5882,7 @@ void GCodeProcessor::update_slice_warnings() warning.params.clear(); warning.level=1; - std::vectornozzle_hrc_lists(m_result.nozzle_type.size(), 0); + std::vector nozzle_hrc_lists(m_result.nozzle_type.size(), 0); // store the nozzle hrc of each extruder for (size_t idx = 0; idx < m_result.nozzle_type.size(); ++idx) nozzle_hrc_lists[idx] = Print::get_hrc_by_nozzle_type(m_result.nozzle_type[idx]); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 12c4659..76fdcff 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -1104,7 +1104,7 @@ namespace Slic3r { public: GCodeProcessor(); - + void init_filament_maps_and_nozzle_type_when_import_only_gcode(); // check whether the gcode path meets the filament_map grouping requirements bool check_multi_extruder_gcode_valid(const std::vector &unprintable_areas, const std::vector &printable_heights, diff --git a/src/libslic3r/GCode/TimelapsePosPicker.cpp b/src/libslic3r/GCode/TimelapsePosPicker.cpp new file mode 100644 index 0000000..cee5fef --- /dev/null +++ b/src/libslic3r/GCode/TimelapsePosPicker.cpp @@ -0,0 +1,583 @@ +#include "TimelapsePosPicker.hpp" +#include "Layer.hpp" + +constexpr int FILTER_THRESHOLD = 5; +constexpr int MAX_CANDIDATE_SIZE = 5; + +namespace Slic3r { + void TimelapsePosPicker::init(const Print* print_, const Point& plate_offset) + { + reset(); + m_plate_offset = plate_offset; + print = print_; + + m_nozzle_height_to_rod = print_->config().extruder_clearance_height_to_rod; + m_nozzle_clearance_radius = print_->config().extruder_clearance_max_radius; + if (print_->config().nozzle_diameter.size() > 1) { + m_extruder_height_gap = std::abs(print_->config().extruder_printable_height.values[0] - print_->config().extruder_printable_height.values[1]); + m_liftable_extruder_id = print_->config().extruder_printable_height.values[0] < print_->config().extruder_printable_height.values[1] ? 0 : 1; + } + m_print_seq = print_->config().print_sequence.value; + m_based_on_all_layer = print_->config().timelapse_type == TimelapseType::tlSmooth; + + construct_printable_area_by_printer(); + } + + void TimelapsePosPicker::reset() + { + print = nullptr; + m_bed_polygon.clear(); + m_extruder_printable_area.clear(); + m_all_layer_pos = std::nullopt; + bbox_cache.clear(); + + m_print_seq = PrintSequence::ByObject; + m_nozzle_height_to_rod = 0; + m_nozzle_clearance_radius = 0; + m_liftable_extruder_id = std::nullopt; + m_extruder_height_gap = std::nullopt; + m_based_on_all_layer = false; + } + + /** + * @brief Retrieves a list of print objects based on the provided optional set of printed objects. + * + * If the optional set of printed objects is provided, it converts the set into a vector. + * Otherwise, it retrieves all objects from the print instance. + */ + std::vector TimelapsePosPicker::get_object_list(const std::optional>& printed_objects) + { + std::vector object_list; + if (printed_objects.has_value()) { + object_list = std::vector(printed_objects->begin(), printed_objects->end()); + } + else { + object_list = std::vector(print->objects().begin(), print->objects().end()); + } + return object_list; + } + + /** + * @brief Constructs the printable area based on printer configuration. + * + * This function initializes the bed polygon, excludes specific areas, accounts for wipe towers, + * and calculates the printable area for each extruder. + */ + void TimelapsePosPicker::construct_printable_area_by_printer() + { + auto config = print->config(); + size_t extruder_count = config.nozzle_diameter.size(); + m_extruder_printable_area.clear(); + m_extruder_printable_area.resize(extruder_count); + + for (size_t idx = 0; idx < config.printable_area.values.size(); ++idx) + m_bed_polygon.points.emplace_back(coord_t(scale_(config.printable_area.values[idx].x())), coord_t(scale_(config.printable_area.values[idx].y()))); + + auto bed_bbox = get_extents(m_bed_polygon); + m_plate_height = unscale_(bed_bbox.max.y()); + m_plate_width = unscale_(bed_bbox.max.x()); + + Polygon bed_exclude_area; + for (size_t idx = 0; idx < config.bed_exclude_area.values.size(); ++idx) + bed_exclude_area.points.emplace_back(coord_t(scale_(config.bed_exclude_area.values[idx].x())), coord_t(scale_(config.bed_exclude_area.values[idx].y()))); + + Point base_wp_pt = print->get_fake_wipe_tower().pos.cast(); + base_wp_pt = Point{ scale_(base_wp_pt.x()),scale_(base_wp_pt.y()) }; + + auto transform_wt_pt = [base_wp_pt](const Point& pt) -> Point { + Point out =pt; + out += base_wp_pt; + return out; + }; + auto wt_box = print->wipe_tower_data().bbx; + Polygon wipe_tower_area{ + {transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.min.y())})}, + {transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.min.y())})}, + {transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.max.y())})}, + {transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.max.y())})} + }; + wipe_tower_area = expand_object_projection(wipe_tower_area, m_print_seq == PrintSequence::ByObject); + + for (size_t idx = 0; idx < extruder_count; ++idx) { + ExPolygons printable_area = diff_ex(diff(m_bed_polygon, bed_exclude_area), { wipe_tower_area }); + if (idx < config.extruder_printable_area.size()) { + Polygon extruder_printable_area; + for (size_t j = 0; j < config.extruder_printable_area.values[idx].size(); ++j) + extruder_printable_area.points.emplace_back(coord_t(scale_(config.extruder_printable_area.values[idx][j].x())), coord_t(scale_(config.extruder_printable_area.values[idx][j].y()))); + printable_area = intersection_ex(printable_area, Polygons{ extruder_printable_area }); + } + m_extruder_printable_area[idx] = printable_area; + } + } + + /** + * @brief Collects object slice data within a specified height range for a given layer. + * + * @param layer The layer for which slices are being collected. + * @param height_range The height range to consider for collecting slices. + * @param object_list List of print objects to process. + * @param by_object Decides the expand length of polygon + * @return ExPolygons representing the collected slice data. + */ + ExPolygons TimelapsePosPicker::collect_object_slices_data(const Layer* layer, float height_range, const std::vector& object_list, bool by_object) + { + auto range_intersect = [](int left1, int right1, int left2, int right2) { + if (left1 <= left2 && left2 <= right1) + return true; + if (left2 <= left1 && left1 <= right2) + return true; + return false; + }; + ExPolygons ret; + float z_target = layer->print_z; + float z_low = height_range < 0 ? layer->print_z + height_range : layer->print_z; + float z_high = height_range < 0 ? layer->print_z : layer->print_z + height_range; + if (z_low <= 0) + return to_expolygons({ m_bed_polygon }); + + for (auto& obj : object_list) { + for (auto& instance : obj->instances()) { + auto instance_bbox = get_real_instance_bbox(instance); + if(range_intersect(instance_bbox.min.z(), instance_bbox.max.z(), z_low, z_high)){ + ExPolygon expoly; + expoly.contour = { + {scale_(instance_bbox.min.x()), scale_(instance_bbox.min.y())}, + {scale_(instance_bbox.max.x()), scale_(instance_bbox.min.y())}, + {scale_(instance_bbox.max.x()), scale_(instance_bbox.max.y())}, + {scale_(instance_bbox.min.x()), scale_(instance_bbox.max.y())} + }; + expoly.contour = expand_object_projection(expoly.contour, by_object); + ret.emplace_back(std::move(expoly)); + } + } + } + ret = union_ex(ret); + return ret; + } + + + Polygons TimelapsePosPicker::collect_limit_areas_for_camera(const std::vector& object_list) + { + Polygons ret; + for (auto& obj : object_list) + ret.emplace_back(get_limit_area_for_camera(obj)); + ret = union_(ret); + return ret; + } + + // scaled data + Polygons TimelapsePosPicker::collect_limit_areas_for_rod(const std::vector& object_list, const PosPickCtx& ctx) + { + double rod_limit_height = m_nozzle_height_to_rod + ctx.curr_layer->print_z; + std::vector rod_collision_candidates; + for(auto& obj : object_list){ + if(ctx.printed_objects && obj == ctx.printed_objects->back()) + continue; + auto bbox = get_real_instance_bbox(obj->instances().front()); + if(bbox.max.z() >= rod_limit_height) + rod_collision_candidates.push_back(obj); + } + + if (rod_collision_candidates.empty()) + return {}; + + + std::vector collision_obj_bboxs; + for (auto obj : rod_collision_candidates) { + collision_obj_bboxs.emplace_back( + expand_object_bbox( + get_real_instance_bbox(obj->instances().front()), + m_print_seq == PrintSequence::ByObject + ) + ); + } + + std::sort(collision_obj_bboxs.begin(), collision_obj_bboxs.end(), [&](const auto& lbbox, const auto& rbbox) { + if (lbbox.min.y() == rbbox.min.y()) + return lbbox.max.y() < rbbox.max.y(); + return lbbox.min.y() < rbbox.min.y(); + }); + + std::vector> object_y_ranges = {{0,0}}; + for(auto& bbox : collision_obj_bboxs){ + if( object_y_ranges.back().second >= bbox.min.y()) + object_y_ranges.back().second = bbox.max.y(); + else + object_y_ranges.emplace_back(bbox.min.y(), bbox.max.y()); + } + + if (object_y_ranges.back().second < m_plate_height) + object_y_ranges.emplace_back(m_plate_height, m_plate_height); + + int lower_y_pos = -1, upper_y_pos =-1; + Point unscaled_curr_pos = {unscale_(ctx.curr_pos.x())-m_plate_offset.x(), unscale_(ctx.curr_pos.y()) - m_plate_offset.y()}; + + for (size_t idx = 1; idx < object_y_ranges.size(); ++idx) { + if (unscaled_curr_pos.y() >= object_y_ranges[idx - 1].second && unscaled_curr_pos.y() <= object_y_ranges[idx].first) { + lower_y_pos = object_y_ranges[idx - 1].second; + upper_y_pos = object_y_ranges[idx].first; + break; + } + } + + if(lower_y_pos == -1 && upper_y_pos == -1) + return { m_bed_polygon }; + + Polygons ret; + + ret.emplace_back( + Polygon{ + Point{scale_(0), scale_(0)}, + Point{scale_(m_plate_width), scale_(0)}, + Point{scale_(m_plate_width), scale_(lower_y_pos)}, + Point{scale_(0), scale_(lower_y_pos)} + } + ); + + ret.emplace_back( + Polygon{ + Point{scale_(0), scale_(upper_y_pos)}, + Point{scale_(m_plate_width), scale_(upper_y_pos)}, + Point{scale_(m_plate_width), scale_(m_plate_height)}, + Point{scale_(0), scale_(m_plate_height)} + } + ); + return ret; + } + + + + // expand the object expolygon by safe distance, scaled data + Polygon TimelapsePosPicker::expand_object_projection(const Polygon& poly, bool by_object) + { + float radius = 0; + if (by_object) + radius = scale_(print->config().extruder_clearance_max_radius.value); + else + radius = scale_(print->config().extruder_clearance_max_radius.value / 2); + + // the input poly is bounding box, so we get the first offseted polygon is ok + auto ret = offset(poly, radius); + if (ret.empty()) + return {}; + return ret[0]; + } + + // unscaled data + BoundingBoxf3 TimelapsePosPicker::expand_object_bbox(const BoundingBoxf3& bbox, bool by_object) + { + float radius = 0; + if (by_object) + radius = print->config().extruder_clearance_max_radius.value; + else + radius = print->config().extruder_clearance_max_radius.value / 2; + + BoundingBoxf3 ret = bbox; + ret.min.x() -= radius; + ret.min.y() -= radius; + ret.max.x() += radius; + ret.max.y() += radius; + + return ret; + } + + + double TimelapsePosPicker::get_raft_height(const PrintObject* obj) + { + if (!obj || !obj->has_raft()) + return 0; + auto slice_params = obj->slicing_parameters(); + int base_raft_layers = slice_params.base_raft_layers; + double base_raft_height = slice_params.base_raft_layer_height; + int interface_raft_layers = slice_params.interface_raft_layers; + double interface_raft_height = slice_params.interface_raft_layer_height; + double contact_raft_layer_height = slice_params.contact_raft_layer_height; + + double ret = print->config().initial_layer_print_height; + if (base_raft_layers - 1 > 0) + ret += (base_raft_layers - 1) * base_raft_height; + if (interface_raft_layers - 1 > 0) + ret += (interface_raft_layers - 1) * interface_raft_height; + if (obj->config().raft_layers > 1) + ret += contact_raft_layer_height; + + return ret + slice_params.gap_raft_object; + } + + // get the real instance bounding box, remove the plate offset and add raft height , unscaled data + BoundingBoxf3 TimelapsePosPicker::get_real_instance_bbox(const PrintInstance& instance) + { + auto iter = bbox_cache.find(&instance); + if (iter != bbox_cache.end()) + return iter->second; + + auto bbox = instance.get_bounding_box(); + double raft_height =get_raft_height(instance.print_object); + bbox.max.z() += raft_height; + // remove plate offset + bbox.min.x() -= m_plate_offset.x(); + bbox.max.x() -= m_plate_offset.x(); + bbox.min.y() -= m_plate_offset.y(); + bbox.max.y() -= m_plate_offset.y(); + + bbox_cache[&instance] = bbox; + + return bbox; + } + + Polygon TimelapsePosPicker::get_limit_area_for_camera(const PrintObject* obj) + { + if (!obj) + return {}; + auto bbox = get_real_instance_bbox(obj->instances().front()); + float radius = m_nozzle_clearance_radius / 2; + + auto offset_bbox = bbox.inflated(sqrt(2) * radius); + // Constrain the coordinates to the first quadrant. + Polygon ret = { + DefaultCameraPos, + Point{std::max(scale_(offset_bbox.max.x()),0.),std::max(scale_(offset_bbox.min.y()),0.)}, + Point{std::max(scale_(offset_bbox.max.x()),0.),std::max(scale_(offset_bbox.max.y()),0.)}, + Point{std::max(scale_(offset_bbox.min.x()),0.),std::max(scale_(offset_bbox.max.y()),0.)} + }; + return ret; + } + + /** + * @brief Selects the nearest position within the given safe areas relative to the current position. + * + * This function determines the closest point in the safe areas to the provided current position. + * If the current position is already inside a safe area, it returns the current position. + * If no safe areas are defined, return default timelapse position. + * + * @param curr_pos The reference point representing the current position. + * @param safe_areas A collection of extended polygons defining the safe areas. + * @return Point The nearest point within the safe areas or the default timelapse position if no safe areas exist. + */ + Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas, const ExPolygons& path_collision_area, bool detect_path_collision) + { + struct CandidatePoint + { + double dist; + Point point; + bool operator<(const CandidatePoint& other) const { + return dist < other.dist; + } + }; + + if (std::any_of(safe_areas.begin(), safe_areas.end(), [&curr_pos](const ExPolygon& p) { return p.contains(curr_pos); })) + return curr_pos; + + if (safe_areas.empty()) + return DefaultTimelapsePos; + + std::priority_queue max_heap; + + double min_distance = std::numeric_limits::max(); + Point nearest_point = DefaultTimelapsePos; + + for (const auto& expoly : safe_areas) { + Polygons polys = to_polygons(expoly); + for (auto& poly : polys) { + for (size_t idx = 0; idx < poly.points.size(); ++idx) { + Line line(poly.points[idx], poly.points[next_idx_modulo(idx, poly.points)]); + Point candidate; + double dist = line.distance_to_squared(curr_pos, &candidate); + max_heap.push({ dist,candidate }); + if (max_heap.size() > MAX_CANDIDATE_SIZE) + max_heap.pop(); + } + } + } + + std::vector top_candidates; + while (!max_heap.empty()) { + top_candidates.push_back(max_heap.top().point); + max_heap.pop(); + } + std::reverse(top_candidates.begin(), top_candidates.end()); + + for (auto& p : top_candidates) { + if (!detect_path_collision) + return p; + + Polyline path(curr_pos, p); + + if (intersection_pl(path, path_collision_area).empty()) + return p; + } + + return DefaultTimelapsePos; + } + + Point TimelapsePosPicker::pick_pos(const PosPickCtx& ctx) + { + Point res; + if (m_based_on_all_layer) + res = pick_pos_for_all_layer(ctx); + else + res = pick_pos_for_curr_layer(ctx); + + return { unscale_(res.x()), unscale_(res.y()) }; + } + + // get center point of curr object, scaled data + Point TimelapsePosPicker::get_object_center(const PrintObject* obj) + { + if (!obj) + return {}; + // in qidi studio, each object only has one instance + const auto& instance = obj->instances().front(); + auto instance_bbox = get_real_instance_bbox(instance); + Point min_p{ instance_bbox.min.x(),instance_bbox.min.y() }; + Point max_p{ instance_bbox.max.x(),instance_bbox.max.y() }; + + return { scale_((min_p.x() + max_p.x()) / 2),scale_((min_p.y() + max_p.y()) / 2) }; + } + + // scaled data + Point TimelapsePosPicker::pick_nearest_object_center(const Point& curr_pos, const std::vector& object_list) + { + if (object_list.empty()) + return {}; + const PrintObject* ptr = object_list.front(); + double distance = std::numeric_limits::max(); + for (auto& obj : object_list) { + Point obj_center = get_object_center(obj); + double dist = (obj_center - curr_pos).cast().norm(); + if (distance > dist) { + distance = dist; + ptr = obj; + } + } + return get_object_center(ptr); + } + + // scaled data + Point TimelapsePosPicker::pick_pos_for_curr_layer(const PosPickCtx& ctx) + { + float height_gap = 0; + if (ctx.curr_extruder_id != ctx.picture_extruder_id) { + if (m_liftable_extruder_id.has_value() && ctx.picture_extruder_id != m_liftable_extruder_id && m_extruder_height_gap.has_value()) + height_gap = -*m_extruder_height_gap; + } + + bool by_object = m_print_seq == PrintSequence::ByObject; + std::vector object_list = get_object_list(ctx.printed_objects); + + ExPolygons layer_slices = collect_object_slices_data(ctx.curr_layer, height_gap, object_list, by_object); + Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list); + Polygons rod_limit_areas; + if (by_object) { + rod_limit_areas = collect_limit_areas_for_rod(object_list, ctx); + } + ExPolygons unplacable_area = union_ex(union_ex(layer_slices, camera_limit_areas), rod_limit_areas); + ExPolygons extruder_printable_area = m_extruder_printable_area[ctx.picture_extruder_id]; + + ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area); + safe_area = opening_ex(safe_area, scale_(FILTER_THRESHOLD)); + + Point center_p; + if (by_object && ctx.printed_objects && !ctx.printed_objects->empty()) + center_p = get_object_center(ctx.printed_objects->back()); + else + center_p = get_objects_center(object_list); + + ExPolygons path_collision_area; + if (by_object) { + auto object_without_curr = ctx.printed_objects; + if (object_without_curr && !object_without_curr->empty()) + object_without_curr->pop_back(); + + ExPolygons layer_slices_without_curr = collect_object_slices_data(ctx.curr_layer, height_gap, get_object_list(object_without_curr), by_object); + path_collision_area = union_ex(layer_slices_without_curr, rod_limit_areas); + } + + return pick_pos_internal(center_p, safe_area,path_collision_area, by_object); + } + + /** + * @brief Calculates the center of multiple objects. + * + * This function computes the average center of all instances of the provided objects. + * + * @param object_list A vector of pointers to PrintObject instances. + * @return Point The average center of all objects.Scaled data + */ + Point TimelapsePosPicker::get_objects_center(const std::vector& object_list) + { + if (object_list.empty()) + return Point(0,0); + double sum_x = 0.0; + double sum_y = 0.0; + size_t total_instances = 0; + for (auto& obj : object_list) { + for (auto& instance : obj->instances()) { + const auto& bbox = get_real_instance_bbox(instance); + Point min_p{ bbox.min.x(),bbox.min.y() }; + Point max_p{ bbox.max.x(),bbox.max.y() }; + double center_x = (min_p.x() + max_p.x()) / 2.f; + double center_y = (min_p.y() + max_p.y()) / 2.f; + sum_x += center_x; + sum_y += center_y; + total_instances += 1; + } + } + return Point{ coord_t(scale_(sum_x / total_instances)),coord_t(scale_(sum_y / total_instances)) }; + } + + Point TimelapsePosPicker::pick_pos_for_all_layer(const PosPickCtx& ctx) + { + bool by_object = m_print_seq == PrintSequence::ByObject; + if (by_object) + return DefaultTimelapsePos; + + float height_gap = 0; + if (ctx.curr_extruder_id != ctx.picture_extruder_id) { + if (m_liftable_extruder_id.has_value() && ctx.picture_extruder_id != m_liftable_extruder_id && m_extruder_height_gap.has_value()) + height_gap = *m_extruder_height_gap; + } + if (ctx.curr_layer->print_z < height_gap) + return DefaultTimelapsePos; + if (m_all_layer_pos) + return *m_all_layer_pos; + + Polygons object_projections; + + auto object_list = get_object_list(std::nullopt); + + for (auto& obj : object_list) { + for (auto& instance : obj->instances()) { + const auto& bbox = get_real_instance_bbox(instance); + Point min_p{ scale_(bbox.min.x()),scale_(bbox.min.y()) }; + Point max_p{ scale_(bbox.max.x()),scale_(bbox.max.y()) }; + Polygon obj_proj{ { min_p.x(),min_p.y() }, + { max_p.x(),min_p.y() }, + { max_p.x(),max_p.y() }, + { min_p.x(),max_p.y() } + }; + object_projections.emplace_back(expand_object_projection(obj_proj,by_object)); + } + }; + + + object_projections = union_(object_projections); + Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list); + Polygons unplacable_area = union_(object_projections, camera_limit_areas); + + ExPolygons extruder_printable_area; + if (m_extruder_printable_area.size() > 1) + extruder_printable_area = intersection_ex(m_extruder_printable_area[0], m_extruder_printable_area[1]); + else if (m_extruder_printable_area.size() == 1) + extruder_printable_area = m_extruder_printable_area.front(); + + ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area); + safe_area = opening_ex(safe_area, scale_(FILTER_THRESHOLD)); + + Point starting_pos = get_objects_center(object_list); + + m_all_layer_pos = pick_pos_internal(starting_pos, safe_area, {}, by_object); + return *m_all_layer_pos; + } + +} \ No newline at end of file diff --git a/src/libslic3r/GCode/TimelapsePosPicker.hpp b/src/libslic3r/GCode/TimelapsePosPicker.hpp new file mode 100644 index 0000000..cf7f9ea --- /dev/null +++ b/src/libslic3r/GCode/TimelapsePosPicker.hpp @@ -0,0 +1,81 @@ +#ifndef TIMELAPSE_POS_PICKER_HPP +#define TIMELAPSE_POS_PICKER_HPP + +#include +#include "libslic3r/Point.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" + +namespace Slic3r { + + const Point DefaultTimelapsePos = Point(0, 0); + const Point DefaultCameraPos = Point(0, 0); + + class Layer; + class Print; + + struct PosPickCtx + { + Point curr_pos; + const Layer* curr_layer; + int picture_extruder_id; // the extruder id to take picture + int curr_extruder_id; + std::optional> printed_objects; // printed objects, only have value in by object mode + }; + + // data are stored without plate offset + class TimelapsePosPicker + { + public: + TimelapsePosPicker() = default; + ~TimelapsePosPicker() = default; + + Point pick_pos(const PosPickCtx& ctx); + void init(const Print* print, const Point& plate_offset); + void reset(); + private: + void construct_printable_area_by_printer(); + + Point pick_pos_for_curr_layer(const PosPickCtx& ctx); + Point pick_pos_for_all_layer(const PosPickCtx& ctx); + + ExPolygons collect_object_slices_data(const Layer* curr_layer, float height_range, const std::vector& object_list,bool by_object); + Polygons collect_limit_areas_for_camera(const std::vector& object_list); + + Polygons collect_limit_areas_for_rod(const std::vector& object_list, const PosPickCtx& ctx); + + Polygon expand_object_projection(const Polygon& poly, bool by_object); + BoundingBoxf3 expand_object_bbox(const BoundingBoxf3& bbox, bool by_object); + + Point pick_nearest_object_center(const Point& curr_pos, const std::vector& object_list); + Point get_objects_center(const std::vector& object_list); + + Polygon get_limit_area_for_camera(const PrintObject* obj); + std::vector get_object_list(const std::optional>& printed_objects); + + double get_raft_height(const PrintObject* obj); + BoundingBoxf3 get_real_instance_bbox(const PrintInstance& instance); + Point get_object_center(const PrintObject* obj); + private: + const Print* print{ nullptr }; + std::vector m_extruder_printable_area; //scaled data + Polygon m_bed_polygon; //scaled_data + Point m_plate_offset; // unscaled data + int m_plate_height; // unscaled data + int m_plate_width; // unscaled data + + PrintSequence m_print_seq; + bool m_based_on_all_layer; + int m_nozzle_height_to_rod; + int m_nozzle_clearance_radius; + std::optional m_liftable_extruder_id; + std::optional m_extruder_height_gap; + + std::unordered_map bbox_cache; + + std::optional m_all_layer_pos; + }; +} + +#endif \ No newline at end of file diff --git a/src/libslic3r/GCode/ToolOrderUtils.cpp b/src/libslic3r/GCode/ToolOrderUtils.cpp index d3243b5..5a6e905 100644 --- a/src/libslic3r/GCode/ToolOrderUtils.cpp +++ b/src/libslic3r/GCode/ToolOrderUtils.cpp @@ -398,7 +398,8 @@ namespace Slic3r int target_cost = std::numeric_limits::max(); for (size_t k = 0; k < is_visited.size(); ++k) { if (!is_visited[k]) { - if (wipe_volumes[*prev_filament][curr_layer_extruders[k]] < target_cost) { + if (wipe_volumes[*prev_filament][curr_layer_extruders[k]] < target_cost || + (wipe_volumes[*prev_filament][curr_layer_extruders[k]] == target_cost && prev_filament == curr_layer_extruders[k])) { target_idx = k; target_cost = wipe_volumes[*prev_filament][curr_layer_extruders[k]]; } @@ -426,8 +427,24 @@ namespace Slic3r std::sort(curr_layer_extruders.begin(), curr_layer_extruders.end()); std::sort(next_layer_extruders.begin(), next_layer_extruders.end()); float best_cost = std::numeric_limits::max(); + int best_change = std::numeric_limits::max(); // add filament change check in case flush volume between different filament is 0 std::vectorbest_seq; + auto get_filament_change_count = [](const std::vector& curr_seq, const std::vector& next_seq,const std::optional& start_extruder_id) { + int count = 0; + auto prev_extruder_id = start_extruder_id; + for (auto seq : { curr_seq,next_seq }) { + for (auto eid : seq) { + if (prev_extruder_id && prev_extruder_id != eid) { + count += 1; + } + prev_extruder_id = eid; + } + } + return count; + + }; + do { std::optionalprev_extruder_1 = start_extruder_id; float curr_layer_cost = 0; @@ -441,6 +458,7 @@ namespace Slic3r do { std::optionalprev_extruder_2 = prev_extruder_1; float total_cost = curr_layer_cost; + int total_change = get_filament_change_count(curr_layer_extruders, next_layer_extruders, start_extruder_id); for (size_t idx = 0; idx < next_layer_extruders.size(); ++idx) { if (prev_extruder_2) @@ -448,9 +466,10 @@ namespace Slic3r prev_extruder_2 = next_layer_extruders[idx]; } - if (total_cost < best_cost) { + if (total_cost < best_cost || (total_cost == best_cost && total_change < best_change)) { best_cost = total_cost; best_seq = curr_layer_extruders; + best_change = total_change; } } while (std::next_permutation(next_layer_extruders.begin(), next_layer_extruders.end())); } while (std::next_permutation(curr_layer_extruders.begin(), curr_layer_extruders.end())); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index aa4c196..d25f2b1 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -67,16 +67,12 @@ bool check_filament_printable_after_group(const std::vector &used_ { for (unsigned int filament_id : used_filaments) { std::string filament_type = print_config->filament_type.get_at(filament_id); - for (size_t idx = 0; idx < print_config->unprintable_filament_types.values.size(); ++idx) { - if (filament_maps[filament_id] == idx) { - std::vector limit_types = split_string(print_config->unprintable_filament_types.get_at(idx), ','); - auto iter = std::find(limit_types.begin(), limit_types.end(), filament_type); - if (iter != limit_types.end()) { - std::string extruder_name = idx == 0 ? _L("left") : _L("right"); - std::string error_msg = _L("Grouping error: ") + filament_type + _L(" can not be placed in the ") + extruder_name + _L(" nozzle"); - throw Slic3r::RuntimeError(error_msg); - } - } + int printable_status = print_config->filament_printable.get_at(filament_id); + int extruder_idx = filament_maps[filament_id]; + if (!(printable_status >> extruder_idx & 1)) { + std::string extruder_name = extruder_idx == 0 ? _L("left") : _L("right"); + std::string error_msg = _L("Grouping error: ") + filament_type + _L(" can not be placed in the ") + extruder_name + _L(" nozzle"); + throw Slic3r::RuntimeError(error_msg); } } return true; @@ -679,33 +675,6 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } } -bool ToolOrdering::check_tpu_group(const std::vector&used_filaments,const std::vector& filament_maps,const PrintConfig* config) -{ - int check_extruder_id = -1; - int master_extruder_id = config->master_extruder_id.value - 1; // transfer to 0 based idx - std::map> extruder_filament_nums; - for (unsigned int filament_id : used_filaments) { - int extruder_id = filament_maps[filament_id]; - extruder_filament_nums[extruder_id].push_back(filament_id); - - std::string filament_type = config->filament_type.get_at(filament_id); - if (filament_type == "TPU") { - // if we meet two TPU filaments, just return false - if(check_extruder_id==-1) - check_extruder_id = filament_maps[filament_id]; - else - return false; - } - } - - // TPU can only place in master extruder, and it should have no other filaments in the same extruder - if (check_extruder_id != -1 && (check_extruder_id != master_extruder_id || extruder_filament_nums[check_extruder_id].size() > 1)) { - return false; - } - - return true; -} - void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height) { if (m_layer_tools.empty()) @@ -1161,13 +1130,6 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first 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); - - if (nozzle_nums > 1 && !check_tpu_group(used_filaments, filament_maps, print_config)) { - int master_extruder_id = print_config->master_extruder_id.value - 1; // to 0 based - std::string nozzle_name = master_extruder_id == 0 ? _L("left") : _L("right"); - std::string exception_str = _L("TPU is incompatible with BOX and must be printed seperately in the ") + nozzle_name + _L(" nozzle.\nPlease adjust the filament group accordingly."); - throw Slic3r::RuntimeError(exception_str); - } } else { // we just need to change the map to 0 based diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 75a0dd6..5e47913 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -243,8 +243,6 @@ public: */ static std::vector get_recommended_filament_maps(const std::vector>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector>& physical_unprintables, const std::vector>& geometric_unprintables); - static bool check_tpu_group(const std::vector&used_filaments,const std::vector& filament_maps,const PrintConfig* config); - // should be called after doing reorder FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode); void cal_most_used_extruder(const PrintConfig &config); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 611e870..0310c33 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -16,7 +16,6 @@ namespace Slic3r { -bool flat_ironing = true; // Whether to enable flat ironing for the wipe tower float flat_iron_area = 4.f; constexpr float flat_iron_speed = 10.f * 60.f; static const double wipe_tower_wall_infill_overlap = 0.0; @@ -25,6 +24,7 @@ static constexpr double WT_SIMPLIFY_TOLERANCE_SCALED = 0.001 / SCALING_FACTOR; static constexpr int arc_fit_size = 20; #define SCALED_WIPE_TOWER_RESOLUTION (WIPE_TOWER_RESOLUTION / SCALING_FACTOR) enum class LimitFlow { None, LimitPrintFlow, LimitRammingFlow }; +static const std::map nozzle_diameter_to_nozzle_change_width{{0.2f, 0.5f}, {0.4f, 1.0f}, {0.6f, 1.2f}, {0.8f, 1.4f}}; inline float align_round(float value, float base) { @@ -1596,10 +1596,12 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_extra_spacing((float)config.prime_tower_infill_gap.value/100.f), m_tower_framework(config.prime_tower_enable_framework.value), m_max_speed((float)config.prime_tower_max_speed.value*60.f), - m_printable_height(config.extruder_printable_height.values), m_accel_to_decel_enable(config.accel_to_decel_enable.value), - m_accel_to_decel_factor(config.accel_to_decel_factor.value) + m_accel_to_decel_factor(config.accel_to_decel_factor.value), + m_printable_height(config.extruder_printable_height.values), + m_flat_ironing(config.prime_tower_flat_ironing.value) { + m_flat_ironing = (m_flat_ironing && m_use_gap_wall); m_normal_accels.clear(); for (auto value : config.default_acceleration.values) { m_normal_accels.emplace_back((unsigned int) floor(value + 0.5)); @@ -1663,7 +1665,6 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_bed_bottom_left = m_bed_shape == RectangularBed ? Vec2f(bed_points.front().x(), bed_points.front().y()) : Vec2f::Zero(); - flat_ironing = config.nozzle_diameter.values.size() > 1;//Only used for dual extrusion m_last_layer_id.resize(config.nozzle_diameter.size(), -1); } @@ -1725,7 +1726,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].ramming_travel_time = float(config.filament_ramming_travel_time.get_at(idx)); m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - m_nozzle_change_perimeter_width = 2*m_perimeter_width; + m_nozzle_change_perimeter_width = nozzle_diameter_to_nozzle_change_width.at(nozzle_diameter); // QDS: remove useless config #if 0 if (m_semm) { @@ -1767,7 +1768,7 @@ Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, fl const float &xr = cleaning_box.rd.x(); int line_count = wipe_length / (xr - xl); - float dy = m_layer_info->extra_spacing * m_perimeter_width; + float dy = m_layer_info->extra_spacing * get_block_gap_width(m_current_tool,false); float y_offset = float(line_count) * dy; const Vec2f pos_offset = Vec2f(0.f, m_depth_traversed); @@ -2607,6 +2608,33 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool return construct_tcr(writer, false, old_tool, true, false, 0.f); } +WipeTower::WipeTowerInfo::ToolChange WipeTower::set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume) +{ + float depth = 0.f; + float width = m_wipe_tower_width - 2 * m_perimeter_width; + float nozzle_change_width = m_wipe_tower_width - (m_nozzle_change_perimeter_width + m_perimeter_width); + float length_to_extrude = volume_to_length(wipe_volume, m_perimeter_width, layer_height); + float toolchange_gap_width = get_block_gap_width(new_tool,false); + float nozzlechange_gap_width = get_block_gap_width(old_tool,true); + depth += std::ceil(length_to_extrude / width) * toolchange_gap_width; + // depth *= m_extra_spacing; + + float nozzle_change_depth = 0; + float nozzle_change_length = 0; + if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) { + double e_flow = nozzle_change_extrusion_flow(layer_height); + double length = m_filaments_change_length[old_tool] / e_flow; + int nozzle_change_line_count = std::ceil(length / nozzle_change_width); + nozzle_change_depth = nozzle_change_line_count * nozzlechange_gap_width; + depth += nozzle_change_depth; + nozzle_change_length = length; + } + WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume); + tool_change.nozzle_change_depth = nozzle_change_depth; + tool_change.nozzle_change_length = nozzle_change_length; + return tool_change; +} + // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume, float purge_volume) @@ -2653,18 +2681,18 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in //depth *= m_extra_spacing; float nozzle_change_depth = 0; + float nozzle_change_length = 0; if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) { double e_flow = nozzle_change_extrusion_flow(layer_height_par); double length = m_filaments_change_length[old_tool] / e_flow; int nozzle_change_line_count = std::ceil(length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width)); - if (m_need_reverse_travel) - nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width; - else - nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; + nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; depth += nozzle_change_depth; + nozzle_change_length = length; } WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume); tool_change.nozzle_change_depth = nozzle_change_depth; + tool_change.nozzle_change_length = nozzle_change_length; m_plan.back().tool_changes.push_back(tool_change); #endif } @@ -2795,7 +2823,7 @@ bool WipeTower::is_tpu_filament(int filament_id) const bool WipeTower::is_need_reverse_travel(int filament_id) const { - return m_filpar[filament_id].ramming_travel_time > EPSILON; + return m_filpar[filament_id].ramming_travel_time > EPSILON && m_filaments_change_length[filament_id]>EPSILON; } // QDS: consider both soluable and support properties @@ -2865,16 +2893,12 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer) const WipeTowerInfo::ToolChange &tool_change = layer.tool_changes[i]; size_t old_filament = tool_change.old_tool; size_t new_filament = tool_change.new_tool; - float spacing = m_layer_info->extra_spacing; - if (m_need_reverse_travel && m_layer_info->extra_spacing < m_tpu_fixed_spacing) spacing = 1; - else if (m_need_reverse_travel) spacing = spacing / m_tpu_fixed_spacing; - float nozzle_change_depth = tool_change.nozzle_change_depth * spacing; - //float nozzle_change_depth = tool_change.nozzle_change_depth * (has_tpu_filament() ? m_tpu_fixed_spacing : layer.extra_spacing); + float nozzle_change_depth = tool_change.nozzle_change_depth; + float wipe_depth = tool_change.required_depth - nozzle_change_depth; + if (!is_valid_last_layer(old_filament)) nozzle_change_depth = 0.f; auto* block = get_block_by_category(m_filpar[new_filament].category, false); if (!block) continue; - //float wipe_depth = tool_change.required_depth - nozzle_change_depth; - float wipe_depth = ceil(tool_change.wipe_length / (m_wipe_tower_width - 2 * m_perimeter_width)) * m_perimeter_width*layer.extra_spacing; float process_depth = 0.f; if (!cur_block_depth.count(m_filpar[new_filament].category)) cur_block_depth[m_filpar[new_filament].category] = block->start_depth; @@ -2892,20 +2916,21 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer) cur_block_depth[m_filpar[old_filament].category] += nozzle_change_depth; } } - { + { + float infill_gap_width = get_block_gap_width(new_filament,false); Vec2f res; int index = m_cur_layer_id % 4; switch (index % 4) { case 0: res = Vec2f(0, process_depth); break; - case 1: res = Vec2f(m_wipe_tower_width, process_depth + wipe_depth - layer.extra_spacing*m_perimeter_width); break; + case 1: res = Vec2f(m_wipe_tower_width, process_depth + wipe_depth - m_layer_info->extra_spacing * infill_gap_width); break; case 2: res = Vec2f(m_wipe_tower_width, process_depth); break; - case 3: res = Vec2f(0, process_depth + wipe_depth - layer.extra_spacing * m_perimeter_width); break; + case 3: res = Vec2f(0, process_depth + wipe_depth - m_layer_info->extra_spacing * infill_gap_width); break; default: break; } m_wall_skip_points.emplace_back(res); } - cur_block_depth[m_filpar[new_filament].category] = process_depth + tool_change.required_depth - tool_change.nozzle_change_depth * layer.extra_spacing; + cur_block_depth[m_filpar[new_filament].category] = process_depth + wipe_depth; } } @@ -2932,21 +2957,17 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol wipe_depth = b.required_depth; purge_volume = b.purge_volume; nozzle_change_depth = b.nozzle_change_depth; - if (m_need_reverse_travel) - nozzle_change_line_count = ((b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width) / 2; - else - nozzle_change_line_count = (b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width; break; } } - + m_current_tool = new_tool; WipeTowerBlock* block = get_block_by_category(m_filpar[new_tool].category, false); if (!block) { assert(block != nullptr); return WipeTower::ToolChangeResult(); } m_cur_block = block; - box_coordinates cleaning_box(Vec2f(m_perimeter_width, block->cur_depth), m_wipe_tower_width - 2 * m_perimeter_width, wipe_depth-m_layer_info->extra_spacing*nozzle_change_depth); + box_coordinates cleaning_box(Vec2f(m_perimeter_width, block->cur_depth), m_wipe_tower_width - 2 * m_perimeter_width, wipe_depth-nozzle_change_depth); WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) @@ -3016,7 +3037,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol } else toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].nozzle_temperature); - block->cur_depth += (wipe_depth - nozzle_change_depth * m_layer_info->extra_spacing); + block->cur_depth += (wipe_depth - nozzle_change_depth); block->last_filament_change_id = new_tool; // QDS @@ -3038,13 +3059,14 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_infill) { int nozzle_change_line_count = 0; + float x_offset = m_perimeter_width + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2; + float nozzle_change_box_width = m_wipe_tower_width - 2 * x_offset; + float nozzle_change_depth = 0.f; if (new_filament_id != (unsigned int) (-1)) { for (const auto &b : m_layer_info->tool_changes) if (b.new_tool == new_filament_id) { - if (m_need_reverse_travel) - nozzle_change_line_count = ((b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width) / 2; - else - nozzle_change_line_count = (b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width; + nozzle_change_line_count = std::ceil(b.nozzle_change_length / nozzle_change_box_width); + nozzle_change_depth = b.nozzle_change_depth; break; } } @@ -3077,14 +3099,10 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, return WipeTower::NozzleChangeResult(); } m_cur_block = block; - float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width; - if (m_need_reverse_travel && m_extra_spacing < m_tpu_fixed_spacing) - dy = m_tpu_fixed_spacing * m_nozzle_change_perimeter_width; - - float x_offset = m_perimeter_width + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2; + float dy = is_first_layer() ? m_nozzle_change_perimeter_width : m_layer_info->extra_spacing * get_block_gap_width(m_current_tool,true); box_coordinates cleaning_box(Vec2f(x_offset,block->cur_depth + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2), - m_wipe_tower_width - 2 * x_offset, - nozzle_change_line_count * dy - (m_nozzle_change_perimeter_width - m_perimeter_width) / 2);//top can not print + nozzle_change_box_width, + nozzle_change_depth); // top can not print Vec2f initial_position = cleaning_box.ld; writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); @@ -3132,7 +3150,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, } writer.set_extrusion_flow(nz_extrusion_flow); // Reset the extrusion flow. - block->cur_depth += nozzle_change_line_count * dy; + block->cur_depth += nozzle_change_depth; block->last_nozzle_change_id = old_filament_id; NozzleChangeResult result; @@ -3165,7 +3183,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, } } else { result.wipe_path.push_back(writer.pos_rotated()); - if (m_left_to_right) { + if (m_left_to_right) { result.wipe_path.push_back(Vec2f(0, writer.pos_rotated().y())); } else { result.wipe_path.push_back(Vec2f(m_wipe_tower_width, writer.pos_rotated().y())); @@ -3559,7 +3577,9 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat const float &xr = cleaning_box.rd.x(); float x_to_wipe = wipe_length; - float dy = solid_tool_toolchange ? m_perimeter_width :m_layer_info->extra_spacing * m_perimeter_width; + float dy = is_first_layer() ? m_perimeter_width : m_layer_info->extra_spacing * get_block_gap_width(m_current_tool,false); + if (solid_tool_toolchange) + dy = m_perimeter_width; x_to_wipe = solid_tool_toolchange ? std::numeric_limits::max(): x_to_wipe; float target_speed = is_first_layer() ? std::min(m_first_layer_speed * 60.f, m_max_speed) : m_max_speed; target_speed = solid_tool_toolchange ? 20.f * 60.f : target_speed; @@ -3597,7 +3617,7 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed); writer.retract(retract_length, retract_speed); writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 600.); - if (flat_ironing) { + if (m_flat_ironing) { writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.); Vec2f pos{writer.x() + 1.f * ironing_length, writer.y()}; writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); @@ -3612,7 +3632,7 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat writer.extrude(writer.x() - ironing_length, writer.y(), wipe_speed); writer.retract(retract_length, retract_speed); writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 600.); - if (flat_ironing) { + if (m_flat_ironing) { writer.travel(writer.x() - 0.5f * ironing_length, writer.y(), 240.); Vec2f pos{writer.x() - 1.0f * ironing_length, writer.y()}; writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); @@ -3866,34 +3886,99 @@ void WipeTower::generate_wipe_tower_blocks() } } +void WipeTower::calc_block_infill_gap() +{ + //1.calc block infill gap width + struct BlockInfo + { + bool has_ramming = false; + bool has_reverse_travel = false; + float depth = 0.f; + }; + std::unordered_map block_info; + std::unordered_map high_block_info; + for (int i= (int)m_plan.size()-1;i>=0;i--) + { + for (auto &toolchange : m_plan[i].tool_changes) { + int new_tool =toolchange.new_tool; + int old_tool =toolchange.old_tool; + if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) { + block_info[m_filpar[old_tool].category].has_ramming=true; + if (is_need_reverse_travel(old_tool)) block_info[m_filpar[old_tool].category].has_reverse_travel = true; + block_info[m_filpar[old_tool].category].depth += toolchange.nozzle_change_depth; + } + if (!block_info.count(m_filpar[new_tool].category)) block_info.insert({m_filpar[new_tool].category,BlockInfo{}}); + block_info[m_filpar[new_tool].category].depth += toolchange.required_depth - toolchange.nozzle_change_depth; + } + for (auto &block : block_info) { + if (high_block_info.count(block.first) && high_block_info[block.first].depth > block.second.depth) + block.second.depth = high_block_info[block.first].depth; + } + high_block_info = block_info; + + for (auto &block : block_info) { block.second.depth = 0.f;} + if (i == 0) block_info = high_block_info; + } + float max_depth = std::accumulate(block_info.begin(), block_info.end(), 0.f, [](float value, const std::pair &block) { return value + block.second.depth; }); + float height_to_depth = get_limit_depth_by_height(m_wipe_tower_height); + float height_to_spacing = max_depth > height_to_depth ? 1.f : height_to_depth / max_depth; + + float spacing_ratio = m_extra_spacing - 1.f; + float extra_width = spacing_ratio * m_perimeter_width; + float line_gap_tol = 2.f * m_nozzle_change_perimeter_width; //If the block's line_gap is greater than it, the block should be aligned. + for (auto &info : block_info) { + //case1: no ramming, it can always align + if (!info.second.has_ramming) { + m_block_infill_gap_width[info.first].first = m_block_infill_gap_width[info.first].second = extra_width + m_perimeter_width; + } + // case2: has ramming, but no reverse travel + // + else if (!info.second.has_reverse_travel) { + float line_gap = m_nozzle_change_perimeter_width + extra_width; + if (!m_use_rib_wall) line_gap *= height_to_spacing; + if (line_gap < line_gap_tol) { + m_block_infill_gap_width[info.first].first = m_perimeter_width + extra_width; + m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width + extra_width; + } else { + m_block_infill_gap_width[info.first].first = m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width + extra_width; + } + } + // case 3: has ramming and reverse travel + else { + float extra_tpu_fix_spacing = m_tpu_fixed_spacing - 1.f; + float line_gap = m_nozzle_change_perimeter_width + std::max(extra_tpu_fix_spacing * m_perimeter_width, extra_width); + if (!m_use_rib_wall) line_gap = height_to_spacing * line_gap; + if (line_gap < line_gap_tol) { + m_block_infill_gap_width[info.first].first = m_perimeter_width + extra_width; + m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width + std::max(extra_tpu_fix_spacing * m_perimeter_width, extra_width); + } else { + m_block_infill_gap_width[info.first].first = m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width + + std::max(extra_tpu_fix_spacing * m_perimeter_width, extra_width); + } + } + } + + //2. recalculate toolchange depth + for (int idx = 0; idx < m_plan.size(); idx++) { + for (auto &toolchange : m_plan[idx].tool_changes) { + toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool, m_plan[idx].height, toolchange.wipe_volume, toolchange.purge_volume); + } + } + 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); + calc_block_infill_gap(); if (m_use_rib_wall) { // recalculate wipe_tower_with and layer's depth generate_wipe_tower_blocks(); float max_depth = std::accumulate(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), 0.f, [](float a, const auto &t) { return a + t.depth; }) + m_perimeter_width; float square_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width * m_extra_spacing), m_perimeter_width); - //std::cout << " before m_wipe_tower_width = " << m_wipe_tower_width << " max_depth = " << max_depth << std::endl; m_wipe_tower_width = square_width; - float width = m_wipe_tower_width - 2 * m_perimeter_width; for (int idx = 0; idx < m_plan.size(); idx++) { for (auto &toolchange : m_plan[idx].tool_changes) { - float length_to_extrude = toolchange.wipe_length; - float depth = std::ceil(length_to_extrude / width) * m_perimeter_width; - float nozzle_change_depth = 0; - if (!m_filament_map.empty() && m_filament_map[toolchange.old_tool] != m_filament_map[toolchange.new_tool]) { - double e_flow = nozzle_change_extrusion_flow(m_plan[idx].height); - double length = m_filaments_change_length[toolchange.old_tool] / e_flow; - int nozzle_change_line_count = std::ceil(length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width)); - if (m_need_reverse_travel) - nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width; - else - nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width; - depth += nozzle_change_depth; - } - toolchange.nozzle_change_depth = nozzle_change_depth; - toolchange.required_depth = depth; + toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool, m_plan[idx].height, toolchange.wipe_volume, toolchange.purge_volume); } } } @@ -3925,28 +4010,29 @@ void WipeTower::plan_tower_new() for (int idx = 0; idx < m_plan.size(); idx++) { auto &info = m_plan[idx]; - if (idx == 0 && m_extra_spacing > 1.f + EPSILON) { + if (idx == 0 /*&& m_extra_spacing > 1.f + EPSILON*/) { // apply solid fill for the first layer info.extra_spacing = 1.f; for (auto &toolchange : info.tool_changes) { - float x_to_wipe = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height); + //float x_to_wipe = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height); float line_len = m_wipe_tower_width - 2 * m_perimeter_width; - float x_to_wipe_new = x_to_wipe * m_extra_spacing; - x_to_wipe_new = std::floor(x_to_wipe_new / line_len) * line_len; - x_to_wipe_new = std::max(x_to_wipe_new, x_to_wipe); + float wipe_depth = (toolchange.required_depth - toolchange.nozzle_change_depth) * m_extra_spacing; + float wipe_line_count = wipe_depth / m_perimeter_width; + float nozzle_change_depth = toolchange.nozzle_change_depth * m_extra_spacing; - int line_count = std::ceil((x_to_wipe_new - WT_EPSILON) / line_len); - // nozzle change length - int nozzle_change_line_count = (toolchange.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width; + int nozzle_change_line_count = (toolchange.nozzle_change_depth * m_extra_spacing + WT_EPSILON) / m_nozzle_change_perimeter_width; - toolchange.required_depth = line_count * m_perimeter_width + nozzle_change_line_count * m_nozzle_change_perimeter_width; - toolchange.wipe_volume = x_to_wipe_new / x_to_wipe * toolchange.wipe_volume; - toolchange.wipe_length = x_to_wipe_new; + toolchange.required_depth = wipe_depth + nozzle_change_depth; + toolchange.wipe_length = wipe_line_count * line_len; + toolchange.wipe_volume = length_to_volume(toolchange.wipe_length, m_perimeter_width, info.height); + toolchange.nozzle_change_length = nozzle_change_line_count * (m_wipe_tower_width - (m_nozzle_change_perimeter_width + m_perimeter_width)); + toolchange.nozzle_change_depth = nozzle_change_depth; } } else { info.extra_spacing = m_extra_spacing; for (auto &toolchange : info.tool_changes) { toolchange.required_depth *= m_extra_spacing; + toolchange.nozzle_change_depth *= m_extra_spacing; toolchange.wipe_length = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height); } } @@ -4629,5 +4715,14 @@ bool WipeTower::is_valid_last_layer(int tool) const if (m_last_layer_id[nozzle_id] == m_cur_layer_id && m_z_pos > m_printable_height[nozzle_id]) return false; return true; } +float WipeTower::get_block_gap_width(int tool,bool is_nozzlechangle) +{ + assert(m_block_infill_gap_width.count(m_filpar[tool].category)); + if (!m_block_infill_gap_width.count(m_filpar[tool].category)) { + return 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; + +} } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 70f50c8..eb22cca 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -184,6 +184,7 @@ public: // to be used before building begins. The entries must be added ordered in z. void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f); + // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" void generate(std::vector> &result); @@ -307,14 +308,13 @@ public: void set_need_reverse_travel(const std::vector & used_extruders) { for (unsigned int filament_id : used_extruders) { - if (m_filpar[filament_id].ramming_travel_time > EPSILON) { + if (m_filpar[filament_id].ramming_travel_time > EPSILON && m_filaments_change_length[filament_id]>EPSILON) { m_need_reverse_travel = true; return; } } } bool has_tpu_filament() const { return m_has_tpu_filament; } - struct FilamentParameters { std::string material = "PLA"; int category; @@ -393,7 +393,7 @@ public: void generate_wipe_tower_blocks(); void update_all_layer_depth(float wipe_tower_depth); void set_nozzle_last_layer_id(); - + void calc_block_infill_gap(); ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false); NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false); ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true); @@ -484,7 +484,7 @@ private: float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. - + std::unordered_map> m_block_infill_gap_width; // categories to infill_gap: toolchange gap, nozzlechange gap // Extruder specific parameters. std::vector m_filpar; @@ -511,12 +511,16 @@ private: std::vector m_printable_height; bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } bool is_valid_last_layer(int tool) const; - + bool m_flat_ironing=false; // Calculates length of extrusion line to extrude given volume float volume_to_length(float volume, float line_width, float layer_height) const { return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); } - + // Calculates volume of extrusion line + float length_to_volume(float length,float line_width, float layer_height) const + { + return std::max(0.f, length * (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); + } // Calculates depth for all layers and propagates them downwards void plan_tower(); @@ -546,6 +550,7 @@ private: float wipe_volume; float wipe_length; float nozzle_change_depth{0}; + float nozzle_change_length{0}; // QDS float purge_volume; ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0) @@ -575,7 +580,7 @@ private: // ot -1 if there is no such toolchange. int first_toolchange_to_nonsoluble_nonsupport( const std::vector& tool_changes) const; - + WipeTowerInfo::ToolChange set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume); void toolchange_Unload( WipeTowerWriter &writer, const box_coordinates &cleaning_box, @@ -597,6 +602,7 @@ private: float wipe_volume); void get_wall_skip_points(const WipeTowerInfo &layer); ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second); + float get_block_gap_width(int tool, bool is_nozzlechangle = false); }; diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index c3bff19..b54639c 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -46,7 +46,7 @@ void GCodeWriter::set_extruders(std::vector extruder_ids) std::string GCodeWriter::preamble() { std::ostringstream gcode; - + if (FLAVOR_IS_NOT(gcfMakerWare)) { gcode << "G90\n"; gcode << "G21\n"; @@ -67,7 +67,7 @@ std::string GCodeWriter::preamble() } gcode << this->reset_e(true); } - + return gcode.str(); } @@ -83,7 +83,7 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in { if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) return ""; - + std::string code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) { code = "M109"; @@ -96,7 +96,7 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in } comment = "set nozzle temperature"; } - + std::ostringstream gcode; gcode << code << " "; if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { @@ -114,10 +114,10 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in } } gcode << " ; " << comment << "\n"; - + if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRapFirmware)) && wait) gcode << "M116 ; wait for temperature to be reached\n"; - + return gcode.str(); } @@ -211,9 +211,9 @@ std::string GCodeWriter::set_acceleration_impl(unsigned int acceleration) if (acceleration == 0 || acceleration == m_last_acceleration) return std::string(); - + m_last_acceleration = acceleration; - + std::ostringstream gcode; if (FLAVOR_IS(gcfRepetier)) { // M201: Set max printing acceleration @@ -242,7 +242,7 @@ std::string GCodeWriter::set_acceleration_impl(unsigned int acceleration) //QDS if (GCodeWriter::full_gcode_comment) gcode << " ; adjust acceleration"; gcode << "\n"; - + return gcode.str(); } @@ -314,10 +314,10 @@ std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, boo { if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) return ""; - + unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5); if (!allow_100) percent = std::min(percent, (unsigned int)99); - + std::ostringstream gcode; gcode << "M73 P" << percent; //QDS @@ -406,6 +406,7 @@ std::string GCodeWriter::lazy_lift(LiftType lift_type, bool spiral_vase, bool to if (tool_change && this->config.prime_tower_lift_height.value > 0) target_lift = this->config.prime_tower_lift_height.value; } } + // QDS if (m_lifted == 0 && m_to_lift == 0 && target_lift > 0) { if (spiral_vase) { @@ -438,20 +439,24 @@ std::string GCodeWriter::eager_lift(const LiftType type, bool tool_change) } } + double to_lift = target_lift - m_lifted; + if (to_lift < EPSILON) + return lift_move; + // QDS: spiral lift only safe with known position // TODO: check the arc will move within bed area if (type == LiftType::SpiralLift && this->is_current_position_clear()) { - double radius = target_lift / (2 * PI * atan(GCodeWriter::slope_threshold)); - // static spiral alignment when no move in x,y plane. - // spiral centra is a radius distance to the right (y=0) - Vec2d ij_offset = { radius, 0 }; - if (target_lift > 0) { - lift_move = this->_spiral_travel_to_z(m_pos(2) + target_lift, ij_offset, "spiral lift Z",tool_change); + if (to_lift > 0) { + double radius = to_lift / (2 * PI * atan(GCodeWriter::slope_threshold)); + // static spiral alignment when no move in x,y plane. + // spiral centra is a radius distance to the right (y=0) + Vec2d ij_offset = { radius, 0 }; + lift_move = this->_spiral_travel_to_z(m_pos(2) + to_lift, ij_offset, "spiral lift Z",tool_change); } } //QDS: if position is unknown use normal lift - else if (target_lift > 0) { - lift_move = _travel_to_z(m_pos(2) + target_lift, "normal lift Z",tool_change); + else if (to_lift > 0) { + lift_move = _travel_to_z(m_pos(2) + to_lift, "normal lift Z", tool_change); } m_lifted = target_lift; m_to_lift = 0; @@ -595,7 +600,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) m_lifted = 0.; return ""; } - + /* In all the other cases, we perform an actual Z move and cancel the lift. */ m_lifted = 0; @@ -760,7 +765,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std gcode = w.string(); } } - + if (FLAVOR_IS(gcfMakerWare)) gcode += "M103 ; extruder off\n"; @@ -770,7 +775,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std std::string GCodeWriter::unretract() { std::string gcode; - + if (FLAVOR_IS(gcfMakerWare)) gcode = "M101 ; extruder on\n"; @@ -790,10 +795,11 @@ std::string GCodeWriter::unretract() gcode += w.string(); } } - + return gcode; } + std::string GCodeWriter::unlift() { std::string gcode; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 1eb1043..266f8fa 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -287,6 +287,9 @@ Model Model::read_from_file(const std::string& ObjDialogInOut in_out; in_out.model = &model; in_out.lost_material_name = obj_info.lost_material_name; + in_out.ml_region = obj_info.ml_region; + in_out.ml_name = obj_info.ml_name; + in_out.ml_id = obj_info.ml_id; if (obj_info.vertex_colors.size() > 0) { if (objFn) { // 1.result is ok and pop up a dialog in_out.input_colors = std::move(obj_info.vertex_colors); diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 605ce55..26d2dcb 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -19,11 +19,11 @@ public: MultiPoint() {} MultiPoint(const MultiPoint &other) : points(other.points) {} - MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} + MultiPoint(MultiPoint &&other) noexcept : points(std::move(other.points)) {} MultiPoint(std::initializer_list list) : points(list) {} explicit MultiPoint(const Points &_points) : points(_points) {} MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; } - MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } + MultiPoint& operator=(MultiPoint &&other) noexcept { points = std::move(other.points); return *this; } void scale(double factor); void scale(double factor_x, double factor_y); void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } diff --git a/src/libslic3r/Orient.cpp b/src/libslic3r/Orient.cpp index 21f044f..88d2a40 100644 --- a/src/libslic3r/Orient.cpp +++ b/src/libslic3r/Orient.cpp @@ -519,6 +519,11 @@ void orient(ModelObject* obj) { auto m = obj->mesh(); AutoOrienter orienter(&m); + if (obj->config.has("support_threshold_angle")) + { + orienter.params.overhang_angle = obj->config.opt_int("support_threshold_angle"); + orienter.params.ASCENT = cos(PI - orienter.params.overhang_angle * PI / 180); + } Vec3d orientation = orienter.process(); Vec3d axis; double angle; diff --git a/src/libslic3r/Orient.hpp b/src/libslic3r/Orient.hpp index 76be064..71d24b1 100644 --- a/src/libslic3r/Orient.hpp +++ b/src/libslic3r/Orient.hpp @@ -102,7 +102,7 @@ struct OrientParams { float FIRST_LAY_H = 0.2f;//0.029; float VECTOR_TOL = -0.0011163303070972383f; float NEGL_FACE_SIZE = 0.1f; - float ASCENT= -0.5f; + float ASCENT= -0.86602540378f; float PLAFOND_ADV = 0.04079208948120519f; float CONTOUR_AMOUNT = 0.0101472219892684f; float OV_H = 1.0370178217794535f; @@ -119,7 +119,7 @@ struct OrientParams { float BOTTOM_HULL_MAX = 2000;// max bottom hull area to clip //600 float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face - float overhang_angle = 60.f; + float overhang_angle = 30.f; bool use_low_angle_face = true; bool min_volume = false; Eigen::Vector3f fun_dir; diff --git a/src/libslic3r/OverhangDetector.cpp b/src/libslic3r/OverhangDetector.cpp new file mode 100644 index 0000000..27bb331 --- /dev/null +++ b/src/libslic3r/OverhangDetector.cpp @@ -0,0 +1,332 @@ +#include "OverhangDetector.hpp" +#include "ExtrusionEntity.hpp" +#include "AABBTreeLines.hpp" +#include "Arachne/utils/ExtrusionLine.hpp" + +namespace Slic3r { + + Polylines ZPath_to_polylines(const ZPaths& paths) + { + Polylines lines; + for (auto& path : paths) { + lines.emplace_back(); + for (auto& p : path) lines.back().points.push_back(Point{ p.x(), p.y() }); + } + return lines; + }; + + ZPaths clip_extrusion(const ZPath& subject, const ZPaths& clip, ClipperLib_Z::ClipType clipType) + { + ClipperLib_Z::Clipper clipper; + 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& pt) { + // The clipping contour may be simplified by clipping it with a bounding box of "subject" path. + // The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are + // harmless to the result of the clipping operation, + // Both ends of each edge belong to the same source: Either they are from subject or from clipping path. + assert(e1bot.z() >= 0 && e1top.z() >= 0); + assert(e2bot.z() >= 0 && e2top.z() >= 0); + assert((e1bot.z() == 0) == (e1top.z() == 0)); + assert((e2bot.z() == 0) == (e2top.z() == 0)); + + // Start & end points of the clipped polyline (extrusion path with a non-zero width). + ClipperLib_Z::IntPoint start = e1bot; + ClipperLib_Z::IntPoint end = e1top; + if (start.z() <= 0 && end.z() <= 0) { + start = e2bot; + end = e2top; + } + + if (start.z() <= 0 && end.z() <= 0) { + // Self intersection on the source contour. + assert(start.z() == 0 && end.z() == 0); + pt.z() = 0; + } + else { + // Interpolate extrusion line width. + assert(start.z() > 0 && end.z() > 0); + + double length_sqr = (end - start).cast().squaredNorm(); + double dist_sqr = (pt - start).cast().squaredNorm(); + double t = std::sqrt(dist_sqr / length_sqr); + + pt.z() = start.z() + coord_t((end.z() - start.z()) * t); + } + }); + + clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); + clipper.AddPaths(clip, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::PolyTree clipped_polytree; + ClipperLib_Z::Paths clipped_paths; + clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths); + + // Clipped path could contain vertices from the clip with a Z coordinate equal to zero. + // For those vertices, we must assign value based on the subject. + // This happens only in sporadic cases. + for (ClipperLib_Z::Path& path : clipped_paths) + for (ClipperLib_Z::IntPoint& c_pt : path) + if (c_pt.z() == 0) { + // Now we must find the corresponding line on with this point is located and compute line width (Z coordinate). + if (subject.size() <= 2) continue; + + const Point pt(c_pt.x(), c_pt.y()); + Point projected_pt_min; + auto it_min = subject.begin(); + auto dist_sqr_min = std::numeric_limits::max(); + Point prev(subject.front().x(), subject.front().y()); + for (auto it = std::next(subject.begin()); it != subject.end(); ++it) { + Point curr(it->x(), it->y()); + Point projected_pt = pt.projection_onto(Line(prev, curr)); + if (double dist_sqr = (projected_pt - pt).cast().squaredNorm(); dist_sqr < dist_sqr_min) { + dist_sqr_min = dist_sqr; + projected_pt_min = projected_pt; + it_min = std::prev(it); + } + prev = curr; + } + + assert(dist_sqr_min <= SCALED_EPSILON); + assert(std::next(it_min) != subject.end()); + + const Point pt_a(it_min->x(), it_min->y()); + const Point pt_b(std::next(it_min)->x(), std::next(it_min)->y()); + const double line_len = (pt_b - pt_a).cast().norm(); + const double dist = (projected_pt_min - pt_a).cast().norm(); + c_pt.z() = coord_t(double(it_min->z()) + (dist / line_len) * double(std::next(it_min)->z() - it_min->z())); + } + + assert([&clipped_paths = std::as_const(clipped_paths)]() -> bool { + for (const ClipperLib_Z::Path& path : clipped_paths) + for (const ClipperLib_Z::IntPoint& pt : path) + if (pt.z() <= 0) return false; + return true; + }()); + + return clipped_paths; + } + + ZPath add_sampling_points(const ZPath& path, double min_sampling_interval) + { + ZPath sampled_path; + if (path.empty()) + return sampled_path; + sampled_path.reserve(1.5 * path.size()); + for (size_t idx = 0; idx < path.size(); ++idx) { + ZPoint curr_zp = path[idx]; + Point curr_p = { curr_zp.x(), curr_zp.y() }; + sampled_path.emplace_back(curr_zp); + if (idx + 1 < path.size()) { + ZPoint next_zp = path[idx + 1]; + Point next_p = { next_zp.x(), next_zp.y() }; + + double dist = (next_p - curr_p).cast().norm(); + if (dist > min_sampling_interval) { + size_t num_samples = static_cast(std::floor(dist / min_sampling_interval)); + for (size_t j = 1; j <= num_samples; ++j) { + double t = j * min_sampling_interval / dist; + ZPoint new_point; + new_point.x() = curr_p.x() + t * (next_p.x() - curr_p.x()); + new_point.y() = curr_p.y() + t * (next_p.y() - curr_p.y()); + new_point.z() = curr_zp.z() + t * (next_zp.z() - curr_zp.z()); + sampled_path.push_back(new_point); + } + } + } + } + sampled_path.shrink_to_fit(); + return sampled_path; + } + ZPaths add_sampling_points(const ZPaths &paths, double min_sampling_interval) { + ZPaths res; + res.resize(paths.size()); + for (size_t i = 0; i < res.size(); i++) res[i] = add_sampling_points(paths[i], min_sampling_interval); + return res; + } + + OverhangDistancer::OverhangDistancer(const Polygons& layer_polygons) + { + for (const Polygon& island : layer_polygons) { + for (const auto& line : island.lines()) { lines.emplace_back(line.a.cast(), line.b.cast()); } + } + tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + } + + float OverhangDistancer::distance_from_perimeter(const Vec2f& point) const + { + Vec2d p = point.cast(); + size_t hit_idx_out{}; + Vec2d hit_point_out = Vec2d::Zero(); + auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, hit_idx_out, hit_point_out); + if (distance < 0) { return std::numeric_limits::max(); } + + distance = sqrt(distance); + return distance; + } + + ExtrusionPaths detect_overhang_degree(const Flow& flow, + const ExtrusionRole role, + const Polygons& lower_polys, + const ClipperLib_Z::Paths& clip_paths, + const ClipperLib_Z::Path& extrusion_path, + const double nozzle_diameter) + { + ExtrusionPaths ret_paths; + std::list ret_path_list; + ZPaths paths_in_range = clip_extrusion(extrusion_path, clip_paths, ClipperLib_Z::ctIntersection); //doing intersection first would be faster. + + paths_in_range = add_sampling_points(paths_in_range, scale_(2));// enough? + + //Polylines polylines = ZPath_to_polylines(subject_paths); + //Polylines path_in_range_debug = ZPath_to_polylines(paths_in_range); + + for (auto& path : paths_in_range) { + for (auto& p : path) { assert(p.z() != 0); } + } + + auto get_base_degree = [](double d){ + return std::floor(d/min_degree_gap_arachne) * min_degree_gap_arachne; + }; + + auto in_same_degree_range = [&](double a, double b) -> bool { + double base_a = get_base_degree(a); + double base_b = get_base_degree(b); + return std::abs(base_a - base_b) < EPSILON; + }; + + struct SplitPoint + { + Point p; + coord_t w; + double degree; + }; + + auto get_split_points = [&](const Point& pa, const Point& pb, const coord_t wa, const coord_t wb, const double da, const double db) -> std::vector { + std::vector ret; + double start_d = get_base_degree(std::min(da, db)) + min_degree_gap_arachne; + double end_d = get_base_degree(std::max(da, db)); + + if (start_d > end_d) return ret; + + double delta_d = db - da; + if (std::abs(delta_d) < 1e-6) return ret; + + if (da < db) { + for (double k = start_d; k <= end_d; k+=min_degree_gap_arachne) { + const double t = (k - da) / delta_d; + const Point pt = pa + (pb - pa) * t; + const coord_t w = wa + coord_t((wb - wa) * t); + ret.emplace_back(SplitPoint{ pt, w, (double)k }); + } + } + else { + for (double k = end_d; k >= start_d; k-=min_degree_gap_arachne) { + const double t = (k - da) / delta_d; + const Point pt = pa + (pb - pa) * t; + const coord_t w = wa + coord_t((wb - wa) * t); + ret.emplace_back(SplitPoint{ pt, w, (double)k }); + } + } + return ret; + }; + + std::unique_ptr prev_layer_distancer = std::make_unique(lower_polys); + + coord_t offset_width = scale_(nozzle_diameter) / 2; + + for (auto& path : paths_in_range) { + std::vector overhang_degree_arr; + if (path.empty()) continue; + for (size_t idx = 0; idx < path.size(); ++idx) { + Point p{ path[idx].x(), path[idx].y() }; + double overhang_dist = prev_layer_distancer->distance_from_perimeter(p.cast()); + float width = path[idx].z(); + double real_dist = offset_width + overhang_dist; + + double degree = 0; + + if (std::abs(real_dist) > (width / 2)) { + degree = real_dist < 0 ? 0 : 100; + } + else { + degree = (width / 2 + real_dist) / width * 100; + } + + double mapped_degree; + // map overhang speed to a range + { + auto it = std::upper_bound(non_uniform_degree_map.begin(), non_uniform_degree_map.end(), degree); + int high_idx = it - non_uniform_degree_map.begin(); + int low_idx = high_idx - 1; + double t = (degree - non_uniform_degree_map[low_idx]) / (non_uniform_degree_map[high_idx] - non_uniform_degree_map[low_idx]); + mapped_degree = low_idx * (1 - t) + t * high_idx; + } + overhang_degree_arr.emplace_back(mapped_degree); + } + // split into extrusion path + + Point prev_p{ path.front().x(), path.front().y() }; + coord_t prev_w = path.front().z(); + double prev_d = overhang_degree_arr.front(); + ZPath prev_line = { path.front() }; + + for (size_t idx = 1; idx < path.size(); ++idx) { + Point curr_p{ path[idx].x(), path[idx].y() }; + coord_t curr_w = path[idx].z(); + double curr_d = overhang_degree_arr[idx]; + + if (in_same_degree_range(prev_d, curr_d)) { + prev_w = curr_w; + prev_d = curr_d; + prev_p = curr_p; + prev_line.push_back(path[idx]); + continue; + } + else { + auto split_points = get_split_points(prev_p, curr_p, prev_w, curr_w, prev_d, curr_d); + for (auto& split_point : split_points) { + prev_line.push_back({ split_point.p.x(), split_point.p.y(), split_point.w }); + double target_degree = 0; + if( prev_d < curr_d) + target_degree = split_point.degree - min_degree_gap_arachne; + else + target_degree = split_point.degree; + extrusion_paths_append(ret_path_list , { prev_line }, role, flow, target_degree); + prev_line.clear(); + prev_line.push_back({ split_point.p.x(), split_point.p.y(), split_point.w }); + } + prev_w = curr_w; + prev_d = curr_d; + prev_p = curr_p; + prev_line.push_back(path[idx]); + } + } + if (prev_line.size() > 1) { extrusion_paths_append(ret_path_list, { prev_line }, role, flow, get_base_degree(prev_d)); } + } + + ret_paths.reserve(ret_path_list.size()); + ret_paths.insert(ret_paths.end(), std::make_move_iterator(ret_path_list.begin()), std::make_move_iterator(ret_path_list.end())); + + return ret_paths; + } + + SignedOverhangDistancer::SignedOverhangDistancer(const Polygons &layer_polygons) + { + std::vector lines; + for (const Polygon &island : layer_polygons) { + for (const auto &line : island.lines()) { lines.emplace_back(line.a.cast(), line.b.cast()); } + } + distancer = AABBTreeLines::LinesDistancer(lines); + } + + double SignedOverhangDistancer::distance_from_perimeter(const Vec2d &point) const + { + return distancer.template distance_from_lines(point); + } + + std::tuple SignedOverhangDistancer::distance_from_perimeter_extra(const Vec2d &point) const + { + return distancer.template distance_from_lines_extra(point); + } +} \ No newline at end of file diff --git a/src/libslic3r/OverhangDetector.hpp b/src/libslic3r/OverhangDetector.hpp new file mode 100644 index 0000000..5ff0724 --- /dev/null +++ b/src/libslic3r/OverhangDetector.hpp @@ -0,0 +1,48 @@ +#ifndef OVERHANG_DETECTOR_HPP +#define OVERHANG_DETECTOR_HPP + +#include "ExtrusionEntityCollection.hpp" +#include "ClipperUtils.hpp" +#include "Flow.hpp" +#include "AABBTreeLines.hpp" +using ZPoint = ClipperLib_Z::IntPoint; +using ZPath = ClipperLib_Z::Path; +using ZPaths = ClipperLib_Z::Paths; + +static const int overhang_sampling_number = 6; +static const double min_degree_gap_classic = 0.1; +static const double min_degree_gap_arachne = 0.25; +static const int max_overhang_degree = overhang_sampling_number - 1; +static const std::vector non_uniform_degree_map = { 0, 10, 25, 50, 75, 100}; +static const int insert_point_count = 3; + +namespace Slic3r { + + class OverhangDistancer + { + std::vector lines; + AABBTreeIndirect::Tree<2, double> tree; + public: + OverhangDistancer(const Polygons& layer_polygons); + float distance_from_perimeter(const Vec2f& point) const; + }; + + class SignedOverhangDistancer + { + AABBTreeLines::LinesDistancer distancer; + + public: + SignedOverhangDistancer(const Polygons &layer_polygons); + double distance_from_perimeter(const Vec2d &point) const; + std::tuple distance_from_perimeter_extra(const Vec2d &point) const; + }; + + ZPaths clip_extrusion(const ZPath& subject, const ZPaths& clip, ClipperLib_Z::ClipType clipType); + + ZPath add_sampling_points(const ZPath& path, double min_sampling_interval); + + ExtrusionPaths detect_overhang_degree(const Flow& flow, const ExtrusionRole role, const Polygons& lower_polys, const ClipperLib_Z::Paths& clip_paths, const ClipperLib_Z::Path& extrusion_paths, const double nozzle_diameter); + +} + +#endif \ No newline at end of file diff --git a/src/libslic3r/PNGReadWrite.cpp b/src/libslic3r/PNGReadWrite.cpp index e4b7489..5b6e7c1 100644 --- a/src/libslic3r/PNGReadWrite.cpp +++ b/src/libslic3r/PNGReadWrite.cpp @@ -185,7 +185,7 @@ bool decode_colored_png(const ReadBuf &in_buf, ImageColorscale &out_img) // Based on https://www.lemoda.net/c/write-png/ // png_color_type is PNG_COLOR_TYPE_RGB or PNG_COLOR_TYPE_GRAY //FIXME maybe better to use tdefl_write_image_to_png_file_in_memory() instead? -static bool write_rgb_or_gray_to_file(const char *file_name_utf8, size_t width, size_t height, int png_color_type, const uint8_t *data) +static bool write_rgb_or_gray_to_file(const char *file_name_utf8, size_t width, size_t height, int png_color_type, const uint8_t *data, bool flip = false) { bool result = false; @@ -235,10 +235,12 @@ static bool write_rgb_or_gray_to_file(const char *file_name_utf8, size_t width, int line_width = width; if (png_color_type == PNG_COLOR_TYPE_RGB) line_width *= 3; + else if (png_color_type == PNG_COLOR_TYPE_RGBA) + line_width *= 4; for (size_t y = 0; y < height; ++ y) { auto row = reinterpret_cast(::png_malloc(png_ptr, line_width)); row_pointers[y] = row; - memcpy(row, data + line_width * y, line_width); + memcpy(row, data + line_width * (flip ? (height - 1 - y) : y), line_width); } } @@ -262,6 +264,11 @@ fopen_failed: return result; } +bool write_gl_rgba_to_file(const char* file_name_utf8, size_t width, size_t height, const uint8_t* data_rgb) +{ + return write_rgb_or_gray_to_file(file_name_utf8, width, height, PNG_COLOR_TYPE_RGBA, data_rgb, true); +} + bool write_rgb_to_file(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb) { return write_rgb_or_gray_to_file(file_name_utf8, width, height, PNG_COLOR_TYPE_RGB, data_rgb); diff --git a/src/libslic3r/PNGReadWrite.hpp b/src/libslic3r/PNGReadWrite.hpp index 6699a7f..0ae41fc 100644 --- a/src/libslic3r/PNGReadWrite.hpp +++ b/src/libslic3r/PNGReadWrite.hpp @@ -84,6 +84,7 @@ bool decode_colored_png(const ReadBuf &in_buf, ImageColorscale &out_img); +bool write_gl_rgba_to_file(const char* file_name_utf8, size_t width, size_t height, const uint8_t* data_rgb); // Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes. bool write_rgb_to_file(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb); bool write_rgb_to_file(const std::string &file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 6e79467..bc9b85a 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -13,13 +13,9 @@ #include #include #include -#include "libslic3r/AABBTreeLines.hpp" -static const int overhang_sampling_number = 6; +#include "OverhangDetector.hpp" + static const double narrow_loop_length_threshold = 10; -static const double min_degree_gap = 0.1; -static const int max_overhang_degree = overhang_sampling_number - 1; -static const std::vector non_uniform_degree_map = { 0, 10, 25, 50, 75, 100}; -static const int insert_point_count = 3; //QDS: when the width of expolygon is smaller than //ext_perimeter_width + ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE), //we think it's small detail area and will generate smaller line width for it @@ -27,7 +23,6 @@ static constexpr double SMALLER_EXT_INSET_OVERLAP_TOLERANCE = 0.22; namespace Slic3r { -//1.9.5 // Produces a random value between 0 and 1. Thread-safe. static double random_value() { thread_local std::random_device rd; @@ -69,7 +64,6 @@ static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuz { 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.; - //1.9.5 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 Point* p0 = &poly.points.back(); Points out; @@ -78,7 +72,6 @@ static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuz { // 'a' is the (next) new point between p0 and p1 Vec2d p0p1 = (p1 - *p0).cast(); double p0p1_size = p0p1.norm(); - //1.9.5 double p0pa_dist = dist_left_over; for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) @@ -250,14 +243,14 @@ static std::deque split_polyline_by_degree(const Polyline &p size_t poly_size = polyline_with_insert_points.size(); // QDS: merge degree in limited range //find first degee base - double degree_base = int(points_overhang[points_overhang.size() - 1] / min_degree_gap) * min_degree_gap + min_degree_gap; + double degree_base = int(points_overhang[points_overhang.size() - 1] / min_degree_gap_classic) * min_degree_gap_classic + min_degree_gap_classic; degree_base = degree_base > max_overhang_degree ? max_overhang_degree : degree_base; double short_poly_len = 0; for (int point_idx = points_overhang.size() - 2; point_idx > 0; --point_idx) { double degree = points_overhang[point_idx]; - if ( degree <= degree_base && degree >= degree_base - min_degree_gap ) + if ( degree <= degree_base && degree >= degree_base - min_degree_gap_classic ) continue; temp_copy.split_at_index(point_idx, &left, &right); @@ -265,7 +258,7 @@ static std::deque split_polyline_by_degree(const Polyline &p temp_copy = std::move(left); out.push_back(PolylineWithDegree(right, degree_base)); - degree_base = int(degree / min_degree_gap) * min_degree_gap + min_degree_gap; + degree_base = int(degree / min_degree_gap_classic) * min_degree_gap_classic + min_degree_gap_classic; degree_base = degree_base > max_overhang_degree ? max_overhang_degree : degree_base; } @@ -286,7 +279,7 @@ static void insert_point_to_line( double left_point_degree, { Line line_temp(left_point, right_point); double line_length = line_temp.length(); - if (std::abs(left_point_degree - right_point_degree) <= 0.5 * min_degree_gap || line_length lines; - AABBTreeIndirect::Tree<2, double> tree; - -public: - OverhangDistancer(const Polygons layer_polygons) - { - for (const Polygon &island : layer_polygons) { - for (const auto &line : island.lines()) { - lines.emplace_back(line.a.cast(), line.b.cast()); - } - } - tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); - } - - float distance_from_perimeter(const Vec2f &point) const - { - Vec2d p = point.cast(); - size_t hit_idx_out{}; - Vec2d hit_point_out = Vec2d::Zero(); - auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, hit_idx_out, hit_point_out); - if (distance < 0) { return std::numeric_limits::max(); } - - distance = sqrt(distance); - return distance; - } -}; - static void sampling_at_line_end(Polyline &poly, double mini_length, int insert_count) { @@ -374,6 +338,7 @@ static void sampling_at_line_end(Polyline &poly, double mini_length, int insert_ poly.append(end_point); } + static std::deque detect_overahng_degree(Polygons lower_polygons, Polylines middle_overhang_polyines, const double &lower_bound, @@ -455,7 +420,6 @@ std::pair PerimeterGenerator::dist_boundary(double width) return out; } -//1.9.5 static void detect_bridge_wall(const PerimeterGenerator &perimeter_generator, ExtrusionPaths &paths, const Polylines &remain_polines, ExtrusionRole role, double mm3_per_mm, float width, float height) { for (Polyline poly : remain_polines) { @@ -484,6 +448,7 @@ static void detect_bridge_wall(const PerimeterGenerator &perimeter_generator, Ex } } + static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) { // loops is an arrayref of ::Loop objects @@ -506,7 +471,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { loop_role = loop.is_contour? elrDefault: elrPerimeterHole; } - + if( loop.depth == 1 ) { if (loop_role == elrDefault) loop_role = elrSecondPerimeter; @@ -638,7 +603,6 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime 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)) { - //1.9.5 //detect if the overhang perimeter is bridge detect_bridge_wall(perimeter_generator, paths, @@ -648,7 +612,6 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime perimeter_generator.overhang_flow.width(), perimeter_generator.overhang_flow.height()); } else { - //1.9.5 detect_bridge_wall( perimeter_generator, paths, remain_polines, @@ -659,7 +622,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } } - + // 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()); @@ -733,99 +696,6 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime return out; } -static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, const ClipperLib_Z::Paths& clip, ClipperLib_Z::ClipType clipType) -{ - ClipperLib_Z::Clipper clipper; - 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& pt) { - // The clipping contour may be simplified by clipping it with a bounding box of "subject" path. - // The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are - // harmless to the result of the clipping operation, - // Both ends of each edge belong to the same source: Either they are from subject or from clipping path. - assert(e1bot.z() >= 0 && e1top.z() >= 0); - assert(e2bot.z() >= 0 && e2top.z() >= 0); - assert((e1bot.z() == 0) == (e1top.z() == 0)); - assert((e2bot.z() == 0) == (e2top.z() == 0)); - - // Start & end points of the clipped polyline (extrusion path with a non-zero width). - ClipperLib_Z::IntPoint start = e1bot; - ClipperLib_Z::IntPoint end = e1top; - if (start.z() <= 0 && end.z() <= 0) { - start = e2bot; - end = e2top; - } - - if (start.z() <= 0 && end.z() <= 0) { - // Self intersection on the source contour. - assert(start.z() == 0 && end.z() == 0); - pt.z() = 0; - } - else { - // Interpolate extrusion line width. - assert(start.z() > 0 && end.z() > 0); - - double length_sqr = (end - start).cast().squaredNorm(); - double dist_sqr = (pt - start).cast().squaredNorm(); - double t = std::sqrt(dist_sqr / length_sqr); - - pt.z() = start.z() + coord_t((end.z() - start.z()) * t); - } - }); - - clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); - clipper.AddPaths(clip, ClipperLib_Z::ptClip, true); - - ClipperLib_Z::PolyTree clipped_polytree; - ClipperLib_Z::Paths clipped_paths; - clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths); - - // Clipped path could contain vertices from the clip with a Z coordinate equal to zero. - // For those vertices, we must assign value based on the subject. - // This happens only in sporadic cases. - for (ClipperLib_Z::Path& path : clipped_paths) - for (ClipperLib_Z::IntPoint& c_pt : path) - if (c_pt.z() == 0) { - // Now we must find the corresponding line on with this point is located and compute line width (Z coordinate). - if (subject.size() <= 2) - continue; - - const Point pt(c_pt.x(), c_pt.y()); - Point projected_pt_min; - auto it_min = subject.begin(); - auto dist_sqr_min = std::numeric_limits::max(); - Point prev(subject.front().x(), subject.front().y()); - for (auto it = std::next(subject.begin()); it != subject.end(); ++it) { - Point curr(it->x(), it->y()); - Point projected_pt = pt.projection_onto(Line(prev, curr)); - if (double dist_sqr = (projected_pt - pt).cast().squaredNorm(); dist_sqr < dist_sqr_min) { - dist_sqr_min = dist_sqr; - projected_pt_min = projected_pt; - it_min = std::prev(it); - } - prev = curr; - } - - assert(dist_sqr_min <= SCALED_EPSILON); - assert(std::next(it_min) != subject.end()); - - const Point pt_a(it_min->x(), it_min->y()); - const Point pt_b(std::next(it_min)->x(), std::next(it_min)->y()); - const double line_len = (pt_b - pt_a).cast().norm(); - const double dist = (projected_pt_min - pt_a).cast().norm(); - c_pt.z() = coord_t(double(it_min->z()) + (dist / line_len) * double(std::next(it_min)->z() - it_min->z())); - } - - assert([&clipped_paths = std::as_const(clipped_paths)]() -> bool { - for (const ClipperLib_Z::Path& path : clipped_paths) - for (const ClipperLib_Z::IntPoint& pt : path) - if (pt.z() <= 0) - return false; - return true; - }()); - - return clipped_paths; -} struct PerimeterGeneratorArachneExtrusion { @@ -920,7 +790,6 @@ static void smooth_overhang_level(ExtrusionPaths &paths) } } -//1.9.5 static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_generator, ExtrusionPaths &paths, const ClipperLib_Z::Paths &path_overhang, const ExtrusionRole role, const Flow &flow) { for (ClipperLib_Z::Path path : path_overhang) { @@ -947,6 +816,9 @@ static void detect_brigde_wall_arachne(const PerimeterGenerator &perimeter_gener static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector& pg_extrusions) { + using ZPath = ClipperLib_Z::Path; + using ZPaths = ClipperLib_Z::Paths; + ExtrusionEntityCollection extrusion_coll; if (perimeter_generator.print_config->z_direction_outwall_speed_continuous) extrusion_coll.loop_node_range.first = perimeter_generator.loop_nodes->size(); @@ -980,97 +852,64 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p ExtrusionPaths paths; // detect overhanging/bridging perimeters - //1.9.5 if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) { - ClipperLib_Z::Path extrusion_path; + ClipperLib_Z::Path extrusion_path; extrusion_path.reserve(extrusion->size()); + + double nozzle_diameter = perimeter_generator.print_config->nozzle_diameter.get_at(perimeter_generator.config->wall_filament - 1); + Polygons lower_layer_polys = perimeter_generator.lower_slices_polygons(); + + coord_t max_extrusion_width = 0; BoundingBox extrusion_path_bbox; for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) { extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); extrusion_path_bbox.merge(Point(ej.p.x(), ej.p.y())); + max_extrusion_width = std::max(max_extrusion_width, ej.w); + } + extrusion_path_bbox.inflated(max_extrusion_width+scale_(nozzle_diameter)); + + Polygons new_lower_polys; + for (size_t idx = 0; idx < lower_layer_polys.size(); ++idx) { + auto new_poly = ClipperUtils::clip_clipper_polygon_with_subject_bbox(lower_layer_polys[idx], extrusion_path_bbox,true); + if (!new_poly.empty()) + new_lower_polys.emplace_back(new_poly); } - ClipperLib_Z::Paths lower_slices_paths; - { - lower_slices_paths.reserve(perimeter_generator.lower_slices_polygons().size()); - Points clipped; - extrusion_path_bbox.offset(SCALED_EPSILON); - for (const Polygon &poly : perimeter_generator.lower_slices_polygons()) { - clipped.clear(); - ClipperUtils::clip_clipper_polygon_with_subject_bbox(poly.points, extrusion_path_bbox, clipped); - if (!clipped.empty()) { - lower_slices_paths.emplace_back(); - ClipperLib_Z::Path &out = lower_slices_paths.back(); - out.reserve(clipped.size()); - for (const Point &pt : clipped) - out.emplace_back(pt.x(), pt.y(), 0); - } - } - } + lower_layer_polys = new_lower_polys; - ExtrusionPaths temp_paths; - // get non-overhang paths by intersecting this loop with the grown lower slices - extrusion_paths_append(temp_paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role, - is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + ZPath subject_path; + for (auto& ej : extrusion->junctions) + subject_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); + + ZPaths clip_paths; + for (auto& poly : lower_layer_polys) { + clip_paths.emplace_back(); + for (auto& p : poly) + clip_paths.back().emplace_back(p.x(), p.y(), 0); + } if (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) { - + bool is_external = extrusion->inset_idx == 0; Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow; - std::map> clipper_serise; - - std::map recognization_paths; - for (const ExtrusionPath &path : temp_paths) { - if (recognization_paths.count(path.width)) - recognization_paths[path.width].emplace_back(std::move(path)); - else - recognization_paths.insert(std::pair(path.width, {std::move(path)})); - } - - for (const auto &it : recognization_paths) { - Polylines be_clipped; - - for (const ExtrusionPath &p : it.second) { - be_clipped.emplace_back(std::move(p.polyline)); - } - - BoundingBox extrusion_bboxs = get_extents(be_clipped); - //ExPolygons lower_slcier_chopped = *perimeter_generator.lower_slices; - Polygons lower_slcier_chopped=ClipperUtils::clip_clipper_polygons_with_subject_bbox(*perimeter_generator.lower_slices, extrusion_bboxs, true); - - double start_pos = -it.first * 0.5; - double end_pos = 0.5 * it.first; - - Polylines remain_polylines; - std::vector degree_polygons; - for (int j = 0; j < overhang_sampling_number; j++) { - Polygons limiton_polygons = offset(lower_slcier_chopped, float(scale_(start_pos + (j + 0.5) * (end_pos - start_pos) / (overhang_sampling_number - 1)))); - - Polylines inside_polines = j == 0 ? intersection_pl_2(be_clipped, limiton_polygons) : intersection_pl_2(remain_polylines, limiton_polygons); - - remain_polylines = j == 0 ? diff_pl_2(be_clipped, limiton_polygons) : diff_pl_2(remain_polylines, limiton_polygons); - - extrusion_paths_append(paths, std::move(inside_polines), j, int(0), role, it.second.front().mm3_per_mm, it.second.front().width, it.second.front().height); - - if (remain_polylines.size() == 0) break; - } - - if (remain_polylines.size() != 0) { - extrusion_paths_append(paths, std::move(remain_polylines), overhang_sampling_number - 1, int(0), erOverhangPerimeter, it.second.front().mm3_per_mm, it.second.front().width, it.second.front().height); - } - } - - } else { - paths = std::move(temp_paths); - + ExtrusionRole role = is_external ? ExtrusionRole::erExternalPerimeter : ExtrusionRole::erPerimeter; + paths = detect_overhang_degree(flow, role, lower_layer_polys, clip_paths, subject_path, nozzle_diameter); + } + else { + ExtrusionPaths temp_paths; + ZPaths path_non_overhang = clip_extrusion(subject_path, clip_paths, ClipperLib_Z::ctIntersection); + // get non-overhang paths by intersecting this loop with the grown lower slices + extrusion_paths_append(temp_paths, path_non_overhang, role, + is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + paths = std::move(temp_paths); } // get 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 - //1.9.5 // detect if the overhang perimeter is bridge - ClipperLib_Z::Paths path_overhang = clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctDifference); + ZPaths path_overhang = clip_extrusion(subject_path, clip_paths, ClipperLib_Z::ctDifference); bool zero_z_support = (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(zero_z_support) detect_brigde_wall_arachne(perimeter_generator, paths, path_overhang, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); else @@ -1119,8 +958,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p } } - } - else { + } else { extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); } @@ -1185,6 +1023,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p return extrusion_coll; } + static Polygons to_polygons_with_flag(const ExPolygon& src, const bool contour_flag, const std::vector& holes_flag, std::vector& flags_out) { Polygons polygons; @@ -1211,7 +1050,12 @@ void PerimeterGenerator::process_classic() m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); + coord_t ext_perimeter_spacing2; + // Orca: ignore precise_outer_wall if wall_sequence is not InnerOuter + if (config->precise_outer_wall) + ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.width() + this->perimeter_flow.width())); + else + ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); @@ -1227,7 +1071,7 @@ void PerimeterGenerator::process_classic() // For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent // external loops (which is the correct way) instead of using ext_perimeter_spacing2 // which is the spacing between external and internal, which is not correct - // and would make the collapsing (thus the details resolution) dependent on + // and would make the collapsing (thus the details resolution) dependent on // internal flow which is unrelated. coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); @@ -1849,12 +1693,15 @@ void PerimeterGenerator::process_arachne() // we need to process each island separately because we might have different // extra perimeters for each one + bool apply_precise_outer_wall = config->precise_outer_wall; for (const Surface& surface : this->slices->surfaces) { // detect how many perimeters must be generated for this island int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops bool apply_circle_compensation = true; - ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled. + ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), + apply_precise_outer_wall ? -float(ext_perimeter_width - ext_perimeter_spacing) : - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); int new_size = std::accumulate(last.begin(), last.end(), 0, [](int prev, const ExPolygon& expoly) { return prev + expoly.num_contours(); }); if (last.size() != 1 || new_size != surface.expolygon.num_contours()) apply_circle_compensation = false; @@ -1902,9 +1749,13 @@ void PerimeterGenerator::process_arachne() std::vector first_perimeters; ExPolygons infill_contour_by_one_wall; + coord_t wall_0_inset = 0; + if (apply_precise_outer_wall) + wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2); + // do detail check whether to enable one wall if (seperate_wall_generation) { - Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, 0, layer_height, input_params); + Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, wall_0_inset, layer_height, input_params); if (apply_circle_compensation) one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); @@ -1928,6 +1779,7 @@ void PerimeterGenerator::process_arachne() seperate_wall_generation = should_enable_top_one_wall(last, top_expolys_by_one_wall); } + if (seperate_wall_generation) { // only generate one wall around top areas // keep the first generated wall @@ -1937,7 +1789,7 @@ void PerimeterGenerator::process_arachne() if (loop_number > 0) { last = diff_ex(infill_contour_by_one_wall, top_expolys_by_one_wall); last_p = to_polygons(last); // disable contour compensation in remaining walls - Arachne::WallToolPaths paths_new(last_p, perimeter_spacing, perimeter_spacing, loop_number, 0, layer_height, input_params); + Arachne::WallToolPaths paths_new(last_p, perimeter_spacing, perimeter_spacing, loop_number, wall_0_inset, layer_height, input_params); auto new_perimeters = paths_new.getToolPaths(); for (auto& perimeters : new_perimeters) { if (!perimeters.empty()) { @@ -1954,7 +1806,7 @@ void PerimeterGenerator::process_arachne() else { if (is_one_wall) { // plan wall width as one wall - Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, 0, layer_height, input_params); + Arachne::WallToolPaths one_wall_paths(last_p, ext_perimeter_spacing, perimeter_spacing, 1, wall_0_inset, layer_height, input_params); if (apply_circle_compensation) one_wall_paths.EnableHoleCompensation(true, circle_poly_indices); total_perimeters = one_wall_paths.getToolPaths(); @@ -1962,7 +1814,7 @@ void PerimeterGenerator::process_arachne() } else { // plan wall width as noraml - Arachne::WallToolPaths normal_paths(last_p, ext_perimeter_spacing, perimeter_spacing, loop_number + 1, 0, layer_height, input_params); + Arachne::WallToolPaths normal_paths(last_p, ext_perimeter_spacing, perimeter_spacing, loop_number + 1, wall_0_inset, layer_height, input_params); if (apply_circle_compensation) normal_paths.EnableHoleCompensation(true, circle_poly_indices); total_perimeters = normal_paths.getToolPaths(); @@ -1973,7 +1825,6 @@ void PerimeterGenerator::process_arachne() else { infill_contour = last; } - #ifdef ARACHNE_DEBUG { static int iRun = 0; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 1a3c0de..58c15fb 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -20,8 +20,8 @@ class Polyline : public MultiPoint { public: Polyline() {}; Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {} - Polyline(Polyline &&other) : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {} - Polyline(std::initializer_list list) : MultiPoint(list) { + Polyline(Polyline &&other) noexcept : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {} + Polyline(std::initializer_list list) : MultiPoint(list) { fitting_result.clear(); } explicit Polyline(const Point &p1, const Point &p2) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f6655fd..43c3344 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -153,6 +153,51 @@ static const std::unordered_map pre_family_model_map { { "SL1", "SL1" }, }}; + +// 中间版本兼容性处理,如果是nil值,先改成default值,再进行扩展 +void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults) +{ + constexpr int default_param_length = 1; + int filament_variant_length = default_param_length; + int process_variant_length = default_param_length; + int machine_variant_length = default_param_length; + + if(config.has("filament_extruder_variant")) + filament_variant_length = config.option("filament_extruder_variant")->size(); + if(config.has("print_extruder_variant")) + process_variant_length = config.option("print_extruder_variant")->size(); + if(config.has("printer_extruder_variant")) + machine_variant_length = config.option("printer_extruder_variant")->size(); + + auto replace_nil_and_resize = [&](const std::string & key, int length){ + ConfigOption* raw_ptr = config.option(key); + ConfigOptionVectorBase* opt_vec = static_cast(raw_ptr); + if(set_nil_to_default && raw_ptr->is_nil() && defaults.has(key) && std::find(filament_extruder_override_keys.begin(), filament_extruder_override_keys.end(), key) == filament_extruder_override_keys.end()){ + opt_vec->clear(); + opt_vec->resize(length, defaults.option(key)); + } + else{ + opt_vec->resize(length, raw_ptr); + } + }; + + for(auto& key :config.keys()){ + if(auto iter = print_options_with_variant.find(key); iter != print_options_with_variant.end()){ + replace_nil_and_resize(key, process_variant_length); + } + else if(auto iter = filament_options_with_variant.find(key); iter != filament_options_with_variant.end()){ + replace_nil_and_resize(key, filament_variant_length); + } + else if(auto iter = printer_options_with_variant_1.find(key); iter != printer_options_with_variant_1.end()){ + replace_nil_and_resize(key, machine_variant_length); + } + else if(auto iter = printer_options_with_variant_2.find(key); iter != printer_options_with_variant_2.end()){ + replace_nil_and_resize(key, machine_variant_length * 2); + } + } +} + + VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all) { static const std::string printer_model_key = "printer_model:"; @@ -851,8 +896,10 @@ static std::vector s_Preset_print_options { "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", "bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle","infill_shift_step", "infill_rotate_step", "symmetric_infill_y_axis", + "seam_position", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_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", "minimum_sparse_infill_area", "reduce_infill_retraction", "ironing_pattern", "ironing_type", "ironing_flow", "ironing_speed", "ironing_spacing","ironing_direction", "ironing_inset", "max_travel_detour_distance", @@ -878,13 +925,14 @@ static std::vector s_Preset_print_options { "bridge_no_support", "thick_bridges", "max_bridge_length", "print_sequence", "filename_format", "wall_filament", "support_bottom_z_distance", "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", - "ooze_prevention", "standby_temperature_delta", "interface_shells", "line_width", "initial_layer_line_width", - "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", + "ooze_prevention", "standby_temperature_delta", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", + "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", + "skin_infill_line_width","skeleton_infill_line_width", "top_surface_line_width", "support_line_width", "infill_wall_overlap", "bridge_flow", "elefant_foot_compensation", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_enable_framework", "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points","prime_tower_max_speed", "prime_tower_rib_wall","prime_tower_extra_rib_length","prime_tower_rib_width","prime_tower_fillet_wall","prime_tower_infill_gap","prime_tower_lift_speed","prime_tower_lift_height", - "enable_circle_compensation", "circle_compensation_manual_offset", "apply_scarf_seam_on_circles", + "prime_tower_flat_ironing","enable_circle_compensation", "circle_compensation_manual_offset", "apply_scarf_seam_on_circles", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "flush_into_infill", "flush_into_objects", "flush_into_support","process_notes", // QDS @@ -907,7 +955,7 @@ static std::vector s_Preset_print_options { "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"*/, + "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" @@ -915,8 +963,9 @@ static std::vector s_Preset_print_options { ,"seal" }; -static std::vector s_Preset_filament_options { - /*"filament_colour", */ "default_filament_colour","required_nozzle_HRC","filament_diameter", "filament_type", "filament_soluble", "filament_is_support","filament_scarf_seam_type", "filament_scarf_height", "filament_scarf_gap","filament_scarf_length", +static std::vector s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "filament_type", + "filament_soluble", "filament_is_support", "filament_printable", "filament_scarf_seam_type", "filament_scarf_height", + "filament_scarf_gap", "filament_scarf_length", "filament_max_volumetric_speed", "impact_strength_z", "filament_ramming_volumetric_speed", "filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower", "nozzle_temperature", "nozzle_temperature_initial_layer", @@ -945,7 +994,8 @@ static std::vector s_Preset_filament_options { "enable_pressure_advance", "pressure_advance", "chamber_temperatures","filament_notes", "filament_long_retractions_when_cut","filament_retraction_distances_when_cut","filament_shrink", //QDS filament change length while the extruder color - "filament_change_length","filament_prime_volume", + "filament_change_length","filament_prime_volume","filament_flush_volumetric_speed","filament_flush_temp", + "long_retractions_when_ec", "retraction_distances_when_ec", //w13 "additional_cooling_fan_speed_unseal" //w14 @@ -968,7 +1018,7 @@ static std::vector s_Preset_printer_options { "single_extruder_multi_material", "machine_start_gcode", "machine_end_gcode","printing_by_object_gcode","before_layer_change_gcode", "layer_change_gcode", "time_lapse_gcode", "change_filament_gcode", "printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type", "printable_height", "extruder_printable_height", "extruder_clearance_dist_to_rod", "extruder_clearance_max_radius","extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", - "nozzle_height", "unprintable_filament_types", "master_extruder_id", + "nozzle_height", "master_extruder_id", "default_print_profile", "inherits", "silent_mode", // QDS @@ -995,6 +1045,8 @@ static std::vector s_Preset_printer_options { //y61 , "box_id" , "is_support_timelapse" + //y65 + , "is_support_multi_box" }; static std::vector s_Preset_sla_print_options { @@ -1278,6 +1330,7 @@ void PresetCollection::load_presets( if (inherit_preset) { preset.config = inherit_preset->config; preset.filament_id = inherit_preset->filament_id; + extend_default_config_length(config, false, {}); preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); } else { @@ -1290,6 +1343,7 @@ void PresetCollection::load_presets( // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. preset.config = default_preset.config; preset.config.apply(std::move(config)); + extend_default_config_length(preset.config, true, default_preset.config); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id; @@ -1854,6 +1908,9 @@ bool PresetCollection::load_user_preset(std::string name, std::map *key_set1 = nullptr, *key_set2 = nullptr; @@ -1861,8 +1918,10 @@ bool PresetCollection::load_user_preset(std::string name, std::map& 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: diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 9fed152..7a1ba5d 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -83,7 +83,16 @@ PresetBundle::PresetBundle() this->filaments.default_preset().compatible_printers_condition(); this->filaments.default_preset().inherits(); // Set all the nullable values to nils. - this->filaments.default_preset().config.null_nullables(); + { + auto& default_config = this->filaments.default_preset().config; + 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()) + continue; + opt->deserialize("nil",ForwardCompatibilitySubstitutionRule::Disable); + } + } this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true); this->sla_materials.default_preset().compatible_printers_condition(); @@ -334,7 +343,7 @@ Semver PresetBundle::get_vendor_profile_version(std::string vendor_name) return result_ver; } -std::optional PresetBundle::get_filament_by_filament_id(const std::string& filament_id) const +std::optional PresetBundle::get_filament_by_filament_id(const std::string& filament_id, const std::string& printer_name) const { if (filament_id.empty()) return std::nullopt; @@ -365,7 +374,20 @@ std::optional PresetBundle::get_filament_by_filament_id(const info.box_temp_range_high = config.option("box_temperature_range_high")->values[0]; if (config.has("box_temperature_range_low")) info.box_temp_range_low = config.option("box_temperature_range_low")->values[0]; - return info; + if(config.has("temperature_vitrification")) + info.temperature_vitrification = config.option("temperature_vitrification")->values[0]; + + if (!printer_name.empty()) { + std::vector compatible_printers = config.option("compatible_printers")->values; + auto iter = std::find(compatible_printers.begin(), compatible_printers.end(), printer_name); + if (iter != compatible_printers.end() && config.has("filament_printable")) { + info.filament_printable = config.option("filament_printable")->values[0]; + return info; + } + } + else { + return info; + } } } return std::nullopt; @@ -802,10 +824,10 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s boost::optional version = Semver::parse(version_str); if (!version) return false; Semver app_version = *(Semver::parse(SLIC3R_VERSION)); - if (version->maj() > app_version.maj()) { + /*if (version->maj() > app_version.maj()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Preset incompatibla, not loading: " << name; return false; - } + }*/ PresetCollection *collection = nullptr; if (config.has("printer_settings_id")) @@ -842,6 +864,7 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s } if (inherit_preset) { new_config = inherit_preset->config; + new_config.apply(std::move(config)); } else { // We support custom root preset now auto inherits_config2 = dynamic_cast(inherits_config); @@ -853,8 +876,9 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. const Preset &default_preset = collection->default_preset_for(config); new_config = default_preset.config; + new_config.apply(std::move(config)); + extend_default_config_length(new_config, true, default_preset.config); } - new_config.apply(std::move(config)); Preset &preset = collection->load_preset(collection->path_from_name(name, inherit_preset == nullptr), name, std::move(new_config), false); if (key_values.find(QDT_JSON_KEY_FILAMENT_ID) != key_values.end()) diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index ae75f0e..2819986 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -57,12 +57,14 @@ struct FilamentBaseInfo std::string vendor; int nozzle_temp_range_low{ 220 }; int nozzle_temp_range_high{ 220 }; + int temperature_vitrification = INT_MAX; //y58 int box_temp_range_low{ 0 }; int box_temp_range_high{ 35 }; bool is_support{ false }; bool is_system{ true }; + int filament_printable = 3; }; // Bundle of Print + Filament + Printer presets. @@ -125,7 +127,7 @@ public: //QDS: get vendor's current version Semver get_vendor_profile_version(std::string vendor_name); - std::optional get_filament_by_filament_id(const std::string& filament_id) const; + std::optional get_filament_by_filament_id(const std::string& filament_id, const std::string& printer_name = std::string()) const; //QDS: project embedded preset logic PresetsConfigSubstitutions load_project_embedded_presets(std::vector project_presets, ForwardCompatibilitySubstitutionRule substitution_rule); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8dc118e..97f5d49 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -202,6 +202,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "filament_retraction_distances_when_cut", "grab_length", "bed_temperature_formula", + "filament_notes", + "process_notes", + "printer_notes", //w13 "additional_cooling_fan_speed_unseal", //w14 @@ -290,6 +293,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "prime_tower_lift_height" || opt_key == "prime_tower_brim_width" || opt_key == "prime_tower_skip_points" + || opt_key == "prime_tower_flat_ironing" || opt_key == "prime_tower_rib_wall" || opt_key == "prime_tower_extra_rib_length" || opt_key == "prime_tower_rib_width" @@ -321,6 +325,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n steps.emplace_back(psSkirtBrim); } else if (opt_key == "filament_soluble" || opt_key == "filament_is_support" + || opt_key == "filament_printable" || opt_key == "impact_strength_z" || opt_key == "filament_scarf_seam_type" || opt_key == "filament_scarf_height" @@ -1414,7 +1419,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (!validate_extrusion_width(object->config(), "support_line_width", layer_height, err_msg)) return {err_msg, object, "support_line_width"}; } - for (const char *opt_key : { "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "top_surface_line_width" }) + for (const char *opt_key : { "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "top_surface_line_width","skin_infill_line_width" ,"skeleton_infill_line_width"}) for (const PrintRegion ®ion : object->all_regions()) if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg)) return {err_msg, object, opt_key}; @@ -2010,12 +2015,6 @@ void Print::process(std::unordered_map* slice_time, bool } // check map valid both in auto and mannual mode std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) {return value - 1; }); - if (!ToolOrdering::check_tpu_group(used_filaments, filament_maps, &m_config)) { - int master_extruder_id = m_config.master_extruder_id.value - 1; // to 0 based - std::string nozzle_name = master_extruder_id == 0 ? L("left") : L("right"); - std::string exception_str = L("TPU is incompatible with BOX and must be printed seperately in the ") + nozzle_name + L(" nozzle.\nPlease adjust the filament group accordingly."); - throw Slic3r::RuntimeError(exception_str); - } // print_object_instances_ordering = sort_object_instances_by_max_z(print); print_object_instance_sequential_active = print_object_instances_ordering.begin(); @@ -2464,6 +2463,7 @@ int Print::get_hrc_by_nozzle_type(const NozzleType&type) nozzle_type_to_hrc = { {"hardened_steel",55}, {"stainless_steel",20}, + {"tungsten_carbide", 85}, {"brass",2}, {"undefine",0} }; @@ -2541,14 +2541,12 @@ std::vector> Print::get_physical_unprintable_filaments(const std:: if (extruder_num < 2) return physical_unprintables; - auto get_unprintable_extruder_id = [&](unsigned int filament_idx)->int { - if (m_config.unprintable_filament_types.empty()) - return -1; - for (int eid = 0; eid < m_config.unprintable_filament_types.values.size(); ++eid) { - std::vector extruder_unprintables = split_string(m_config.unprintable_filament_types.values[eid], ','); - auto iter = std::find(extruder_unprintables.begin(), extruder_unprintables.end(), m_config.filament_type.values[filament_idx]); - if (iter != extruder_unprintables.end()) - return eid; + auto get_unprintable_extruder_id = [&](unsigned int filament_idx) -> int { + int status = m_config.filament_printable.values[filament_idx]; + for (int i = 0; i < extruder_num; ++i) { + if (!(status >> i & 1)) { + return i; + } } return -1; }; @@ -2559,9 +2557,6 @@ std::vector> Print::get_physical_unprintable_filaments(const std:: if (m_config.filament_type.get_at(f) == "TPU") tpu_filaments.insert(f); } - if (tpu_filaments.size() > 1) { - throw Slic3r::RuntimeError(_u8L("Only supports up to one TPU filament.")); - } for (auto f : used_filaments) { int extruder_id = get_unprintable_extruder_id(f); @@ -4177,7 +4172,7 @@ int Print::load_cached_data(const std::string& directory) return ret; } -BoundingBoxf3 PrintInstance::get_bounding_box() { +BoundingBoxf3 PrintInstance::get_bounding_box() const { return print_object->model_object()->instance_bounding_box(*model_instance, false); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 50429e4..926dbf0 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -36,7 +36,7 @@ class SupportLayer; // QDS class TreeSupportData; class TreeSupport; -struct ExtrusionLayers; +class ExtrusionLayers; #define MARGIN_HEIGHT 1.5 #define MAX_OUTER_NOZZLE_RADIUS 4 @@ -206,7 +206,7 @@ struct PrintInstance // Shift of this instance's center into the world coordinates. Point shift; - BoundingBoxf3 get_bounding_box(); + BoundingBoxf3 get_bounding_box() const; Polygon get_convex_hull_2d(); // OrcaSlicer // diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 6c8a199..cb2cc08 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -288,6 +288,51 @@ static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &cu return full_config_diff; } +static bool is_printable_filament_changed(const DynamicPrintConfig& new_full_config, const Polygon& old_poly, const Polygon& new_poly) +{ + if (old_poly != new_poly) { + auto map_mode_opt = new_full_config.option>("filament_map_mode"); + if (map_mode_opt && map_mode_opt->value == FilamentMapMode::fmmManual) + return false; + + Pointfs printable_area = new_full_config.option("printable_area")->values; + std::vector extruder_areas = new_full_config.option("extruder_printable_area")->values; + Points pts; + for (auto pt : printable_area) { pts.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); } + Polygon printable_poly(pts); + + std::vector extruder_polys; + for (auto extruder_area : extruder_areas) { + pts.clear(); + for (auto pt : extruder_area) { pts.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); } + extruder_polys.emplace_back(Polygon(pts)); + } + + std::vector split_polys; + for (const Polygon poly : extruder_polys) { + Polygons res = diff(printable_poly, poly); + if (!res.empty()) { split_polys.emplace_back(res[0]); } + } + + Polygons all_extruder_polys = intersection({printable_poly}, extruder_polys); + if (!all_extruder_polys.empty()) split_polys.emplace_back(all_extruder_polys[0]); + + auto find_intersections = [](const Polygon &poly, const std::vector &contours) -> std::set { + std::set result; + for (size_t i = 0; i < contours.size(); ++i) { + if (!intersection(poly, contours[i]).empty()) { result.insert(static_cast(i)); } + } + return result; + }; + + std::set old_poly_ids = find_intersections(old_poly, split_polys); + std::set new_poly_ids = find_intersections(new_poly, split_polys); + + return old_poly_ids != new_poly_ids; + } + return false; +} + // Repository for solving partial overlaps of ModelObject::layer_config_ranges. // Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges. class LayerRanges @@ -1451,7 +1496,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Synchronize the content of instances. auto new_instance = model_object_new.instances.begin(); for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { - (*old_instance)->set_transformation((*new_instance)->get_transformation()); + if (is_printable_filament_changed(new_full_config, (*old_instance)->convex_hull_2d(), (*new_instance)->convex_hull_2d())) { + update_apply_status(this->invalidate_steps({psWipeTower, psGCodeExport})); + } + (*old_instance)->set_transformation((*new_instance)->get_transformation()); (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; (*old_instance)->printable = (*new_instance)->printable; } @@ -1514,9 +1562,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); if (status != PrintBase::APPLY_STATUS_UNCHANGED) { size_t extruder_num = new_full_config.option("nozzle_diameter")->size(); - if (extruder_num > 1) { - update_apply_status(this->invalidate_steps({ psWipeTower,psSkirtBrim, psGCodeExport })); - } update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); } print_objects_new.emplace_back((*it_old)->print_object); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index df1b13c..10480f8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -58,6 +58,29 @@ namespace Slic3r { #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) + +const std::vector filament_extruder_override_keys = { + // floats + "filament_retraction_length", + "filament_z_hop", + "filament_z_hop_types", + "filament_retract_lift_above", + "filament_retract_lift_below", + "filament_retraction_speed", + "filament_deretraction_speed", + "filament_retract_restart_extra", + "filament_retraction_minimum_travel", + // BBS: floats + "filament_wipe_distance", + // bools + "filament_retract_when_changing_layer", + "filament_wipe", + // percents + "filament_retract_before_wipe", + "filament_long_retractions_when_cut", + "filament_retraction_distances_when_cut" +}; + size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id) { if (filament_id < config.filament_map.size()) { @@ -162,7 +185,8 @@ static t_config_enum_values s_keys_map_InfillPattern { { "lightning", ipLightning }, { "crosshatch", ipCrossHatch}, { "zigzag", ipZigZag }, - { "crosszag", ipCrossZag } + { "crosszag", ipCrossZag }, + { "lockedzag", ipLockedZag } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) @@ -369,7 +393,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LayerSeq) static t_config_enum_values s_keys_map_NozzleType { { "undefine", int(NozzleType::ntUndefine) }, { "hardened_steel", int(NozzleType::ntHardenedSteel) }, - { "stainless_steel",int(NozzleType::ntStainlessSteel) }, + { "stainless_steel", int(NozzleType::ntStainlessSteel)}, + { "tungsten_carbide", int(NozzleType::ntTungstenCarbide)}, { "brass", int(NozzleType::ntBrass) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleType) @@ -593,12 +618,6 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatsNullable{0}); - def = this->add("unprintable_filament_types", coStrings); - def->label = L("Unprintable filament type"); - def->tooltip = L("Unprintable filament type"); - def->mode = comDevelop; - def->set_default_value(new ConfigOptionStrings{""}); - // Options used by physical printers def = this->add("preset_names", coStrings); @@ -1784,6 +1803,26 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(fmmAutoForFlush)); + def = this->add("filament_flush_temp", coInts); + def->label = L("Flush temperature"); + def->tooltip = L("temperature when flushing filament. 0 indicates the upper bound of the recommended nozzle temperature range"); + def->mode = comAdvanced; + def->nullable = true; + def->min = 0; + def->max = max_temp; + def->sidetext = "°C"; + def->set_default_value(new ConfigOptionIntsNullable{0}); + + def = this->add("filament_flush_volumetric_speed", coFloats); + def->label = L("Flush volumetric speed"); + def->tooltip = L("Volumetric speed when flushing filament. 0 indicates the max volumetric speed"); + def->mode = comAdvanced; + def->nullable = true; + def->min = 0; + def->max = 200; + def->sidetext = L("mm³/s"); + def->set_default_value(new ConfigOptionFloatsNullable{ 0 }); + def = this->add("filament_max_volumetric_speed", coFloats); def->label = L("Max volumetric speed"); def->tooltip = L("This setting stands for how much volume of filament can be melted and extruded per second. " @@ -1927,7 +1966,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("PPA-CF"); def->enum_values.push_back("PPA-GF"); def->enum_values.push_back("ABS-GF"); - def->enum_values.push_back("ASA-AERO"); + def->enum_values.push_back("ASA-Aero"); def->enum_values.push_back("PE"); def->enum_values.push_back("PP"); def->enum_values.push_back("EVA"); @@ -1999,6 +2038,16 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionBools{false}); + // defined in bits + // 0 means cannot support, 1 means support + // 0 bit: can support in left extruder + // 1 bit: can support in right extruder + def = this->add("filament_printable", coInts); + def->label = L("Filament printable"); + def->tooltip = L("The filament is printable in extruder"); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionInts{3}); + def = this->add("filament_prime_volume", coFloats); def->label = L("Filament prime volume"); def->tooltip = L("The volume of material to prime extruder on tower."); @@ -2104,6 +2153,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("crosshatch"); def->enum_values.push_back("zigzag"); def->enum_values.push_back("crosszag"); + def->enum_values.push_back("lockedzag"); def->enum_labels.push_back(L("Concentric")); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); @@ -2124,6 +2174,7 @@ void PrintConfigDef::init_fff_params() 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->enum_labels.push_back(L("Locked Zag")); def->set_default_value(new ConfigOptionEnum(ipCubic)); def = this->add("top_surface_acceleration", coFloats); @@ -2352,6 +2403,13 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("precise_outer_wall", coBool); + def->label = L("Precise wall"); + def->category = L("Quality"); + def->tooltip = L("Improve shell precision by adjusting outer wall spacing. This also improves layer consistency."); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionBool{false}); + def = this->add("gap_infill_speed", coFloats); def->label = L("Gap infill"); def->category = L("Speed"); @@ -2453,10 +2511,12 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("undefine"); def->enum_values.push_back("hardened_steel"); def->enum_values.push_back("stainless_steel"); + def->enum_values.push_back("tungsten_carbide"); def->enum_values.push_back("brass"); def->enum_labels.push_back(L("Undefine")); def->enum_labels.push_back(L("Hardened steel")); def->enum_labels.push_back(L("Stainless steel")); + def->enum_labels.push_back(L("Tungsten carbide")); def->enum_labels.push_back(L("Brass")); def->mode = comDevelop; def->nullable = true; @@ -2589,6 +2649,68 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("skeleton_infill_density", coPercent); + def->label = L("Skeleton infill density"); + def->category = L("Strength"); + def->tooltip = L("The remaining part of the model contour after removing a certain depth from the surface is called the skeleton. This parameter is used to adjust the density of this section." + "When two regions have the same sparse infill settings but different skeleton densities, their skeleton areas will develop overlapping sections." + "default is as same as infill density."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(15)); + + def = this->add("skin_infill_density", coPercent); + def->label = L("Skin infill density"); + def->category = L("Strength"); + def->tooltip = L("The portion of the model's outer surface within a certain depth range is called the skin. This parameter is used to adjust the density of this section." + "When two regions have the same sparse infill settings but different skin densities, This area will not be split into two separate regions." + "default is as same as infill density."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(15)); + + def = this->add("skin_infill_depth", coFloat); + def->label = L("Skin infill depth"); + def->category = L("Strength"); + def->tooltip = L("The parameter sets the depth of skin."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2.0)); + + def = this->add("infill_lock_depth", coFloat); + def->label = L("Infill lock depth"); + def->category = L("Strength"); + def->tooltip = L("The parameter sets the overlapping depth between the interior and skin."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("skin_infill_line_width", coFloat); + def->label = L("Skin line width"); + def->category = L("Strength"); + def->tooltip = L("Adjust the line width of the selected skin paths."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + def = this->add("skeleton_infill_line_width", coFloat); + def->label = L("Skeleton line width"); + def->category = L("Strength"); + def->tooltip = L("Adjust the line width of the selected skeleton paths."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + def = this->add("symmetric_infill_y_axis", coBool); def->label = L("Symmetric infill y axis"); def->category = L("Strength"); @@ -3286,6 +3408,11 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + //y65 + def = this->add("is_support_multi_box", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("printer_variant", coString); //def->label = L("Printer variant"); def->label = "Printer variant"; @@ -3415,6 +3542,21 @@ void PrintConfigDef::init_fff_params() def->nullable = true; def->set_default_value(new ConfigOptionFloatsNullable {18}); + def = this->add("long_retractions_when_ec", coBools); + def->label = L("Long retraction when extruder change"); + def->mode = comAdvanced; + def->nullable = true; + def->set_default_value(new ConfigOptionBoolsNullable {false}); + + def = this->add("retraction_distances_when_ec", coFloats); + def->label = L("Retraction distance when extruder change"); + def->mode = comAdvanced; + def->nullable = true; + def->min = 0; + def->max = 10; + def->sidetext = L("mm"); + def->set_default_value(new ConfigOptionFloatsNullable{10}); + def = this->add("retract_length_toolchange", coFloats); def->label = L("Length"); //def->full_label = L("Retraction Length (Toolchange)"); @@ -4683,6 +4825,10 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("prime_tower_flat_ironing", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("prime_tower_rib_wall", coBool); def->label = L("Rib wall"); def->tooltip = L("The wall of prime tower will add four ribs and make its " @@ -4856,21 +5002,12 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionEnum(GCodeThumbnailsFormat::PNG)); // Declare retract values for filament profile, overriding the printer's extruder profile. - for (const char *opt_key : { - // floats - "retraction_length", "z_hop", "z_hop_types", "retract_lift_above", "retract_lift_below","retraction_speed", "deretraction_speed", "retract_restart_extra", "retraction_minimum_travel", - // QDS: floats - "wipe_distance", - // bools - "retract_when_changing_layer", "wipe", - // percents - "retract_before_wipe", - "long_retractions_when_cut", - "retraction_distances_when_cut" - }) { - auto it_opt = options.find(opt_key); + for (auto& opt_key : filament_extruder_override_keys) { + const std::string filament_prefix = "filament_"; + std::string extruder_raw_key = opt_key.substr(opt_key.find(filament_prefix) + filament_prefix.length()); + auto it_opt = options.find(extruder_raw_key); assert(it_opt != options.end()); - def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); + def = this->add_nullable(opt_key, it_opt->second.type); def->label = it_opt->second.label; def->full_label = it_opt->second.full_label; def->tooltip = it_opt->second.tooltip; @@ -4881,10 +5018,10 @@ void PrintConfigDef::init_fff_params() def->min = it_opt->second.min; def->max = it_opt->second.max; //QDS: shown specific filament retract config because we hide the machine retract into comDevelop mode - if ((strcmp(opt_key, "retraction_length") == 0) || - (strcmp(opt_key, "z_hop") == 0)|| - (strcmp(opt_key, "long_retractions_when_cut") == 0)|| - (strcmp(opt_key, "retraction_distances_when_cut") == 0)) + if (opt_key =="filament_retraction_length"|| + opt_key=="filament_z_hop" || + opt_key== "filament_long_retractions_when_cut" || + opt_key=="filament_retraction_distances_when_cut") def->mode = comSimple; else def->mode = comAdvanced; @@ -5726,7 +5863,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); @@ -5866,8 +6003,12 @@ std::set filament_options_with_variant = { "filament_retract_before_wipe", "filament_long_retractions_when_cut", "filament_retraction_distances_when_cut", + "long_retractions_when_ec", + "retraction_distances_when_ec", "nozzle_temperature_initial_layer", - "nozzle_temperature" + "nozzle_temperature", + "filament_flush_volumetric_speed", + "filament_flush_temp" }; @@ -6172,7 +6313,6 @@ size_t DynamicPrintConfig::get_parameter_size(const std::string& param_name, siz if (nozzle_volume_type_opt) { volume_type_size = nozzle_volume_type_opt->values.size(); } - bool flag = (param_name == "nozzle_volume"); if (printer_options_with_variant_1.count(param_name) > 0) { return extruder_nums * volume_type_size; } @@ -7515,7 +7655,9 @@ std::map validate(const FullPrintConfig &cfg, bool und "internal_solid_infill_line_width", "top_surface_line_width", "support_line_width", - "initial_layer_line_width" }; + "initial_layer_line_width", + "skin_infill_line_width", + "skeleton_infill_line_width"}; for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) { std::string key(widths[i]); if (cfg.get_abs_value(key) > 2.5 * max_nozzle_diameter) { @@ -7621,6 +7763,12 @@ 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."; @@ -7668,6 +7816,13 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->cli_params = "option"; def->set_default_value(new ConfigOptionInt(0)); + def = this->add("export_png", coInt); + def->label = "Export png of plate"; + def->tooltip = "Export png of plate: 0-all plates, i-plate i, others-invalid"; + def->cli = "export-png"; + def->cli_params = "option"; + def->set_default_value(new ConfigOptionInt(-1)); + def = this->add("help", coBool); def->label = "Help"; def->tooltip = "Show command help."; @@ -8023,7 +8178,7 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->label = "Skip generating useless pick/top images into 3mf"; def->tooltip = "Skip generating useless pick/top images into 3mf"; def->cli_params = "option"; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("makerlab_name", coString); def->label = "MakerLab name"; @@ -8060,6 +8215,12 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->tooltip = "Allow filaments with high/low temperature to be printed together"; def->cli_params = "option"; def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("camera_view", coInt); + def->label = "Camera view angle for exporting png"; + def->tooltip = "Camera view angle for exporting png: 0-Iso, 1-Top_Front, 2-Left, 3-Right, 10-Iso_1, 11-Iso_2, 12-Iso_3"; + def->cli_params = "angle"; + def->set_default_value(new ConfigOptionInt(0)); } const CLIActionsConfigDef cli_actions_config_def; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 87292db..605a97a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -55,7 +55,7 @@ enum AuthorizationType { enum InfillPattern : int { ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal, - ipLightning, ipCrossHatch, ipZigZag, ipCrossZag,ipFloatingConcentric, + ipLightning, ipCrossHatch, ipZigZag, ipCrossZag,ipFloatingConcentric, ipLockedZag, ipCount, }; @@ -265,6 +265,7 @@ enum NozzleType { ntUndefine = 0, ntHardenedSteel, ntStainlessSteel, + ntTungstenCarbide, ntBrass, ntCount }; @@ -273,6 +274,7 @@ static std::unordered_mapNozzleTypeEumnToStr = { {NozzleType::ntUndefine, "undefine"}, {NozzleType::ntHardenedSteel, "hardened_steel"}, {NozzleType::ntStainlessSteel, "stainless_steel"}, + {NozzleType::ntTungstenCarbide, "tungsten_carbide"}, {NozzleType::ntBrass, "brass"} }; @@ -280,6 +282,7 @@ static std::unordered_mapNozzleTypeStrToEumn = { {"undefine", NozzleType::ntUndefine}, {"hardened_steel", NozzleType::ntHardenedSteel}, {"stainless_steel", NozzleType::ntStainlessSteel}, + {"tungsten_carbide", NozzleType::ntTungstenCarbide}, {"brass", NozzleType::ntBrass} }; @@ -398,6 +401,8 @@ static std::string get_bed_temp_1st_layer_key(const BedType type) return ""; } +extern const std::vector filament_extruder_override_keys; + // for parse extruder_ams_count extern std::vector> get_extruder_ams_count(const std::vector &strs); extern std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count); @@ -919,7 +924,11 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, symmetric_infill_y_axis)) ((ConfigOptionFloat, infill_shift_step)) ((ConfigOptionFloat, infill_rotate_step)) + ((ConfigOptionPercent, skeleton_infill_density)) + ((ConfigOptionPercent, skin_infill_density)) ((ConfigOptionPercent, sparse_infill_density)) + ((ConfigOptionFloat, infill_lock_depth)) + ((ConfigOptionFloat, skin_infill_depth)) ((ConfigOptionEnum, sparse_infill_pattern)) ((ConfigOptionEnum, fuzzy_skin)) ((ConfigOptionFloat, fuzzy_skin_thickness)) @@ -927,6 +936,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsNullable, gap_infill_speed)) ((ConfigOptionInt, sparse_infill_filament)) ((ConfigOptionFloat, sparse_infill_line_width)) + ((ConfigOptionFloat, skin_infill_line_width)) + ((ConfigOptionFloat, skeleton_infill_line_width)) ((ConfigOptionPercent, infill_wall_overlap)) ((ConfigOptionFloatsNullable, sparse_infill_speed)) //QDS @@ -975,6 +986,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, top_solid_infill_flow_ratio)) ((ConfigOptionFloat, initial_layer_flow_ratio)) ((ConfigOptionFloat, filter_out_gap_fill)) + ((ConfigOptionBool, precise_outer_wall)) //calib ((ConfigOptionFloat, print_flow_ratio)) // Orca: seam slopes @@ -1043,7 +1055,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionStrings, filament_type)) ((ConfigOptionBools, filament_soluble)) ((ConfigOptionStrings, filament_ids)) + ((ConfigOptionStrings, filament_vendor)) ((ConfigOptionBools, filament_is_support)) + ((ConfigOptionInts, filament_printable)) ((ConfigOptionEnumsGeneric, filament_scarf_seam_type)) ((ConfigOptionFloatsOrPercents, filament_scarf_height)) ((ConfigOptionFloatsOrPercents, filament_scarf_gap)) @@ -1074,6 +1088,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatsNullable, hotend_cooling_rate)) ((ConfigOptionFloatsNullable, hotend_heating_rate)) ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) + ((ConfigOptionFloatsNullable, filament_flush_volumetric_speed)) + ((ConfigOptionIntsNullable, filament_flush_temp)) // QDS ((ConfigOptionBool, scan_first_layer)) //w12 @@ -1094,6 +1110,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, enable_long_retraction_when_cut)) ((ConfigOptionFloatsNullable, retraction_distances_when_cut)) ((ConfigOptionBoolsNullable, long_retractions_when_cut)) + ((ConfigOptionFloatsNullable, retraction_distances_when_ec)) + ((ConfigOptionBoolsNullable, long_retractions_when_ec)) ((ConfigOptionFloatsNullable, z_hop)) // QDS ((ConfigOptionEnumsGenericNullable,z_hop_types)) @@ -1156,7 +1174,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionPoints, bed_exclude_area)) ((ConfigOptionPoints, head_wrap_detect_zone)) // QDS - ((ConfigOptionStrings, unprintable_filament_types)) ((ConfigOptionString, bed_custom_texture)) ((ConfigOptionString, bed_custom_model)) ((ConfigOptionEnum, curr_bed_type)) @@ -1262,6 +1279,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, prime_tower_rib_width)) ((ConfigOptionPercent, prime_tower_infill_gap)) ((ConfigOptionBool, prime_tower_skip_points)) + ((ConfigOptionBool, prime_tower_flat_ironing)) ((ConfigOptionBool, prime_tower_rib_wall)) ((ConfigOptionBool, prime_tower_fillet_wall)) //((ConfigOptionFloat, wipe_tower_bridging)) @@ -1310,7 +1328,9 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloats, filament_prime_volume)) //y61 ((ConfigOptionString, box_id)) - ((ConfigOptionBool, is_support_timelapse))) + ((ConfigOptionBool, is_support_timelapse)) + //y65 + ((ConfigOptionBool, is_support_multi_box))) // This object is mapped to Perl as Slic3r::Config::Full. PRINT_CONFIG_CLASS_DERIVED_DEFINE0( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c2fd911..2de07ff 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -423,6 +423,7 @@ void PrintObject::make_perimeters() #endif BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start"; +#if 1 tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), [this](const tbb::blocked_range& range) { @@ -432,6 +433,12 @@ void PrintObject::make_perimeters() } } ); +#else + for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { + m_print->throw_if_canceled(); + m_layers[layer_idx]->make_perimeters(); + } +#endif m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end"; @@ -1077,6 +1084,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "sparse_infill_filament" || opt_key == "solid_infill_filament" || opt_key == "sparse_infill_line_width" + || opt_key == "skin_infill_line_width" + || opt_key == "skeleton_infill_line_width" || opt_key == "infill_direction" || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bridge_angle" @@ -1095,9 +1104,13 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "detect_floating_vertical_shell") { steps.emplace_back(posInfill); } else if (opt_key == "sparse_infill_pattern" - || opt_key == "symmetric_infill_y_axis" - || opt_key == "infill_shift_step" - || opt_key == "infill_rotate_step") { + || opt_key == "symmetric_infill_y_axis" + || opt_key == "infill_shift_step" + || opt_key == "infill_rotate_step" + || opt_key == "skeleton_infill_density" + || opt_key == "skin_infill_density" + || opt_key == "infill_lock_depth" + || opt_key == "skin_infill_depth") { 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), @@ -1123,7 +1136,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "detect_overhang_wall" //QDS || opt_key == "enable_overhang_speed" - || opt_key == "detect_thin_wall") { + || opt_key == "detect_thin_wall" + || opt_key == "precise_outer_wall") { steps.emplace_back(posPerimeters); steps.emplace_back(posSupportMaterial); } else if (opt_key == "bridge_flow") { @@ -2780,6 +2794,7 @@ void PrintObject::bridge_over_infill() } ExPolygons new_internal_solids = to_expolygons(internal_solids); + new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end()); new_internal_solids = diff_ex(new_internal_solids, cut_from_infill); new_internal_solids = union_safety_offset_ex(new_internal_solids); for (const ExPolygon &ep : new_internal_solids) { diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 3c83dce..e9b2b42 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1911,7 +1911,7 @@ static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polyl out.reserve(polylines.size()); for (const FlipEdge &edge : edges) { Polyline &pl = polylines[edge.source_index]; - out.emplace_back(std::move(pl)); + out.emplace_back(pl); if (edge.p2 == pl.first_point().cast()) { // Polyline is flipped. out.back().reverse(); diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 546d038..d1bfff2 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -110,7 +110,7 @@ SlicingParameters SlicingParameters::create_from_config( params.min_layer_height = std::min(params.min_layer_height, params.layer_height); params.max_layer_height = std::max(params.max_layer_height, params.layer_height); - params.gap_raft_object = object_config.raft_contact_distance.value; + params.gap_raft_object = soluble_interface ? 0 : object_config.raft_contact_distance.value; //QDS params.gap_object_support = object_config.support_bottom_z_distance.value; params.gap_support_object = object_config.support_top_z_distance.value; diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 9eb0f72..23e6388 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -118,9 +118,10 @@ std::pair generate_interfa } return nullptr; }; + const bool istree = is_tree(config.support_type); tbb::parallel_for(tbb::blocked_range(0, int(intermediate_layers.size())), [&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params, - snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range& range) { + snug_supports, &interface_layers, &base_interface_layers, &istree](const tbb::blocked_range& range) { // Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below // this intermediate layer. // Index of the first top contact layer intersecting the current intermediate layer. @@ -139,27 +140,55 @@ std::pair generate_interfa Polygons polygons_bottom_contact_projected_interface; Polygons polygons_bottom_contact_projected_base; if (support_params.num_top_interface_layers > 0) { - // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces - coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)]->print_z; - coordf_t top_inteface_z = std::numeric_limits::max(); - if (support_params.num_top_base_interface_layers > 0) - // Some top base interface layers will be generated. - top_inteface_z = support_params.num_top_interface_layers_only() == 0 ? - // Only base interface layers to generate. - - std::numeric_limits::max() : - intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z; - // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON - // Collect the top contact areas above this intermediate layer, below top_z. - for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { - const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; - //FIXME maybe this adds one interface layer in excess? - if (top_contact_layer.bottom_z - EPSILON > top_z) - break; - polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, - // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers. - // For grid supports, merging of support regions will be performed by the projection into grid. - snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + if (istree) { + // Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces + coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)] + ->print_z; + coordf_t top_inteface_z = std::numeric_limits::max(); + if (support_params.num_top_base_interface_layers > 0) + // Some top base interface layers will be generated. + top_inteface_z = + support_params.num_top_interface_layers_only() == 0 ? + // Only base interface layers to generate. + -std::numeric_limits::max() : + intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)] + ->print_z; + // Move idx_top_contact_first up until above the current print_z. + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer) { + return layer->print_z >= intermediate_layer.print_z; + }); // - EPSILON + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++idx_top_contact) { + const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; + // FIXME maybe this adds one interface layer in excess? + if (top_contact_layer.bottom_z - EPSILON > top_z) break; + polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : + polygons_top_contact_projected_interface, + // For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support + // polygons of the other layers. For grid supports, merging of support regions will be performed by the projection into grid. + snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + } + } else { + // Move idx_top_contact_first up until above the current print_z. + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON + // Collect the top contact areas above this intermediate layer, below top_z. + for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) { + const SupportGeneratorLayer &top_contact_layer = *top_contacts[idx_top_contact]; + const bool is_top_contact = is_approx(top_contact_layer.bottom_z, intermediate_layers[num_intermediate - 1]->print_z); + if (is_top_contact) { + if (idx_intermediate_layer > num_intermediate - support_params.num_top_interface_layers) + polygons_append(polygons_top_contact_projected_interface, snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + else if (idx_intermediate_layer > num_intermediate - support_params.num_top_interface_layers - support_params.num_top_base_interface_layers) + polygons_append(polygons_top_contact_projected_base, snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + } else { + if (top_contact_layer.print_z - EPSILON < + intermediate_layers[std::min(int(idx_intermediate_layer + support_params.num_top_interface_layers - 1), num_intermediate - 1)]->print_z) + polygons_append(polygons_top_contact_projected_interface, snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + else if (top_contact_layer.print_z - EPSILON < intermediate_layers[std::min(int(idx_intermediate_layer + support_params.num_top_interface_layers - + 1 + support_params.num_top_base_interface_layers),num_intermediate - 1)]->print_z) + polygons_append(polygons_top_contact_projected_base, snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons); + } + } } } if (support_params.num_bottom_interface_layers > 0) { @@ -208,16 +237,24 @@ std::pair generate_interfa } }); - tbb::parallel_for(tbb::blocked_range(1, int(base_interface_layers.size())), [&base_interface_layers](const tbb::blocked_range &range) { - for (int layer_id = range.begin(); layer_id < range.end(); ++layer_id) { - auto &base_interface_layer = base_interface_layers[layer_id]; - if (!base_interface_layer) return; - - auto &lower_layer = base_interface_layers[layer_id - 1]; - if (!lower_layer) return; - if (base_interface_layer->polygons == lower_layer->polygons) base_interface_layer->up = true; - } - }); + if (support_params.num_top_base_interface_layers > 1 && !istree) + tbb::parallel_for(tbb::blocked_range(1, int(base_interface_layers.size())), + [&base_interface_layers, &top_contacts, &support_params, &intermediate_layers](const tbb::blocked_range &range) { + for (int layer_id = range.begin(); layer_id < range.end(); ++layer_id) { + auto &base_interface_layer = base_interface_layers[layer_id]; + if (!base_interface_layer) return; + + for (const auto &contact : top_contacts) { + if (is_approx(contact->bottom_z, + intermediate_layers[std::min(layer_id - 1 + support_params.num_top_interface_layers, intermediate_layers.size() - 1)] + ->print_z, 0.01) && + overlaps(base_interface_layer->polygons, contact->polygons)) { + base_interface_layer->up = true; + break; + } + } + } + }); // Compress contact_out, remove the nullptr items. // The parallel_for above may not have merged all the interface and base_interface layers @@ -583,8 +620,9 @@ void tree_supports_generate_paths( for (ExPolygon& expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5 * flow.scaled_width()))) { std::unique_ptr eec; ExPolygons regions_to_draw_inner_wall{expoly}; + double area = expoly.area(); if (support_params.tree_branch_diameter_double_wall_area_scaled > 0) - if (double area = expoly.area(); area > support_params.tree_branch_diameter_double_wall_area_scaled) { + if (area > support_params.tree_branch_diameter_double_wall_area_scaled) { BOOST_LOG_TRIVIAL(debug)<< "TreeSupports: double wall area: " << area<< " > " << support_params.tree_branch_diameter_double_wall_area_scaled; eec = std::make_unique(); // Don't reorder internal / external loops of the same island, always start with the internal loop. @@ -655,12 +693,12 @@ void tree_supports_generate_paths( } } } - if (d2min < sqr(flow.scaled_width() * 3.)) { + if (d2min < sqr(flow.scaled_width() * 3.) && Slic3r::area(regions_to_draw_inner_wall) + scale_(EPSILON) > area && !shrink_ex({expoly}, -2.*flow.scaled_width()).empty()) { // Try to cut an anchor from the closest_contour. // Both closest_contour and pl are CW oriented. pl.points.emplace_back(closest_point.cast()); const ClipperLib_Z::Path &path = *closest_contour; - double remaining_length = anchor_length - (seam_pt - closest_point).norm(); + double remaining_length = std::min(anchor_length - (seam_pt - closest_point).norm(), pl.length() / 12.); int i = closest_point_idx; int j = next_idx_modulo(i, *closest_contour); Vec2d pi(path[i].x(), path[i].y()); @@ -1436,7 +1474,7 @@ void generate_support_toolpaths( angles.push_back(support_params.interface_angle); std::vector interface_angles; - if (config.support_interface_pattern == smipRectilinearInterlaced) + if (config.support_interface_pattern == smipRectilinearInterlaced || config.support_interface_pattern == smipAuto) interface_angles.push_back(support_params.base_angle); interface_angles.push_back(support_params.interface_angle); diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 0766841..811ce05 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -33,19 +33,35 @@ struct SupportParameters { num_top_interface_layers : object_config.support_interface_bottom_layers; this->has_top_contacts = num_top_interface_layers > 0; this->has_bottom_contacts = num_bottom_interface_layers > 0; - if (this->soluble_interface_non_soluble_base) { - // Try to support soluble dense interfaces with non-soluble dense interfaces. - this->num_top_base_interface_layers = num_top_interface_layers > 0 ? 2 : 0; - this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2)); - } else { - // QDS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion - // Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if - // support_filament==0 - bool differnt_support_interface_filament = object_config.support_interface_filament != 0 && - object_config.support_interface_filament != object_config.support_filament; - this->num_top_base_interface_layers = differnt_support_interface_filament && num_top_interface_layers > 0 ? 2 : 0; - this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0; - } + if (is_tree(object_config.support_type)) { + if (this->soluble_interface_non_soluble_base) { + // Try to support soluble dense interfaces with non-soluble dense interfaces. + this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2)); + this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2)); + } else { + // QDS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion + // Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if + // support_filament==0 + bool differnt_support_interface_filament = object_config.support_interface_filament != 0 && + object_config.support_interface_filament != object_config.support_filament; + this->num_top_base_interface_layers = differnt_support_interface_filament ? 1 : 0; + this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0; + } + } else { + if (this->soluble_interface_non_soluble_base) { + // Try to support soluble dense interfaces with non-soluble dense interfaces. + this->num_top_base_interface_layers = num_top_interface_layers > 0 ? 2 : 0; + this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2)); + } else { + // QDS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion + // Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if + // support_filament==0 + bool differnt_support_interface_filament = object_config.support_interface_filament != 0 && + object_config.support_interface_filament != object_config.support_filament; + this->num_top_base_interface_layers = num_top_interface_layers > 0 ? differnt_support_interface_filament ? 2 : 1 : 0; + this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0; + } + } } this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height)); this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height)); @@ -130,6 +146,7 @@ struct SupportParameters { } support_base_pattern = object_config.support_base_pattern; + if (support_base_pattern == smpLightning && !is_tree(object_config.support_type)) support_base_pattern = smpRectilinear; if (support_base_pattern == smpDefault) { if (is_tree(object_config.support_type)) support_base_pattern = support_style == smsTreeHybrid ? smpRectilinear : smpNone; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 1bbef6f..7a3ce3a 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -876,25 +876,48 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) std::chrono::time_point t0{ clock_::now() }; // main part of overhang detection can be parallel tbb::concurrent_vector overhangs_all_layers(m_object->layer_count()); + + auto enforcers = m_object->slice_support_enforcers(); + auto blockers = m_object->slice_support_blockers(); + m_vertical_enforcer_points.clear(); + m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); + m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers, &m_vertical_enforcer_points); + auto trim_tail_empty = [](auto &vec) { + auto rit = std::find_if(vec.rbegin(), vec.rend(), [](const auto &e) { return !e.empty(); }); + if (rit != vec.rend()) { + vec.erase(rit.base(), vec.end()); + } else { + vec.clear(); + } + }; + trim_tail_empty(enforcers); + trim_tail_empty(blockers); + tbb::parallel_for(tbb::blocked_range(0, m_object->layer_count()), [&](const tbb::blocked_range& range) { for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) { if (m_object->print()->canceled()) break; - - if (!is_auto(stype) && layer_nr > enforce_support_layers) + // FIXME the param enforce_support_layers is not set yet + if (!(is_auto(stype) || (enforce_support_layers > 0 && layer_nr >= enforce_support_layers) || (layer_nr < enforcers.size() && !enforcers[layer_nr].empty()))) continue; Layer* layer = m_object->get_layer(layer_nr); if (layer->lower_layer == nullptr) { - for (auto& slice : layer->lslices_extrudable) { + ExPolygons curr_polys = layer->lslices_extrudable; + if (layer_nr < blockers.size() && !blockers[layer_nr].empty()) + curr_polys = diff_ex(curr_polys, offset_ex(union_(blockers[layer_nr]), scale_(radius_sample_resolution))); + for (auto& slice : curr_polys) { auto bbox_size = get_extents(slice).size(); if (!((bbox_size.x() > length_thresh_well_supported || bbox_size.y() > length_thresh_well_supported)) && g_config_support_sharp_tails) { layer->sharp_tails.push_back(slice); layer->sharp_tails_height.push_back(layer->height); } +#ifdef SUPPORT_TREE_DEBUG_TO_SVG + SVG::export_expolygons(debug_out_path("sharp_tail_orig_%.02f.svg", layer->print_z), {slice}); +#endif } continue; } @@ -905,10 +928,32 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) coordf_t support_offset_scaled = scale_(lower_layer_offset); ExPolygons& curr_polys = layer->lslices_extrudable; ExPolygons& lower_polys = lower_layer->lslices_extrudable; + ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - // normal overhang - ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); - overhangs_all_layers[layer_nr] = std::move(diff_ex(curr_polys, lower_layer_offseted)); + // add enforcer + ExPolygons enforced_overhangs; + ExPolygons blocker; + if (!enforcers.empty()) + enforced_overhangs = intersection_ex(diff_ex(layer->lslices_extrudable, lower_layer->lslices_extrudable), enforcers[layer_nr]); + if (is_auto(stype)) { + // normal overhang + overhangs_all_layers[layer_nr] = std::move(diff_ex(curr_polys, lower_layer_offseted)); + // if is auto, add blocker first + if (layer_nr < blockers.size() && !blockers[layer_nr].empty()) { + // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, + // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. + 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); + } + else if (layer_nr < enforcers.size() && lower_layer) { + if (!enforced_overhangs.empty()) { + // FIXME this is a hack to make enforcers work on steep overhangs. See STUDIO-7538. + enforced_overhangs = diff_ex(offset_ex(enforced_overhangs, enforcer_overhang_offset), lower_layer->lslices_extrudable); + overhangs_all_layers[layer_nr] = std::move(enforced_overhangs); + } + } double duration{ std::chrono::duration_cast(clock_::now() - t0).count() }; if (duration > 30 || overhangs_all_layers[layer_nr].size() > 100) { @@ -919,8 +964,11 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } if (is_auto(stype) && config_detect_sharp_tails) { + ExPolygons curr = curr_polys; + if (!blocker.empty()) curr = diff_ex(curr, blocker); + if (!enforced_overhangs.empty()) curr = union_ex(curr, enforced_overhangs); // QDS detect sharp tail - for (const ExPolygon& expoly : curr_polys) { + for (const ExPolygon& expoly : curr) { bool is_sharp_tail = false; // 1. nothing below // this is a sharp tail region if it's floating and non-ignorable @@ -1015,7 +1063,13 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) // QDS detect sharp tail const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails; const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height; - for (ExPolygon& expoly : layer->lslices_extrudable) { + + ExPolygons curr_polys = layer->lslices_extrudable; + if (!blockers.empty() && layer_nr < blockers.size() && !blockers[layer_nr].empty()) + curr_polys = diff_ex(curr_polys, offset_ex(union_(blockers[layer_nr]), scale_(radius_sample_resolution))); + if (!enforcers.empty() && layer_nr < enforcers.size() && !enforcers[layer_nr].empty()) + curr_polys = union_ex(curr_polys, intersection_ex(diff_ex(layer->lslices_extrudable, lower_layer->lslices_extrudable), enforcers[layer_nr])); + for (ExPolygon& expoly : curr_polys) { bool is_sharp_tail = false; float accum_height = layer->height; do { @@ -1088,13 +1142,8 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } } - auto enforcers = m_object->slice_support_enforcers(); - auto blockers = m_object->slice_support_blockers(); - m_vertical_enforcer_points.clear(); - m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers, &m_vertical_enforcer_points); - m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); - for (auto &cluster : overhangClusters) { + bool enforce_add = false; // remove small overhangs if (is_auto(stype) && config_remove_small_overhangs) { // 3. check whether the small overhang is sharp tail @@ -1106,6 +1155,16 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) break; } } + if (!enforcers.empty() && cluster.min_layer= enforcers.size()) break; + if (enforcers[layer_id].empty()) continue; + if (overlaps(to_expolygons(enforcers[layer_id]), cluster.layer_overhangs[layer_id])) { + enforce_add = true; + break; + } + } + } cluster.check_small_overhang(extrusion_width_scaled, length_thresh_small_overhang, radius_thresh_small_overhang); @@ -1121,11 +1180,15 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) #endif } - if (!cluster.is_type(Small)) { + if (!cluster.is_type(Small) || enforce_add) { cluster.check_polygon_node(m_support_params.thresh_big_overhang, m_ts_data->m_layer_outlines_below[cluster.min_layer - 1]); for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) { int layer_nr = it->first; ExPolygons overhangs = it->second; + if (cluster.is_type(Small)) { + if (layer_nr >= enforcers.size() || enforcers[layer_nr].empty() || !overlaps(to_expolygons(enforcers[layer_nr]), overhangs)) continue; + overhangs = intersection_ex(overhangs, enforcers[layer_nr]); + } for (const auto &overhang : overhangs) add_overhang(m_object->get_layer(layer_nr), overhang, cluster.type); } } @@ -1141,22 +1204,50 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) auto lower_layer = layer->lower_layer; if (support_critical_regions_only && is_auto(stype)) { - layer->loverhangs.clear(); // remove oridinary overhangs, only keep cantilevers and sharp tails (added later) - layer->loverhangs_with_type.clear(); - for (auto &cantilever : layer->cantilevers) add_overhang(layer, cantilever, OverhangType::Cantilever); + if (lower_layer == nullptr || enforcers.empty() || layer_nr >= enforcers.size() || enforcers[layer_nr].empty()) { + layer->loverhangs.clear(); + 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 loverhangs_new; + std::vector> loverhangs_with_type_new; + ExPolygons bigflat; + for (auto &overhang_part : layer->loverhangs_with_type) { + const auto &overhang = overhang_part.first; + auto type = overhang_part.second; + ExPolygons overhangs; + if (type & OverhangType::Cantilever || type & OverhangType::SharpTail) + overhangs = {overhang}; + else if (overlaps(enforced_overhangs, overhang)) { + overhangs = intersection_ex(enforced_overhangs, overhang); + } + if (!overhangs.empty()) { + if (type & OverhangType::BigFlat) + append(bigflat, overhangs); + else + for (const auto &expoly : overhangs) { + loverhangs_new.emplace_back(expoly); + loverhangs_with_type_new.emplace_back(std::make_pair(expoly, type)); + } + } + } + for (const auto &expoly : union_ex(bigflat)) { + loverhangs_new.emplace_back(expoly); + loverhangs_with_type_new.emplace_back(std::make_pair(expoly, OverhangType::BigFlat)); + } + layer->loverhangs = std::move(loverhangs_new); + layer->loverhangs_with_type = std::move(loverhangs_with_type_new); + } } // add support for every 1mm height for sharp tails - ExPolygons sharp_tail_overhangs; - if (lower_layer == nullptr) - sharp_tail_overhangs = layer->sharp_tails; - else { + if (lower_layer){ ExPolygons lower_layer_expanded = offset_ex(lower_layer->lslices_extrudable, SCALED_RESOLUTION); for (size_t i = 0; i < layer->sharp_tails_height.size();i++) { ExPolygons areas = diff_clipped({ layer->sharp_tails[i]}, lower_layer_expanded); float accum_height = layer->sharp_tails_height[i]; if (!areas.empty() && int(accum_height * 10) % 5 == 0) { - append(sharp_tail_overhangs, areas); has_sharp_tails = true; for (auto &area : areas) add_overhang(layer, area, accum_height < EPSILON ? (OverhangType::SharpTail | OverhangType::SharpTailLowesst) : OverhangType::SharpTail); @@ -1167,19 +1258,6 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } } - if (layer_nr < blockers.size()) { - // Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise, - // which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right. - ExPolygons blocker = offset_ex(union_(blockers[layer_nr]), scale_(radius_sample_resolution)); - auto old_overhangs_with_type = layer->loverhangs_with_type; - layer->loverhangs.clear(); - layer->loverhangs_with_type.clear(); - for (auto &poly : old_overhangs_with_type) { - ExPolygons polydiff = diff_ex(poly.first, blocker); - for (auto &diff : polydiff) add_overhang(layer, diff, OverhangType(poly.second)); - } - } - if (max_bridge_length > 0 && layer->loverhangs.size() > 0 && lower_layer) { // do not break bridge as the interface will be poor, see #4318 bool break_bridge = false; @@ -1187,19 +1265,6 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) } int nDetected = layer->loverhangs.size(); - // enforcers now follow same logic as normal support. See STUDIO-3692 - if (layer_nr < enforcers.size() && lower_layer) { - ExPolygons enforced_overhangs = intersection_ex(diff_ex(layer->lslices_extrudable, lower_layer->lslices_extrudable), enforcers[layer_nr]); - if (!enforced_overhangs.empty()) { - // FIXME this is a hack to make enforcers work on steep overhangs. See STUDIO-7538. - enforced_overhangs = diff_ex(offset_ex(enforced_overhangs, enforcer_overhang_offset), lower_layer->lslices_extrudable); - for (auto &poly : enforced_overhangs) add_overhang(layer, poly, OverhangType::Normal); - } - } - int nEnforced = layer->loverhangs.size(); - - //// add sharp tail overhangs - //append(layer->loverhangs, sharp_tail_overhangs); //// fill overhang_types //for (size_t i = 0; i < layer->loverhangs.size(); i++) @@ -1654,7 +1719,7 @@ void TreeSupport::generate_toolpaths() filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value); fill_params.dont_sort = true; } - if (m_support_params.contact_fill_pattern = ipRectilinear) { + if (m_support_params.contact_fill_pattern == ipRectilinear) { bool bridge_found = false; for (size_t i = 0; i < m_support_params.num_top_interface_layers; i++) { auto cur_ts_layer = m_object->get_support_layer(layer_id + i); @@ -2818,6 +2883,8 @@ void TreeSupport::drop_nodes() } if (node.to_buildplate || parts.empty()) //It's outside, so make it go towards the build plate. { + if (node.type ==eCircle && nodes_per_part[0][node.position]) + p_node->position += Point(1, 1); nodes_per_part[0][node.position] = p_node; continue; } @@ -2852,6 +2919,8 @@ void TreeSupport::drop_nodes() } } //Put it in the best one. + if (node.type == eCircle && nodes_per_part[closest_part + 1][node.position]) + p_node->position += Point(1, 1); nodes_per_part[closest_part + 1][node.position] = p_node; //Index + 1 because the 0th index is the outside part. } @@ -3047,6 +3116,9 @@ void TreeSupport::drop_nodes() p_node->origin_area = node.overhang.area(); densify_polygon(p_node->overhang.contour, 2.); } + if (m_support_params.num_top_interface_layers > 0 && obj_layer_nr_next > 0 && node.support_roof_layers_below == 1 && + node.distance_to_top >= m_support_params.num_top_interface_layers) + overhangs_next = safe_offset_inc(overhangs_next, scale_(max_move_distance), get_collision(0, obj_layer_nr_next), scale_(MIN_BRANCH_RADIUS * 1.75), 0, 1); for(auto& overhang:overhangs_next) { if (overhang.empty()) continue; if (overhang.area() > node.origin_area / 2. && overhang.area() > SQ(scale_(10.))) { @@ -3060,7 +3132,8 @@ void TreeSupport::drop_nodes() } } // if the part would fall straight to th buildplate, shrink it a little - if (overhang.area() > node.origin_area / 2. && overhang.area() > double(SQ(scale_(10.)))) { + if (node.support_roof_layers_below<0 && overhang.area() > node.origin_area / 2. && + overhang.area() > double(SQ(scale_(10.)))) { ExPolygons shrink_overhangs = union_ex(shrink_ex(safe_union({overhang}), double(scale_(max_move_distance / 2.)))); if (shrink_overhangs.size() == 1 && shrink_overhangs[0].area() > double(SQ(scale_(10.))) && !overlaps({overhang}, m_ts_data->m_layer_outlines_below[obj_layer_nr_next])) { diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index 67d62d7..8120a7b 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -61,7 +61,7 @@ struct TreeSupportMeshGroupSettings { config.support_interface_top_layers.value) * this->layer_height : 0; this->support_material_buildplate_only = config.support_on_build_plate_only; - this->support_xy_distance = scaled(config.support_object_xy_distance.value); + this->support_xy_distance = scaled(std::max(0.01, config.support_object_xy_distance.value)); this->support_xy_distance_1st_layer = scaled(config.support_object_first_layer_gap.value); // Separation of interfaces, it is likely smaller than support_xy_distance. this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled(0.5 * external_perimeter_width)); diff --git a/src/libslic3r/VariableWidth.cpp b/src/libslic3r/VariableWidth.cpp index 8837cd2..4b0eeb0 100644 --- a/src/libslic3r/VariableWidth.cpp +++ b/src/libslic3r/VariableWidth.cpp @@ -3,7 +3,7 @@ namespace Slic3r { //1.9.5 -ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyline, ExtrusionRole role, const Flow& flow, const float tolerance, const float merge_tolerance, int overhang) +ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyline, ExtrusionRole role, const Flow& flow, const float tolerance, const float merge_tolerance, double overhang) { ExtrusionMultiPath multi_path; ExtrusionPath path(role); diff --git a/src/libslic3r/VariableWidth.hpp b/src/libslic3r/VariableWidth.hpp index 8b81421..ee350c9 100644 --- a/src/libslic3r/VariableWidth.hpp +++ b/src/libslic3r/VariableWidth.hpp @@ -7,7 +7,7 @@ namespace Slic3r { //1.9.5 - ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyline, ExtrusionRole role, const Flow& flow, const float tolerance, const float merge_tolerance, int overhang); + ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyline, ExtrusionRole role, const Flow& flow, const float tolerance, const float merge_tolerance, double overhang); void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow& flow, std::vector& out); } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index ad6077c..127afda 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -75,6 +75,7 @@ static constexpr double INSET_OVERLAP_TOLERANCE = 0.4; static constexpr double EXTERNAL_INFILL_MARGIN = 3; static constexpr double BRIDGE_INFILL_MARGIN = 1; static constexpr double WIPE_TOWER_MARGIN = 15.; +static constexpr double WIPE_TOWER_MARGIN_AFTER_SLICING = 0.2; //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR)