diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 0b69ee47bb6..6b75c0ee98c 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -340,6 +340,115 @@ double ExtrusionLoop::min_mm3_per_mm() const return min_mm3_per_mm; } +ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths, + double seam_gap, + double slope_min_length, + double slope_max_segment_length, + double start_slope_ratio, + ExtrusionLoopRole role) + : ExtrusionLoop(role) +{ + // create slopes + const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath& path, const Polyline& poly, + double ratio_begin, double ratio_end) { + if (poly.empty()) { + return; + } + + // Ensure `slope_max_segment_length` + Polyline detailed_poly; + { + detailed_poly.append(poly.first_point()); + + // Recursively split the line into half until no longer than `slope_max_segment_length` + const std::function handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line& line) { + if (line.length() <= slope_max_segment_length) { + detailed_poly.append(line.b); + } else { + // Then process left half + handle_line({line.a, line.midpoint()}); + // Then process right half + handle_line({line.midpoint(), line.b}); + } + }; + + for (const auto& l : poly.lines()) { + handle_line(l); + } + } + + starts.emplace_back(detailed_poly, path, ExtrusionPathSloped::Slope{ratio_begin, ratio_begin}, + ExtrusionPathSloped::Slope{ratio_end, ratio_end}); + + if (is_approx(ratio_end, 1.) && seam_gap > 0) { + // Remove the segments that has no extrusion + const auto seg_length = detailed_poly.length(); + if (seg_length > seam_gap) { + // Split the segment and remove the last `seam_gap` bit + const Polyline orig = detailed_poly; + Polyline tmp; + orig.split_at_length(seg_length - seam_gap, &detailed_poly, &tmp); + + ratio_end = lerp(ratio_begin, ratio_end, (seg_length - seam_gap) / seg_length); + assert(1. - ratio_end > EPSILON); + } else { + // Remove the entire segment + detailed_poly.clear(); + } + } + if (!detailed_poly.empty()) { + ends.emplace_back(detailed_poly, path, ExtrusionPathSloped::Slope{1., 1. - ratio_begin}, + ExtrusionPathSloped::Slope{1., 1. - ratio_end}); + } + }; + + double remaining_length = slope_min_length; + + ExtrusionPaths::iterator path = original_paths.begin(); + double start_ratio = start_slope_ratio; + for (; path != original_paths.end() && remaining_length > 0; ++path) { + const double path_len = unscale_(path->length()); + if (path_len > remaining_length) { + // Split current path into slope and non-slope part + Polyline slope_path; + Polyline flat_path; + path->polyline.split_at_length(scale_(remaining_length), &slope_path, &flat_path); + + add_slop(*path, slope_path, start_ratio, 1); + start_ratio = 1; + + paths.emplace_back(std::move(flat_path), *path); + remaining_length = 0; + } else { + remaining_length -= path_len; + const double end_ratio = lerp(1.0, start_slope_ratio, remaining_length / slope_min_length); + add_slop(*path, path->polyline, start_ratio, end_ratio); + start_ratio = end_ratio; + } + } + assert(remaining_length <= 0); + assert(start_ratio == 1.); + + // Put remaining flat paths + paths.insert(paths.end(), path, original_paths.end()); +} + +std::vector ExtrusionLoopSloped::get_all_paths() const { + std::vector r; + r.reserve(starts.size() + paths.size() + ends.size()); + for (const auto& p : starts) { + r.push_back(&p); + } + for (const auto& p : paths) { + r.push_back(&p); + } + for (const auto& p : ends) { + r.push_back(&p); + } + + return r; +} + std::string ExtrusionEntity::role_to_string(ExtrusionRole role) { diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 9921887b76d..cb350386e9e 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -301,6 +301,42 @@ class ExtrusionPath : public ExtrusionEntity bool m_no_extrusion = false; }; +class ExtrusionPathSloped : public ExtrusionPath +{ +public: + struct Slope + { + double z_ratio{1.}; + double e_ratio{1.}; + }; + + Slope slope_begin; + Slope slope_end; + + ExtrusionPathSloped(const ExtrusionPath& rhs, const Slope& begin, const Slope& end) + : ExtrusionPath(rhs), slope_begin(begin), slope_end(end) + {} + ExtrusionPathSloped(ExtrusionPath&& rhs, const Slope& begin, const Slope& end) + : ExtrusionPath(std::move(rhs)), slope_begin(begin), slope_end(end) + {} + ExtrusionPathSloped(const Polyline& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end) + : ExtrusionPath(polyline, rhs), slope_begin(begin), slope_end(end) + {} + ExtrusionPathSloped(Polyline&& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end) + : ExtrusionPath(std::move(polyline), rhs), slope_begin(begin), slope_end(end) + {} + + Slope interpolate(const double ratio) const + { + return { + lerp(slope_begin.z_ratio, slope_end.z_ratio, ratio), + lerp(slope_begin.e_ratio, slope_end.e_ratio, ratio), + }; + } + + bool is_flat() const { return is_approx(slope_begin.z_ratio, slope_end.z_ratio); } +}; + class ExtrusionPathOriented : public ExtrusionPath { public: @@ -459,6 +495,22 @@ class ExtrusionLoop : public ExtrusionEntity ExtrusionLoopRole m_loop_role; }; +class ExtrusionLoopSloped : public ExtrusionLoop +{ +public: + std::vector starts; + std::vector ends; + + ExtrusionLoopSloped(ExtrusionPaths& original_paths, + double seam_gap, + double slope_min_length, + double slope_max_segment_length, + double start_slope_ratio, + ExtrusionLoopRole role = elrDefault); + + [[nodiscard]] std::vector get_all_paths() const; +}; + inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 323d22a7c54..ed09de84a4d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4534,7 +4534,6 @@ static std::unique_ptr calculate_layer_edge_grid(const Layer& la return out; } - std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters) { // get a copy; don't modify the orientation of the original loop object otherwise @@ -4557,11 +4556,17 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou } else loop.split_at(last_pos, false); + const auto seam_scarf_type = m_config.seam_slope_type.value; + const bool enable_seam_slope = ((seam_scarf_type == SeamScarfType::External && !is_hole) || seam_scarf_type == SeamScarfType::All) && + !m_config.spiral_mode && + (loop.role() == erExternalPerimeter || (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && + layer_id() > 0; + // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case - double clip_length = m_enable_loop_clipping ? - scale_(m_config.seam_gap.get_abs_value(EXTRUDER_CONFIG(nozzle_diameter))) : 0; + const double seam_gap = scale_(m_config.seam_gap.get_abs_value(EXTRUDER_CONFIG(nozzle_diameter))); + const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0; // get paths ExtrusionPaths paths; @@ -4650,15 +4655,54 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou } } - - - bool is_small_peri = false; - for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { -// description += ExtrusionLoop::role_to_string(loop.loop_role()); -// description += ExtrusionEntity::role_to_string(path->role); + + const auto speed_for_path = [&speed, &small_peri_speed](const ExtrusionPath& path) { // don't apply small perimeter setting for overhangs/bridges/non-perimeters - is_small_peri = is_perimeter(path->role()) && !is_bridge(path->role()) && small_peri_speed > 0 && (path->get_overhang_degree() == 0 || path->get_overhang_degree() > 5); - gcode += this->_extrude(*path, description, is_small_peri ? small_peri_speed : speed); + const bool is_small_peri = is_perimeter(path.role()) && !is_bridge(path.role()) && small_peri_speed > 0 && (path.get_overhang_degree() == 0 || path.get_overhang_degree() > 5); + return is_small_peri ? small_peri_speed : speed; + }; + + if (!enable_seam_slope) { + for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { + gcode += this->_extrude(*path, description, speed_for_path(*path)); + } + } else { + // Create seam slope + double start_slope_ratio; + if (m_config.seam_slope_start_height.percent) { + start_slope_ratio = m_config.seam_slope_start_height.value / 100.; + } else { + // Get the ratio against current layer height + double h = paths.front().height; + start_slope_ratio = m_config.seam_slope_start_height.value / h; + } + + double loop_length = 0.; + for (const auto & path : paths) { + loop_length += unscale_(path.length()); + } + + const bool slope_entire_loop = m_config.seam_slope_entire_loop; + const double slope_min_length = slope_entire_loop ? loop_length : std::min(m_config.seam_slope_min_length.value, loop_length); + const int slope_steps = m_config.seam_slope_steps; + const double slope_max_segment_length = scale_(slope_min_length / slope_steps); + + // Calculate the sloped loop + ExtrusionLoopSloped new_loop(paths, seam_gap, slope_min_length, slope_max_segment_length, start_slope_ratio, loop.loop_role()); + + // Then extrude it + for (const auto& p : new_loop.get_all_paths()) { + gcode += this->_extrude(*p, description, speed_for_path(*p)); + } + + // Fix path for wipe + if (!new_loop.ends.empty()) { + paths.clear(); + // The start slope part is ignored as it overlaps with the end part + paths.reserve(new_loop.paths.size() + new_loop.ends.size()); + paths.insert(paths.end(), new_loop.paths.begin(), new_loop.paths.end()); + paths.insert(paths.end(), new_loop.ends.begin(), new_loop.ends.end()); + } } // BBS @@ -4932,14 +4976,22 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (is_bridge(path.role())) description += " (bridge)"; + const ExtrusionPathSloped* sloped = dynamic_cast(&path); + + const auto get_sloped_z = [&sloped, this](double z_ratio) { + const auto height = sloped->height; + return lerp(m_nominal_z - height, m_nominal_z, z_ratio); + }; + // go to first point of extrusion path //BBS: path.first_point is 2D point. But in lazy raise case, lift z is done in travel_to function. //Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance - if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z) { + if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || (sloped != nullptr && !sloped->is_flat())) { gcode += this->travel_to( path.first_point(), path.role(), - "move to first " + description + " point" + "move to first " + description + " point", + sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio) ); m_need_change_layer_lift_z = false; } @@ -5290,7 +5342,6 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (!variable_speed) { // F is mm per minute. gcode += m_writer.set_speed(F, "", comment); - double path_length = 0.; { if (m_enable_cooling_markers) { if (enable_overhang_bridge_fan) { @@ -5323,9 +5374,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } } } - // BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode + // BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode or we are doing sloped extrusion // Attention: G2 and G3 is not supported in spiral_mode mode - if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode) { + if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr) { + double path_length = 0.; + double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR; for (const Line& line : path.polyline.lines()) { std::string tempDescription = description; const double line_length = line.length() * SCALING_FACTOR; @@ -5339,10 +5392,22 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length); } } - gcode += m_writer.extrude_to_xy( - this->point_to_gcode(line.b), - dE, - GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); + if (sloped == nullptr) { + // Normal extrusion + gcode += m_writer.extrude_to_xy( + this->point_to_gcode(line.b), + dE, + GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); + } else { + // Sloped extrusion + const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length); + Vec2d dest2d = this->point_to_gcode(line.b); + Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio)); + gcode += m_writer.extrude_to_xyz( + dest3d, + dE * e_ratio, + GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion()); + } } } else { // BBS: start to generate gcode from arc fitting data which includes line and arc @@ -5356,7 +5421,6 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) { const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); const double line_length = line.length() * SCALING_FACTOR; - path_length += line_length; auto dE = e_per_mm * line_length; if (m_small_area_infill_flow_compensator && m_config.small_area_infill_flow_compensation.value) { auto oldE = dE; @@ -5378,7 +5442,6 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, const ArcSegment& arc = fitting_result[fitting_index].arc_data; const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; const Vec2d center_offset = this->point_to_gcode(arc.center) - this->point_to_gcode(arc.start_point); - path_length += arc_length; auto dE = e_per_mm * arc_length; if (m_small_area_infill_flow_compensator && m_config.small_area_infill_flow_compensation.value) { auto oldE = dE; @@ -5407,6 +5470,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else { double last_set_speed = new_points[0].speed * 60.0; + double total_length = 0; + if (sloped != nullptr) { + // Calculate total extrusion length + Points p; + p.reserve(new_points.size()); + std::transform(new_points.begin(), new_points.end(), std::back_inserter(p), [](const ProcessedPoint& pp) { return pp.p; }); + Polyline l(p); + total_length = l.length() * SCALING_FACTOR; + } gcode += m_writer.set_speed(last_set_speed, "", comment); Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); bool pre_fan_enabled = false; @@ -5414,6 +5486,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if( m_enable_cooling_markers && enable_overhang_bridge_fan) pre_fan_enabled = check_overhang_fan(new_points[0].overlap, path.role()); + double path_length = 0.; for (size_t i = 1; i < new_points.size(); i++) { std::string tempDescription = description; const ProcessedPoint &processed_point = new_points[i]; @@ -5449,6 +5522,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } const double line_length = (p - prev).norm(); + path_length += line_length; double new_speed = pre_processed_point.speed * 60.0; if (last_set_speed != new_speed) { gcode += m_writer.set_speed(new_speed, "", comment); @@ -5463,8 +5537,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length); } } - gcode += - m_writer.extrude_to_xy(p, dE, GCodeWriter::full_gcode_comment ? tempDescription : ""); + if (sloped == nullptr) { + // Normal extrusion + gcode += m_writer.extrude_to_xy(p, dE, GCodeWriter::full_gcode_comment ? tempDescription : ""); + } else { + // Sloped extrusion + const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length); + Vec3d dest3d(p(0), p(1), get_sloped_z(z_ratio)); + gcode += m_writer.extrude_to_xyz(dest3d, dE * e_ratio, GCodeWriter::full_gcode_comment ? tempDescription : ""); + } prev = p; @@ -5543,7 +5624,7 @@ std::string GCode::_encode_label_ids_to_base64(std::vector ids) } // This method accepts &point in print coordinates. -std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) +std::string GCode::travel_to(const Point& point, ExtrusionRole role, std::string comment, double z/* = DBL_MAX*/) { /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by @@ -5628,15 +5709,36 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // use G1 because we rely on paths being straight (G0 may make round paths) if (travel.size() >= 2) { - for (size_t i = 1; i < travel.size(); ++ i) { - // BBS. Process lazy layer change, but don't do lazy layer change when enable spiral vase - Vec3d curr_pos = m_writer.get_position(); - if (i == 1 && !m_spiral_vase) { - Vec2d dest2d = this->point_to_gcode(travel.points[i]); - Vec3d dest3d(dest2d(0), dest2d(1), m_nominal_z); - gcode += m_writer.travel_to_xyz(dest3d, comment+" travel_to_xyz"); + if (m_spiral_vase) { + // No lazy z lift for spiral vase mode + for (size_t i = 1; i < travel.size(); ++i) { + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment + " travel_to_xy"); + } + } else { + if (travel.size() == 2) { + // No extra movements emitted by avoid_crossing_perimeters, simply move to the end point with z change + const auto& dest2d = this->point_to_gcode(travel.points.back()); + Vec3d dest3d(dest2d(0), dest2d(1), z == DBL_MAX ? m_nominal_z : z); + gcode += m_writer.travel_to_xyz(dest3d, comment + " travel_to_xyz"); } else { - gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment+" travel_to_xy"); + // Extra movements emitted by avoid_crossing_perimeters, lift the z to normal height at the beginning, then apply the z + // ratio at the last point + for (size_t i = 1; i < travel.size(); ++i) { + if (i == 1) { + // Lift to normal z at beginning + Vec2d dest2d = this->point_to_gcode(travel.points[i]); + Vec3d dest3d(dest2d(0), dest2d(1), m_nominal_z); + gcode += m_writer.travel_to_xyz(dest3d, comment + " travel_to_xyz"); + } else if (z != DBL_MAX && i == travel.size() - 1) { + // Apply z_ratio for the very last point + Vec2d dest2d = this->point_to_gcode(travel.points[i]); + Vec3d dest3d(dest2d(0), dest2d(1), z); + gcode += m_writer.travel_to_xyz(dest3d, comment + " travel_to_xyz"); + } else { + // For all points in between, no z change + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment + " travel_to_xy"); + } + } } } this->set_last_pos(travel.points.back()); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 09aed39d2d9..03ba079fae1 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace Slic3r { @@ -219,7 +220,7 @@ class GCode { void set_layer_count(unsigned int value) { m_layer_count = value; } void apply_print_config(const PrintConfig &print_config); - std::string travel_to(const Point& point, ExtrusionRole role, std::string comment); + std::string travel_to(const Point& point, ExtrusionRole role, std::string comment, double z = DBL_MAX); bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type); std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift); std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 40976c9c555..e10e32ef921 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1080,7 +1080,11 @@ void GCodeProcessor::apply_config(const PrintConfig& config) const ConfigOptionBool* spiral_vase = config.option("spiral_mode"); if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; + m_detect_layer_based_on_tag = spiral_vase->value; + + const ConfigOptionBool* has_scarf_joint_seam = config.option("has_scarf_joint_seam"); + if (has_scarf_joint_seam != nullptr) + m_detect_layer_based_on_tag = m_detect_layer_based_on_tag || has_scarf_joint_seam->value; const ConfigOptionBool* manual_filament_change = config.option("manual_filament_change"); if (manual_filament_change != nullptr) @@ -1397,7 +1401,11 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionBool* spiral_vase = config.option("spiral_mode"); if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; + m_detect_layer_based_on_tag = spiral_vase->value; + + const ConfigOptionBool* has_scarf_joint_seam = config.option("has_scarf_joint_seam"); + if (has_scarf_joint_seam != nullptr) + m_detect_layer_based_on_tag = m_detect_layer_based_on_tag || has_scarf_joint_seam->value; const ConfigOptionEnumGeneric *bed_type = config.option("curr_bed_type"); if (bed_type != nullptr) @@ -1479,7 +1487,9 @@ void GCodeProcessor::reset() m_options_z_corrector.reset(); - m_spiral_vase_active = false; + m_detect_layer_based_on_tag = false; + + m_seams_count = 0; #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_mm3_per_mm_compare.reset(); @@ -2344,12 +2354,12 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers // layer change tag if (comment == reserved_tag(ETags::Layer_Change)) { ++m_layer_id; - if (m_spiral_vase_active) { + if (m_detect_layer_based_on_tag) { if (m_result.moves.empty() || m_result.spiral_vase_layers.empty()) // add a placeholder for layer height. the actual value will be set inside process_G1() method m_result.spiral_vase_layers.push_back({ FLT_MAX, { 0, 0 } }); else { - const size_t move_id = m_result.moves.size() - 1; + const size_t move_id = m_result.moves.size() - 1 - m_seams_count; if (!m_result.spiral_vase_layers.empty()) m_result.spiral_vase_layers.back().second.second = move_id; // add a placeholder for layer height. the actual value will be set inside process_G1() method @@ -3215,12 +3225,22 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) machine.calculate_time(TimeProcessor::Planner::queue_size); } + const Vec3f plate_offset = {(float) m_x_offset, (float) m_y_offset, 0.0f}; + if (m_seams_detector.is_active()) { // check for seam starting vertex - if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) { + if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { //BBS: m_result.moves.back().position has plate offset, must minus plate offset before calculate the real seam position - const Vec3f real_first_pos = Vec3f(m_result.moves.back().position.x() - m_x_offset, m_result.moves.back().position.y() - m_y_offset, m_result.moves.back().position.z()); - m_seams_detector.set_first_vertex(real_first_pos - m_extruder_offsets[m_extruder_id]); + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset; + if (!m_seams_detector.has_first_vertex()) { + m_seams_detector.set_first_vertex(new_pos); + } else if (m_detect_layer_based_on_tag) { + // We may have sloped loop, drop any previous start pos if we have z increment + const std::optional first_vertex = m_seams_detector.get_first_vertex(); + if (new_pos.z() > first_vertex->z()) { + m_seams_detector.set_first_vertex(new_pos); + } + } } // check for seam ending vertex and store the resulting move else if ((type != EMoveType::Extrude || (m_extrusion_role != erExternalPerimeter && m_extrusion_role != erOverhangPerimeter)) && m_seams_detector.has_first_vertex()) { @@ -3230,8 +3250,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); //BBS: m_result.moves.back().position has plate offset, must minus plate offset before calculate the real seam position - const Vec3f real_last_pos = Vec3f(m_result.moves.back().position.x() - m_x_offset, m_result.moves.back().position.y() - m_y_offset, m_result.moves.back().position.z()); - const Vec3f new_pos = real_last_pos - m_extruder_offsets[m_extruder_id]; + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset; const std::optional first_vertex = m_seams_detector.get_first_vertex(); // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later @@ -3246,16 +3265,21 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } else if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { m_seams_detector.activate(true); - Vec3f plate_offset = {(float) m_x_offset, (float) m_y_offset, 0.0f}; m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset); } - if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty()) { - if (m_result.spiral_vase_layers.back().first == FLT_MAX && delta_pos[Z] >= 0.0) + if (m_detect_layer_based_on_tag && !m_result.spiral_vase_layers.empty()) { + if (delta_pos[Z] >= 0.0 && type == EMoveType::Extrude) { + const float current_z = static_cast(m_end_position[Z]); // replace layer height placeholder with correct value - m_result.spiral_vase_layers.back().first = static_cast(m_end_position[Z]); + if (m_result.spiral_vase_layers.back().first == FLT_MAX) { + m_result.spiral_vase_layers.back().first = current_z; + } else { + m_result.spiral_vase_layers.back().first = std::max(m_result.spiral_vase_layers.back().first, current_z); + } + } if (!m_result.moves.empty()) - m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; + m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1 - m_seams_count; } // store move @@ -3637,8 +3661,17 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line) if (m_seams_detector.is_active()) { //BBS: check for seam starting vertex - if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) { - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset); + if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter) { + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id] - plate_offset; + if (!m_seams_detector.has_first_vertex()) { + m_seams_detector.set_first_vertex(new_pos); + } else if (m_detect_layer_based_on_tag) { + // We may have sloped loop, drop any previous start pos if we have z increment + const std::optional first_vertex = m_seams_detector.get_first_vertex(); + if (new_pos.z() > first_vertex->z()) { + m_seams_detector.set_first_vertex(new_pos); + } + } } //BBS: check for seam ending vertex and store the resulting move else if ((type != EMoveType::Extrude || (m_extrusion_role != erExternalPerimeter && m_extrusion_role != erOverhangPerimeter)) && m_seams_detector.has_first_vertex()) { @@ -4267,6 +4300,10 @@ void GCodeProcessor::store_move_vertex(EMoveType type, EMovePathType path_type) m_interpolation_points, }); + if (type == EMoveType::Seam) { + m_seams_count++; + } + // stores stop time placeholders for later use if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 9a509d9f672..2b3a46206a7 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -700,7 +700,8 @@ namespace Slic3r { SeamsDetector m_seams_detector; OptionsZCorrector m_options_z_corrector; size_t m_last_default_color_id; - bool m_spiral_vase_active; + bool m_detect_layer_based_on_tag {false}; + int m_seams_count; #if ENABLE_GCODE_VIEWER_STATISTICS std::chrono::time_point m_start_time; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -770,6 +771,10 @@ namespace Slic3r { //BBS: set offset for gcode writer void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; } + // Orca: if true, only change new layer if ETags::Layer_Change occurs + // otherwise when we got a lift of z during extrusion, a new layer will be added + void detect_layer_based_on_tag(bool enabled) { m_detect_layer_based_on_tag = enabled; } + private: void apply_config(const DynamicPrintConfig& config); void apply_config_simplify3d(const std::string& filename); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index d4a1ee6fb58..edea7411ef5 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -184,7 +184,13 @@ void Layer::make_perimeters() && config.fuzzy_skin == other_config.fuzzy_skin && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness && config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance - && config.fuzzy_skin_first_layer == other_config.fuzzy_skin_first_layer) + && config.fuzzy_skin_first_layer == other_config.fuzzy_skin_first_layer + && config.seam_slope_type == other_config.seam_slope_type + && config.seam_slope_start_height == other_config.seam_slope_start_height + && config.seam_slope_entire_loop == other_config.seam_slope_entire_loop + && config.seam_slope_min_length == other_config.seam_slope_min_length + && config.seam_slope_steps == other_config.seam_slope_steps + && config.seam_slope_inner_walls == other_config.seam_slope_inner_walls) { other_layerm->perimeters.clear(); other_layerm->fills.clear(); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 2e867b0df5d..bab74a93cf0 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -309,6 +309,53 @@ bool Polyline::split_at_index(const size_t index, Polyline* p1, Polyline* p2) co return true; } +bool Polyline::split_at_length(const double length, Polyline* p1, Polyline* p2) const +{ + if (this->points.empty()) return false; + if (length < 0 || length > this->length()) { + return false; + } + + if (length < SCALED_EPSILON) { + p1->clear(); + p1->append(this->first_point()); + *p2 = *this; + } else if (is_approx(length, this->length(), SCALED_EPSILON)) { + p2->clear(); + p2->append(this->last_point()); + *p1 = *this; + } else { + // 1 find the line to split at + size_t line_idx = 0; + double acc_length = 0; + Point p = this->first_point(); + for (const auto& l : this->lines()) { + p = l.b; + + const double current_length = l.length(); + if (acc_length + current_length >= length) { + p = lerp(l.a, l.b, (length - acc_length) / current_length); + break; + } + acc_length += current_length; + line_idx++; + } + + //2 judge whether the cloest point is one vertex of polyline. + // and spilit the polyline at different index + int index = this->find_point(p); + if (index != -1) { + this->split_at_index(index, p1, p2); + } else { + Polyline temp; + this->split_at_index(line_idx, p1, &temp); + p1->append(p); + this->split_at_index(line_idx + 1, &temp, p2); + p2->append_before(p); + } + } + return true; +} bool Polyline::is_straight() const { diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 580e463d154..6b5479e6697 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -130,6 +130,7 @@ class Polyline : public MultiPoint { // template void simplify_by_visibility(const T &area); void split_at(Point &point, Polyline* p1, Polyline* p2) const; bool split_at_index(const size_t index, Polyline* p1, Polyline* p2) const; + bool split_at_length(const double length, Polyline* p1, Polyline* p2) const; bool is_straight() const; bool is_closed() const { return this->points.front() == this->points.back(); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 48c52d96864..f9ed88d4f15 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -820,6 +820,7 @@ static std::vector s_Preset_print_options { "wipe_tower_rotation_angle", "tree_support_branch_distance_organic", "tree_support_branch_diameter_organic", "tree_support_branch_angle_organic", "hole_to_polyhole", "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", "small_area_infill_flow_compensation", "small_area_infill_flow_compensation_model", + "seam_slope_type", "seam_slope_start_height", "seam_slope_entire_loop", "seam_slope_min_length", "seam_slope_steps", "seam_slope_inner_walls", }; static std::vector s_Preset_filament_options { diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 76598cca220..50fdee7cb98 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1041,6 +1041,24 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ else m_support_used = false; + { + const auto& o = model.objects; + const auto opt_has_scarf_joint_seam = [](const DynamicConfig& c) { + return c.has("seam_slope_type") && c.opt_enum("seam_slope_type") != SeamScarfType::None; + }; + const bool has_scarf_joint_seam = std::any_of(o.begin(), o.end(), [&new_full_config, &opt_has_scarf_joint_seam](ModelObject* obj) { + return obj->get_config_value>(new_full_config, "seam_slope_type")->value != SeamScarfType::None || + std::any_of(obj->volumes.begin(), obj->volumes.end(), [&opt_has_scarf_joint_seam](const ModelVolume* v) { return opt_has_scarf_joint_seam(v->config.get());}) || + std::any_of(obj->layer_config_ranges.begin(), obj->layer_config_ranges.end(), [&opt_has_scarf_joint_seam](const auto& r) { return opt_has_scarf_joint_seam(r.second.get());}); + }); + + if (has_scarf_joint_seam) { + new_full_config.set("has_scarf_joint_seam", true); + } + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", has_scarf_joint_seam:" << has_scarf_joint_seam; + } + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. DynamicPrintConfig filament_overrides; //BBS: add plate index diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 345db353b1e..984192bd9f6 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -264,6 +264,14 @@ static t_config_enum_values s_keys_map_SeamPosition { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamPosition) +// Orca +static t_config_enum_values s_keys_map_SeamScarfType{ + { "none", int(SeamScarfType::None) }, + { "external", int(SeamScarfType::External) }, + { "all", int(SeamScarfType::All) }, +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamScarfType) + // Orca static t_config_enum_values s_keys_map_InternalBridgeFilter { { "disabled", ibfDisabled }, @@ -2795,6 +2803,10 @@ def = this->add("filament_loading_speed", coFloats); def->height = 15; def->set_default_value(new ConfigOptionStrings{"0,0", "\n0.2,0.4444", "\n0.4,0.6145", "\n0.6,0.7059", "\n0.8,0.7619", "\n1.5,0.8571", "\n2,0.8889", "\n3,0.9231", "\n5,0.9520", "\n10,1"}); + def = this->add("has_scarf_joint_seam", coBool); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + { struct AxisDefault { std::string name; @@ -3523,6 +3535,55 @@ def = this->add("filament_loading_speed", coFloats); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(10,true)); + def = this->add("seam_slope_type", coEnum); + def->label = L("Scarf joint seam (beta)"); + def->tooltip = L("Use scarf joint to minimize seam visibility and increase seam strength."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("none"); + def->enum_values.push_back("external"); + def->enum_values.push_back("all"); + def->enum_labels.push_back(L("None")); + def->enum_labels.push_back(L("Contour")); + def->enum_labels.push_back(L("Contour and hole")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(SeamScarfType::None)); + + def = this->add("seam_slope_start_height", coFloatOrPercent); + def->label = L("Scarf start height"); + def->tooltip = L("Start height of the scarf.\n" + "This amount can be specified in millimeters or as a percentage of the current layer height. The default value for this parameter is 0."); + def->sidetext = L("mm or %"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + + def = this->add("seam_slope_entire_loop", coBool); + def->label = L("Scarf around entire wall"); + def->tooltip = L("The scarf extends to the entire length of the wall."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("seam_slope_min_length", coFloat); + def->label = L("Scarf length"); + def->tooltip = L("Length of the scarf. Setting this parameter to zero effectively disables the scarf."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(20)); + + def = this->add("seam_slope_steps", coInt); + def->label = L("Scarf steps"); + def->tooltip = L("Minimum number of segments of each scarf."); + def->min = 1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(10)); + + def = this->add("seam_slope_inner_walls", coBool); + def->label = L("Scarf joint for inner walls"); + def->tooltip = L("Use scarf joint for inner walls as well."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("role_based_wipe_speed", coBool); def->label = L("Role base wipe speed"); def->tooltip = L("The wipe speed is determined by the speed of the current extrusion role." diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b4955abc840..07be77ab3d8 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -169,6 +169,13 @@ enum SeamPosition { spNearest, spAligned, spRear, spRandom }; +// Orca +enum class SeamScarfType { + None, + External, + All, +}; + //Orca enum InternalBridgeFilter { ibfDisabled, ibfLimited, ibfNofilter @@ -382,6 +389,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) // BBS CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamScarfType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) @@ -838,7 +846,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, top_surface_jerk)) ((ConfigOptionFloat, initial_layer_jerk)) ((ConfigOptionFloat, travel_jerk)) - ) // This object is mapped to Perl as Slic3r::Config::PrintRegion. @@ -943,6 +950,14 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, is_infill_first)) ((ConfigOptionBool, small_area_infill_flow_compensation)) ((ConfigOptionEnum, wall_direction)) + + // Orca: seam slopes + ((ConfigOptionEnum, seam_slope_type)) + ((ConfigOptionFloatOrPercent, seam_slope_start_height)) + ((ConfigOptionBool, seam_slope_entire_loop)) + ((ConfigOptionFloat, seam_slope_min_length)) + ((ConfigOptionInt, seam_slope_steps)) + ((ConfigOptionBool, seam_slope_inner_walls)) ) PRINT_CONFIG_CLASS_DEFINE( @@ -1092,6 +1107,8 @@ PRINT_CONFIG_CLASS_DEFINE( // Small Area Infill Flow Compensation ((ConfigOptionStrings, small_area_infill_flow_compensation_model)) + + ((ConfigOptionBool, has_scarf_joint_seam)) ) // This object is mapped to Perl as Slic3r::Config::Print. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index fc3c41803c1..2233057ece7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1142,6 +1142,12 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" + || opt_key == "seam_slope_type" + || opt_key == "seam_slope_start_height" + || opt_key == "seam_slope_entire_loop" + || opt_key == "seam_slope_min_length" + || opt_key == "seam_slope_steps" + || opt_key == "seam_slope_inner_walls" || opt_key == "support_speed" || opt_key == "support_interface_speed" || opt_key == "overhang_1_4_speed" diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 2469ebd9ab1..1c0667b298a 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -752,6 +752,16 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_small_area_infill_flow_compensation = config->opt_bool("small_area_infill_flow_compensation"); toggle_line("small_area_infill_flow_compensation_model", have_small_area_infill_flow_compensation); + + + toggle_field("seam_slope_type", !has_spiral_vase); + bool has_seam_slope = !has_spiral_vase && config->opt_enum("seam_slope_type") != SeamScarfType::None; + toggle_line("seam_slope_start_height", has_seam_slope); + toggle_line("seam_slope_entire_loop", has_seam_slope); + toggle_line("seam_slope_min_length", has_seam_slope); + toggle_line("seam_slope_steps", has_seam_slope); + toggle_line("seam_slope_inner_walls", has_seam_slope); + toggle_field("seam_slope_min_length", !config->opt_bool("seam_slope_entire_loop")); } void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b1f4dbbf0f0..212fdf6f2ff 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1976,6 +1976,12 @@ void TabPrint::build() optgroup->append_single_option_line("seam_position", "seam"); optgroup->append_single_option_line("staggered_inner_seams", "seam"); optgroup->append_single_option_line("seam_gap","seam"); + optgroup->append_single_option_line("seam_slope_type"); + optgroup->append_single_option_line("seam_slope_start_height"); + optgroup->append_single_option_line("seam_slope_entire_loop"); + optgroup->append_single_option_line("seam_slope_min_length"); + optgroup->append_single_option_line("seam_slope_steps"); + optgroup->append_single_option_line("seam_slope_inner_walls"); optgroup->append_single_option_line("role_based_wipe_speed","seam"); optgroup->append_single_option_line("wipe_speed", "seam"); optgroup->append_single_option_line("wipe_on_loops","seam");