diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f60512868..2a3d24274 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -344,14 +344,13 @@ static std::vector get_path_of_change_filament(const Print& print) return gcode_out; } - float get_wipe_avoid_pos_x(const Vec2f &wt_ori, float wt_width, float offset, bool is_default) + float get_wipe_avoid_pos_x(const Vec2f &wt_min, const Vec2f &wt_max, float offset) { float left = 100, right = 250; float default_value = 110.f; float a = 0.f, b = 0.f; - if (is_default) return default_value; - a = wt_ori.x() + wt_width + offset; - b = wt_ori.x() - offset; + a = wt_max.x() + offset; + b = wt_min.x() - offset; if (a > left && a < right) return a; if (b > left && b < right) return b; return default_value; @@ -455,40 +454,33 @@ static std::vector get_path_of_change_filament(const Print& print) outer_wall_volumetric_speed = filament_max_volumetric_speed; return outer_wall_volumetric_speed; } - // BBS // start_pos refers to the last position before the wipe_tower. // end_pos refers to the wipe tower's start_pos. // using the print coordinate system - std::string WipeTowerIntegration::generate_path_to_wipe_tower( - GCode &gcodegen, const Point &wipe_tower_left_front, const Point &start_pos, const Point &end_pos, int width, int depth, int brim_width) const + Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point& start_pos,const Point &end_pos , const BoundingBox& avoid_polygon , const BoundingBox& printer_bbx) const { - std::string gcode; - int alpha = scaled(2.f); // offset distance - BoundingBox wipe_tower_offset_bbx(wipe_tower_left_front, wipe_tower_left_front + Point(width, depth)); - wipe_tower_offset_bbx.offset(brim_width); - wipe_tower_offset_bbx.offset(alpha); - Polygon wipe_tower_offset_polygon = wipe_tower_offset_bbx.polygon(); - Polygon bed_polygon; - for (size_t i = 0; i < gcodegen.m_config.printable_area.values.size(); i++) { - bed_polygon.points.push_back( - wipe_tower_point_to_object_point(gcodegen, gcodegen.m_config.printable_area.values[i].cast() + Vec2f{m_plate_origin[0], m_plate_origin[1]})); - } // gcode coordinate system to printing coordinate system + Polyline res; + coord_t alpha = scaled(2.f); // offset distance + BoundingBox avoid_polygon_inner = avoid_polygon; + avoid_polygon_inner.offset(alpha); + coord_t width = avoid_polygon_inner.max[0] - avoid_polygon_inner.min[0]; + Polygon bed_polygon = printer_bbx.polygon(); Vec2f v(1, 0); // the first print direction of end_pos. - if (abs(end_pos[0] - wipe_tower_left_front[0]) < width / 2) v = -v; // judge whether the wipe tower's infill goes to the left or right. - // Judge whether the wipe_tower_bbx_offset is outside the bed_boundary. + if (abs(end_pos[0] - avoid_polygon_inner.min[0]) < width / 2) v = -v; // judge whether the wipe tower's infill goes to the left or right. + // Judge whether the avoid_polygon_inner is outside the printer_bbx. // If so, do nothing and just go directly to the end_pos. bool is_bbx_in_bed = true; - for (auto &wipe_tower_bbx_p : wipe_tower_offset_polygon.points) { + Points avoid_points = avoid_polygon_inner.polygon().points; + for (auto &wipe_tower_bbx_p : avoid_points) { if (ClipperLib::PointInPolygon(wipe_tower_bbx_p, bed_polygon.points) != 1) { is_bbx_in_bed = false; break; } } if (!is_bbx_in_bed) { - gcode += gcodegen.travel_to(end_pos, erMixed, "Move to start pos"); - check_add_eol(gcode); - return gcode; + res.points.push_back(end_pos); + return res; } // Ray-Line Segment Intersection auto ray_intersetion_line = [](const Vec2d &a, const Vec2d &v1, const Vec2d &b, const Vec2d &c) -> std::pair { @@ -533,13 +525,13 @@ static std::vector get_path_of_change_filament(const Print& print) len += (unscale(end_info.inter_p) - unscale(points[end])).squaredNorm(); return {path, len}; }; - // calculate the intersection point of end_pos along vector v with the wipe_tower_offset_polygon. + // calculate the intersection point of end_pos along vector v with the avoid_polygon. // store in inter_info. // represent this intersection by 'p'. Inter_info inter_info; - for (size_t i = 0; i < wipe_tower_offset_polygon.points.size(); i++) { - auto &a = wipe_tower_offset_polygon[i]; - auto &b = wipe_tower_offset_polygon[(i + 1) % wipe_tower_offset_polygon.points.size()]; + for (size_t i = 0; i < avoid_points.size(); i++) { + const auto &a = avoid_points[i]; + const auto &b = avoid_points[(i + 1) % avoid_points.size()]; auto [is_inter, inter_p] = ray_intersetion_line(unscale(end_pos), v.cast(), unscale(a), unscale(b)); if (is_inter) { inter_info.inter_idx0 = i; @@ -548,18 +540,17 @@ static std::vector get_path_of_change_filament(const Print& print) } } if (inter_info.inter_idx0 == -1) { - gcode += gcodegen.travel_to(end_pos, erMixed, "Move to start pos"); - check_add_eol(gcode); - return gcode; + res.points.push_back(end_pos); + return res; } - // calculate the other intersection of start_to_p with the wipe_tower_offset_polygon. + // calculate the other intersection of start_to_p with the avoid_polygon. // represent this intersection by 'p_'. Inter_info inter_info2; Linef start_to_p(unscale(start_pos), unscale(inter_info.inter_p)); - for (size_t i = 0; i < wipe_tower_offset_polygon.points.size(); i++) { + for (size_t i = 0; i < avoid_points.size(); i++) { if (i == inter_info.inter_idx0) continue; - Vec2d a = unscale(wipe_tower_offset_polygon.points[i]); - Vec2d b = unscale(wipe_tower_offset_polygon.points[(i + 1) % wipe_tower_offset_polygon.points.size()]); + Vec2d a = unscale(avoid_points[i]); + Vec2d b = unscale(avoid_points[(i + 1) % avoid_points.size()]); Linef tower_edge(a, b); Vec2d inter; if (line_alg::intersection(start_to_p, tower_edge, &inter)) { @@ -571,21 +562,18 @@ static std::vector get_path_of_change_filament(const Print& print) // if p_ does not exist, go directly to p. // else p travels along the shorter path on the wipe_tower_offset_polygon to p_ if (inter_info2.inter_idx0 == -1) { - gcode += gcodegen.travel_to(inter_info.inter_p, erMixed, "Move to start pos"); - check_add_eol(gcode); + res.points.push_back(inter_info.inter_p); } else { std::vector path; - auto [path1, len1] = calc_path_len(wipe_tower_offset_polygon.points, inter_info2, inter_info, true); - auto [path2, len2] = calc_path_len(wipe_tower_offset_polygon.points, inter_info2, inter_info, false); + auto [path1, len1] = calc_path_len(avoid_points, inter_info2, inter_info, true); + auto [path2, len2] = calc_path_len(avoid_points, inter_info2, inter_info, false); path = len1 < len2 ? path1 : path2; for (size_t i = 0; i < path.size(); i++) { - gcode += gcodegen.travel_to(path[i], erMixed, "Move to start pos"); - check_add_eol(gcode); + res.points.push_back(path[i]); } } - gcode += gcodegen.travel_to(end_pos, erMixed, "Move to start pos"); - check_add_eol(gcode); - return gcode; + res.points.push_back(end_pos); + return res; } std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const @@ -736,7 +724,13 @@ static std::vector get_path_of_change_filament(const Print& print) old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_filament_id) / filament_area); new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; - float wipe_avoid_pos_x = get_wipe_avoid_pos_x(m_wipe_tower_pos, gcodegen.m_config.prime_tower_width.value, 3.f + gcodegen.m_config.prime_tower_brim_width.value,false); + float wipe_avoid_pos_x = 0.f; + { + //set wipe_avoid_pos_x + Vec2f box_min = transform_wt_pt(m_wipe_tower_bbx.min.cast()); + Vec2f box_max = transform_wt_pt(m_wipe_tower_bbx.max.cast()); + wipe_avoid_pos_x = get_wipe_avoid_pos_x(box_min, box_max, 3.f); + } config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); config.set_key_value("relative_e_axis", new ConfigOptionBool(full_config.use_relative_e_distances)); @@ -817,9 +811,32 @@ static std::vector get_path_of_change_filament(const Print& print) Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]}; Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast() + plate_origin_2d.cast()); Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d); - std::string travel_to_wipe_tower_gcode = generate_path_to_wipe_tower(gcodegen, wipe_tower_point_to_object_point(gcodegen, m_wipe_tower_pos + plate_origin_2d), - gcode_last_pos2d_object, start_wipe_pos, scaled(gcodegen.m_config.prime_tower_width.value), - scaled(m_wipe_tower_depth), scaled(gcodegen.m_config.prime_tower_brim_width.value)); + BoundingBox avoid_bbx, printer_bbx; + { + //set printer_bbx + Pointfs bed_pointsf = gcodegen.m_config.printable_area.values; + Points bed_points; + for (auto p : bed_pointsf) { + bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast() + plate_origin_2d)); + } + printer_bbx = BoundingBox(bed_points); + } + { + //set avoid_bbx + avoid_bbx = scaled(m_wipe_tower_bbx); + Polygon avoid_points = avoid_bbx.polygon(); + for (auto& p : avoid_points.points) { + Vec2f pp = transform_wt_pt(unscale(p).cast()); + p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d); + } + avoid_bbx = BoundingBox(avoid_points.points); + } + std::string travel_to_wipe_tower_gcode; + Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, printer_bbx); + for (const auto &p : travel_polyline.points) { + travel_to_wipe_tower_gcode += gcodegen.travel_to(p, erMixed, "Move to start pos"); + check_add_eol(travel_to_wipe_tower_gcode); + } toolchange_gcode_str += travel_to_wipe_tower_gcode; gcodegen.set_last_pos(start_wipe_pos); } @@ -913,7 +930,8 @@ static std::vector get_path_of_change_filament(const Print& print) // All G1 commands should be translated and rotated. X and Y coords are // only pushed to the output when they differ from last time. // WT generator can override this by appending the never_skip_tag - if (line.find("G1 ") == 0) { + if (line.find("G1 ") == 0 || line.find("G2 ") == 0 || line.find("G3 ") == 0) { + std::string cur_gcode_start = line.find("G1 ") == 0 ? "G1 " : (line.find("G2 ") == 0 ? "G2 " : "G3 "); bool never_skip = false; auto it = line.find(WipeTower::never_skip_tag()); if (it != std::string::npos) { @@ -937,13 +955,13 @@ static std::vector get_path_of_change_filament(const Print& print) if (transformed_pos != old_pos || never_skip) { line = line_out.str(); std::ostringstream oss; - oss << std::fixed << std::setprecision(3) << "G1 "; + oss << std::fixed << std::setprecision(3) << cur_gcode_start; if (transformed_pos.x() != old_pos.x() || never_skip) oss << " X" << transformed_pos.x() - extruder_offset.x(); if (transformed_pos.y() != old_pos.y() || never_skip) oss << " Y" << transformed_pos.y() - extruder_offset.y(); oss << " "; - line.replace(line.find("G1 "), 3, oss.str()); + line.replace(line.find(cur_gcode_start), 3, oss.str()); old_pos = transformed_pos; } } @@ -2495,6 +2513,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); m_wipe_tower->set_wipe_tower_depth(print.get_wipe_tower_depth()); + m_wipe_tower->set_wipe_tower_bbx(print.get_wipe_tower_bbx()); // BBS // file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height")); file.write(m_writer.travel_to_z(initial_layer_print_height, "Move to the first layer height")); @@ -6004,7 +6023,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo // set volumetric speed of outer wall ,ignore per obejct,just use default setting float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(m_config, *m_print, new_filament_id, get_extruder_id(new_filament_id)); - float wipe_avoid_pos_x = get_wipe_avoid_pos_x(Vec2f{0, 0}, 0, 0, true); + float wipe_avoid_pos_x = 110.f; DynamicConfig dyn_config; dyn_config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id)); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 645b992e4..20009ff21 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -107,11 +107,12 @@ public: bool enable_timelapse_print() const { return m_enable_timelapse_print; } void set_wipe_tower_depth(float depth) { m_wipe_tower_depth = depth; } + void set_wipe_tower_bbx(const BoundingBoxf & bbx) { m_wipe_tower_bbx = bbx; } private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; - std::string generate_path_to_wipe_tower(GCode &gcodegen, const Point &wipe_tower_left_front, const Point &start_pos, const Point &end_pos, int width, int depth, int brim_width) const; + Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const; // Postprocesses gcode: rotates and moves G1 extrusions and returns result std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const; @@ -139,6 +140,8 @@ private: bool m_is_first_print; const PrintConfig * m_print_config; float m_wipe_tower_depth; + BoundingBoxf m_wipe_tower_bbx; + }; class ColorPrintColors diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 879521b6e..86c552294 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -16,6 +16,8 @@ namespace Slic3r { static const double wipe_tower_wall_infill_overlap = 0.0; static const double wipe_tower_wall_infill_overlap_new = -0.2; +static constexpr double WIPE_TOWER_RESOLUTION = 0.0375; +#define SCALED_WIPE_TOWER_RESOLUTION (WIPE_TOWER_RESOLUTION / SCALING_FACTOR) inline float align_round(float value, float base) { return std::round(value / base) * base; @@ -60,11 +62,230 @@ static bool is_valid_gcode(const std::string &gcode) return is_valid; } +Polygon chamfer_polygon(Polygon &polygon, double chamfer_dis = 2., double angle_tol = 30. / 180. * PI) +{ + if (polygon.points.size() < 3) return polygon; + Polygon res; + res.points.reserve(polygon.points.size() * 2); + int mod = polygon.points.size(); + double cos_angle_tol = abs(std::cos(angle_tol)); + + for (int i = 0; i < polygon.points.size(); i++) { + Vec2d a = unscaled(polygon.points[(i - 1 + mod) % mod]); + Vec2d b = unscaled(polygon.points[i]); + Vec2d c = unscaled(polygon.points[(i + 1) % mod]); + double ab_len = (a - b).norm(); + double bc_len = (b - c).norm(); + Vec2d ab = (b - a) / ab_len; + Vec2d bc = (c - b) / bc_len; + assert(ab_len != 0); + assert(bc_len != 0); + float cosangle = ab.dot(bc); + //std::cout << " angle " << acos(cosangle) << " cosangle " << cosangle << std::endl; + //std::cout << " ab_len " << ab_len << " bc_len " << bc_len << std::endl; + if (abs(cosangle) < cos_angle_tol) { + float real_chamfer_dis = std::min({chamfer_dis, ab_len / 2.1, bc_len / 2.1}); // 2.1 to ensure the points do not coincide + Vec2d left = b - ab * real_chamfer_dis; + Vec2d right = b + bc * real_chamfer_dis; + res.points.push_back(scaled(left)); + res.points.push_back(scaled(right)); + } else + res.points.push_back(polygon.points[i]); + } + res.points.shrink_to_fit(); + return res; +} + +Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI) +{ + if (polygon.points.size() < 3) return polygon; + Polygon res; + res.points.reserve(polygon.points.size() * 2); + int mod = polygon.points.size(); + double cos_angle_tol = abs(std::cos(angle_tol)); + + for (int i = 0; i < polygon.points.size(); i++) { + Vec2d a = unscaled(polygon.points[(i - 1 + mod) % mod]); + Vec2d b = unscaled(polygon.points[i]); + Vec2d c = unscaled(polygon.points[(i + 1) % mod]); + double ab_len = (a - b).norm(); + double bc_len = (b - c).norm(); + Vec2d ab = (b - a) / ab_len; + Vec2d bc = (c - b) / bc_len; + assert(ab_len != 0); + assert(bc_len != 0); + float cosangle = ab.dot(bc); + cosangle = std::clamp(cosangle, -1.f, 1.f); + bool is_ccw = cross2(ab, bc) > 0; + if (abs(cosangle) < cos_angle_tol) { + float real_rounding_dis = std::min({rounding, ab_len / 2.1, bc_len / 2.1}); // 2.1 to ensure the points do not coincide + Vec2d left = b - ab * real_rounding_dis; + Vec2d right = b + bc * real_rounding_dis; + //Point r_left = scaled(left); + //Point r_right = scaled(right); + // std::cout << " r_left " << r_left[0] << " " << r_left[1] << std::endl; + //std::cout << " r_right " << r_right[0] << " " << r_right[1] << std::endl; + { + float half_angle = std::acos(cosangle)/2.f; + //std::cout << " half_angle " << cos(half_angle) << std::endl; + + Vec2d dir = (right - left).normalized(); + dir = Vec2d{-dir[1], dir[0]}; + dir = is_ccw ? dir : -dir; + double dis = real_rounding_dis / sin(half_angle); + //std::cout << " dis " << dis << std::endl; + + Vec2d center = b + dir * dis; + double radius = (left - center).norm(); + ArcSegment arc(scaled(center), scaled(radius), scaled(left), scaled(right), is_ccw ? ArcDirection::Arc_Dir_CCW : ArcDirection::Arc_Dir_CW); + float sample_angle = 5.f / 180.f * PI; + int n = std::ceil(abs(arc.angle_radians) / sample_angle); + //std::cout << "start " << arc.start_point[0] << " " << arc.start_point[1] << std::endl; + //std::cout << "end " << arc.end_point[0] << " " << arc.end_point[1] << std::endl; + //std::cout << "start angle " << arc.polar_start_theta << " end angle " << arc.polar_end_theta << std::endl; + for (int j = 0; j < n; j++) { + float cur_angle = arc.polar_start_theta + (float)j/n * arc.angle_radians ; + //std::cout << " cur_angle " << cur_angle << std::endl; + if (cur_angle > 2 * PI) + cur_angle -= 2 * PI; + else if (cur_angle < 0) + cur_angle += 2 * PI; + Point tmp = arc.center + Point{arc.radius * std::cos(cur_angle), arc.radius *std::sin(cur_angle)}; + //std::cout << "j = " << j << std::endl; + //std::cout << "tmp = " << tmp[0]<<" "< 0; + if (abs(cosangle) < cos_angle_tol) { + float real_rounding_dis = std::min({rounding, ab_len / 2.1, bc_len / 2.1}); // 2.1 to ensure the points do not coincide + Vec2d left = b - ab * real_rounding_dis; + Vec2d right = b + bc * real_rounding_dis; + //Point r_left = scaled(left); + //Point r_right = scaled(right); + // std::cout << " r_left " << r_left[0] << " " << r_left[1] << std::endl; + // std::cout << " r_right " << r_right[0] << " " << r_right[1] << std::endl; + { + Vec2d center = b; + double radius = real_rounding_dis; + ArcSegment arc(scaled(center), scaled(radius), scaled(left), scaled(right), is_ccw ? ArcDirection::Arc_Dir_CCW : ArcDirection::Arc_Dir_CW); + float sample_angle = 5.f / 180.f * PI; + int n = std::ceil(abs(arc.angle_radians) / sample_angle); + // std::cout << "start " << arc.start_point[0] << " " << arc.start_point[1] << std::endl; + // std::cout << "end " << arc.end_point[0] << " " << arc.end_point[1] << std::endl; + // std::cout << "start angle " << arc.polar_start_theta << " end angle " << arc.polar_end_theta << std::endl; + for (int j = 0; j < n; j++) { + float cur_angle = arc.polar_start_theta + (float) j / n * arc.angle_radians; + // std::cout << " cur_angle " << cur_angle << std::endl; + if (cur_angle > 2 * PI) + cur_angle -= 2 * PI; + else if (cur_angle < 0) + cur_angle += 2 * PI; + Point tmp = arc.center + Point{arc.radius * std::cos(cur_angle), arc.radius * std::sin(cur_angle)}; + // std::cout << "j = " << j << std::endl; + // std::cout << "tmp = " << tmp[0]<<" "< ray_intersetion_line(const Vec2f &a, const Vec2f &v1, const Vec2f &b, const Vec2f &c) +{ + const Vec2f v2 = c - b; + double denom = cross2(v1, v2); + if (fabs(denom) < EPSILON) return {false, Vec2f(0, 0)}; + const Vec2f v12 = (a - b); + double nume_a = cross2(v2, v12); + double nume_b = cross2(v1, v12); + double t1 = nume_a / denom; + double t2 = nume_b / denom; + if (t1 >= 0 && t2 >= 0 && t2 <= 1.) { + // Get the intersection point. + Vec2f res = a + t1 * v1; + return std::pair(true, res); + } + return std::pair(false, Vec2f{0, 0}); +} +Polygon scale_polygon(const std::vector &points) { + Polygon res; + for (const auto &p : points) res.points.push_back(scaled(p)); + return res; +} +std::vector unscale_polygon(const Polygon& polygon) +{ + std::vector res; + for (const auto &p : polygon.points) res.push_back(unscaled(p)); + return res; +} + +Polygon generate_rectange(const Line &line, coord_t offset) +{ + Point p1 = line.a; + Point p2 = line.b; + + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + + double length = std::sqrt(dx * dx + dy * dy); + + double ux = dx / length; + double uy = dy / length; + + double vx = -uy; + double vy = ux; + + double ox = vx * offset; + double oy = vy * offset; + + Points rect; + rect.resize(4); + rect[0] = {p1.x() + ox, p1.y() + oy}; + rect[1] = {p1.x() - ox, p1.y() - oy}; + rect[2] = {p2.x() - ox, p2.y() - oy}; + rect[3] = {p2.x() + ox, p2.y() + oy}; + Polygon poly(rect); + return poly; +}; + struct Segment { Vec2f start; Vec2f end; - + bool is_arc = false; + ArcSegment arcsegment; Segment(const Vec2f &s, const Vec2f &e) : start(s), end(e) {} bool is_valid() const { return start.y() < end.y(); } }; @@ -93,6 +314,216 @@ std::vector remove_points_from_segment(const Segment &segment, const st return result; } +struct IntersectionInfo +{ + Vec2f pos; + int idx; + int pair_idx; // gap_pair idx + float dis_from_idx; + bool is_forward; +}; + +struct PointWithFlag +{ + Vec2f pos; + int pair_idx; // gap_pair idx + bool is_forward; +}; +IntersectionInfo move_point_along_polygon(const std::vector &points, const Vec2f &startPoint, int startIdx, float offset, bool forward, int pair_idx) +{ + float remainingDistance = offset; + IntersectionInfo res; + int mod = points.size(); + if (forward) { + int next = (startIdx + 1) % mod; + remainingDistance -= (points[next] - startPoint).norm(); + if (remainingDistance <= 0) { + res.idx = startIdx; + res.pos = startPoint + (points[next] - startPoint).normalized() * offset; + res.pair_idx = pair_idx; + res.dis_from_idx = (points[startIdx] - res.pos).norm(); + return res; + } else { + for (int i = (startIdx + 1) % mod; i != startIdx; i = (i + 1) % mod) { + float segmentLength = (points[(i + 1) % mod] - points[i]).norm(); + if (remainingDistance <= segmentLength) { + float ratio = remainingDistance / segmentLength; + res.idx = i; + res.pos = points[i] + ratio * (points[(i + 1) % mod] - points[i]); + res.dis_from_idx = remainingDistance; + res.pair_idx = pair_idx; + return res; + } + remainingDistance -= segmentLength; + } + res.idx = (startIdx - 1 + mod) % mod; + res.pos = points[startIdx]; + res.pair_idx = pair_idx; + res.dis_from_idx = (res.pos - points[res.idx]).norm(); + } + } else { + int next = (startIdx + 1) % mod; + remainingDistance -= (points[startIdx] - startPoint).norm(); + if (remainingDistance <= 0) { + res.idx = startIdx; + res.pos = startPoint - (points[next] - points[startIdx]).normalized() * offset; + res.dis_from_idx = (res.pos - points[startIdx]).norm(); + res.pair_idx = pair_idx; + return res; + } + for (int i = (startIdx - 1 + mod) % mod; i != startIdx; i = (i - 1 + mod) % mod) { + float segmentLength = (points[(i + 1) % mod] - points[i]).norm(); + if (remainingDistance <= segmentLength) { + float ratio = remainingDistance / segmentLength; + res.idx = i; + res.pos = points[(i + 1) % mod] - ratio * (points[(i + 1) % mod] - points[i]); + res.dis_from_idx = segmentLength - remainingDistance; + res.pair_idx = pair_idx; + return res; + } + remainingDistance -= segmentLength; + } + res.idx = startIdx; + res.pos = points[res.idx]; + res.pair_idx = pair_idx; + res.dis_from_idx = 0; + } + return res; +}; + +void insert_points(std::vector &pl, int idx, Vec2f pos, int pair_idx, bool is_forward) +{ + int next = (idx + 1) % pl.size(); + Vec2f pos1 = pl[idx].pos; + Vec2f pos2 = pl[next].pos; + if ((pos - pos1).squaredNorm() < EPSILON) { + pl[idx].pair_idx = pair_idx; + pl[idx].is_forward = is_forward; + } else if ((pos - pos2).squaredNorm() < EPSILON) { + pl[next].pair_idx = pair_idx; + pl[next].is_forward = is_forward; + } else { + pl.insert(pl.begin() + idx + 1, PointWithFlag{pos, pair_idx, is_forward}); + } +} + +Polylines remove_points_from_polygon(const Polygon &polygon, const std::vector &skip_points, double range, bool is_left ,Polygon& insert_skip_pg) +{ + Polylines result; + std::vector new_pl; // add intersection points for gaps, where bool indicates whether it's a gap point. + std::vector inter_info; + Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0); + std::vector points; + points.reserve(polygon.points.size()); + for (auto &p : polygon.points) points.push_back(unscale(p).cast()); + for (int i = 0; i < skip_points.size(); i++) { + for (int j = 0; j < points.size(); j++) { + Vec2f& p1 = points[j]; + Vec2f& p2 = points[(j + 1) % points.size()]; + auto [is_inter, inter_pos] = ray_intersetion_line(skip_points[i], ray, p1, p2); + if (is_inter) { + IntersectionInfo forward = move_point_along_polygon(points, inter_pos, j, range, true, i); + IntersectionInfo backward = move_point_along_polygon(points, inter_pos, j, range, false, i); + backward.is_forward = false; + forward.is_forward = true; + inter_info.push_back(backward); + inter_info.push_back(forward); + break; + } + } + } + + // insert point to new_pl + for (const auto &p : points) new_pl.push_back({p, -1}); + std::sort(inter_info.begin(), inter_info.end(), [](const IntersectionInfo &lhs, const IntersectionInfo &rhs) { + if (rhs.idx == lhs.idx) return lhs.dis_from_idx < rhs.dis_from_idx; + return lhs.idx < rhs.idx; + }); + for (int i = inter_info.size() - 1; i >= 0; i--) { insert_points(new_pl, inter_info[i].idx, inter_info[i].pos, inter_info[i].pair_idx, inter_info[i].is_forward); } + + { + //set insert_pg for wipe_path + for (auto &p : new_pl) insert_skip_pg.points.push_back(scaled(p.pos)); + } + + //assume that no interval is completely contained within another interval. + int beg = -1; + for (int i = 0; i < skip_points.size(); i++) { + if (beg != -1) break; + for (int j = 0; j < new_pl.size(); j++) { + if (new_pl[j].pair_idx == i && !new_pl[j].is_forward) { + bool is_include_pair = false; + int k = (j + 1) % new_pl.size(); + while (k != j) { + if (new_pl[k].pair_idx == i && new_pl[k].is_forward) { break; } + if (new_pl[k].pair_idx != -1 && new_pl[k].pair_idx != i && new_pl[k].is_forward) { + is_include_pair = true; + break; + } + k = (k + 1) % new_pl.size(); + } + if (!is_include_pair) { + beg = k; + break; + } + } + } + } + if (beg == -1) beg = 0; + bool skip = true; + int i = beg; + Polyline pl; + + do { + if (skip || new_pl[i].pair_idx == -1) { + pl.points.push_back(scaled(new_pl[i].pos)); + i = (i + 1) % new_pl.size(); + skip = false; + } else { + if (!pl.points.empty()) { + pl.points.push_back(scaled(new_pl[i].pos)); + result.push_back(pl); + pl.points.clear(); + } + int left = new_pl[i].pair_idx; + int j = (i + 1) % new_pl.size(); + while (j != beg && new_pl[j].pair_idx != left) j = (j + 1) % new_pl.size(); + i = j; + skip = true; + } + } while (i != beg); + + if (!pl.points.empty()) { + if (new_pl[i].pair_idx==-1) pl.points.push_back(scaled(new_pl[i].pos)); + result.push_back(pl); + } + return result; +} + +Polylines contrust_gap_for_skip_points(const Polygon &polygon, const std::vector & skip_points ,float wt_width,float gap_length,Polygon& insert_skip_polygon) +{ + if (skip_points.empty()) { + insert_skip_polygon = polygon; + return Polylines{to_polyline(polygon)}; + } + bool is_left = false; + const auto &pt = skip_points.front(); + if (abs(pt.x()) < wt_width/2.f) { + is_left = true; + } + return remove_points_from_polygon(polygon, skip_points, gap_length, is_left, insert_skip_polygon); + +}; + +Polygon generate_rectange_polygon(const Vec2f &wt_box_min ,const Vec2f & wt_box_max) { + Polygon res; + res.points.push_back(scaled(wt_box_min)); + res.points.push_back(scaled(Vec2f{wt_box_max[0], wt_box_min[1]})); + res.points.push_back(scaled(wt_box_max)); + res.points.push_back(scaled(Vec2f{wt_box_min[0], wt_box_max[1]})); + return res; +} + class WipeTowerWriter { public: @@ -261,6 +692,74 @@ public: return *this; } + // Extrude with an explicitely provided amount of extrusion. + WipeTowerWriter &extrude_arc_explicit(ArcSegment &arc, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { + float x = (float)unscale(arc.end_point).x(); + float y = (float)unscale(arc.end_point).y(); + float len = unscaled(arc.length); + float e = len * m_extrusion_flow; + if (len < (float) EPSILON && e == 0.f && (f == 0.f || f == m_current_feedrate)) + // Neither extrusion nor a travel move. + return *this; + if (record_length) m_used_filament_length += e; + + // Now do the "internal rotation" with respect to the wipe tower center + Vec2f rotated_current_pos(this->pos_rotated()); + Vec2f rot(this->rotate(Vec2f(x, y))); // this is where we want to go + + if (!m_preview_suppressed && e > 0.f && len > 0.f) { +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + change_analyzer_mm3_per_mm(len, e); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. + // This is left zero if it is a travel move. + float width = e * m_filpar[0].filament_area / (len * m_layer_height); + // Correct for the roundings of a squished extrusion. + width += m_layer_height * float(1. - M_PI / 4.); + if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); + { + float sample_angle = 5.f / 180.f * PI; + int n = std::ceil(abs(arc.angle_radians) / sample_angle); + for (int j = 0; j < n; j++) { + float cur_angle = arc.polar_start_theta + (float) j / n * arc.angle_radians; + if (cur_angle > 2 * PI) + cur_angle -= 2 * PI; + else if (cur_angle < 0) + cur_angle += 2 * PI; + Point tmp = arc.center + Point{arc.radius * std::cos(cur_angle), arc.radius * std::sin(cur_angle)}; + m_extrusions.emplace_back(WipeTower::Extrusion(this->rotate(unscaled(tmp)), width, m_current_tool)); + } + m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); + } + + } + m_gcode += arc.direction == ArcDirection::Arc_Dir_CCW ? "G3" : "G2"; + const Vec2f center_offset = this->rotate(unscaled(arc.center)) - rotated_current_pos; + m_gcode += set_format_X(rot.x()); + m_gcode += set_format_Y(rot.y()); + m_gcode += set_format_I(center_offset.x()); + m_gcode += set_format_J(center_offset.y()); + + if (e != 0.f) m_gcode += set_format_E(e); + + if (f != 0.f && f != m_current_feedrate) { + if (limit_volumetric_flow) { + float e_speed = e / (((len == 0.f) ? std::abs(e) : len) / f * 60.f); + f /= std::max(1.f, e_speed / m_filpar[m_current_tool].max_e_speed); + } + m_gcode += set_format_F(f); + } + + m_current_pos.x() = x; + m_current_pos.y() = y; + + // Update the elapsed time with a rough estimate. + m_elapsed_time += ((len == 0.f) ? std::abs(e) : len) / m_current_feedrate * 60.f; + m_gcode += "\n"; + return *this; + } + WipeTowerWriter& extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) { return extrude_explicit(dest.x(), dest.y(), e, f, record_length); } @@ -278,6 +777,10 @@ public: float dy = y - m_current_pos.y(); return extrude_explicit(x, y, std::sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); } + WipeTowerWriter &extrude_arc(ArcSegment &arc, float f = 0.f) + { + return extrude_arc_explicit(arc, f, true); + } WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) { return extrude(dest.x(), dest.y(), f); } @@ -353,6 +856,48 @@ public: box.ru.y() - box.rd.y(), f); return (*this); } + WipeTowerWriter &polygon(const Polygon &wall_polygon, const float f = 0.f) + { + Polyline pl = to_polyline(wall_polygon); + pl.simplify_by_fitting_arc(SCALED_WIPE_TOWER_RESOLUTION); + + auto get_closet_idx = [this](std::vector &corners) -> int { + Vec2f anchor{this->m_current_pos.x(), this->m_current_pos.y()}; + int closestIndex = -1; + float minDistance = std::numeric_limits::max(); + for (int i = 0; i < corners.size(); ++i) { + float distance = (corners[i].start - anchor).squaredNorm(); + if (distance < minDistance) { + minDistance = distance; + closestIndex = i; + } + } + return closestIndex; + }; + std::vector segments; + for (int i = 0; i < pl.fitting_result.size(); i++) { + if (pl.fitting_result[i].path_type == EMovePathType::Linear_move) { + for (int j = pl.fitting_result[i].start_point_index; j < pl.fitting_result[i].end_point_index; j++) + segments.push_back({unscaled(pl.points[j]), unscaled(pl.points[j + 1])}); + } else { + int beg = pl.fitting_result[i].start_point_index; + int end = pl.fitting_result[i].end_point_index; + segments.push_back({unscaled(pl.points[beg]), unscaled(pl.points[end])}); + segments.back().is_arc = true; + segments.back().arcsegment = pl.fitting_result[i].arc_data; + } + } + int index_of_closest = get_closet_idx(segments); + int i = index_of_closest; + travel(segments[i].start); // travel to the closest points + segments[i].is_arc ? extrude_arc(segments[i].arcsegment, f) : extrude(segments[i].end, f); + do { + i = (i + 1) % segments.size(); + if (i == index_of_closest) break; + segments[i].is_arc ? extrude_arc(segments[i].arcsegment, f) : extrude(segments[i].end, f); + } while (1); + return (*this); + } WipeTowerWriter& load(float e, float f = 0.f) { @@ -531,6 +1076,71 @@ public: return add_wipe_point(Vec2f(x, y)); } + WipeTowerWriter &add_wipe_path(const Polygon & polygon,double wipe_dist) + { + int closest_idx = polygon.closest_point_index(scaled(m_current_pos)); + Polyline wipe_path = polygon.split_at_index(closest_idx); + wipe_path.reverse(); + for (int i = 0; i < wipe_path.size(); ++i) { + if (wipe_dist < EPSILON) break; + add_wipe_point(unscaled(wipe_path[i])); + if (i != 0) wipe_dist -= (unscaled(wipe_path[i]) - unscaled(wipe_path[i - 1])).norm(); + } + return *this; + } + void generate_path(Polylines &pls, float feedrate, float retract_length, float retract_speed, bool used_fillet) + { + auto get_closet_idx = [this](std::vector &corners) -> int { + Vec2f anchor{this->m_current_pos.x(), this->m_current_pos.y()}; + int closestIndex = -1; + float minDistance = std::numeric_limits::max(); + for (int i = 0; i < corners.size(); ++i) { + float distance = (corners[i].start - anchor).squaredNorm(); + if (distance < minDistance) { + minDistance = distance; + closestIndex = i; + } + } + return closestIndex; + }; + for (auto &pl : pls) pl.simplify_by_fitting_arc(SCALED_WIPE_TOWER_RESOLUTION); + + std::vector segments; + for (const auto &pl : pls) { + if (pl.points.size()<2) continue; + for (int i = 0; i < pl.fitting_result.size(); i++) { + if (pl.fitting_result[i].path_type == EMovePathType::Linear_move) { + for (int j = pl.fitting_result[i].start_point_index; j < pl.fitting_result[i].end_point_index; j++) + segments.push_back({unscaled(pl.points[j]), unscaled(pl.points[j + 1])}); + } else { + int beg = pl.fitting_result[i].start_point_index; + int end = pl.fitting_result[i].end_point_index; + segments.push_back({unscaled(pl.points[beg]), unscaled(pl.points[end])}); + segments.back().is_arc = true; + segments.back().arcsegment = pl.fitting_result[i].arc_data; + } + } + } + int index_of_closest = get_closet_idx(segments); + int i = index_of_closest; + travel(segments[i].start); // travel to the closest points + segments[i].is_arc? extrude_arc(segments[i].arcsegment,feedrate) : extrude(segments[i].end, feedrate); + do { + i = (i + 1) % segments.size(); + if (i == index_of_closest) break; + float dx = segments[i].start.x() - m_current_pos.x(); + float dy = segments[i].start.y() - m_current_pos.y(); + float len = std::sqrt(dx * dx + dy * dy); + if (len > EPSILON) { + retract(retract_length, retract_speed); + travel(segments[i].start, 600.); + retract(-retract_length, retract_speed); + } + segments[i].is_arc ? extrude_arc(segments[i].arcsegment, feedrate) : extrude(segments[i].end, feedrate); + } while (1); + } + + private: Vec2f m_start_pos; Vec2f m_current_pos; @@ -582,6 +1192,8 @@ private: m_current_feedrate = f; return buf; } + std::string set_format_I(float i) { return " I" + Slic3r::float_to_string_decimal_point(i, 3); } + std::string set_format_J(float j) { return " J" + Slic3r::float_to_string_decimal_point(j, 3); } WipeTowerWriter& operator=(const WipeTowerWriter &rhs); @@ -679,7 +1291,11 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_filaments_change_length(config.filament_change_length.values), m_is_multi_extruder(config.nozzle_diameter.size() > 1), m_is_print_outer_first(config.prime_tower_outer_first.value), - m_use_gap_wall(config.prime_tower_skip_points.value) + m_use_gap_wall(config.prime_tower_skip_points.value), + m_use_rib_wall(config.prime_tower_rib_wall.value), + m_extra_rib_length(config.prime_tower_extra_rib_length.value), + m_rib_width(config.prime_tower_rib_width.value), + m_used_fillet(config.prime_tower_fillet_wall.value) { // Read absolute value of first layer speed, if given as percentage, // it is taken over following default. Speeds from config are not @@ -781,6 +1397,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].retract_length = config.retraction_length.get_at(idx); m_filpar[idx].retract_speed = config.retraction_speed.get_at(idx); + m_filpar[idx].wipe_dist = config.wipe_distance.get_at(idx); } @@ -2280,12 +2897,15 @@ WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter, } box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, wipe_tower_depth); wt_box = align_perimeter(wt_box); - if (extrude_perimeter) { - if (m_use_gap_wall) - generate_support_wall(writer, wt_box, feedrate, first_layer); - else - writer.rectangle(wt_box, feedrate); - } + + //if (extrude_perimeter && !m_use_rib_wall) { + // if (!m_use_gap_wall) + // writer.rectangle(wt_box, feedrate); + // else + // generate_support_wall(writer, wt_box, feedrate, first_layer); + //} + Polygon outer_wall; + outer_wall = generate_support_wall_new(writer, wt_box, feedrate, first_layer, m_use_rib_wall, extrude_perimeter, m_use_gap_wall); // brim chamfer float spacing = m_perimeter_width - m_layer_height * float(1. - M_PI_4); @@ -2305,28 +2925,49 @@ WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter, } if (loops_num > 0) { - box_coordinates box = wt_box; + //box_coordinates box = wt_box; for (size_t i = 0; i < loops_num; ++i) { - box.expand(spacing); - writer.rectangle(box, feedrate); + outer_wall = offset(outer_wall, scaled(spacing)).front(); + writer.polygon(outer_wall, feedrate); } + /*for (size_t i = 0; i < loops_num; ++i) { + box.expand(spacing); + writer.rectangle(box, feedrate); + }*/ if (first_layer) { // Save actual brim width to be later passed to the Print object, which will use it // for skirt calculation and pass it to GLCanvas for precise preview box - m_wipe_tower_brim_width_real = wt_box.ld.x() - box.ld.x() + spacing / 2.f; + m_wipe_tower_brim_width_real = loops_num * spacing + spacing / 2.f; + //m_wipe_tower_brim_width_real = wt_box.ld.x() - box.ld.x() + spacing / 2.f; + + // set wipe_tower_bbx + auto real_polygon = outer_wall; + real_polygon = offset(real_polygon, scaled(spacing/2.f)).front(); + auto real_box = get_extents(real_polygon); + m_wipe_tower_bbx = BoundingBoxf(unscale(real_box.min), unscale(real_box.max)); + } + //wt_box = box; + } + else { + if (first_layer) { + auto real_polygon = outer_wall; + real_polygon = offset(real_polygon, scaled(spacing / 2.f)).front(); + auto real_box = get_extents(real_polygon); + m_wipe_tower_bbx = BoundingBoxf(unscale(real_box.min), unscale(real_box.max)); } - wt_box = box; } - // Now prepare future wipe. box contains rectangle that was extruded last (ccw). - Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); + if (extrude_perimeter) writer.add_wipe_path(outer_wall, m_filpar[m_current_tool].wipe_dist); + else { + // Now prepare future wipe. box contains rectangle that was extruded last (ccw). + Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); - // BBS: add wipe_path for this case: only with finish rectangle - if (finish_rect_wipe_path.size() == 2 && finish_rect_wipe_path[0] == writer.pos()) target = finish_rect_wipe_path[1]; - - writer.add_wipe_point(writer.pos()).add_wipe_point(target); + // BBS: add wipe_path for this case: only with finish rectangle + if (finish_rect_wipe_path.size() == 2 && finish_rect_wipe_path[0] == writer.pos()) target = finish_rect_wipe_path[1]; + writer.add_wipe_point(writer.pos()).add_wipe_point(target); + } writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); // Ask our writer about how much material was consumed. @@ -2720,6 +3361,7 @@ void WipeTower::update_all_layer_depth(float wipe_tower_depth) void WipeTower::generate_wipe_tower_blocks() { // 1. generate all layer depth + m_all_layers_depth.clear(); m_all_layers_depth.resize(m_plan.size()); m_cur_layer_id = 0; for (auto& info : m_plan) { @@ -2798,12 +3440,42 @@ void WipeTower::generate_wipe_tower_blocks() void WipeTower::plan_tower_new() { + 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_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 = extrusion_flow(m_plan[idx].height); + double length = m_nozzle_change_length / e_flow; + int nozzle_change_line_count = length / (m_wipe_tower_width - 2*m_perimeter_width) + 1; + if (has_tpu_filament()) + nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_perimeter_width; + else + nozzle_change_depth = nozzle_change_line_count * m_perimeter_width; + depth += nozzle_change_depth; + } + toolchange.nozzle_change_depth = nozzle_change_depth; + toolchange.required_depth = depth; + } + } + } + generate_wipe_tower_blocks(); float max_depth = 0.f; for (const auto &block : m_wipe_tower_blocks) { max_depth += block.depth; } + //std::cout << " after square " << m_wipe_tower_width << " depth " << max_depth << std::endl; float min_wipe_tower_depth = 0.f; auto iter = WipeTower::min_depth_per_height.begin(); @@ -2844,6 +3516,10 @@ void WipeTower::plan_tower_new() if (max_depth + EPSILON < min_wipe_tower_depth) { m_extra_spacing = min_wipe_tower_depth / max_depth; + if (m_use_rib_wall) { + m_rib_length = std::max(m_rib_length, min_wipe_tower_depth * (float) std::sqrt(2)); + m_extra_spacing = 1.f; + } } for (int idx = 0; idx < m_plan.size(); idx++) { @@ -2880,6 +3556,8 @@ void WipeTower::plan_tower_new() } update_all_layer_depth(max_depth); + m_rib_length = std::max({m_rib_length, sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width)}); + m_rib_length += m_extra_rib_length; } int WipeTower::get_wall_filament_for_all_layer() @@ -3249,14 +3927,16 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode) wipe_tower_depth = m_wipe_tower_depth; box_coordinates wt_box(Vec2f(0.f, (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)), m_wipe_tower_width, wipe_tower_depth); wt_box = align_perimeter(wt_box); - if (m_use_gap_wall) - generate_support_wall(writer, wt_box, feedrate, first_layer); - else - writer.rectangle(wt_box, feedrate); - + Polygon outer_wall; + //if (m_use_gap_wall) + // generate_support_wall(writer, wt_box, feedrate, first_layer); + //else + // writer.rectangle(wt_box, feedrate); + outer_wall = generate_support_wall_new(writer, wt_box, feedrate, first_layer, m_use_rib_wall, true, m_use_gap_wall); // Now prepare future wipe. box contains rectangle that was extruded last (ccw). - Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); - writer.add_wipe_point(writer.pos()).add_wipe_point(target); + // Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : (writer.pos() == wt_box.rd ? wt_box.ru : (writer.pos() == wt_box.ru ? wt_box.lu : wt_box.ld))); + //writer.add_wipe_point(writer.pos()).add_wipe_point(target); + writer.add_wipe_path(outer_wall, m_filpar[m_current_tool].wipe_dist); writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); @@ -3268,6 +3948,83 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode) return construct_tcr(writer, false, old_tool, true, false, 0.f); } +Polygon WipeTower::generate_rib_polygon(const box_coordinates &wt_box) +{ + auto get_current_layer_rib_len = [](float cur_height, float max_height, float max_len) -> float { return std::abs(max_height - cur_height) / max_height * max_len; }; + coord_t diagonal_width = scaled(m_rib_width)/2; + float a = this->m_wipe_tower_width, b = this->m_wipe_tower_depth; + Line line_1(Point::new_scale(Vec2f{0, 0}), Point::new_scale(Vec2f{a, b})); + Line line_2(Point::new_scale(Vec2f{a, 0}), Point::new_scale(Vec2f{0, b})); + float diagonal_extra_length = std::max(0.f, m_rib_length - (float) unscaled(line_1.length())) / 2.f; + diagonal_extra_length = scaled(get_current_layer_rib_len(this->m_z_pos, this->m_wipe_tower_height, diagonal_extra_length)); + Point y_shift{0, scaled(this->m_y_shift)}; + + line_1.extend(double(diagonal_extra_length)); + line_2.extend(double(diagonal_extra_length)); + line_1.translate(-y_shift); + line_2.translate(-y_shift); + + Polygon poly_1 = generate_rectange(line_1, diagonal_width); + Polygon poly_2 = generate_rectange(line_2, diagonal_width); + Polygon poly; + poly.points.push_back(Point::new_scale(wt_box.ld)); + poly.points.push_back(Point::new_scale(wt_box.rd)); + poly.points.push_back(Point::new_scale(wt_box.ru)); + poly.points.push_back(Point::new_scale(wt_box.lu)); + + Polygons p_1_2 = union_({poly_1, poly_2, poly}); + //Polygon res_poly = p_1_2.front(); + //for (auto &p : res_poly.points) res.push_back(unscale(p).cast()); + /*if (p_1_2.front().points.size() != 16) + std::cout << "error " << std::endl;*/ + return p_1_2.front(); +}; + +Polygon WipeTower::generate_support_wall_new(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer,bool rib_wall, bool extrude_perimeter, bool skip_points) +{ + auto get_closet_idx = [this, &writer](Polylines &pls) -> std::pair { + Vec2f anchor{writer.x(), writer.y()}; + int closestIndex = -1; + int closestPl = -1; + float minDistance = std::numeric_limits::max(); + for (int i = 0; i < pls.size(); ++i) { + for (int j = 0; j < pls[i].size(); ++j) { + float distance = (unscaled(pls[i][j]) - anchor).squaredNorm(); + if (distance < minDistance) { + minDistance = distance; + closestPl = i; + closestIndex = j; + } + } + } + return {closestPl, closestIndex}; + }; + + float retract_length = m_filpar[m_current_tool].retract_length; + float retract_speed = m_filpar[m_current_tool].retract_speed * 60; + Polygon wall_polygon = rib_wall ? generate_rib_polygon(wt_box) : generate_rectange_polygon(wt_box.ld, wt_box.ru); + Polylines result_wall; + Polygon insert_skip_polygon; + if (m_used_fillet) { + if (!rib_wall && m_y_shift > EPSILON)// do nothing because the fillet will cause it to be suspended. + { + } else { + wall_polygon = rib_wall ? rounding_polygon(wall_polygon) : wall_polygon; // rectangle_wall do nothing + Polygon wt_box_polygon = generate_rectange_polygon(wt_box.ld, wt_box.ru); + wall_polygon = union_({wall_polygon, wt_box_polygon}).front(); + } + } + if (!extrude_perimeter) return wall_polygon; + + if (skip_points) { result_wall = contrust_gap_for_skip_points(wall_polygon,m_wall_skip_points,m_wipe_tower_width,2.5*m_perimeter_width,insert_skip_polygon); } + else { + result_wall.push_back(to_polyline(wall_polygon)); + insert_skip_polygon = wall_polygon; + } + writer.generate_path(result_wall, feedrate, retract_length, retract_speed,m_used_fillet); + return insert_skip_polygon; +} + Polygon WipeTower::generate_support_wall(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer) { float retract_length = m_filpar[m_current_tool].retract_length; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 635bf47f7..ce0c25b72 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -179,9 +179,12 @@ public: WipeTower::ToolChangeResult only_generate_out_wall(bool is_new_mode = false); Polygon generate_support_wall(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer); + Polygon generate_support_wall_new(WipeTowerWriter &writer, const box_coordinates &wt_box, double feedrate, bool first_layer,bool rib_wall, bool extrude_perimeter, bool skip_points); + Polygon generate_rib_polygon(const box_coordinates &wt_box); float get_depth() const { return m_wipe_tower_depth; } float get_brim_width() const { return m_wipe_tower_brim_width_real; } + BoundingBoxf get_bbx() const { return m_wipe_tower_bbx; } float get_height() const { return m_wipe_tower_height; } float get_layer_height() const { return m_layer_height; } @@ -302,6 +305,7 @@ public: float filament_area; float retract_length; float retract_speed; + float wipe_dist; }; @@ -397,6 +401,11 @@ private: bool m_is_multi_extruder{false}; bool m_is_print_outer_first{false}; bool m_use_gap_wall{false}; + bool m_use_rib_wall{false}; + float m_rib_length=0.f; + float m_rib_width=0.f; + float m_extra_rib_length=0.f; + bool m_used_fillet{false}; // G-code generator parameters. // BBS: remove useless config @@ -444,8 +453,8 @@ private: bool m_left_to_right = true; float m_extra_spacing = 1.f; float m_tpu_fixed_spacing = 2; - - std::vector m_wall_skip_points; + BoundingBoxf m_wipe_tower_bbx; + std::vector m_wall_skip_points; bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } // Calculates length of extrusion line to extrude given volume diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 59afbd0bc..42b1cd499 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -880,6 +880,7 @@ static std::vector s_Preset_print_options { "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_width", "prime_tower_brim_width", "prime_tower_outer_first", "prime_tower_skip_points", "prime_volume", + "prime_tower_rib_wall","prime_tower_extra_rib_length","prime_tower_rib_width","prime_tower_fillet_wall", "enable_circle_compensation", "circle_compensation_speed", "circle_compensation_manual_offset", "counter_coef_1", "counter_coef_2", "counter_coef_3", "hole_coef_1", "hole_coef_2", "hole_coef_3", "counter_limit_min", "counter_limit_max", "hole_limit_min", "hole_limit_max", "diameter_limit", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a05c80e8f..8c8a3ed41 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -256,6 +256,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "prime_tower_brim_width" || opt_key == "prime_tower_outer_first" || opt_key == "prime_tower_skip_points" + || opt_key == "prime_tower_rib_wall" + || opt_key == "prime_tower_extra_rib_length" + || opt_key == "prime_tower_rib_width" + || opt_key == "prime_tower_fillet_wall" || opt_key == "first_layer_print_sequence" || opt_key == "other_layers_print_sequence" || opt_key == "other_layers_print_sequence_nums" @@ -2020,6 +2024,7 @@ void Print::process(std::unordered_map* slice_time, bool std::optional wipe_tower_opt = {}; if (this->has_wipe_tower()) { m_fake_wipe_tower.set_pos({m_config.wipe_tower_x.get_at(m_plate_index), m_config.wipe_tower_y.get_at(m_plate_index)}); + m_fake_wipe_tower.set_bbx(); wipe_tower_opt = std::make_optional(&m_fake_wipe_tower); } auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(m_objects, wipe_tower_opt); @@ -2693,6 +2698,7 @@ void Print::_make_wipe_tower() wipe_tower.generate_new(m_wipe_tower_data.tool_changes); m_wipe_tower_data.depth = wipe_tower.get_depth(); m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); + m_wipe_tower_data.bbx = wipe_tower.get_bbx(); // Unload the current filament over the purge tower. coordf_t layer_height = m_objects.front()->config().layer_height.value; @@ -2718,6 +2724,8 @@ void Print::_make_wipe_tower() const Vec3d origin = this->get_plate_origin(); m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_height(), wipe_tower.get_layer_height(), m_wipe_tower_data.depth, m_wipe_tower_data.brim_width, {scale_(origin.x()), scale_(origin.y())}); + m_fake_wipe_tower.real_bbx = wipe_tower.get_bbx(); + m_config.prime_tower_width.set(new ConfigOptionFloat( wipe_tower.width())); } // Generate a recommended G-code output file name based on the format template, default extension, and template parameters diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index cfce8cd4e..5a45c1409 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -614,6 +614,7 @@ struct FakeWipeTower float depth; float brim_width; Vec2d plate_origin; + BoundingBoxf real_bbx; void set_fake_extrusion_data(Vec2f p, float w, float h, float lh, float d, float bd, Vec2d o) { @@ -625,6 +626,11 @@ struct FakeWipeTower brim_width = bd; plate_origin = o; } + void set_bbx() + { + real_bbx.min += pos.cast(); + real_bbx.max += pos.cast(); + } void set_pos(Vec2f p) { pos = p; } @@ -633,8 +639,10 @@ struct FakeWipeTower int d = scale_(depth); int w = scale_(width); int bd = scale_(brim_width); - Point minCorner = {scale_(pos.x()), scale_(pos.y())}; - Point maxCorner = {minCorner.x() + w, minCorner.y() + d}; + //Point minCorner = {scale_(pos.x()), scale_(pos.y())}; + //Point maxCorner = {minCorner.x() + w, minCorner.y() + d}; + Point minCorner = {scale_(real_bbx.min.x()), scale_(real_bbx.min.y())}; + Point maxCorner = {scale_(real_bbx.max.x()), scale_(real_bbx.max.y())}; std::vector paths; for (float h = 0.f; h < height; h += layer_height) { @@ -642,13 +650,13 @@ struct FakeWipeTower path.polyline = {minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner}; paths.push_back({path}); - if (h == 0.f) { // add brim - ExtrusionPath fakeBrim(ExtrusionRole::erBrim, 0.0, 0.0, layer_height); - Point wtbminCorner = {minCorner - Point{bd, bd}}; - Point wtbmaxCorner = {maxCorner + Point{bd, bd}}; - fakeBrim.polyline = {wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner}; - paths.back().push_back(fakeBrim); - } + //if (h == 0.f) { // add brim + // ExtrusionPath fakeBrim(ExtrusionRole::erBrim, 0.0, 0.0, layer_height); + // Point wtbminCorner = {minCorner - Point{bd, bd}}; + // Point wtbmaxCorner = {maxCorner + Point{bd, bd}}; + // fakeBrim.polyline = {wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner}; + // paths.back().push_back(fakeBrim); + //} } return paths; } @@ -670,6 +678,7 @@ struct WipeTowerData // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: float depth; float brim_width; + BoundingBoxf bbx; void clear() { priming.reset(nullptr); @@ -939,6 +948,7 @@ public: const Calib_Params& calib_params() const { return m_calib_params; } Vec2d translate_to_print_space(const Vec2d& point) const; float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; } + BoundingBoxf get_wipe_tower_bbx() const { return m_wipe_tower_data.bbx; } // scaled point Vec2d translate_to_print_space(const Point& point) const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 712cb9411..6935cc147 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4346,6 +4346,22 @@ void PrintConfigDef::init_fff_params() def->min = 0.; def->set_default_value(new ConfigOptionFloat(3.)); + def = this->add("prime_tower_extra_rib_length", coFloat); + def->label = L("Extra rib length"); + def->tooltip = L("Extra rib length"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("prime_tower_rib_width", coFloat); + def->label = L("Rib width"); + def->tooltip = L("Rib width"); + def->sidetext = L("mm"); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloat(4)); + def = this->add("prime_tower_outer_first", coBool); def->label = L("Outer first"); def->tooltip = L("The prime tower print outer first"); @@ -4358,6 +4374,18 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); + 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"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("prime_tower_fillet_wall", coBool); + def->label = L("fillet wall"); + def->tooltip = L("The wall of prime tower will fillet"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("flush_into_infill", coBool); def->category = L("Flush options"); def->label = L("Flush into objects' infill"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 899fb1fad..4423f6c23 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1198,8 +1198,12 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, wipe_tower_per_color_wipe)) ((ConfigOptionFloat, wipe_tower_rotation_angle)) ((ConfigOptionFloat, prime_tower_brim_width)) + ((ConfigOptionFloat, prime_tower_extra_rib_length)) + ((ConfigOptionFloat, prime_tower_rib_width)) ((ConfigOptionBool, prime_tower_outer_first)) ((ConfigOptionBool, prime_tower_skip_points)) + ((ConfigOptionBool, prime_tower_rib_wall)) + ((ConfigOptionBool, prime_tower_fillet_wall)) //((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionFloats, flush_volumes_matrix)) ((ConfigOptionFloats, flush_volumes_vector)) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index c0384300a..224a2e337 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -678,7 +678,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_prime_tower = config->opt_bool("enable_prime_tower"); - for (auto el : {"prime_tower_width", "prime_volume", "prime_tower_brim_width", "prime_tower_outer_first", "prime_tower_skip_points"}) + for (auto el : {"prime_tower_width", "prime_volume", "prime_tower_brim_width", "prime_tower_outer_first", "prime_tower_skip_points", "prime_tower_rib_wall", + "prime_tower_extra_rib_length", "prime_tower_rib_width", "prime_tower_fillet_wall"}) toggle_line(el, have_prime_tower); for (auto el : {"flush_into_infill", "flush_into_support", "flush_into_objects"}) diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 576fb392f..399887a68 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -1477,7 +1477,7 @@ arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const Dynamic } x = std::clamp(x, margin, (float)plate_width - w - margin - wp_brim_width); - y = std::clamp(y, margin, (float)plate_depth - depth - margin - wp_brim_width); + y = std::clamp(y, margin, (float)plate_depth - depth - margin - wp_brim_width); arrangement::ArrangePolygon wipe_tower_ap; Polygon ap({ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index de6c85ff1..e5d589e9d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4007,6 +4007,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "nozzle_height", "skirt_loops", "skirt_distance", "brim_width", "brim_object_gap", "brim_type", "nozzle_diameter", "single_extruder_multi_material", "enable_prime_tower", "wipe_tower_x", "wipe_tower_y", "prime_tower_width", "prime_tower_brim_width", "prime_tower_outer_first", "prime_tower_skip_points", "prime_volume", + "prime_tower_rib_wall","prime_tower_extra_rib_length", "prime_tower_rib_width","prime_tower_fillet_wall", "extruder_colour", "filament_colour", "filament_type", "material_colour", "printable_height", "extruder_printable_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 244973f08..35bdf6505 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2226,9 +2226,13 @@ void TabPrint::build() optgroup->append_single_option_line("enable_prime_tower","parameter/prime-tower"); optgroup->append_single_option_line("prime_tower_outer_first", "parameter/prime-tower"); optgroup->append_single_option_line("prime_tower_skip_points", "parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_rib_wall", "parameter/prime-tower"); optgroup->append_single_option_line("prime_tower_width","parameter/prime-tower"); optgroup->append_single_option_line("prime_volume","parameter/prime-tower"); optgroup->append_single_option_line("prime_tower_brim_width","parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_extra_rib_length","parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_rib_width","parameter/prime-tower"); + optgroup->append_single_option_line("prime_tower_fillet_wall","parameter/prime-tower"); optgroup = page->new_optgroup(L("Flush options"), L"param_flush"); optgroup->append_single_option_line("flush_into_infill", "reduce-wasting-during-filament-change#wipe-into-infill");