diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 691cdea79d..6ae084772a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1263,7 +1263,7 @@ void GCodeGenerator::process_layers( size_t layer_to_print_idx = 0; const GCode::SmoothPathCache::InterpolationParameters interpolation_params { scaled(print.config().gcode_resolution.value), - print.config().arc_fitting && ! print.config().spiral_vase ? + print.config().arc_fitting != ArcFittingType::Disabled && ! print.config().spiral_vase ? Geometry::ArcWelder::default_arc_length_percent_tolerance : 0 }; @@ -1362,7 +1362,7 @@ void GCodeGenerator::process_layers( size_t layer_to_print_idx = 0; const GCode::SmoothPathCache::InterpolationParameters interpolation_params { scaled(print.config().gcode_resolution.value), - print.config().arc_fitting && ! print.config().spiral_vase ? + print.config().arc_fitting != ArcFittingType::Disabled && ! print.config().spiral_vase ? Geometry::ArcWelder::default_arc_length_percent_tolerance : 0 }; @@ -2828,6 +2828,7 @@ std::string GCodeGenerator::_extrude( Vec2d prev = this->point_to_gcode_quantized(path.front().point); auto it = path.begin(); auto end = path.end(); + const bool emit_radius = m_config.arc_fitting == ArcFittingType::EmitRadius; for (++ it; it != end; ++ it) { Vec2d p = this->point_to_gcode_quantized(it->point); if (it->radius == 0) { @@ -2837,12 +2838,25 @@ std::string GCodeGenerator::_extrude( gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); } else { // Extrude an arc. - double radius = GCodeFormatter::quantize_xyzf(it->radius); - Vec2f center = Geometry::ArcWelder::arc_center(prev.cast(), p.cast(), float(radius), it->ccw()); - float angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), float(radius)); + assert(m_config.arc_fitting == ArcFittingType::EmitCenter || + m_config.arc_fitting == ArcFittingType::EmitRadius); + double radius = unscaled(it->radius); + if (emit_radius) + // Only quantize radius if emitting it directly into G-code. Otherwise use the exact radius for calculating the IJ values. + radius = GCodeFormatter::quantize_xyzf(radius); + Vec2d ij; + if (! emit_radius) { + // Calculate quantized IJ circle center offset. + Vec2d center_raw = Geometry::ArcWelder::arc_center(prev.cast(), p.cast(), double(radius), it->ccw()) - prev; + ij = Vec2d{ GCodeFormatter::quantize_xyzf(center_raw.x()), GCodeFormatter::quantize_xyzf(center_raw.y()) }; + } + double angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), double(radius)); const double line_length = angle * std::abs(radius); path_length += line_length; - gcode += m_writer.extrude_to_xy_G2G3R(p, radius, it->ccw(), e_per_mm * line_length, comment); + const double dE = e_per_mm * line_length; + gcode += emit_radius ? + m_writer.extrude_to_xy_G2G3R(p, radius, it->ccw(), dE, comment) : + m_writer.extrude_to_xy_G2G3IJ(p, ij, it->ccw(), dE, comment); } prev = p; } diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index 4dd155d5f3..4375e2e1de 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -384,9 +384,13 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: return w.string(); } -std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, double dE, const bool ccw, const std::string_view comment) +std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment) { - assert(dE > 0); + assert(dE != 0); + assert(std::abs(point.x()) < 1200.); + assert(std::abs(point.y()) < 1200.); + assert(std::abs(ij.x()) < 1200.); + assert(std::abs(ij.y()) < 1200.); m_pos.head<2>() = point.head<2>(); GCodeG2G3Formatter w(ccw); @@ -397,9 +401,12 @@ std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &i return w.string(); } -std::string GCodeWriter::extrude_to_xy_G2G3R(const Vec2d &point, const double radius, double dE, const bool ccw, const std::string_view comment) +std::string GCodeWriter::extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment) { - assert(dE > 0); + assert(dE != 0); + assert(std::abs(point.x()) < 1200.); + assert(std::abs(point.y()) < 1200.); + assert(std::abs(radius) < 1800.); m_pos.head<2>() = point.head<2>(); GCodeG2G3Formatter w(ccw); diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index 904dac93dd..45f905efd4 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -66,8 +66,8 @@ public: std::string travel_to_z(double z, const std::string_view comment = {}); bool will_move_z(double z) const; std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); - std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, double dE, const bool ccw, const std::string_view comment); - std::string extrude_to_xy_G2G3R(const Vec2d &point, const double radius, double dE, const bool ccw, const std::string_view comment); + std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment); + std::string extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment); // std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); diff --git a/src/libslic3r/GCode/Wipe.cpp b/src/libslic3r/GCode/Wipe.cpp index c77984d4da..90ac401cc2 100644 --- a/src/libslic3r/GCode/Wipe.cpp +++ b/src/libslic3r/GCode/Wipe.cpp @@ -76,6 +76,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) { std::string gcode; const Extruder &extruder = *gcodegen.writer().extruder(); + static constexpr const std::string_view wipe_retract_comment = "wipe and retract"sv; // Remaining quantized retraction length. if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length()); @@ -103,14 +104,16 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) dE = retract_length; done = true; } - gcode += gcodegen.writer().extrude_to_xy(p, -dE, "wipe and retract"sv); + gcode += gcodegen.writer().extrude_to_xy(p, -dE, wipe_retract_comment); retract_length -= dE; return done; }; - auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e](const Vec2d &prev, Vec2d &p, float radius_in, const bool ccw) { - double radius = GCodeFormatter::quantize_xyzf(radius_in); - Vec2f center = Geometry::ArcWelder::arc_center(prev.cast(), p.cast(), float(radius), ccw); - float angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), float(radius)); + const bool emit_radius = gcodegen.config().arc_fitting == ArcFittingType::EmitRadius; + auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e, emit_radius](const Vec2d &prev, Vec2d &p, double radius_in, const bool ccw) { + // Only quantize radius if emitting it directly into G-code. Otherwise use the exact radius for calculating the IJ values. + double radius = emit_radius ? GCodeFormatter::quantize_xyzf(radius_in) : radius_in; + Vec2d center = Geometry::ArcWelder::arc_center(prev.cast(), p.cast(), double(radius), ccw); + float angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), double(radius)); double segment_length = angle * std::abs(radius); double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length); bool done = false; @@ -121,7 +124,13 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) dE = retract_length; done = true; } - gcode += gcodegen.writer().extrude_to_xy_G2G3R(p, radius, ccw, -dE, "wipe and retract"sv); + if (emit_radius) { + gcode += gcodegen.writer().extrude_to_xy_G2G3R(p, radius, ccw, -dE, wipe_retract_comment); + } else { + // Calculate quantized IJ circle center offset. + Vec2d ij{ GCodeFormatter::quantize_xyzf(center.x() - prev.x()), GCodeFormatter::quantize_xyzf(center.y() - prev.y()) }; + gcode += gcodegen.writer().extrude_to_xy_G2G3IJ(p, ij, ccw, -dE, wipe_retract_comment); + } retract_length -= dE; return done; }; @@ -144,7 +153,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) start_wipe(); if (it->linear() ? wipe_linear(prev, p) : - wipe_arc(prev, p, it->radius, it->ccw())) + wipe_arc(prev, p, unscaled(it->radius), it->ccw())) break; prev = p; } diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index 51c9ffe1f5..41c3b5f249 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -386,6 +386,14 @@ Path fit_path(const Points &src, double tolerance, double fit_circle_percent_tol out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end()); } +#if 0 + // Verify that all the source points are at tolerance distance from the interpolated path. + for (const Point &p : src) { + PathSegmentProjection proj = point_to_path_projection(out, p); + assert(proj.distance2 < sqr(tolerance + SCALED_EPSILON)); + } +#endif + return out; } diff --git a/src/libslic3r/Geometry/ArcWelder.hpp b/src/libslic3r/Geometry/ArcWelder.hpp index 00a1cc4397..86e4eb31a9 100644 --- a/src/libslic3r/Geometry/ArcWelder.hpp +++ b/src/libslic3r/Geometry/ArcWelder.hpp @@ -54,7 +54,10 @@ inline Eigen::Matrix arc_cente auto v = end_pos - start_pos; Float q2 = v.squaredNorm(); assert(q2 > 0); - Float t = sqrt(sqr(radius) / q2 - Float(.25f)); + Float t2 = sqr(radius) / q2 - Float(.25f); + // If the start_pos and end_pos are nearly antipodal, t2 may become slightly negative. + // In that case return a centroid of start_point & end_point. + Float t = t2 > 0 ? sqrt(t2) : Float(0); auto mid = Float(0.5) * (start_pos + end_pos); Vector vp{ -v.y() * t, v.x() * t }; return (radius > Float(0)) == is_ccw ? (mid + vp).eval() : (mid - vp).eval(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 416ceb2802..c1d8d68d83 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -34,6 +34,13 @@ static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values & template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values() { return s_keys_map_##NAME; } \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names() { return s_keys_names_##NAME; } +static const t_config_enum_values s_keys_map_ArcFittingType { + { "disabled", int(ArcFittingType::Disabled) }, + { "emit_center", int(ArcFittingType::EmitCenter) }, + { "emit_radius", int(ArcFittingType::EmitRadius) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ArcFittingType) + static t_config_enum_values s_keys_map_PrinterTechnology { { "FFF", ptFFF }, { "SLA", ptSLA } @@ -397,12 +404,17 @@ void PrintConfigDef::init_fff_params() { ConfigOptionDef* def; - def = this->add("arc_fitting", coBool); + def = this->add("arc_fitting", coEnum); def->label = L("Arc fitting"); def->tooltip = L("Enable this to get a G-code file which has G2 and G3 moves. " "And the fitting tolerance is same with resolution"); + def->set_enum({ + { "disabled", "Disabled" }, + { "emit_center", "Enabled: G2/3 I J" }, + { "emit_radius", "Enabled: G2/3 R" } + }); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(false)); + def->set_default_value(new ConfigOptionEnum(ArcFittingType::Disabled)); def = this->add("arc_fitting_tolerance", coFloatOrPercent); def->label = L("Arc fitting tolerance"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 60e4c81196..229aec7f75 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -31,6 +31,12 @@ namespace Slic3r { +enum class ArcFittingType { + Disabled, + EmitCenter, + EmitRadius, +}; + enum GCodeFlavor : unsigned char { gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, @@ -142,6 +148,7 @@ enum class GCodeThumbnailsFormat { template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ArcFittingType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage) @@ -662,7 +669,7 @@ PRINT_CONFIG_CLASS_DEFINE( PRINT_CONFIG_CLASS_DEFINE( GCodeConfig, - ((ConfigOptionBool, arc_fitting)) + ((ConfigOptionEnum, arc_fitting)) ((ConfigOptionFloatOrPercent, arc_fitting_tolerance)) ((ConfigOptionBool, autoemit_temperature_commands)) ((ConfigOptionString, before_layer_gcode))