From 3649ab7fe0ff3ad61769d7c311bc052bedf2f098 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Wed, 3 Mar 2021 18:48:01 -0600 Subject: Add extrusion rate variance percent parameter for cura Arachne. --- ArcWelder/arc_welder.cpp | 169 +++++++++++++++++++------------- ArcWelder/arc_welder.h | 5 + ArcWelderConsole/ArcWelderConsole.cpp | 22 ++++- ArcWelderTest/ArcWelderTest.cpp | 1 + GcodeProcessorLib/utilities.cpp | 9 ++ GcodeProcessorLib/utilities.h | 1 + PyArcWelder/py_arc_welder.h | 2 + PyArcWelder/py_arc_welder_extension.cpp | 17 +++- PyArcWelder/py_arc_welder_extension.h | 3 + 9 files changed, 157 insertions(+), 72 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index c49f64d..a1f4ca7 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -49,6 +49,7 @@ arc_welder::arc_welder( bool allow_dynamic_precision, unsigned char default_xyz_precision, unsigned char default_e_precision, + double extrusion_rate_variance_percent, int buffer_size, progress_callback callback) : current_arc_( DEFAULT_MIN_SEGMENTS, @@ -83,6 +84,7 @@ arc_welder::arc_welder( gcode_position_args_ = get_args_(g90_g91_influences_extruder, buffer_size); allow_3d_arcs_ = allow_3d_arcs; allow_dynamic_precision_ = allow_dynamic_precision; + extrusion_rate_variance_percent_ = extrusion_rate_variance_percent; notification_period_seconds = 1; lines_processed_ = 0; gcodes_processed_ = 0; @@ -93,6 +95,7 @@ arc_welder::arc_welder( waiting_for_arc_ = false; previous_feedrate_ = -1; gcode_position_args_.set_num_extruders(8); + previous_extrusion_rate_ = 0; for (int index = 0; index < 8; index++) { gcode_position_args_.retraction_lengths[0] = .0001; @@ -399,6 +402,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess bool is_previous_extruder_relative = p_pre_pos->is_extruder_relative; extruder extruder_current = p_cur_pos->get_current_extruder(); extruder previous_extruder = p_pre_pos->get_current_extruder(); + //std::cout << lines_processed_ << " - " << cmd.gcode << ", CurrentEAbsolute: " << cur_extruder.e <<", ExtrusionLength: " << cur_extruder.extrusion_length << ", Retraction Length: " << cur_extruder.retraction_length << ", IsExtruding: " << cur_extruder.is_extruding << ", IsRetracting: " << cur_extruder.is_retracting << ".\n"; int lines_written = 0; @@ -425,6 +429,22 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess } } + // calculate the extrusion rate (mm/mm) and see how much it changes + double mm_extruded_per_mm_travel = 0; + double extrusion_rate_change_percent = 0; + if (movement_length_mm > 0) + { + mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; + if (previous_extrusion_rate_ > 0) + { + extrusion_rate_change_percent = std::fabs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); + } + } + if (extrusion_rate_change_percent > extrusion_rate_variance_percent_) + { + std::cout << "Extrusion Rate Change Percent: " << extrusion_rate_change_percent << "\n"; + } + // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position // TODO: Handle relative XYZ axis. This is possible, but maybe not so important. bool is_g1_g2 = cmd.command == "G0" || cmd.command == "G1"; @@ -458,6 +478,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess utilities::is_equal(p_cur_pos->x_firmware_offset, p_pre_pos->x_firmware_offset) && utilities::is_equal(p_cur_pos->y_firmware_offset, p_pre_pos->y_firmware_offset) && utilities::is_equal(p_cur_pos->z_firmware_offset, p_pre_pos->z_firmware_offset) && + (previous_extrusion_rate_ == 0 || utilities::less_than_or_equal(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) && !p_cur_pos->is_relative && ( !waiting_for_arc_ || @@ -511,86 +532,95 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess } } } - else if (debug_logging_enabled_ ){ - if (is_end) - { - p_logger_->log(logger_type_, DEBUG, "Procesing final shape, if one exists."); - } - else if (!cmd.is_empty) - { - if (!cmd.is_known_command) - { - p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); - } - else if (cmd.command != "G0" && cmd.command != "G1") - { - p_logger_->log(logger_type_, DEBUG, "Command '"+ cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); - } - else if (!allow_3d_arcs_ && !utilities::is_equal(p_cur_pos->z, p_pre_pos->z)) + else { + previous_extrusion_rate_ = 0; + if (debug_logging_enabled_) { + if (is_end) { - p_logger_->log(logger_type_, DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); + p_logger_->log(logger_type_, DEBUG, "Procesing final shape, if one exists."); } - else if (p_cur_pos->is_relative) + else if (!cmd.is_empty) { - p_logger_->log(logger_type_, DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); - } - else if ( - waiting_for_arc_ && !( - (previous_extruder.is_extruding && extruder_current.is_extruding) || - (previous_extruder.is_retracting && extruder_current.is_retracting) - ) - ) - { - std::string message = "Extruding or retracting state changed, cannot add point to current arc: " + cmd.gcode; - if (verbose_logging_enabled_) + if (!cmd.is_known_command) { - - message.append( - " - Verbose Info\n\tCurrent Position Info - Absolute E:" + utilities::to_string(extruder_current.e) + - ", Offset E:" + utilities::to_string(extruder_current.get_offset_e()) + - ", Mode:" + (p_cur_pos->is_extruder_relative_null ? "NULL" : p_cur_pos->is_extruder_relative ? "relative" : "absolute") + - ", Retraction: " + utilities::to_string(extruder_current.retraction_length) + - ", Extrusion: " + utilities::to_string(extruder_current.extrusion_length) + - ", Retracting: " + (extruder_current.is_retracting ? "True" : "False") + - ", Extruding: " + (extruder_current.is_extruding ? "True" : "False") - ); - message.append( - "\n\tPrevious Position Info - Absolute E:" + utilities::to_string(previous_extruder.e) + - ", Offset E:" + utilities::to_string(previous_extruder.get_offset_e()) + - ", Mode:" + (p_pre_pos->is_extruder_relative_null ? "NULL" : p_pre_pos->is_extruder_relative ? "relative" : "absolute") + - ", Retraction: " + utilities::to_string(previous_extruder.retraction_length) + - ", Extrusion: " + utilities::to_string(previous_extruder.extrusion_length) + - ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") + - ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False") - ); - p_logger_->log(logger_type_, VERBOSE, message); + p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); + } + else if (cmd.command != "G0" && cmd.command != "G1") + { + p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); + } + else if (!allow_3d_arcs_ && !utilities::is_equal(p_cur_pos->z, p_pre_pos->z)) + { + p_logger_->log(logger_type_, DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); + } + else if (p_cur_pos->is_relative) + { + p_logger_->log(logger_type_, DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); + } + else if ( + waiting_for_arc_ && !( + (previous_extruder.is_extruding && extruder_current.is_extruding) || + (previous_extruder.is_retracting && extruder_current.is_retracting) + ) + ) + { + std::string message = "Extruding or retracting state changed, cannot add point to current arc: " + cmd.gcode; + if (verbose_logging_enabled_) + { + + message.append( + " - Verbose Info\n\tCurrent Position Info - Absolute E:" + utilities::to_string(extruder_current.e) + + ", Offset E:" + utilities::to_string(extruder_current.get_offset_e()) + + ", Mode:" + (p_cur_pos->is_extruder_relative_null ? "NULL" : p_cur_pos->is_extruder_relative ? "relative" : "absolute") + + ", Retraction: " + utilities::to_string(extruder_current.retraction_length) + + ", Extrusion: " + utilities::to_string(extruder_current.extrusion_length) + + ", Retracting: " + (extruder_current.is_retracting ? "True" : "False") + + ", Extruding: " + (extruder_current.is_extruding ? "True" : "False") + ); + message.append( + "\n\tPrevious Position Info - Absolute E:" + utilities::to_string(previous_extruder.e) + + ", Offset E:" + utilities::to_string(previous_extruder.get_offset_e()) + + ", Mode:" + (p_pre_pos->is_extruder_relative_null ? "NULL" : p_pre_pos->is_extruder_relative ? "relative" : "absolute") + + ", Retraction: " + utilities::to_string(previous_extruder.retraction_length) + + ", Extrusion: " + utilities::to_string(previous_extruder.extrusion_length) + + ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") + + ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False") + ); + p_logger_->log(logger_type_, VERBOSE, message); + } + else + { + p_logger_->log(logger_type_, DEBUG, message); + } + + } + else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative) + { + p_logger_->log(logger_type_, DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); + } + else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f) + { + p_logger_->log(logger_type_, DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); + } + else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag) + { + p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); + } + else if (previous_extrusion_rate_ != 0 && !utilities::is_equal(previous_extrusion_rate_, mm_extruded_per_mm_travel)) + { + p_logger_->log(logger_type_, DEBUG, "Previus extrusion rate changed: " + cmd.gcode); } else { - p_logger_->log(logger_type_, DEBUG, message); + // Todo: Add all the relevant values + p_logger_->log(logger_type_, DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); } - - } - else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative) - { - p_logger_->log(logger_type_, DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); - } - else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f) - { - p_logger_->log(logger_type_, DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); - } - else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag) - { - p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); - } - else - { - // Todo: Add all the relevant values - p_logger_->log(logger_type_, DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); } } } + previous_extrusion_rate_ = mm_extruded_per_mm_travel; + if (!arc_added && !(cmd.is_empty && cmd.comment.length() == 0)) { if (current_arc_.get_num_segments() < current_arc_.get_min_segments()) { @@ -837,7 +867,8 @@ void arc_welder::add_arcwelder_comment_to_target() stream << "; allow_dynamic_precision=True\n"; } stream << "; default_xyz_precision=" << std::setprecision(0) << static_cast(current_arc_.get_xyz_precision()) << "\n"; - stream << "; default_e_precision=" << std::setprecision(0) << static_cast(current_arc_.get_e_precision()) << "\n\n"; + stream << "; default_e_precision=" << std::setprecision(0) << static_cast(current_arc_.get_e_precision()) << "\n"; + stream << "; extrusion_rate_variance_percent=" << std::setprecision(3) << static_cast(extrusion_rate_variance_percent_) << "%\n\n"; output_file_ << stream.str(); diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index 6d00ec4..2da8929 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -415,6 +415,8 @@ struct arc_welder_results { #define DEFAULT_GCODE_BUFFER_SIZE 1000 #define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false #define DEFAULT_ALLOW_DYNAMIC_PRECISION false +#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 1 +//#define DEFAULT_EXTRUSION_RATE_VARIANCE 0.0001 class arc_welder { public: @@ -432,6 +434,7 @@ public: bool allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION, unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION, unsigned char default_e_precision = DEFAULT_E_PRECISION, + double extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, int buffer_size = DEFAULT_GCODE_BUFFER_SIZE, progress_callback callback = NULL); void set_logger_type(int logger_type); @@ -478,6 +481,8 @@ private: // We don't care about the printer settings, except for g91 influences extruder. gcode_position* p_source_position_; double previous_feedrate_; + double previous_extrusion_rate_; + double extrusion_rate_variance_percent_; gcode_parser parser_; bool verbose_output_; int logger_type_; diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp index 0ebe847..691f155 100644 --- a/ArcWelderConsole/ArcWelderConsole.cpp +++ b/ArcWelderConsole/ArcWelderConsole.cpp @@ -50,6 +50,7 @@ int main(int argc, char* argv[]) bool allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION; unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION; unsigned char default_e_precision = DEFAULT_E_PRECISION; + double extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; std::string log_level_string; std::string log_level_string_default = "INFO"; int log_level_value; @@ -140,6 +141,13 @@ int main(int argc, char* argv[]) arg_description_stream << "The default precision of E output gcode parameters. The precision may be larger than this value if allow-dynamic-precision is set to true. Default Value: " << DEFAULT_E_PRECISION; TCLAP::ValueArg default_e_precision_arg("e", "default-e-precision", arg_description_stream.str(), false, DEFAULT_E_PRECISION, "unsigned char"); + // -v --extrusion-rate-variance + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "The allowed variance in extrusion rate by percent. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; + TCLAP::ValueArg extrusion_rate_variance_percent_arg("v", "extrusion-rate-variance-percent", arg_description_stream.str(), false, DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, "double"); + + // -g --hide-progress TCLAP::SwitchArg hide_progress_arg("p", "hide-progress", "If supplied, prevents progress updates from being displayed.", false); @@ -171,6 +179,7 @@ int main(int argc, char* argv[]) cmd.add(allow_dynamic_precision_arg); cmd.add(default_xyz_precision_arg); cmd.add(default_e_precision_arg); + cmd.add(extrusion_rate_variance_percent_arg); cmd.add(g90_arg); cmd.add(hide_progress_arg); cmd.add(log_level_arg); @@ -197,6 +206,7 @@ int main(int argc, char* argv[]) allow_dynamic_precision = allow_dynamic_precision_arg.getValue(); default_xyz_precision = default_xyz_precision_arg.getValue(); default_e_precision = default_e_precision_arg.getValue(); + extrusion_rate_variance_percent = extrusion_rate_variance_percent_arg.getValue(); hide_progress = hide_progress_arg.getValue(); log_level_string = log_level_arg.getValue(); @@ -275,6 +285,13 @@ int main(int argc, char* argv[]) default_e_precision = 6; } + if (extrusion_rate_variance_percent < 0) + { + // warning + std::cout << "warning: The provided extrusion_rate_variance_percent " << extrusion_rate_variance_percent << " is less than 0. Setting to the default." << std::endl; + extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; + } + if (has_error) { return 1; @@ -353,6 +370,7 @@ int main(int argc, char* argv[]) log_messages << "\tAllow Dynamic Precision : " << (allow_dynamic_precision ? "True" : "False") << "\n"; log_messages << "\tDefault XYZ Precision : " << std::setprecision(0) << static_cast(default_xyz_precision) << "\n"; log_messages << "\tDefault E Precision : " << std::setprecision(0) << static_cast(default_e_precision) << "\n"; + log_messages << "\tExtrusion Rate Variance % : " << std::setprecision(3) << extrusion_rate_variance_percent << "%\n"; log_messages << "\tG90/G91 Influences Extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n"; log_messages << "\tLog Level : " << log_level_string << "\n"; log_messages << "\tHide Progress Updates : " << (hide_progress ? "True" : "False"); @@ -364,9 +382,9 @@ int main(int argc, char* argv[]) target_file_path = temp_file_path; } if (!hide_progress) - p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, DEFAULT_GCODE_BUFFER_SIZE, on_progress); + p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, on_progress); else - p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, DEFAULT_GCODE_BUFFER_SIZE, suppress_progress); + p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, suppress_progress); arc_welder_results results = p_arc_welder->process(); if (results.success) diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 9add004..3cb916c 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -305,6 +305,7 @@ static void TestAntiStutter(std::string filePath) DEFAULT_ALLOW_DYNAMIC_PRECISION, DEFAULT_XYZ_PRECISION, DEFAULT_E_PRECISION, + DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, DEFAULT_GCODE_BUFFER_SIZE, on_progress); //FIRMWARE_COMPENSATION_TEST_1 diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index dbe23fb..041f91f 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -171,6 +171,15 @@ double utilities::get_percent_change(int v1, int v2) return 0; } +double utilities::get_percent_change(double v1, double v2) +{ + if (v1 != 0) + { + return ((v2 - v1) / v1) * 100.0; + } + return 0; +} + std::string utilities::get_percent_change_string(int v1, int v2, int precision) { std::stringstream format_stream; diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index 401bed4..b41d6f6 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -49,6 +49,7 @@ public: static std::istream& safe_get_line(std::istream& is, std::string& t); static std::string center(std::string input, int width); static double get_percent_change(int v1, int v2); + static double get_percent_change(double v1, double v2); static std::string get_percent_change_string(int v1, int v2, int precision); static int get_num_digits(int x); diff --git a/PyArcWelder/py_arc_welder.h b/PyArcWelder/py_arc_welder.h index ed4e8cc..6813c34 100644 --- a/PyArcWelder/py_arc_welder.h +++ b/PyArcWelder/py_arc_welder.h @@ -49,6 +49,7 @@ public: bool allow_dynamic_precision, unsigned char default_xyz_precision, unsigned char default_e_precision, + double extrusion_rate_variance_percent, int buffer_size, PyObject* py_progress_callback ): arc_welder( @@ -65,6 +66,7 @@ public: allow_dynamic_precision, default_xyz_precision, default_e_precision, + extrusion_rate_variance_percent, buffer_size ){ guid_ = guid; diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp index 1e0728b..71c3855 100644 --- a/PyArcWelder/py_arc_welder_extension.cpp +++ b/PyArcWelder/py_arc_welder_extension.cpp @@ -210,6 +210,7 @@ extern "C" args.allow_dynamic_precision, args.default_xyz_precision, args.default_e_precision, + args.extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, py_progress_callback ); @@ -342,11 +343,25 @@ static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** py_ args.default_e_precision = 6; } + // Extract the extrusion_rate_variance + PyObject* py_extrusion_rate_variance_percent = PyDict_GetItemString(py_args, "extrusion_rate_variance_percent"); + if (py_extrusion_rate_variance_percent == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the extrusion_rate_variance_percent parameter from the args."; + p_py_logger->log_exception(GCODE_CONVERSION, message); + return false; + } + args.extrusion_rate_variance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_extrusion_rate_variance_percent); + if (args.extrusion_rate_variance_percent < 0) + { + args.extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; // Set to the default if no resolution is provided, or if it is less than 0. + } + // Extract the path tolerance_percent PyObject* py_path_tolerance_percent = PyDict_GetItemString(py_args, "path_tolerance_percent"); if (py_path_tolerance_percent == NULL) { - std::string message = "ParseArgs - Unable to retrieve the py_path_tolerance_percent parameter from the args."; + std::string message = "ParseArgs - Unable to retrieve the path_tolerance_percent parameter from the args."; p_py_logger->log_exception(GCODE_CONVERSION, message); return false; } diff --git a/PyArcWelder/py_arc_welder_extension.h b/PyArcWelder/py_arc_welder_extension.h index 2c2ecec..9b979fe 100644 --- a/PyArcWelder/py_arc_welder_extension.h +++ b/PyArcWelder/py_arc_welder_extension.h @@ -69,6 +69,7 @@ struct py_gcode_arc_args { bool allow_dynamic_precision_, unsigned char default_xyz_precision_, unsigned char default_e_precision_, + double extrusion_rate_variance_percent_, int log_level_ ) { guid = guid_; @@ -83,6 +84,7 @@ struct py_gcode_arc_args { allow_dynamic_precision = allow_dynamic_precision_; default_xyz_precision = default_xyz_precision_; default_e_precision = default_e_precision_; + extrusion_rate_variance_percent = extrusion_rate_variance_percent_; g90_g91_influences_extruder = g90_g91_influences_extruder_; log_level = log_level_; } @@ -95,6 +97,7 @@ struct py_gcode_arc_args { bool allow_dynamic_precision; unsigned char default_xyz_precision; unsigned char default_e_precision; + double extrusion_rate_variance_percent; bool g90_g91_influences_extruder; double max_radius_mm; int min_arc_segments; -- cgit v1.2.3 From 65768d59a9ed785ddb740fc6df67d2912d2a5bdf Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sun, 28 Mar 2021 11:48:09 -0500 Subject: Fix relative extrusion for #49. --- ArcWelderInverseProcessor/inverse_processor.cpp | 5 ++++- ArcWelderInverseProcessor/inverse_processor.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ArcWelderInverseProcessor/inverse_processor.cpp b/ArcWelderInverseProcessor/inverse_processor.cpp index c2f0b4d..c6edb6f 100644 --- a/ArcWelderInverseProcessor/inverse_processor.cpp +++ b/ArcWelderInverseProcessor/inverse_processor.cpp @@ -60,6 +60,7 @@ inverse_processor::inverse_processor(std::string source_path, std::string target target_path_ = target_path; p_source_position_ = new gcode_position(get_args_(g90_g91_influences_extruder, buffer_size)); cs_ = cs; + offset_absolute_e_ = 0; // ** Gloabal Variable Definition ** // 20200417 - FormerLurker - Declare two globals and pre-calculate some values that will reduce the // amount of trig funcitons we need to call while printing. For the price of having two globals we @@ -200,6 +201,7 @@ void inverse_processor::process() float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc uint8_t isclockwise = cmd.command == "G2" ? 1 : 0; output_relative_ = p_cur_pos->is_extruder_relative; + offset_absolute_e_ = p_pre_pos->get_current_extruder().get_offset_e(); mc_arc(position, target, offset, static_cast(p_cur_pos->f), radius, isclockwise, 0); } else @@ -425,7 +427,8 @@ void inverse_processor::plan_buffer_line(float x, float y, bool has_z, float z, double output_e = e; if (previous_pos->is_extruder_relative) { - output_e = e - previous_pos->get_current_extruder().get_offset_e(); + output_e = e - offset_absolute_e_; + offset_absolute_e_ = e; } stream << std::setprecision(5) << " E" << output_e; diff --git a/ArcWelderInverseProcessor/inverse_processor.h b/ArcWelderInverseProcessor/inverse_processor.h index 42dd8bc..ab33133 100644 --- a/ArcWelderInverseProcessor/inverse_processor.h +++ b/ArcWelderInverseProcessor/inverse_processor.h @@ -77,6 +77,7 @@ private: gcode_position* p_source_position_; std::ofstream output_file_; bool output_relative_; + double offset_absolute_e_; float arc_max_radius_threshold; //float arc_min_radius_threshold; float total_e_adjustment; -- cgit v1.2.3 From 18d1e992d3773485d2b7b05ffaec1ea00b16c777 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Fri, 2 Jul 2021 14:24:47 -0500 Subject: Add support for variable line widths. --- ArcWelder/arc_welder.cpp | 49 ++- ArcWelder/arc_welder.h | 9 +- ArcWelder/segmented_arc.cpp | 10 +- ArcWelder/segmented_shape.cpp | 40 +- ArcWelder/segmented_shape.h | 3 +- .../ArcWelderInverseProcessor.cpp | 4 +- .../ArcWelderInverseProcessor.vcxproj | 8 +- .../ArcWelderInverseProcessor.vcxproj.filters | 16 +- ArcWelderInverseProcessor/firmware_types.cpp | 1 + ArcWelderInverseProcessor/firmware_types.h | 4 + ArcWelderInverseProcessor/inverse_processor.cpp | 448 --------------------- ArcWelderInverseProcessor/inverse_processor.h | 95 ----- ArcWelderInverseProcessor/marlin_2_arc.cpp | 448 +++++++++++++++++++++ ArcWelderInverseProcessor/marlin_2_arc.h | 95 +++++ ArcWelderInverseProcessor/repiter_arc.cpp | 97 +++++ ArcWelderInverseProcessor/repiter_arc.h | 7 + ArcWelderTest/ArcWelderTest.cpp | 8 +- ArcWelderTest/ArcWelderTest.h | 5 +- GcodeProcessorLib/GcodeProcessorLib.vcxproj | 2 - .../GcodeProcessorLib.vcxproj.filters | 12 +- GcodeProcessorLib/array_list.cpp | 22 - GcodeProcessorLib/circular_buffer.cpp | 22 - GcodeProcessorLib/circular_buffer.h | 108 ++++- GcodeProcessorLib/gcode_position.cpp | 107 ++--- GcodeProcessorLib/gcode_position.h | 8 +- GcodeProcessorLib/position.cpp | 4 + GcodeProcessorLib/position.h | 1 + GcodeProcessorLib/utilities.cpp | 19 +- GcodeProcessorLib/utilities.h | 3 + PyArcWelder/py_arc_welder.cpp | 24 +- 30 files changed, 953 insertions(+), 726 deletions(-) create mode 100644 ArcWelderInverseProcessor/firmware_types.cpp create mode 100644 ArcWelderInverseProcessor/firmware_types.h delete mode 100644 ArcWelderInverseProcessor/inverse_processor.cpp delete mode 100644 ArcWelderInverseProcessor/inverse_processor.h create mode 100644 ArcWelderInverseProcessor/marlin_2_arc.cpp create mode 100644 ArcWelderInverseProcessor/marlin_2_arc.h create mode 100644 ArcWelderInverseProcessor/repiter_arc.cpp create mode 100644 ArcWelderInverseProcessor/repiter_arc.h delete mode 100644 GcodeProcessorLib/array_list.cpp delete mode 100644 GcodeProcessorLib/circular_buffer.cpp diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index a1f4ca7..95fbcda 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -34,6 +34,7 @@ #include #include #include +#include arc_welder::arc_welder( std::string source_path, @@ -53,7 +54,7 @@ arc_welder::arc_welder( int buffer_size, progress_callback callback) : current_arc_( DEFAULT_MIN_SEGMENTS, - buffer_size - 5, + buffer_size, resolution_mm, path_tolerance_percent, max_radius, @@ -92,6 +93,7 @@ arc_welder::arc_welder( last_gcode_line_written_ = 0; points_compressed_ = 0; arcs_created_ = 0; + arcs_aborted_by_flow_rate_ = 0; waiting_for_arc_ = false; previous_feedrate_ = -1; gcode_position_args_.set_num_extruders(8); @@ -114,6 +116,10 @@ gcode_position_args arc_welder::get_args_(bool g90_g91_influences_extruder, int gcode_position_args args; // Configure gcode_position_args args.g90_influences_extruder = g90_g91_influences_extruder; + if (buffer_size < 2) + { + buffer_size = 2; + } args.position_buffer_size = buffer_size; args.autodetect_position = true; args.home_x = 0; @@ -258,7 +264,8 @@ arc_welder_results results; p_logger_->log(logger_type_, DEBUG, "Sending initial progress update."); continue_processing = on_progress_(get_progress_(static_cast(gcodeFile.tellg()), static_cast(start_clock))); p_logger_->log(logger_type_, DEBUG, "Processing source file."); - + + bool arc_Welder_comment_added = false; while (std::getline(gcodeFile, line) && continue_processing) { lines_processed_++; @@ -267,17 +274,20 @@ arc_welder_results results; if (lines_processed_ == 1) { bool isUltiGCode = line == ";FLAVOR:UltiGCode"; - if (isUltiGCode) + bool isPrusaSlicer = line.rfind("; generated by PrusaSlicer", 0) == 0; + if (isUltiGCode || isPrusaSlicer) { write_gcode_to_file(line); } add_arcwelder_comment_to_target(); - if (isUltiGCode) + if (isUltiGCode || isPrusaSlicer) { lines_with_no_commands++; continue; } } + + cmd.clear(); if (verbose_logging_enabled_) { @@ -368,6 +378,7 @@ arc_welder_progress arc_welder::get_progress_(long source_file_position, double progress.lines_processed = lines_processed_; progress.points_compressed = points_compressed_; progress.arcs_created = arcs_created_; + progress.arcs_aborted_by_flow_rate = arcs_aborted_by_flow_rate_; progress.source_file_position = source_file_position; progress.target_file_size = static_cast(output_file_.tellp()); progress.source_file_size = file_size_; @@ -432,6 +443,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess // calculate the extrusion rate (mm/mm) and see how much it changes double mm_extruded_per_mm_travel = 0; double extrusion_rate_change_percent = 0; + bool aborted_by_flow_rate = false; if (movement_length_mm > 0) { mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; @@ -440,9 +452,10 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess extrusion_rate_change_percent = std::fabs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); } } - if (extrusion_rate_change_percent > extrusion_rate_variance_percent_) + if (previous_extrusion_rate_ != 0 && utilities::greater_than(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) { - std::cout << "Extrusion Rate Change Percent: " << extrusion_rate_change_percent << "\n"; + arcs_aborted_by_flow_rate_++; + aborted_by_flow_rate = true; } // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position @@ -483,6 +496,8 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess ( !waiting_for_arc_ || extruder_current.is_extruding || + // Test for travel conversion + //(previous_extruder.is_extruding && extruder_current.is_extruding) || // Test to see if // we can get more arcs. (previous_extruder.is_retracting && extruder_current.is_retracting) @@ -493,6 +508,8 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess ) ) { + // Record the extrusion rate + previous_extrusion_rate_ = mm_extruded_per_mm_travel; printer_point p(p_cur_pos->get_gcode_x(), p_cur_pos->get_gcode_y(), p_cur_pos->get_gcode_z(), extruder_current.e_relative, movement_length_mm); if (!waiting_for_arc_) { @@ -514,6 +531,11 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess arc_added = current_arc_.try_add_point(p); if (arc_added) { + // Make sure our position list is large enough to handle all the segments + if (current_arc_.get_num_segments()+2 > p_source_position_->get_max_positions()) + { + p_source_position_->grow_max_positions(p_source_position_->get_max_positions()*2); + } if (!waiting_for_arc_) { waiting_for_arc_ = true; @@ -533,7 +555,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess } } else { - previous_extrusion_rate_ = 0; + if (debug_logging_enabled_) { if (is_end) { @@ -606,9 +628,12 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); } - else if (previous_extrusion_rate_ != 0 && !utilities::is_equal(previous_extrusion_rate_, mm_extruded_per_mm_travel)) + else if (aborted_by_flow_rate) { - p_logger_->log(logger_type_, DEBUG, "Previus extrusion rate changed: " + cmd.gcode); + std::stringstream stream; + stream << std::fixed << std::setprecision(5); + stream << "Arc Canceled - The extrusion rate variance of " << extrusion_rate_variance_percent_ << "% exceeded by " << extrusion_rate_change_percent - extrusion_rate_variance_percent_ <<"% on line " << lines_processed_ << ". Extruded " << extruder_current.e_relative << "mm over " << movement_length_mm << "mm of travel (" << mm_extruded_per_mm_travel << "mm/mm). Previous rate: " << previous_extrusion_rate_ << "mm/mm."; + p_logger_->log(logger_type_, DEBUG, stream.str()); } else { @@ -617,9 +642,12 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess } } } + + // Reset the previous extrusion rate + previous_extrusion_rate_ = 0; } - previous_extrusion_rate_ = mm_extruded_per_mm_travel; + if (!arc_added && !(cmd.is_empty && cmd.comment.length() == 0)) { @@ -844,6 +872,7 @@ void arc_welder::add_arcwelder_comment_to_target() stream << std::fixed; stream << "; Postprocessed by [ArcWelder](https://github.com/FormerLurker/ArcWelderLib)\n"; stream << "; Copyright(C) 2020 - Brad Hochgesang\n"; + stream << "; Version: " << GIT_TAGGED_VERSION << ", Branch: " << GIT_BRANCH << ", BuildDate: " << BUILD_DATE << "\n"; stream << "; resolution=" << std::setprecision(2) << resolution_mm_ << "mm\n"; stream << "; path_tolerance=" << std::setprecision(0) << (current_arc_.get_path_tolerance_percent() * 100.0) << "%\n"; stream << "; max_radius=" << std::setprecision(2) << (current_arc_.get_max_radius()) << "mm\n"; diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index 2da8929..a4fdded 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -354,6 +354,7 @@ struct arc_welder_progress { lines_processed = 0; points_compressed = 0; arcs_created = 0; + arcs_aborted_by_flow_rate = 0; num_firmware_compensations = 0; source_file_size = 0; source_file_position = 0; @@ -368,6 +369,7 @@ struct arc_welder_progress { int lines_processed; int points_compressed; int arcs_created; + int arcs_aborted_by_flow_rate; int num_firmware_compensations; double compression_ratio; double compression_percent; @@ -385,6 +387,7 @@ struct arc_welder_progress { stream << ", current_file_line: " << lines_processed; stream << ", points_compressed: " << points_compressed; stream << ", arcs_created: " << arcs_created; + stream << ", arcs_aborted_by_flowrate: " << arcs_aborted_by_flow_rate; stream << ", num_firmware_compensations: " << num_firmware_compensations; stream << ", compression_ratio: " << compression_ratio; stream << ", size_reduction: " << compression_percent << "% "; @@ -412,11 +415,10 @@ struct arc_welder_results { std::string message; arc_welder_progress progress; }; -#define DEFAULT_GCODE_BUFFER_SIZE 1000 +#define DEFAULT_GCODE_BUFFER_SIZE 10 #define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false #define DEFAULT_ALLOW_DYNAMIC_PRECISION false -#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 1 -//#define DEFAULT_EXTRUSION_RATE_VARIANCE 0.0001 +#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 5 class arc_welder { public: @@ -469,6 +471,7 @@ private: int last_gcode_line_written_; int points_compressed_; int arcs_created_; + int arcs_aborted_by_flow_rate_; source_target_segment_statistics segment_statistics_; long get_file_size(const std::string& file_path); double get_time_elapsed(double start_clock, double end_clock); diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 4da8f8e..62593d7 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -127,11 +127,12 @@ bool segmented_arc::try_add_point(printer_point p) bool point_added = false; // if we don't have enough segnemts to check the shape, just add - if (points_.count() > get_max_segments() - 1) + + if (points_.count() == points_.get_max_size()) { // Too many points, we can't add more - return false; - } + points_.resize(points_.get_max_size()*2); + } if (points_.count() > 0) { printer_point p1 = points_[points_.count() - 1]; @@ -193,9 +194,6 @@ bool segmented_arc::try_add_point_internal_(printer_point p) if (points_.count() < get_min_segments() - 1) return false; - // Create a test circle - circle target_circle; - // the circle is new.. we have to test it now, which is expensive :( points_.push_back(p); double previous_shape_length = original_shape_length_; diff --git a/ArcWelder/segmented_shape.cpp b/ArcWelder/segmented_shape.cpp index 42aadcb..ec0611e 100644 --- a/ArcWelder/segmented_shape.cpp +++ b/ArcWelder/segmented_shape.cpp @@ -86,6 +86,11 @@ bool point::is_near_collinear(const point& p1, const point& p2, const point& p3, return fabs((p1.y - p2.y) * (p1.x - p3.x) - (p1.y - p3.y) * (p1.x - p2.x)) <= 1e-9; } +double point::cartesian_distance(const point& p1, const point& p2) +{ + return utilities::get_cartesian_distance(p1.x, p1.y, p2.x, p2.y); +} + #pragma endregion Point Functions #pragma region Segment Functions @@ -148,15 +153,12 @@ double vector::cross_product_magnitude(vector v1, vector v2) #pragma region Circle Functions - bool circle::try_create_circle(const point& p1, const point& p2, const point& p3, const double max_radius, circle& new_circle) { if (point::is_near_collinear(p1,p2,p3, 0.001)) { return false; } - - double x1 = p1.x; double y1 = p1.y; double x2 = p2.x; @@ -196,22 +198,40 @@ bool circle::try_create_circle(const point& p1, const point& p2, const point& p3 bool circle::try_create_circle(const array_list& points, const double max_radius, const double resolution_mm, const double xyz_tolerance, bool allow_3d_arcs, circle& new_circle) { int count = points.count(); - int middle_index = count / 2; + int end_index = count - 1; + - // The middle point will almost always produce the best arcs. - if (circle::try_create_circle(points[0], points[middle_index], points[count - 1], max_radius, new_circle) && !new_circle.is_over_deviation(points, resolution_mm, xyz_tolerance, allow_3d_arcs)) + + if (circle::try_create_circle(points[0], points[middle_index], points[end_index], max_radius, new_circle) && !new_circle.is_over_deviation(points, resolution_mm, xyz_tolerance, allow_3d_arcs)) { return true; } - + + /* + // This could be a near complete circle. In that case, the endpoints might be too close together to generate an accurate circle with the + // precision we have to work with. Let's adjust our circle into thirds and test those points as a last ditch effort. + if (count > 5) + { + middle_index = count / 3; + end_index = middle_index + middle_index; + if (circle::try_create_circle(points[0], points[middle_index], points[end_index], max_radius, test_circle) && !test_circle.is_over_deviation(points, resolution_mm, xyz_tolerance, allow_3d_arcs)) + { + new_circle = test_circle; + return true; + } + } + return false; + */ + // Find the circle with the least deviation, if one exists. // Note, this could possibly take a LONG time in the worst case, but it's a pretty unlikely. // However, if the midpoint check doesn't pass, it's worth it to spend a bit more time // finding the best fit for the circle (least squares deviation) - circle test_circle; + double least_deviation; bool found_circle=false; + for (int index = 1; index < count - 1; index++) { @@ -220,7 +240,7 @@ bool circle::try_create_circle(const array_list& points, const do // We already checked this one, and it failed, continue. continue; } - + circle test_circle; double current_deviation; if (circle::try_create_circle(points[0], points[index], points[count - 1], max_radius, test_circle) && test_circle.get_deviation_sum_squared(points, resolution_mm, xyz_tolerance, allow_3d_arcs, current_deviation)) { @@ -234,6 +254,7 @@ bool circle::try_create_circle(const array_list& points, const do } } return found_circle; + } double circle::get_polar_radians(const point& p1) const @@ -332,6 +353,7 @@ bool circle::is_over_deviation(const array_list& points, const do } // Check the point perpendicular from the segment to the circle's center, if any such point exists + if (segment::get_closest_perpendicular_point(current_point, points[index + 1], center, point_to_test)) { double distance = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, center.x, center.y); diff --git a/ArcWelder/segmented_shape.h b/ArcWelder/segmented_shape.h index 8b014bc..ec4bde9 100644 --- a/ArcWelder/segmented_shape.h +++ b/ArcWelder/segmented_shape.h @@ -47,6 +47,7 @@ public: double z; static point get_midpoint(point p1, point p2); static bool is_near_collinear(const point& p1, const point& p2, const point& p3, double percent_tolerance); + static double cartesian_distance(const point& p1, const point& p2); }; struct printer_point : point @@ -99,7 +100,7 @@ struct vector : point }; -#define DEFAULT_MAX_RADIUS_MM 1000000.0 // 1km +#define DEFAULT_MAX_RADIUS_MM 10000.0 // 10m struct circle { circle() { center.x = 0; diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp index ea333b3..ca64ff3 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp @@ -26,7 +26,7 @@ #define _CRT_SECURE_NO_DEPRECATE #endif -#include "inverse_processor.h" +#include "marlin_2_arc.h" #include "ArcWelderInverseProcessor.h" #include #include @@ -233,7 +233,7 @@ int main(int argc, char* argv[]) target_file_path = temp_file_path; } - inverse_processor processor(source_file_path, target_file_path, g90_g91_influences_extruder, 50, cs); + marlin_2_arc processor(source_file_path, target_file_path, g90_g91_influences_extruder, 50, cs); processor.process(); // Todo: get some results! if (true) diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj index 37ec7d0..f67571c 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj @@ -202,11 +202,15 @@ - + + + - + + + diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters index fe669d6..bc338f9 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters @@ -18,7 +18,13 @@ Header Files - + + Header Files + + + Header Files + + Header Files @@ -26,7 +32,13 @@ Source Files - + + Source Files + + + Source Files + + Source Files diff --git a/ArcWelderInverseProcessor/firmware_types.cpp b/ArcWelderInverseProcessor/firmware_types.cpp new file mode 100644 index 0000000..61820cf --- /dev/null +++ b/ArcWelderInverseProcessor/firmware_types.cpp @@ -0,0 +1 @@ +#include "firmware_types.h" diff --git a/ArcWelderInverseProcessor/firmware_types.h b/ArcWelderInverseProcessor/firmware_types.h new file mode 100644 index 0000000..b3507ad --- /dev/null +++ b/ArcWelderInverseProcessor/firmware_types.h @@ -0,0 +1,4 @@ +#pragma once +enum firmware_types { Marlin2, Repiter }; + + diff --git a/ArcWelderInverseProcessor/inverse_processor.cpp b/ArcWelderInverseProcessor/inverse_processor.cpp deleted file mode 100644 index c6edb6f..0000000 --- a/ArcWelderInverseProcessor/inverse_processor.cpp +++ /dev/null @@ -1,448 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Arc Welder: Inverse Processor Console Application -// -// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. -// This reduces file size and the number of gcodes per second. -// -// Built using the 'Arc Welder: Anti Stutter' library -// -// Copyright(C) 2020 - Brad Hochgesang -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// This program is free software : you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -// GNU Affero General Public License for more details. -// -// -// You can contact the author at the following email address: -// FormerLurker@pm.me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// This file includes portions from Marlin's motion_control.c file since it is intended to test some firmware modifications. -// This file was included in the AntiStutter project for convenience, and will not be included within the final version. -/* - motion_control.c - high level interface for issuing motion commands - Part of Grbl - - Copyright (c) 2009-2011 Simen Svale Skogsrud - Copyright (c) 2011 Sungeun K. Jeon - Copyright (c) 2020 Brad Hochgesang - - Grbl is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Grbl is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Grbl. If not, see . -*/ - -#include "inverse_processor.h" -#include - -//#include "Marlin.h" -//#include "stepper.h" -//#include "planner.h" - -inverse_processor::inverse_processor(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs) -{ - source_path_ = source_path; - target_path_ = target_path; - p_source_position_ = new gcode_position(get_args_(g90_g91_influences_extruder, buffer_size)); - cs_ = cs; - offset_absolute_e_ = 0; - // ** Gloabal Variable Definition ** - // 20200417 - FormerLurker - Declare two globals and pre-calculate some values that will reduce the - // amount of trig funcitons we need to call while printing. For the price of having two globals we - // save one trig calc per G2/G3 for both MIN_ARC_SEGMENTS and MIN_MM_PER_ARC_SEGMENT. This is a good trade IMHO. - - -#ifdef MIN_ARC_SEGMENTS -// Determines the radius at which the transition from using MM_PER_ARC_SEGMENT to MIN_ARC_SEGMENTS - arc_max_radius_threshold = MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS)); -#endif -/* -#if defined(MIN_ARC_SEGMENTS) && defined(MIN_MM_PER_ARC_SEGMENT) - // Determines the radius at which the transition from using MIN_ARC_SEGMENTS to MIN_MM_PER_ARC_SEGMENT. - arc_min_radius_threshold = MIN_MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS)); -#endif -*/ -} - -gcode_position_args inverse_processor::get_args_(bool g90_g91_influences_extruder, int buffer_size) -{ - gcode_position_args args; - // Configure gcode_position_args - args.g90_influences_extruder = g90_g91_influences_extruder; - args.position_buffer_size = buffer_size; - args.autodetect_position = true; - args.home_x = 0; - args.home_x_none = true; - args.home_y = 0; - args.home_y_none = true; - args.home_z = 0; - args.home_z_none = true; - args.shared_extruder = true; - args.zero_based_extruder = true; - - - args.default_extruder = 0; - args.xyz_axis_default_mode = "absolute"; - args.e_axis_default_mode = "absolute"; - args.units_default = "millimeters"; - args.location_detection_commands = std::vector(); - args.is_bound_ = false; - args.is_circular_bed = false; - args.x_min = -9999; - args.x_max = 9999; - args.y_min = -9999; - args.y_max = 9999; - args.z_min = -9999; - args.z_max = 9999; - return args; -} - -inverse_processor::~inverse_processor() -{ - delete p_source_position_; -} - -void inverse_processor::process() -{ - // Create a stringstream we can use for messaging. - std::stringstream stream; - - int read_lines_before_clock_check = 5000; - //std::cout << "stabilization::process_file - Processing file.\r\n"; - stream << "Decompressing gcode file."; - stream << "Source File: " << source_path_ << "\n"; - stream << "Target File: " << target_path_ << "\n"; - std::cout << stream.str(); - const clock_t start_clock = clock(); - - // Create the source file read stream and target write stream - std::ifstream gcode_file; - gcode_file.open(source_path_.c_str()); - output_file_.open(target_path_.c_str()); - std::string line; - int lines_with_no_commands = 0; - gcode_file.sync_with_stdio(false); - output_file_.sync_with_stdio(false); - gcode_parser parser; - int gcodes_processed = 0; - if (gcode_file.is_open()) - { - if (output_file_.is_open()) - { - //stream.clear(); - //stream.str(""); - //stream << "Opened file for reading. File Size: " << file_size_ << "\n"; - //std::cout << stream.str(); - parsed_command cmd; - // Communicate every second - while (std::getline(gcode_file, line)) - { - lines_processed_++; - - cmd.clear(); - parser.try_parse_gcode(line.c_str(), cmd); - bool has_gcode = false; - if (cmd.gcode.length() > 0) - { - has_gcode = true; - gcodes_processed++; - } - else - { - lines_with_no_commands++; - } - - p_source_position_->update(cmd, lines_processed_, gcodes_processed, -1); - - if (cmd.command == "G2" || cmd.command == "G3") - { - position* p_cur_pos = p_source_position_->get_current_position_ptr(); - position* p_pre_pos = p_source_position_->get_previous_position_ptr(); - float position[4]; - position[X_AXIS] = static_cast(p_pre_pos->get_gcode_x()); - position[Y_AXIS] = static_cast(p_pre_pos->get_gcode_y()); - position[Z_AXIS] = static_cast(p_pre_pos->get_gcode_z()); - position[E_AXIS] = static_cast(p_pre_pos->get_current_extruder().get_offset_e()); - float target[4]; - target[X_AXIS] = static_cast(p_cur_pos->get_gcode_x()); - target[Y_AXIS] = static_cast(p_cur_pos->get_gcode_y()); - target[Z_AXIS] = static_cast(p_cur_pos->get_gcode_z()); - target[E_AXIS] = static_cast(p_cur_pos->get_current_extruder().get_offset_e()); - float offset[2]; - offset[0] = 0.0; - offset[1] = 0.0; - for (unsigned int index = 0; index < cmd.parameters.size(); index++) - { - parsed_command_parameter p = cmd.parameters[index]; - if (p.name == "I") - { - offset[0] = static_cast(p.double_value); - } - else if (p.name == "J") - { - offset[1] = static_cast(p.double_value); - } - } - float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc - uint8_t isclockwise = cmd.command == "G2" ? 1 : 0; - output_relative_ = p_cur_pos->is_extruder_relative; - offset_absolute_e_ = p_pre_pos->get_current_extruder().get_offset_e(); - mc_arc(position, target, offset, static_cast(p_cur_pos->f), radius, isclockwise, 0); - } - else - { - output_file_ << line << "\n"; - } - - } - output_file_.close(); - } - else - { - std::cout << "Unable to open the output file for writing.\n"; - } - std::cout << "Closing the input file.\n"; - gcode_file.close(); - } - else - { - std::cout << "Unable to open the gcode file for processing.\n"; - } - - const clock_t end_clock = clock(); - const double total_seconds = (static_cast(end_clock) - static_cast(start_clock)) / CLOCKS_PER_SEC; - - stream.clear(); - stream.str(""); - stream << "Completed file processing\r\n"; - stream << "\tLines Processed : " << lines_processed_ << "\r\n"; - stream << "\tTotal Seconds : " << total_seconds << "\r\n"; - stream << "\tExtra Trig Count : " << trig_calc_count << "\r\n"; - stream << "\tTotal E Adjustment : " << total_e_adjustment << "\r\n"; - std::cout << stream.str(); -} - -// The arc is approximated by generating a huge number of tiny, linear segments. The length of each -// segment is configured in settings.mm_per_arc_segment. -void inverse_processor::mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder) -{ - // Extract the position to reduce indexing at the cost of a few bytes of mem - float p_x = position[X_AXIS]; - float p_y = position[Y_AXIS]; - float p_z = position[Z_AXIS]; - float p_e = position[E_AXIS]; - - float t_x = target[X_AXIS]; - float t_y = target[Y_AXIS]; - float t_z = target[Z_AXIS]; - float t_e = target[E_AXIS]; - - float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location - float r_axis_y = -offset[Y_AXIS]; - float center_axis_x = p_x - r_axis_x; - float center_axis_y = p_y - r_axis_y; - float travel_z = t_z - p_z; - float extruder_travel_total = t_e - p_e; - - float rt_x = t_x - center_axis_x; - float rt_y = t_y - center_axis_y; - // 20200419 - Add a variable that will be used to hold the arc segment length - float mm_per_arc_segment = cs_.mm_per_arc_segment; - - // CCW angle between position and target from circle center. Only one atan2() trig computation required. - float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y); - if (angular_travel_total < 0) { angular_travel_total += (float)(2 * M_PI); } - - bool check_mm_per_arc_segment_max = false; - if (cs_.min_arc_segments > 0) - { - // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation - // Do this before converting the angular travel for clockwise rotation - mm_per_arc_segment = (float)(radius * ((2.0f * M_PI) / cs_.min_arc_segments)); - check_mm_per_arc_segment_max = true; - } - - if (cs_.arc_segments_per_sec > 0) - { - // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation - float mm_per_arc_segment_sec = (float)((feed_rate / 60.0f) * (1.0f / cs_.arc_segments_per_sec)); - if (mm_per_arc_segment_sec < mm_per_arc_segment) - mm_per_arc_segment = mm_per_arc_segment_sec; - check_mm_per_arc_segment_max = true; - } - - if (cs_.min_mm_per_arc_segment > 0) - { - check_mm_per_arc_segment_max = true; - // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined - // This prevents a very high number of segments from being generated for curves of a short radius - if (mm_per_arc_segment < cs_.min_mm_per_arc_segment) mm_per_arc_segment = cs_.min_mm_per_arc_segment; - } - - if (check_mm_per_arc_segment_max && mm_per_arc_segment > cs_.mm_per_arc_segment) mm_per_arc_segment = cs_.mm_per_arc_segment; - - - - // Adjust the angular travel if the direction is clockwise - if (isclockwise) { angular_travel_total -= (float)(2 * M_PI); } - - //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving - //to compensate when start pos = target pos && angle is zero -> angle = 2Pi - if (p_x == t_x && p_y == t_y && angular_travel_total == 0) - { - angular_travel_total += (float)(2 * M_PI); - } - //end fix G03 - - // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are - // calculating here - float millimeters_of_travel_arc = hypot(angular_travel_total * radius, std::fabs(travel_z)); - if (millimeters_of_travel_arc < 0.001) { return; } - // Calculate the total travel per segment - // Calculate the number of arc segments - uint16_t segments = static_cast(ceil(millimeters_of_travel_arc / mm_per_arc_segment)); - - - // Calculate theta per segments and linear (z) travel per segment - float theta_per_segment = angular_travel_total / segments; - float linear_per_segment = travel_z / (segments); - // Calculate the extrusion amount per segment - float segment_extruder_travel = extruder_travel_total / (segments); - /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, - and phi is the angle of rotation. Based on the solution approach by Jens Geisler. - r_T = [cos(phi) -sin(phi); - sin(phi) cos(phi] * r ; - - For arc generation, the center of the circle is the axis of rotation and the radius vector is - defined from the circle center to the initial position. Each line segment is formed by successive - vector rotations. This requires only two cos() and sin() computations to form the rotation - matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since - all double numbers are single precision on the Arduino. (True double precision will not have - round off issues for CNC applications.) Single precision error can accumulate to be greater than - tool precision in some cases. Therefore, arc path correction is implemented. - - The small angle approximation was removed because of excessive errors for small circles (perhaps unique to - 3d printing applications, causing significant path deviation and extrusion issues). - Now there will be no corrections applied, but an accurate initial sin and cos will be calculated. - This seems to work with a very high degree of accuracy and results in much simpler code. - - Finding a faster way to approximate sin, knowing that there can be substantial deviations from the true - arc when using the previous approximation, would be beneficial. - */ - - // Don't bother calculating cot_T or sin_T if there is only 1 segment. - if (segments > 1) - { - // Initialize the extruder axis - float cos_T, sin_T, sin_Ti, cos_Ti; - //float cos_T = cos(theta_per_segment); - //float sin_T = sin(theta_per_segment); - float sq_theta_per_segment = theta_per_segment * theta_per_segment; - sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6; - cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation - - //cos_T = 1 - 0.5 * theta_per_segment * theta_per_segment; // Small angle approximation - //sin_T = theta_per_segment; - float r_axisi; - uint16_t i; - int8_t count = 0; - for (i = 1; i < segments; i++) { // Increment (segments-1) - - if (count < cs_.n_arc_correction) { - // Apply vector rotation matrix - r_axisi = r_axis_x * sin_T + r_axis_y * cos_T; - r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T; - r_axis_y = r_axisi; - count++; - } - else { - // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. - // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = cos(i * theta_per_segment); - sin_Ti = sin(i * theta_per_segment); - r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti; - r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti; - count = 0; - - } - - - - // Update arc_target location - p_x = center_axis_x + r_axis_x; - p_y = center_axis_y + r_axis_y; - p_z += linear_per_segment; - p_e += segment_extruder_travel; - // We can't clamp to the target because we are interpolating! We would need to update a position, clamp to it - // after updating from calculated values. - //clamp_to_software_endstops(position); - plan_buffer_line(p_x, p_y, travel_z > 0, p_z, p_e, feed_rate, extruder); - } - } - // Ensure last segment arrives at target location. - // Here we could clamp, but why bother. We would need to update our current position, clamp to it - //clamp_to_software_endstops(target); - plan_buffer_line(t_x, t_y, travel_z> 0, t_z, t_e, feed_rate, extruder); - position[X_AXIS] = t_x; - position[Y_AXIS] = t_y; - position[Z_AXIS] = t_z; - position[E_AXIS] = t_e; -} - -void inverse_processor::clamp_to_software_endstops(float* target) -{ - // Do nothing, just added to keep mc_arc identical to the firmware version - return; -} - -void inverse_processor::plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) -{ - std::stringstream stream; - stream << std::fixed; - - position * previous_pos = p_source_position_->get_previous_position_ptr(); - position* current_pos = p_source_position_->get_current_position_ptr(); - - stream << "G1 X" << std::setprecision(3) << x << " Y" << y; - if (has_z) - { - stream << " Z" << z; - } - - double output_e = e; - if (previous_pos->is_extruder_relative) - { - output_e = e - offset_absolute_e_; - offset_absolute_e_ = e; - } - - stream << std::setprecision(5) << " E" << output_e; - - - if (feed_rate != previous_pos->f) - { - stream << std::setprecision(0) << " F" << feed_rate; - } - - if (!current_pos->command.comment.empty()) - { - stream << ";" << current_pos->command.comment; - } - stream << "\n"; - output_file_ << stream.str(); -} diff --git a/ArcWelderInverseProcessor/inverse_processor.h b/ArcWelderInverseProcessor/inverse_processor.h deleted file mode 100644 index ab33133..0000000 --- a/ArcWelderInverseProcessor/inverse_processor.h +++ /dev/null @@ -1,95 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Arc Welder: Inverse Processor Console Application -// -// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. -// This reduces file size and the number of gcodes per second. -// -// Built using the 'Arc Welder: Anti Stutter' library -// -// Copyright(C) 2020 - Brad Hochgesang -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// This program is free software : you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -// GNU Affero General Public License for more details. -// -// -// You can contact the author at the following email address: -// FormerLurker@pm.me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma once - -#include -#include "gcode_position.h" -#include -#include -#include -#include -#include - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef signed char int8_t; -#define M_PI 3.14159265358979323846 // pi -enum AxisEnum { X_AXIS = 0, Y_AXIS= 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 }; -// Arc interpretation settings: -#define DEFAULT_MM_PER_ARC_SEGMENT 1.0 // REQUIRED - The enforced maximum length of an arc segment -#define DEFAULT_MIN_MM_PER_ARC_SEGMENT 0 /* OPTIONAL - the enforced minimum length of an interpolated segment. Must be smaller than - MM_PER_ARC_SEGMENT. Only has an effect if MIN_ARC_SEGMENTS > 0 or ARC_SEGMENTS_PER_SEC > 0 */ - // If both MIN_ARC_SEGMENTS and ARC_SEGMENTS_PER_SEC is defined, the minimum calculated segment length is used. -#define DEFAULT_MIN_ARC_SEGMENTS 0 // OPTIONAL - The enforced minimum segments in a full circle of the same radius. -#define DEFAULT_ARC_SEGMENTS_PER_SEC 0 // OPTIONAL - Use feedrate to choose segment length. -// approximation will not be used for the first segment. Subsequent segments will be corrected following DEFAULT_N_ARC_CORRECTION. -#define DEFAULT_N_ARC_CORRECTIONS 24 - -struct ConfigurationStore { - ConfigurationStore() { - mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; - min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT; - min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; - arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC; - n_arc_correction = DEFAULT_N_ARC_CORRECTIONS; - } - float mm_per_arc_segment; // This value is ALWAYS used. - float min_mm_per_arc_segment; // if less than or equal to 0, this is disabled - int min_arc_segments; // If less than or equal to zero, this is disabled - double arc_segments_per_sec; // If less than or equal to zero, this is disabled - int n_arc_correction; - -}; -class inverse_processor { -public: - inverse_processor(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs = ConfigurationStore()); - virtual ~inverse_processor(); - void process(); - void mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); - -private: - ConfigurationStore cs_; - gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size); - std::string source_path_; - std::string target_path_; - gcode_position* p_source_position_; - std::ofstream output_file_; - bool output_relative_; - double offset_absolute_e_; - float arc_max_radius_threshold; - //float arc_min_radius_threshold; - float total_e_adjustment; - int trig_calc_count = 0; - int lines_processed_ = 0; - void clamp_to_software_endstops(float* target); - - void plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target=NULL); - -}; - - - - - diff --git a/ArcWelderInverseProcessor/marlin_2_arc.cpp b/ArcWelderInverseProcessor/marlin_2_arc.cpp new file mode 100644 index 0000000..b7b3246 --- /dev/null +++ b/ArcWelderInverseProcessor/marlin_2_arc.cpp @@ -0,0 +1,448 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Inverse Processor Console Application +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2020 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// This file includes portions from Marlin's motion_control.c file since it is intended to test some firmware modifications. +// This file was included in the AntiStutter project for convenience, and will not be included within the final version. +/* + motion_control.c - high level interface for issuing motion commands + Part of Grbl + + Copyright (c) 2009-2011 Simen Svale Skogsrud + Copyright (c) 2011 Sungeun K. Jeon + Copyright (c) 2020 Brad Hochgesang + + Grbl is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Grbl is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grbl. If not, see . +*/ + +#include "marlin_2_arc.h" +#include + +//#include "Marlin.h" +//#include "stepper.h" +//#include "planner.h" + +marlin_2_arc::marlin_2_arc(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs) +{ + source_path_ = source_path; + target_path_ = target_path; + p_source_position_ = new gcode_position(get_args_(g90_g91_influences_extruder, buffer_size)); + cs_ = cs; + offset_absolute_e_ = 0; + // ** Gloabal Variable Definition ** + // 20200417 - FormerLurker - Declare two globals and pre-calculate some values that will reduce the + // amount of trig funcitons we need to call while printing. For the price of having two globals we + // save one trig calc per G2/G3 for both MIN_ARC_SEGMENTS and MIN_MM_PER_ARC_SEGMENT. This is a good trade IMHO. + + +#ifdef MIN_ARC_SEGMENTS +// Determines the radius at which the transition from using MM_PER_ARC_SEGMENT to MIN_ARC_SEGMENTS + arc_max_radius_threshold = MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS)); +#endif +/* +#if defined(MIN_ARC_SEGMENTS) && defined(MIN_MM_PER_ARC_SEGMENT) + // Determines the radius at which the transition from using MIN_ARC_SEGMENTS to MIN_MM_PER_ARC_SEGMENT. + arc_min_radius_threshold = MIN_MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS)); +#endif +*/ +} + +gcode_position_args marlin_2_arc::get_args_(bool g90_g91_influences_extruder, int buffer_size) +{ + gcode_position_args args; + // Configure gcode_position_args + args.g90_influences_extruder = g90_g91_influences_extruder; + args.position_buffer_size = buffer_size; + args.autodetect_position = true; + args.home_x = 0; + args.home_x_none = true; + args.home_y = 0; + args.home_y_none = true; + args.home_z = 0; + args.home_z_none = true; + args.shared_extruder = true; + args.zero_based_extruder = true; + + + args.default_extruder = 0; + args.xyz_axis_default_mode = "absolute"; + args.e_axis_default_mode = "absolute"; + args.units_default = "millimeters"; + args.location_detection_commands = std::vector(); + args.is_bound_ = false; + args.is_circular_bed = false; + args.x_min = -9999; + args.x_max = 9999; + args.y_min = -9999; + args.y_max = 9999; + args.z_min = -9999; + args.z_max = 9999; + return args; +} + +marlin_2_arc::~marlin_2_arc() +{ + delete p_source_position_; +} + +void marlin_2_arc::process() +{ + // Create a stringstream we can use for messaging. + std::stringstream stream; + + int read_lines_before_clock_check = 5000; + //std::cout << "stabilization::process_file - Processing file.\r\n"; + stream << "Decompressing gcode file."; + stream << "Source File: " << source_path_ << "\n"; + stream << "Target File: " << target_path_ << "\n"; + std::cout << stream.str(); + const clock_t start_clock = clock(); + + // Create the source file read stream and target write stream + std::ifstream gcode_file; + gcode_file.open(source_path_.c_str()); + output_file_.open(target_path_.c_str()); + std::string line; + int lines_with_no_commands = 0; + gcode_file.sync_with_stdio(false); + output_file_.sync_with_stdio(false); + gcode_parser parser; + int gcodes_processed = 0; + if (gcode_file.is_open()) + { + if (output_file_.is_open()) + { + //stream.clear(); + //stream.str(""); + //stream << "Opened file for reading. File Size: " << file_size_ << "\n"; + //std::cout << stream.str(); + parsed_command cmd; + // Communicate every second + while (std::getline(gcode_file, line)) + { + lines_processed_++; + + cmd.clear(); + parser.try_parse_gcode(line.c_str(), cmd); + bool has_gcode = false; + if (cmd.gcode.length() > 0) + { + has_gcode = true; + gcodes_processed++; + } + else + { + lines_with_no_commands++; + } + + p_source_position_->update(cmd, lines_processed_, gcodes_processed, -1); + + if (cmd.command == "G2" || cmd.command == "G3") + { + position* p_cur_pos = p_source_position_->get_current_position_ptr(); + position* p_pre_pos = p_source_position_->get_previous_position_ptr(); + float position[4]; + position[X_AXIS] = static_cast(p_pre_pos->get_gcode_x()); + position[Y_AXIS] = static_cast(p_pre_pos->get_gcode_y()); + position[Z_AXIS] = static_cast(p_pre_pos->get_gcode_z()); + position[E_AXIS] = static_cast(p_pre_pos->get_current_extruder().get_offset_e()); + float target[4]; + target[X_AXIS] = static_cast(p_cur_pos->get_gcode_x()); + target[Y_AXIS] = static_cast(p_cur_pos->get_gcode_y()); + target[Z_AXIS] = static_cast(p_cur_pos->get_gcode_z()); + target[E_AXIS] = static_cast(p_cur_pos->get_current_extruder().get_offset_e()); + float offset[2]; + offset[0] = 0.0; + offset[1] = 0.0; + for (unsigned int index = 0; index < cmd.parameters.size(); index++) + { + parsed_command_parameter p = cmd.parameters[index]; + if (p.name == "I") + { + offset[0] = static_cast(p.double_value); + } + else if (p.name == "J") + { + offset[1] = static_cast(p.double_value); + } + } + float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc + uint8_t isclockwise = cmd.command == "G2" ? 1 : 0; + output_relative_ = p_cur_pos->is_extruder_relative; + offset_absolute_e_ = p_pre_pos->get_current_extruder().get_offset_e(); + mc_arc(position, target, offset, static_cast(p_cur_pos->f), radius, isclockwise, 0); + } + else + { + output_file_ << line << "\n"; + } + + } + output_file_.close(); + } + else + { + std::cout << "Unable to open the output file for writing.\n"; + } + std::cout << "Closing the input file.\n"; + gcode_file.close(); + } + else + { + std::cout << "Unable to open the gcode file for processing.\n"; + } + + const clock_t end_clock = clock(); + const double total_seconds = (static_cast(end_clock) - static_cast(start_clock)) / CLOCKS_PER_SEC; + + stream.clear(); + stream.str(""); + stream << "Completed file processing\r\n"; + stream << "\tLines Processed : " << lines_processed_ << "\r\n"; + stream << "\tTotal Seconds : " << total_seconds << "\r\n"; + stream << "\tExtra Trig Count : " << trig_calc_count << "\r\n"; + stream << "\tTotal E Adjustment : " << total_e_adjustment << "\r\n"; + std::cout << stream.str(); +} + +// The arc is approximated by generating a huge number of tiny, linear segments. The length of each +// segment is configured in settings.mm_per_arc_segment. +void marlin_2_arc::mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder) +{ + // Extract the position to reduce indexing at the cost of a few bytes of mem + float p_x = position[X_AXIS]; + float p_y = position[Y_AXIS]; + float p_z = position[Z_AXIS]; + float p_e = position[E_AXIS]; + + float t_x = target[X_AXIS]; + float t_y = target[Y_AXIS]; + float t_z = target[Z_AXIS]; + float t_e = target[E_AXIS]; + + float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location + float r_axis_y = -offset[Y_AXIS]; + float center_axis_x = p_x - r_axis_x; + float center_axis_y = p_y - r_axis_y; + float travel_z = t_z - p_z; + float extruder_travel_total = t_e - p_e; + + float rt_x = t_x - center_axis_x; + float rt_y = t_y - center_axis_y; + // 20200419 - Add a variable that will be used to hold the arc segment length + float mm_per_arc_segment = cs_.mm_per_arc_segment; + + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y); + if (angular_travel_total < 0) { angular_travel_total += (float)(2 * M_PI); } + + bool check_mm_per_arc_segment_max = false; + if (cs_.min_arc_segments > 0) + { + // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation + // Do this before converting the angular travel for clockwise rotation + mm_per_arc_segment = (float)(radius * ((2.0f * M_PI) / cs_.min_arc_segments)); + check_mm_per_arc_segment_max = true; + } + + if (cs_.arc_segments_per_sec > 0) + { + // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation + float mm_per_arc_segment_sec = (float)((feed_rate / 60.0f) * (1.0f / cs_.arc_segments_per_sec)); + if (mm_per_arc_segment_sec < mm_per_arc_segment) + mm_per_arc_segment = mm_per_arc_segment_sec; + check_mm_per_arc_segment_max = true; + } + + if (cs_.min_mm_per_arc_segment > 0) + { + check_mm_per_arc_segment_max = true; + // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined + // This prevents a very high number of segments from being generated for curves of a short radius + if (mm_per_arc_segment < cs_.min_mm_per_arc_segment) mm_per_arc_segment = cs_.min_mm_per_arc_segment; + } + + if (check_mm_per_arc_segment_max && mm_per_arc_segment > cs_.mm_per_arc_segment) mm_per_arc_segment = cs_.mm_per_arc_segment; + + + + // Adjust the angular travel if the direction is clockwise + if (isclockwise) { angular_travel_total -= (float)(2 * M_PI); } + + //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving + //to compensate when start pos = target pos && angle is zero -> angle = 2Pi + if (p_x == t_x && p_y == t_y && angular_travel_total == 0) + { + angular_travel_total += (float)(2 * M_PI); + } + //end fix G03 + + // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are + // calculating here + float millimeters_of_travel_arc = hypot(angular_travel_total * radius, std::fabs(travel_z)); + if (millimeters_of_travel_arc < 0.001) { return; } + // Calculate the total travel per segment + // Calculate the number of arc segments + uint16_t segments = static_cast(ceil(millimeters_of_travel_arc / mm_per_arc_segment)); + + + // Calculate theta per segments and linear (z) travel per segment + float theta_per_segment = angular_travel_total / segments; + float linear_per_segment = travel_z / (segments); + // Calculate the extrusion amount per segment + float segment_extruder_travel = extruder_travel_total / (segments); + /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + r_T = [cos(phi) -sin(phi); + sin(phi) cos(phi] * r ; + + For arc generation, the center of the circle is the axis of rotation and the radius vector is + defined from the circle center to the initial position. Each line segment is formed by successive + vector rotations. This requires only two cos() and sin() computations to form the rotation + matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + all double numbers are single precision on the Arduino. (True double precision will not have + round off issues for CNC applications.) Single precision error can accumulate to be greater than + tool precision in some cases. Therefore, arc path correction is implemented. + + The small angle approximation was removed because of excessive errors for small circles (perhaps unique to + 3d printing applications, causing significant path deviation and extrusion issues). + Now there will be no corrections applied, but an accurate initial sin and cos will be calculated. + This seems to work with a very high degree of accuracy and results in much simpler code. + + Finding a faster way to approximate sin, knowing that there can be substantial deviations from the true + arc when using the previous approximation, would be beneficial. + */ + + // Don't bother calculating cot_T or sin_T if there is only 1 segment. + if (segments > 1) + { + // Initialize the extruder axis + float cos_T, sin_T, sin_Ti, cos_Ti; + //float cos_T = cos(theta_per_segment); + //float sin_T = sin(theta_per_segment); + float sq_theta_per_segment = theta_per_segment * theta_per_segment; + sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6; + cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation + + //cos_T = 1 - 0.5 * theta_per_segment * theta_per_segment; // Small angle approximation + //sin_T = theta_per_segment; + float r_axisi; + uint16_t i; + int8_t count = 0; + for (i = 1; i < segments; i++) { // Increment (segments-1) + + if (count < cs_.n_arc_correction /*&& theta_per_segment > 0.001 */) { + // Apply vector rotation matrix + r_axisi = r_axis_x * sin_T + r_axis_y * cos_T; + r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T; + r_axis_y = r_axisi; + count++; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + cos_Ti = cos(i * theta_per_segment); + sin_Ti = sin(i * theta_per_segment); + r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti; + r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti; + count = 0; + + } + + + + // Update arc_target location + p_x = center_axis_x + r_axis_x; + p_y = center_axis_y + r_axis_y; + p_z += linear_per_segment; + p_e += segment_extruder_travel; + // We can't clamp to the target because we are interpolating! We would need to update a position, clamp to it + // after updating from calculated values. + //clamp_to_software_endstops(position); + plan_buffer_line(p_x, p_y, travel_z > 0, p_z, p_e, feed_rate, extruder); + } + } + // Ensure last segment arrives at target location. + // Here we could clamp, but why bother. We would need to update our current position, clamp to it + //clamp_to_software_endstops(target); + plan_buffer_line(t_x, t_y, travel_z> 0, t_z, t_e, feed_rate, extruder); + position[X_AXIS] = t_x; + position[Y_AXIS] = t_y; + position[Z_AXIS] = t_z; + position[E_AXIS] = t_e; +} + +void marlin_2_arc::clamp_to_software_endstops(float* target) +{ + // Do nothing, just added to keep mc_arc identical to the firmware version + return; +} + +void marlin_2_arc::plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) +{ + std::stringstream stream; + stream << std::fixed; + + position * previous_pos = p_source_position_->get_previous_position_ptr(); + position* current_pos = p_source_position_->get_current_position_ptr(); + + stream << "G1 X" << std::setprecision(3) << x << " Y" << y; + if (has_z) + { + stream << " Z" << z; + } + + double output_e = e; + if (previous_pos->is_extruder_relative) + { + output_e = e - offset_absolute_e_; + offset_absolute_e_ = e; + } + + stream << std::setprecision(5) << " E" << output_e; + + + if (feed_rate != previous_pos->f) + { + stream << std::setprecision(0) << " F" << feed_rate; + } + + if (!current_pos->command.comment.empty()) + { + stream << ";" << current_pos->command.comment; + } + stream << "\n"; + output_file_ << stream.str(); +} diff --git a/ArcWelderInverseProcessor/marlin_2_arc.h b/ArcWelderInverseProcessor/marlin_2_arc.h new file mode 100644 index 0000000..a7a2ac3 --- /dev/null +++ b/ArcWelderInverseProcessor/marlin_2_arc.h @@ -0,0 +1,95 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Inverse Processor Console Application +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2020 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include "gcode_position.h" +#include +#include +#include +#include +#include + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef signed char int8_t; +#define M_PI 3.14159265358979323846 // pi +enum AxisEnum { X_AXIS = 0, Y_AXIS= 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 }; +// Arc interpretation settings: +#define DEFAULT_MM_PER_ARC_SEGMENT 1.0 // REQUIRED - The enforced maximum length of an arc segment +#define DEFAULT_MIN_MM_PER_ARC_SEGMENT 0 /* OPTIONAL - the enforced minimum length of an interpolated segment. Must be smaller than + MM_PER_ARC_SEGMENT. Only has an effect if MIN_ARC_SEGMENTS > 0 or ARC_SEGMENTS_PER_SEC > 0 */ + // If both MIN_ARC_SEGMENTS and ARC_SEGMENTS_PER_SEC is defined, the minimum calculated segment length is used. +#define DEFAULT_MIN_ARC_SEGMENTS 0 // OPTIONAL - The enforced minimum segments in a full circle of the same radius. +#define DEFAULT_ARC_SEGMENTS_PER_SEC 0 // OPTIONAL - Use feedrate to choose segment length. +// approximation will not be used for the first segment. Subsequent segments will be corrected following DEFAULT_N_ARC_CORRECTION. +#define DEFAULT_N_ARC_CORRECTIONS 24 + +struct ConfigurationStore { + ConfigurationStore() { + mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; + min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT; + min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; + arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC; + n_arc_correction = DEFAULT_N_ARC_CORRECTIONS; + } + float mm_per_arc_segment; // This value is ALWAYS used. + float min_mm_per_arc_segment; // if less than or equal to 0, this is disabled + int min_arc_segments; // If less than or equal to zero, this is disabled + double arc_segments_per_sec; // If less than or equal to zero, this is disabled + int n_arc_correction; + +}; +class marlin_2_arc { +public: + marlin_2_arc(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs = ConfigurationStore()); + virtual ~marlin_2_arc(); + void process(); + void mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); + +private: + ConfigurationStore cs_; + gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size); + std::string source_path_; + std::string target_path_; + gcode_position* p_source_position_; + std::ofstream output_file_; + bool output_relative_; + double offset_absolute_e_; + float arc_max_radius_threshold; + //float arc_min_radius_threshold; + float total_e_adjustment; + int trig_calc_count = 0; + int lines_processed_ = 0; + void clamp_to_software_endstops(float* target); + + void plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target=NULL); + +}; + + + + + diff --git a/ArcWelderInverseProcessor/repiter_arc.cpp b/ArcWelderInverseProcessor/repiter_arc.cpp new file mode 100644 index 0000000..83818a0 --- /dev/null +++ b/ArcWelderInverseProcessor/repiter_arc.cpp @@ -0,0 +1,97 @@ +#include "repiter_arc.h" + + +/* +// Arc function taken from grbl +// The arc is approximated by generating a huge number of tiny, linear segments. The length of each +// segment is configured in settings.mm_per_arc_segment. +void repiter_arc::arc(float* position, float* target, float* offset, float radius, uint8_t isclockwise) { + // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled(); + // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc + float center_axis0 = position[X_AXIS] + offset[X_AXIS]; + float center_axis1 = position[Y_AXIS] + offset[Y_AXIS]; + //float linear_travel = 0; //target[axis_linear] - position[axis_linear]; + float extruder_travel = (Printer::destinationSteps[E_AXIS] - Printer::currentPositionSteps[E_AXIS]) * Printer::invAxisStepsPerMM[E_AXIS]; + float r_axis0 = -offset[0]; // Radius vector from center to current location + float r_axis1 = -offset[1]; + float rt_axis0 = target[0] - center_axis0; + float rt_axis1 = target[1] - center_axis1; + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1); + if ((!isclockwise && angular_travel <= 0.00001) || (isclockwise && angular_travel < -0.000001)) { + angular_travel += 2.0f * M_PI; + } + if (isclockwise) { + angular_travel -= 2.0f * M_PI; + } + + float millimeters_of_travel = fabs(angular_travel) * radius; //hypot(angular_travel*radius, fabs(linear_travel)); + if (millimeters_of_travel < 0.001f) { + return; // treat as succes because there is nothing to do; + } + //uint16_t segments = (radius>=BIG_ARC_RADIUS ? floor(millimeters_of_travel/MM_PER_ARC_SEGMENT_BIG) : floor(millimeters_of_travel/MM_PER_ARC_SEGMENT)); + // Increase segment size if printing faster then computation speed allows + uint16_t segments = (Printer::feedrate > 60.0f ? floor(millimeters_of_travel / RMath::min(static_cast(MM_PER_ARC_SEGMENT_BIG), Printer::feedrate * 0.01666f * static_cast(MM_PER_ARC_SEGMENT))) : floor(millimeters_of_travel / static_cast(MM_PER_ARC_SEGMENT))); + if (segments == 0) + segments = 1; + + float theta_per_segment = angular_travel / segments; + //float linear_per_segment = linear_travel/segments; + float extruder_per_segment = extruder_travel / segments; + + + // Vector rotation matrix values + float cos_T = 1 - 0.5 * theta_per_segment * theta_per_segment; // Small angle approximation + float sin_T = theta_per_segment; + + float arc_target[4]; + float sin_Ti; + float cos_Ti; + float r_axisi; + uint16_t i; + int8_t count = 0; + + // Initialize the linear axis + //arc_target[axis_linear] = position[axis_linear]; + + // Initialize the extruder axis + arc_target[E_AXIS] = Printer::currentPositionSteps[E_AXIS] * Printer::invAxisStepsPerMM[E_AXIS]; + + for (i = 1; i < segments; i++) { + // Increment (segments-1) + + if ((count & 3) == 0) { + //GCode::readFromSerial(); + Commands::checkForPeriodicalActions(false); + UI_MEDIUM; // do check encoder + } + + if (count < N_ARC_CORRECTION) { //25 pieces + // Apply vector rotation matrix + r_axisi = r_axis0 * sin_T + r_axis1 * cos_T; + r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T; + r_axis1 = r_axisi; + count++; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + cos_Ti = cos(i * theta_per_segment); + sin_Ti = sin(i * theta_per_segment); + r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti; + r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti; + count = 0; + } + + // Update arc_target location + arc_target[X_AXIS] = center_axis0 + r_axis0; + arc_target[Y_AXIS] = center_axis1 + r_axis1; + //arc_target[axis_linear] += linear_per_segment; + arc_target[E_AXIS] += extruder_per_segment; + Printer::moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], IGNORE_COORDINATE, arc_target[E_AXIS], IGNORE_COORDINATE); + } + // Ensure last segment arrives at target location. + Printer::moveToReal(target[X_AXIS], target[Y_AXIS], IGNORE_COORDINATE, target[E_AXIS], IGNORE_COORDINATE); +} + +*/ \ No newline at end of file diff --git a/ArcWelderInverseProcessor/repiter_arc.h b/ArcWelderInverseProcessor/repiter_arc.h new file mode 100644 index 0000000..353c040 --- /dev/null +++ b/ArcWelderInverseProcessor/repiter_arc.h @@ -0,0 +1,7 @@ +#pragma once +#include +class repiter_arc +{ + void arc(float* position, float* target, float* offset, float radius, uint8_t isclockwise); +}; + diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 3cb916c..9f3aa2f 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -269,6 +269,7 @@ static void TestAntiStutter(std::string filePath) //double max_resolution = DEFAULT_RESOLUTION_MM; double max_resolution = 0.05; double max_radius_mm = 100000; + //double max_radius_mm = 10000; //int min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; int min_arc_segments = 0; double mm_per_arc_segment = 0; @@ -288,19 +289,20 @@ static void TestAntiStutter(std::string filePath) logger_levels.push_back(log_levels::CRITICAL); logger* p_logger = new logger(logger_names, logger_levels); p_logger->set_log_level(INFO); + //p_logger->set_log_level(DEBUG); //p_logger->set_log_level_by_value(5); //arc_welder arc_welder_obj(BENCHY_0_5_MM_NO_WIPE, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, static_cast(on_progress)); //arc_welder arc_welder_obj(SIX_SPEED_TEST, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, on_progress); arc_welder arc_welder_obj( - BENCHY_L1_DIFFICULT, + METALTEST, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, path_tolerance_percent, - max_radius_mm, + DEFAULT_MAX_RADIUS_MM, min_arc_segments, mm_per_arc_segment, - false, + true, true, DEFAULT_ALLOW_DYNAMIC_PRECISION, DEFAULT_XYZ_PRECISION, diff --git a/ArcWelderTest/ArcWelderTest.h b/ArcWelderTest/ArcWelderTest.h index 249d70b..13f4940 100644 --- a/ArcWelderTest/ArcWelderTest.h +++ b/ArcWelderTest/ArcWelderTest.h @@ -94,6 +94,8 @@ static std::string BENCHY_MIN_RADIUS_TEST = "C:\\Users\\Brad\\Documents\\3DPrint static std::string ISSUE_93 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\93\\FailingGCode.gcode"; static std::string ISSUE_99 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\99\\FailingGCode.gcode"; static std::string ISSUE_134 = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\134\\BirdHouse [PETG] [brim]+Infill [20%,cubic]+Noz [0.6]+LH [0.2]+LW-[0.6]+Temps [240+70]+50.0mms+Support [normal (56)]+Coast-[False].gcode" ; +static std::string ISSUE_170 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\170\\lampenring.gcode"; +static std::string ISSUE_184 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\184\\Dome_60_1.2mm_max_test15_0.2mm_PETG_MK3S_2h1m.gcode"; static std::string CONE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\ConeTest.gcode"; static std::string CONE_TEST_VASE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\ConeTestVase.gcode"; @@ -107,7 +109,8 @@ static std::string ISSUE_34 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutte static std::string DIFFICULT_ARCS_ISSUE_34 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\DifficultArcs\\issue_34.gcode"; static std::string TORTURE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\performance\\torture_test.gcode"; - +static std::string CURA_PLUGIN_ISSUE_18 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\CuraPlugin\\18\\Unwelded.gcode"; +static std::string METALTEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\MetalAltered.gcode"; diff --git a/GcodeProcessorLib/GcodeProcessorLib.vcxproj b/GcodeProcessorLib/GcodeProcessorLib.vcxproj index 956df18..778d3f3 100644 --- a/GcodeProcessorLib/GcodeProcessorLib.vcxproj +++ b/GcodeProcessorLib/GcodeProcessorLib.vcxproj @@ -210,8 +210,6 @@ - - diff --git a/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters b/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters index 60b8ca9..3aed5ba 100644 --- a/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters +++ b/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters @@ -18,9 +18,6 @@ Header Files - - Header Files - Header Files @@ -54,14 +51,11 @@ Header Files + + Header Files + - - Source Files - - - Source Files - Source Files diff --git a/GcodeProcessorLib/array_list.cpp b/GcodeProcessorLib/array_list.cpp deleted file mode 100644 index b2e46ea..0000000 --- a/GcodeProcessorLib/array_list.cpp +++ /dev/null @@ -1,22 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Gcode Processor Library -// -// Tools for parsing gcode and calculating printer state from parsed gcode commands. -// -// Copyright(C) 2020 - Brad Hochgesang -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// This program is free software : you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -// GNU Affero General Public License for more details. -// -// -// You can contact the author at the following email address: -// FormerLurker@pm.me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "array_list.h" \ No newline at end of file diff --git a/GcodeProcessorLib/circular_buffer.cpp b/GcodeProcessorLib/circular_buffer.cpp deleted file mode 100644 index c17c240..0000000 --- a/GcodeProcessorLib/circular_buffer.cpp +++ /dev/null @@ -1,22 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Gcode Processor Library -// -// Tools for parsing gcode and calculating printer state from parsed gcode commands. -// -// Copyright(C) 2020 - Brad Hochgesang -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// This program is free software : you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -// GNU Affero General Public License for more details. -// -// -// You can contact the author at the following email address: -// FormerLurker@pm.me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "circular_buffer.h" \ No newline at end of file diff --git a/GcodeProcessorLib/circular_buffer.h b/GcodeProcessorLib/circular_buffer.h index 0312a29..97f25ab 100644 --- a/GcodeProcessorLib/circular_buffer.h +++ b/GcodeProcessorLib/circular_buffer.h @@ -1,7 +1,9 @@ +#pragma once //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Gcode Processor Library // // Tools for parsing gcode and calculating printer state from parsed gcode commands. + // // Copyright(C) 2020 - Brad Hochgesang //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -19,6 +21,7 @@ // You can contact the author at the following email address: // FormerLurker@pm.me //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + #pragma once #include template @@ -32,6 +35,7 @@ public: count_ = 0; items_ = new T[max_size_]; } + circular_buffer(int max_size) { max_size_ = max_size; @@ -39,13 +43,24 @@ public: count_ = 0; items_ = new T[max_size]; } + virtual ~circular_buffer() { delete[] items_; } + + void initialize(T object) + { + for (int index = 0; index < max_size_; index++) + { + push_back(object); + } + count_ = 0; + front_index_ = 0; + } + void resize(int max_size) { T* new_items = new T[max_size]; - int count = count_; for (int index = 0; index < count_; index++) { new_items[index] = items_[(front_index_ + index + max_size_) % max_size_]; @@ -55,12 +70,61 @@ public: items_ = new_items; max_size_ = max_size; } + + void resize(int max_size, T object) + { + T* new_items = new T[max_size]; + for (int index = 0; index < count_; index++) + { + new_items[index] = items_[(front_index_ + index + max_size_) % max_size_]; + } + // Initialize the rest of the entries + for (int index = count_; index < max_size; index++) + { + new_items[index] = object; + } + front_index_ = 0; + delete[] items_; + items_ = new_items; + max_size_ = max_size; + } + + inline int get_index_position(int index) const + { + int index_position = index + front_index_ + max_size_; + while (index_position >= max_size_) + { + index_position = index_position - max_size_; + } + return index_position; + } + void push_front(T object) { - front_index_ = (front_index_ - 1 + max_size_) % max_size_; - count_++; + //front_index_ = (front_index_ - 1 + max_size_) % max_size_; + front_index_ -= 1; + if (front_index_ < 0) + { + front_index_ = max_size_ - 1; + } + if (count_ != max_size_) + { + count_++; + } items_[front_index_] = object; } + + void push_back(T object) + { + int pos = get_index_position(count_); + items_[pos] = object; + count_++; + if (count_ != max_size_) + { + count_++; + } + } + T& pop_front() { if (count_ == 0) @@ -69,30 +133,55 @@ public: } int prev_start = front_index_; - front_index_ = (front_index_ + 1 + max_size_) % max_size_; + + front_index_ += 1; + if (front_index_ >= max_size_) + { + front_index_ = 0; + } count_--; return items_[prev_start]; } - T& get(int index) + T& pop_back() { - return items_[(front_index_ + index + max_size_) % max_size_]; + if (count_ == 0) + { + throw std::exception(); + } + int pos = get_index_position(count_ - 1); + count_--; + return items_[pos]; } - int count() + T& operator[] (int index) const { - return count_; + //int opos = get_index_position(index); + return items_[get_index_position(index)]; + } + + T& get(int index) const + { + int opos = get_index_position(index); + return items_[opos]; + } + int count() const + { + return count_; } - int get_max_size() + + int get_max_size() const { return max_size_; } + void clear() { count_ = 0; front_index_ = 0; } + void copy(const circular_buffer& source) { if (max_size_ < source.max_size_) @@ -106,7 +195,6 @@ public: } front_index_ = source.front_index_; count_ = source.count_; - } protected: diff --git a/GcodeProcessorLib/gcode_position.cpp b/GcodeProcessorLib/gcode_position.cpp index fc469cd..444a879 100644 --- a/GcodeProcessorLib/gcode_position.cpp +++ b/GcodeProcessorLib/gcode_position.cpp @@ -206,10 +206,9 @@ void gcode_position_args::delete_y_firmware_offsets() } } -gcode_position::gcode_position() +gcode_position::gcode_position() : positions_(50), initial_position_(1) { position_buffer_size_ = 50; - positions_ = new position[position_buffer_size_]; autodetect_position_ = false; home_x_ = 0; home_y_ = 0; @@ -247,23 +246,19 @@ gcode_position::gcode_position() z_max_ = 0; is_circular_bed_ = false; - cur_pos_ = -1; - num_pos_ = 0; - for(int index = 0; index < position_buffer_size_; index ++) - { - position initial_pos(num_extruders_); - initial_pos.set_xyz_axis_mode(xyz_axis_default_mode_); - initial_pos.set_e_axis_mode(e_axis_default_mode_); - initial_pos.set_units_default(units_default_); - add_position(initial_pos); - } - num_pos_ = 0; + + position initial_pos(num_extruders_); + initial_pos.set_xyz_axis_mode(xyz_axis_default_mode_); + initial_pos.set_e_axis_mode(e_axis_default_mode_); + initial_pos.set_units_default(units_default_); + + positions_.initialize(initial_pos); + initial_position_ = initial_pos; } -gcode_position::gcode_position(gcode_position_args args) +gcode_position::gcode_position(gcode_position_args args) : positions_(args.position_buffer_size) { position_buffer_size_ = args.position_buffer_size; - positions_ = new position[args.position_buffer_size] ; autodetect_position_ = args.autodetect_position; home_x_ = args.home_x; home_y_ = args.home_y; @@ -330,9 +325,6 @@ gcode_position::gcode_position(gcode_position_args args) z_max_ = args.z_max; is_circular_bed_ = args.is_circular_bed; - - cur_pos_ = -1; - num_pos_ = 0; num_extruders_ = args.num_extruders; // Configure the initial position @@ -346,15 +338,11 @@ gcode_position::gcode_position(gcode_position_args args) initial_pos.p_extruders[index].x_firmware_offset = args.x_firmware_offsets[index]; initial_pos.p_extruders[index].y_firmware_offset = args.y_firmware_offsets[index]; } - - for (int index = 0; index < position_buffer_size_; index++) - { - - add_position(initial_pos); - } - num_pos_ = 0; + positions_.initialize(initial_pos); + initial_position_ = initial_pos; } + gcode_position::gcode_position(const gcode_position &source) { // Private copy constructor - you can't copy this class @@ -362,11 +350,7 @@ gcode_position::gcode_position(const gcode_position &source) gcode_position::~gcode_position() { - if (positions_ != NULL) - { - delete [] positions_; - positions_ = NULL; - } + delete_retraction_lengths_(); delete_z_lift_heights_(); } @@ -376,7 +360,6 @@ bool gcode_position::get_g90_91_influences_extruder() return g90_influences_extruder_; } - void gcode_position::set_num_extruders(int num_extruders) { delete_retraction_lengths_(); @@ -413,32 +396,44 @@ void gcode_position::delete_z_lift_heights_() int gcode_position::get_num_positions() { - return num_pos_; + return positions_.count(); +} + +int gcode_position::get_max_positions() +{ + return positions_.get_max_size(); +} + +void gcode_position::grow_max_positions(int size) +{ + int current_size = positions_.get_max_size(); + if (size < current_size) + { + return; + } + positions_.resize(size, initial_position_); + } void gcode_position::add_position(position& pos) { - cur_pos_ = (cur_pos_+1) % position_buffer_size_; - positions_[cur_pos_] = pos; - if (num_pos_ < position_buffer_size_) - num_pos_++; + positions_.push_front(pos); } void gcode_position::add_position(parsed_command& cmd) { - const int prev_pos = cur_pos_; - cur_pos_ = (cur_pos_+1) % position_buffer_size_; - positions_[cur_pos_] = positions_[prev_pos]; - positions_[cur_pos_].reset_state(); - positions_[cur_pos_].command = cmd; - positions_[cur_pos_].is_empty = false; - if (num_pos_ < position_buffer_size_) - num_pos_++; + + position current_position = positions_[0]; + current_position.reset_state(); + current_position.command = cmd; + current_position.is_empty = false; + positions_.push_front(current_position); + } position gcode_position::get_position(int index) { - return positions_[(cur_pos_ - index + position_buffer_size_) % position_buffer_size_]; + return positions_[index]; } position gcode_position::get_current_position() @@ -453,7 +448,7 @@ position gcode_position::get_previous_position() position * gcode_position::get_position_ptr(int index) { - return &positions_[(cur_pos_ - index + position_buffer_size_) % position_buffer_size_]; + return &positions_[index]; } position * gcode_position::get_current_position_ptr() @@ -697,11 +692,7 @@ void gcode_position::update(parsed_command& command, const long file_line_number void gcode_position::undo_update() { - if (num_pos_ != 0) - { - cur_pos_ = (cur_pos_ - 1 + position_buffer_size_) % position_buffer_size_; - num_pos_--; - } + positions_.pop_front(); } position* gcode_position::undo_update(int num_updates) @@ -715,21 +706,9 @@ position* gcode_position::undo_update(int num_updates) // add the positions we will undo to the array for (int index = 0; index < num_updates; index++) { - p_undo_positions[index] = get_position(index); - } - - if (num_pos_ < num_updates) - { - num_pos_ = 0; - cur_pos_ = 0; - } - else - { - cur_pos_ = (cur_pos_ - num_updates + position_buffer_size_) % position_buffer_size_; - num_pos_ -= num_updates; + p_undo_positions[index] = positions_.pop_front(); } return p_undo_positions; - } // Private Members diff --git a/GcodeProcessorLib/gcode_position.h b/GcodeProcessorLib/gcode_position.h index 521f0d3..5930cf6 100644 --- a/GcodeProcessorLib/gcode_position.h +++ b/GcodeProcessorLib/gcode_position.h @@ -25,6 +25,7 @@ #include #include #include +#include "circular_buffer.h" #include "gcode_parser.h" #include "position.h" #include "gcode_comment_processor.h" @@ -139,6 +140,8 @@ public: void undo_update(); position * undo_update(int num_updates); int get_num_positions(); + int get_max_positions(); + void grow_max_positions(int size); position get_position(int index); position get_current_position(); position get_previous_position(); @@ -149,10 +152,9 @@ public: bool get_g90_91_influences_extruder(); private: gcode_position(const gcode_position &source); + position initial_position_; int position_buffer_size_; - position* positions_; - int cur_pos_; - int num_pos_; + circular_buffer positions_; void add_position(parsed_command &); void add_position(position &); bool autodetect_position_; diff --git a/GcodeProcessorLib/position.cpp b/GcodeProcessorLib/position.cpp index 6a85801..8fcd8cd 100644 --- a/GcodeProcessorLib/position.cpp +++ b/GcodeProcessorLib/position.cpp @@ -356,6 +356,10 @@ position& position::operator=(const position& pos) { return *this; } +bool position::is_travel() +{ + return is_xyz_travel || is_xy_travel; +} void position::set_num_extruders(int num_extruders_) { if (num_extruders_ == num_extruders) diff --git a/GcodeProcessorLib/position.h b/GcodeProcessorLib/position.h index 0958cd5..5059d5f 100644 --- a/GcodeProcessorLib/position.h +++ b/GcodeProcessorLib/position.h @@ -102,5 +102,6 @@ struct position void set_e_axis_mode(const std::string& e_axis_default_mode); void set_units_default(const std::string& units_default); bool can_take_snapshot(); + bool is_travel() }; #endif \ No newline at end of file diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index 041f91f..b5a09a6 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "fpconv.h" const std::string utilities::WHITESPACE_ = " \n\r\t\f\v"; @@ -320,4 +321,20 @@ std::string utilities::dtos(double x, unsigned char precision) } */ return buffer; -} \ No newline at end of file +} + +bool utilities::case_insensitive_compare_char(char& c1, char& c2) +{ + if (c1 == c2) + return true; + else if (std::toupper(c1) == std::toupper(c2)) + return true; + return false; +} +/* + * Case Insensitive String Comparision + */ +bool utilities::case_insensitive_compare(std::string& str1, std::string& str2) +{ + return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), &utilities::case_insensitive_compare_char)); +} diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index b41d6f6..33afd51 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -41,6 +41,9 @@ public: static double get_cartesian_distance(double x1, double y1, double x2, double y2); static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2); + + static bool case_insensitive_compare_char(char& c1, char& c2); + static bool case_insensitive_compare(std::string& str1, std::string& str2); static std::string to_string(double value); static std::string to_string(int value); static std::string ltrim(const std::string& s); diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp index 36588f2..f811e6d 100644 --- a/PyArcWelder/py_arc_welder.cpp +++ b/PyArcWelder/py_arc_welder.cpp @@ -47,28 +47,30 @@ PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, progress.points_compressed, //6 "arcs_created", progress.arcs_created, //7 + "arcs_aborted_by_flowrate", + progress.arcs_aborted_by_flow_rate, //8 "num_firmware_compensations", - progress.num_firmware_compensations, //8 + progress.num_firmware_compensations, //9 "source_file_position", - progress.source_file_position, //9 + progress.source_file_position, //10 "source_file_size", - progress.source_file_size, //10 + progress.source_file_size, //11 "target_file_size", - progress.target_file_size, //11 + progress.target_file_size, //12 "compression_ratio", - progress.compression_ratio, //12 + progress.compression_ratio, //13 "compression_percent", - progress.compression_percent, //13 + progress.compression_percent, //14 "source_file_total_length", - progress.segment_statistics.total_length_source, //14 + progress.segment_statistics.total_length_source, //15 "target_file_total_length", - progress.segment_statistics.total_length_target, //15 + progress.segment_statistics.total_length_target, //16 "source_file_total_count", - progress.segment_statistics.total_count_source, //16 + progress.segment_statistics.total_count_source, //17 "target_file_total_count", - progress.segment_statistics.total_count_target, //17 + progress.segment_statistics.total_count_target, //18 "total_count_reduction_percent", - total_count_reduction_percent //18 + total_count_reduction_percent //19 ); -- cgit v1.2.3 From 9a838679c1ae3cdbfd0a3eedade6bf94ebb79409 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 3 Jul 2021 13:42:58 -0500 Subject: Alpha travel move conversion support. --- ArcWelder/arc_welder.cpp | 20 +++++++++++++------- ArcWelder/arc_welder.h | 22 +++++++++++++--------- ArcWelder/segmented_arc.cpp | 15 +++++++++++++++ ArcWelder/segmented_shape.cpp | 8 ++++---- ArcWelderConsole/ArcWelderConsole.cpp | 20 +++++++++++++++----- ArcWelderTest/ArcWelderTest.cpp | 6 ++++-- ArcWelderTest/ArcWelderTest.h | 1 + GcodeProcessorLib/position.h | 2 +- GcodeProcessorLib/utilities.cpp | 9 ++++++--- GcodeProcessorLib/utilities.h | 3 ++- PyArcWelder/py_arc_welder.cpp | 2 +- PyArcWelder/py_arc_welder.h | 5 ++++- PyArcWelder/py_arc_welder_extension.cpp | 13 ++++++++++++- PyArcWelder/py_arc_welder_extension.h | 4 ++++ 14 files changed, 95 insertions(+), 35 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index 95fbcda..b45f9e9 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -47,6 +47,7 @@ arc_welder::arc_welder( double mm_per_arc_segment, bool g90_g91_influences_extruder, bool allow_3d_arcs, + bool allow_travel_arcs, bool allow_dynamic_precision, unsigned char default_xyz_precision, unsigned char default_e_precision, @@ -84,6 +85,7 @@ arc_welder::arc_welder( target_path_ = target_path; gcode_position_args_ = get_args_(g90_g91_influences_extruder, buffer_size); allow_3d_arcs_ = allow_3d_arcs; + allow_travel_arcs_ = allow_travel_arcs; allow_dynamic_precision_ = allow_dynamic_precision; extrusion_rate_variance_percent_ = extrusion_rate_variance_percent; notification_period_seconds = 1; @@ -204,12 +206,15 @@ arc_welder_results results; stream << std::fixed << std::setprecision(2); stream << "arc_welder::process - Parameters received: source_file_path: '" << source_path_ << "', target_file_path:'" << target_path_ << "', resolution_mm:" << - resolution_mm_ << "mm (+-" << current_arc_.get_resolution_mm() << "mm), path_tolerance_percent: " << current_arc_.get_path_tolerance_percent() - << ", max_radius_mm:" << current_arc_.get_max_radius() + resolution_mm_ << "mm (+-" << current_arc_.get_resolution_mm() + << "mm), path_tolerance_percent: " << current_arc_.get_path_tolerance_percent() + << "%, extrusion_rate_variance_percent: " << extrusion_rate_variance_percent_ + << "%, max_radius_mm:" << current_arc_.get_max_radius() << ", min_arc_segments:" << std::setprecision(0) <get_g90_91_influences_extruder() ? "True" : "False") << ", allow_3d_arcs: " << (allow_3d_arcs_ ? "True" : "False") + << ", allow_travel_arcs: " << (allow_travel_arcs_ ? "True" : "False") << ", allow_dynamic_precision: " << (allow_dynamic_precision_ ? "True" : "False") << ", default_xyz_precision: " << std::setprecision(0) << static_cast(current_arc_.get_xyz_precision()) << ", default_e_precision: " << std::setprecision(0) << static_cast(current_arc_.get_e_precision()); @@ -424,7 +429,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess double movement_length_mm = 0; bool has_e_changed = extruder_current.is_extruding || extruder_current.is_retracting; // Update the source file statistics - if (p_cur_pos->has_xy_position_changed && (has_e_changed)) + if (p_cur_pos->has_xy_position_changed) { if (allow_3d_arcs_) { movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z); @@ -444,7 +449,8 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess double mm_extruded_per_mm_travel = 0; double extrusion_rate_change_percent = 0; bool aborted_by_flow_rate = false; - if (movement_length_mm > 0) + // TODO: MAKE SURE THIS WORKS FOR TRANSITIONS FROM TRAVEL TO NON TRAVEL MOVES + if (movement_length_mm > 0 && has_e_changed) { mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; if (previous_extrusion_rate_ > 0) @@ -497,7 +503,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess !waiting_for_arc_ || extruder_current.is_extruding || // Test for travel conversion - + (allow_travel_arcs_ && p_cur_pos->is_travel()) || //(previous_extruder.is_extruding && extruder_current.is_extruding) || // Test to see if // we can get more arcs. (previous_extruder.is_retracting && extruder_current.is_retracting) @@ -874,7 +880,7 @@ void arc_welder::add_arcwelder_comment_to_target() stream << "; Copyright(C) 2020 - Brad Hochgesang\n"; stream << "; Version: " << GIT_TAGGED_VERSION << ", Branch: " << GIT_BRANCH << ", BuildDate: " << BUILD_DATE << "\n"; stream << "; resolution=" << std::setprecision(2) << resolution_mm_ << "mm\n"; - stream << "; path_tolerance=" << std::setprecision(0) << (current_arc_.get_path_tolerance_percent() * 100.0) << "%\n"; + stream << "; path_tolerance=" << std::setprecision(1) << (current_arc_.get_path_tolerance_percent() * 100.0) << "%\n"; stream << "; max_radius=" << std::setprecision(2) << (current_arc_.get_max_radius()) << "mm\n"; if (gcode_position_args_.g90_influences_extruder) { @@ -897,7 +903,7 @@ void arc_welder::add_arcwelder_comment_to_target() } stream << "; default_xyz_precision=" << std::setprecision(0) << static_cast(current_arc_.get_xyz_precision()) << "\n"; stream << "; default_e_precision=" << std::setprecision(0) << static_cast(current_arc_.get_e_precision()) << "\n"; - stream << "; extrusion_rate_variance_percent=" << std::setprecision(3) << static_cast(extrusion_rate_variance_percent_) << "%\n\n"; + stream << "; extrusion_rate_variance_percent=" << std::setprecision(1) << (extrusion_rate_variance_percent_ * 100.0) << "%\n\n"; output_file_ << stream.str(); diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index a4fdded..98564ec 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -418,7 +418,9 @@ struct arc_welder_results { #define DEFAULT_GCODE_BUFFER_SIZE 10 #define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false #define DEFAULT_ALLOW_DYNAMIC_PRECISION false -#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 5 +#define DEFAULT_ALLOW_TRAVEL_ARCS false +#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 0.05 +#define DEFAULT_CONVERT_TRAVEL_MOVES false class arc_welder { public: @@ -431,14 +433,15 @@ public: double max_radius, int min_arc_segments, double mm_per_arc_segment, - bool g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTRUDER, - bool allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS, - bool allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION, - unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION, - unsigned char default_e_precision = DEFAULT_E_PRECISION, - double extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, - int buffer_size = DEFAULT_GCODE_BUFFER_SIZE, - progress_callback callback = NULL); + bool g90_g91_influences_extruder, + bool allow_3d_arcs, + bool allow_travel_arcs, + bool allow_dynamic_precision, + unsigned char default_xyz_precision, + unsigned char default_e_precision, + double extrusion_rate_variance_percent, + int buffer_size, + progress_callback callback); void set_logger_type(int logger_type); virtual ~arc_welder(); arc_welder_results process(); @@ -465,6 +468,7 @@ private: gcode_position_args gcode_position_args_; bool allow_dynamic_precision_; bool allow_3d_arcs_; + bool allow_travel_arcs_; long file_size_; int lines_processed_; int gcodes_processed_; diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 62593d7..51ef57e 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -142,6 +142,21 @@ bool segmented_arc::try_add_point(printer_point p) return false; } + // Need to separate travel moves from moves with extrusion + if (points_.count() > 2) + { + // We already have at least an initial point and a second point. Make cure the current point and the previous are either both + // travel moves, or both extrusion + if (!( + (p1.e_relative != 0 && p.e_relative != 0) // Extrusions + || (p1.e_relative == 0 && p.e_relative == 0) // Travel + ) + ) + { + return false; + } + } + if (utilities::is_zero(p.distance)) { // there must be some distance between the points diff --git a/ArcWelder/segmented_shape.cpp b/ArcWelder/segmented_shape.cpp index ec0611e..4e17d3c 100644 --- a/ArcWelder/segmented_shape.cpp +++ b/ArcWelder/segmented_shape.cpp @@ -447,8 +447,8 @@ bool arc::try_create_arc( } } // Calculate the percent difference of the original path - double difference = (arc_length - approximate_length) / approximate_length; - if (!utilities::is_zero(difference, path_tolerance_percent)) + double path_difference_percent = utilities::get_percent_change(arc_length, approximate_length); + if (!utilities::is_zero(path_difference_percent, path_tolerance_percent)) { // So it's possible our vector calculation above got the direction wrong. // This can happen if there is a crazy arrangement of points @@ -468,8 +468,8 @@ bool arc::try_create_arc( test_arc_length = utilities::hypot(test_arc_length, end_point.z - start_point.z); } } - difference = (test_arc_length - approximate_length) / approximate_length; - if (!utilities::is_zero(difference, path_tolerance_percent)) + path_difference_percent = utilities::get_percent_change(test_arc_length,approximate_length); + if (!utilities::is_zero(path_difference_percent, path_tolerance_percent)) { return false; } diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp index 691f155..16a6923 100644 --- a/ArcWelderConsole/ArcWelderConsole.cpp +++ b/ArcWelderConsole/ArcWelderConsole.cpp @@ -46,7 +46,8 @@ int main(int argc, char* argv[]) bool g90_g91_influences_extruder; bool hide_progress; bool overwrite_source_file = false; - bool allow_3d_arcs = false; + bool allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS; + bool allow_travel_arcs = DEFAULT_ALLOW_TRAVEL_ARCS; bool allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION; unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION; unsigned char default_e_precision = DEFAULT_E_PRECISION; @@ -123,6 +124,12 @@ int main(int argc, char* argv[]) arg_description_stream << "(experimental) - If supplied, 3D arcs will be allowed (supports spiral vase mode). Not all firmware supports this. Default Value: " << DEFAULT_ALLOW_3D_ARCS; TCLAP::SwitchArg allow_3d_arcs_arg("z", "allow-3d-arcs", arg_description_stream.str(), DEFAULT_ALLOW_3D_ARCS); + // -y --allow-travel-arcs + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "(experimental) - If supplied, travel arcs will be allowed. Default Value: " << DEFAULT_ALLOW_TRAVEL_ARCS; + TCLAP::SwitchArg allow_travel_arcs_arg("y", "allow-travel-arcs", arg_description_stream.str(), DEFAULT_ALLOW_TRAVEL_ARCS); + // -d --allow-dynamic-precision arg_description_stream.clear(); arg_description_stream.str(""); @@ -144,7 +151,7 @@ int main(int argc, char* argv[]) // -v --extrusion-rate-variance arg_description_stream.clear(); arg_description_stream.str(""); - arg_description_stream << "The allowed variance in extrusion rate by percent. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; + arg_description_stream << "(experimental) - The allowed variance in extrusion rate by percent. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; TCLAP::ValueArg extrusion_rate_variance_percent_arg("v", "extrusion-rate-variance-percent", arg_description_stream.str(), false, DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, "double"); @@ -176,6 +183,7 @@ int main(int argc, char* argv[]) cmd.add(min_arc_segments_arg); cmd.add(mm_per_arc_segment_arg); cmd.add(allow_3d_arcs_arg); + cmd.add(allow_travel_arcs_arg); cmd.add(allow_dynamic_precision_arg); cmd.add(default_xyz_precision_arg); cmd.add(default_e_precision_arg); @@ -202,6 +210,7 @@ int main(int argc, char* argv[]) mm_per_arc_segment = mm_per_arc_segment_arg.getValue(); path_tolerance_percent = path_tolerance_percent_arg.getValue(); allow_3d_arcs = allow_3d_arcs_arg.getValue(); + allow_travel_arcs = allow_travel_arcs_arg.getValue(); g90_g91_influences_extruder = g90_arg.getValue(); allow_dynamic_precision = allow_dynamic_precision_arg.getValue(); default_xyz_precision = default_xyz_precision_arg.getValue(); @@ -367,10 +376,11 @@ int main(int argc, char* argv[]) log_messages << "\tMin Arc Segments : " << std::setprecision(0) << min_arc_segments << "\n"; log_messages << "\tMM Per Arc Segment : " << std::setprecision(3) << mm_per_arc_segment << "\n"; log_messages << "\tAllow 3D Arcs : " << (allow_3d_arcs ? "True" : "False") << "\n"; + log_messages << "\tAllow Travel Arcs : " << (allow_travel_arcs ? "True" : "False") << "\n"; log_messages << "\tAllow Dynamic Precision : " << (allow_dynamic_precision ? "True" : "False") << "\n"; log_messages << "\tDefault XYZ Precision : " << std::setprecision(0) << static_cast(default_xyz_precision) << "\n"; log_messages << "\tDefault E Precision : " << std::setprecision(0) << static_cast(default_e_precision) << "\n"; - log_messages << "\tExtrusion Rate Variance % : " << std::setprecision(3) << extrusion_rate_variance_percent << "%\n"; + log_messages << "\tExtrusion Rate Variance % : " << std::setprecision(3) << extrusion_rate_variance_percent*100.0 << "%\n"; log_messages << "\tG90/G91 Influences Extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n"; log_messages << "\tLog Level : " << log_level_string << "\n"; log_messages << "\tHide Progress Updates : " << (hide_progress ? "True" : "False"); @@ -382,9 +392,9 @@ int main(int argc, char* argv[]) target_file_path = temp_file_path; } if (!hide_progress) - p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, on_progress); + p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_travel_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, on_progress); else - p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, suppress_progress); + p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_travel_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, suppress_progress); arc_welder_results results = p_arc_welder->process(); if (results.success) diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 9f3aa2f..95aebc5 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -294,7 +294,7 @@ static void TestAntiStutter(std::string filePath) //arc_welder arc_welder_obj(BENCHY_0_5_MM_NO_WIPE, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, static_cast(on_progress)); //arc_welder arc_welder_obj(SIX_SPEED_TEST, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, on_progress); arc_welder arc_welder_obj( - METALTEST, + BENCHY_DIFFICULT, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, @@ -304,10 +304,12 @@ static void TestAntiStutter(std::string filePath) mm_per_arc_segment, true, true, + true, //DEFAULT_ALLOW_TRAVEL_ARCS, DEFAULT_ALLOW_DYNAMIC_PRECISION, DEFAULT_XYZ_PRECISION, DEFAULT_E_PRECISION, - DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, + //DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, + 1000000, DEFAULT_GCODE_BUFFER_SIZE, on_progress); //FIRMWARE_COMPENSATION_TEST_1 diff --git a/ArcWelderTest/ArcWelderTest.h b/ArcWelderTest/ArcWelderTest.h index 13f4940..b4331e9 100644 --- a/ArcWelderTest/ArcWelderTest.h +++ b/ArcWelderTest/ArcWelderTest.h @@ -82,6 +82,7 @@ static std::string ISSUE_PRICKLYPEAR_LAYER_0_114 = "C:\\Users\\Brad\\Documents\\ // Sanity tests static std::string COLINEAR_TEST_1 = "C:\\Users\\Brad\\Documents\\AntiStutter\\Sanity Checks\\G2_colinear_test.gcode"; static std::string SPIRAL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TEST.gcode"; +static std::string SPIRAL_TRAVEL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TRAVEL_TEST.gcode"; static std::string SPIRAL_TEST_PRECISION = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TEST_precision.gcode"; static std::string SPIRAL_VASE_TEST_DOUBLE_SPIRAL = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\SpiralVaseTest\\SpiralVaseTest_DOUBLE_SPIRAL.gcode"; diff --git a/GcodeProcessorLib/position.h b/GcodeProcessorLib/position.h index 5059d5f..7470ae9 100644 --- a/GcodeProcessorLib/position.h +++ b/GcodeProcessorLib/position.h @@ -102,6 +102,6 @@ struct position void set_e_axis_mode(const std::string& e_axis_default_mode); void set_units_default(const std::string& units_default); bool can_take_snapshot(); - bool is_travel() + bool is_travel(); }; #endif \ No newline at end of file diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index b5a09a6..a723cdf 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -176,7 +176,7 @@ double utilities::get_percent_change(double v1, double v2) { if (v1 != 0) { - return ((v2 - v1) / v1) * 100.0; + return ((v2 - v1) / v1); } return 0; } @@ -322,7 +322,7 @@ std::string utilities::dtos(double x, unsigned char precision) */ return buffer; } - +/* bool utilities::case_insensitive_compare_char(char& c1, char& c2) { if (c1 == c2) @@ -331,10 +331,13 @@ bool utilities::case_insensitive_compare_char(char& c1, char& c2) return true; return false; } + /* * Case Insensitive String Comparision - */ + bool utilities::case_insensitive_compare(std::string& str1, std::string& str2) { return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), &utilities::case_insensitive_compare_char)); } + +*/ \ No newline at end of file diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index 33afd51..acbea6c 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -41,9 +41,10 @@ public: static double get_cartesian_distance(double x1, double y1, double x2, double y2); static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2); - + /* Todo: Implement for gcode comment processor static bool case_insensitive_compare_char(char& c1, char& c2); static bool case_insensitive_compare(std::string& str1, std::string& str2); + */ static std::string to_string(double value); static std::string to_string(int value); static std::string ltrim(const std::string& s); diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp index f811e6d..7fdf9bd 100644 --- a/PyArcWelder/py_arc_welder.cpp +++ b/PyArcWelder/py_arc_welder.cpp @@ -32,7 +32,7 @@ PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, if (pyMessage == NULL) return NULL; double total_count_reduction_percent = progress.segment_statistics.get_total_count_reduction_percent(); - PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i,s:f}", + PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i,s:f}", "percent_complete", progress.percent_complete, //1 "seconds_elapsed", diff --git a/PyArcWelder/py_arc_welder.h b/PyArcWelder/py_arc_welder.h index 6813c34..40a9cab 100644 --- a/PyArcWelder/py_arc_welder.h +++ b/PyArcWelder/py_arc_welder.h @@ -46,6 +46,7 @@ public: double mm_per_arc_segment, bool g90_g91_influences_extruder, bool allow_3d_arcs, + bool allow_travel_arcs, bool allow_dynamic_precision, unsigned char default_xyz_precision, unsigned char default_e_precision, @@ -63,11 +64,13 @@ public: mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, + allow_travel_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, - buffer_size + buffer_size, + NULL ){ guid_ = guid; py_progress_callback_ = py_progress_callback; diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp index 71c3855..19a2274 100644 --- a/PyArcWelder/py_arc_welder_extension.cpp +++ b/PyArcWelder/py_arc_welder_extension.cpp @@ -206,7 +206,8 @@ extern "C" args.min_arc_segments, args.mm_per_arc_segment, args.g90_g91_influences_extruder, - args.allow_3d_arcs, + args.allow_3d_arcs, + args.allow_travel_arcs, args.allow_dynamic_precision, args.default_xyz_precision, args.default_e_precision, @@ -423,6 +424,16 @@ static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** py_ } args.allow_3d_arcs = PyLong_AsLong(py_allow_3d_arcs) > 0; + // extract allow_travel_arcs + PyObject* py_allow_travel_arcs = PyDict_GetItemString(py_args, "allow_travel_arcs"); + if (py_allow_travel_arcs == NULL) + { + std::string message = "ParseArgs - Unable to retrieve allow_travel_arcs from the args."; + p_py_logger->log_exception(GCODE_CONVERSION, message); + return false; + } + args.allow_travel_arcs = PyLong_AsLong(py_allow_travel_arcs) > 0; + // Extract G90/G91 influences extruder // g90_influences_extruder PyObject* py_g90_g91_influences_extruder = PyDict_GetItemString(py_args, "g90_g91_influences_extruder"); diff --git a/PyArcWelder/py_arc_welder_extension.h b/PyArcWelder/py_arc_welder_extension.h index 9b979fe..2212748 100644 --- a/PyArcWelder/py_arc_welder_extension.h +++ b/PyArcWelder/py_arc_welder_extension.h @@ -53,6 +53,7 @@ struct py_gcode_arc_args { mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTREUDER; allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS; + allow_travel_arcs = DEFAULT_ALLOW_TRAVEL_ARCS; log_level = 0; } py_gcode_arc_args( @@ -66,6 +67,7 @@ struct py_gcode_arc_args { double mm_per_arc_segment_, bool g90_g91_influences_extruder_, bool allow_3d_arcs_, + bool allow_travel_arcs_, bool allow_dynamic_precision_, unsigned char default_xyz_precision_, unsigned char default_e_precision_, @@ -81,6 +83,7 @@ struct py_gcode_arc_args { min_arc_segments = min_arc_segments_; mm_per_arc_segment = mm_per_arc_segment_; allow_3d_arcs = allow_3d_arcs_; + allow_travel_arcs = allow_travel_arcs_; allow_dynamic_precision = allow_dynamic_precision_; default_xyz_precision = default_xyz_precision_; default_e_precision = default_e_precision_; @@ -94,6 +97,7 @@ struct py_gcode_arc_args { double resolution_mm; double path_tolerance_percent; bool allow_3d_arcs; + bool allow_travel_arcs; bool allow_dynamic_precision; unsigned char default_xyz_precision; unsigned char default_e_precision; -- cgit v1.2.3 From 4700ebf2b905c2676628b53d81d4fbaa0413aa09 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 3 Jul 2021 13:58:15 -0500 Subject: Remove deleted file from cmake sourcelist. --- GcodeProcessorLib/sourcelist.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/GcodeProcessorLib/sourcelist.cmake b/GcodeProcessorLib/sourcelist.cmake index a4e86aa..8e234a6 100644 --- a/GcodeProcessorLib/sourcelist.cmake +++ b/GcodeProcessorLib/sourcelist.cmake @@ -1,5 +1,4 @@ set(GcodeProcessorLibSources ${GcodeProcessorLibSources} - array_list.cpp array_list.h circular_buffer.cpp circular_buffer.h -- cgit v1.2.3 From b2675b665c1d37cff0b7a400c09f11054bad2b87 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 3 Jul 2021 13:59:30 -0500 Subject: Remove another deleted file from sourcelist. --- GcodeProcessorLib/sourcelist.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/GcodeProcessorLib/sourcelist.cmake b/GcodeProcessorLib/sourcelist.cmake index 8e234a6..0dcd0cb 100644 --- a/GcodeProcessorLib/sourcelist.cmake +++ b/GcodeProcessorLib/sourcelist.cmake @@ -1,6 +1,5 @@ set(GcodeProcessorLibSources ${GcodeProcessorLibSources} array_list.h - circular_buffer.cpp circular_buffer.h extruder.cpp extruder.h -- cgit v1.2.3 From b2e44aa85c9da175c6e78fd909c9f92531f5f393 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 3 Jul 2021 14:04:38 -0500 Subject: Update inverse processor source list. --- ArcWelderInverseProcessor/sourcelist.cmake | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ArcWelderInverseProcessor/sourcelist.cmake b/ArcWelderInverseProcessor/sourcelist.cmake index d0ea63c..d5f94b2 100644 --- a/ArcWelderInverseProcessor/sourcelist.cmake +++ b/ArcWelderInverseProcessor/sourcelist.cmake @@ -1,4 +1,10 @@ set(ArcWelderInverseProcessorSources ${ArcWelderInverseProcessorSources} + ArcWelderInverseProcessor.h ArcWelderInverseProcessor.cpp - inverse_processor.cpp + firmware_types.h + firmware_types.cpp + marlin_2_arc.h + marlin_2_arc.cpp + repiter_arc.h + repiter_arc.cpp ) \ No newline at end of file -- cgit v1.2.3 From 9d3361e145c806a2ea0d3c7f1f6fc7252e4d0ef1 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Mon, 5 Jul 2021 18:57:33 -0500 Subject: Add alpha gcode length restrictions. Enhance output statistics. Create arc_welder_args. Add new progress type args to command line processor. --- ArcWelder/arc_welder.cpp | 165 ++++++----- ArcWelder/arc_welder.h | 174 ++++++++++- ArcWelder/segmented_arc.cpp | 83 ++++-- ArcWelder/segmented_arc.h | 13 +- ArcWelder/segmented_shape.cpp | 8 +- ArcWelder/segmented_shape.h | 20 +- ArcWelder/unwritten_command.h | 34 +-- ArcWelderConsole/ArcWelderConsole.cpp | 258 ++++++++-------- ArcWelderConsole/ArcWelderConsole.h | 6 +- ArcWelderTest/ArcWelderTest.cpp | 46 +-- ArcWelderTest/ArcWelderTest.h | 2 +- GcodeProcessorLib/logger.cpp | 14 + GcodeProcessorLib/logger.h | 5 +- GcodeProcessorLib/utilities.cpp | 2 +- GcodeProcessorLib/utilities.h | 1 - PyArcWelder/py_arc_welder.cpp | 507 ++++++++++++++++++++++++++------ PyArcWelder/py_arc_welder.h | 63 ++-- PyArcWelder/py_arc_welder_extension.cpp | 257 +--------------- PyArcWelder/py_arc_welder_extension.h | 71 +---- 19 files changed, 947 insertions(+), 782 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index b45f9e9..0531a67 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -52,6 +52,7 @@ arc_welder::arc_welder( unsigned char default_xyz_precision, unsigned char default_e_precision, double extrusion_rate_variance_percent, + int max_gcode_length, int buffer_size, progress_callback callback) : current_arc_( DEFAULT_MIN_SEGMENTS, @@ -63,13 +64,19 @@ arc_welder::arc_welder( mm_per_arc_segment, allow_3d_arcs, default_xyz_precision, - default_e_precision + default_e_precision, + max_gcode_length ), segment_statistics_( segment_statistic_lengths, segment_statistic_lengths_count, log - ) + ), + travel_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + log + ) { p_logger_ = log; debug_logging_enabled_ = false; @@ -203,24 +210,6 @@ arc_welder_results results; error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, ERROR); std::stringstream stream; - stream << std::fixed << std::setprecision(2); - stream << "arc_welder::process - Parameters received: source_file_path: '" << - source_path_ << "', target_file_path:'" << target_path_ << "', resolution_mm:" << - resolution_mm_ << "mm (+-" << current_arc_.get_resolution_mm() - << "mm), path_tolerance_percent: " << current_arc_.get_path_tolerance_percent() - << "%, extrusion_rate_variance_percent: " << extrusion_rate_variance_percent_ - << "%, max_radius_mm:" << current_arc_.get_max_radius() - << ", min_arc_segments:" << std::setprecision(0) <get_g90_91_influences_extruder() ? "True" : "False") - << ", allow_3d_arcs: " << (allow_3d_arcs_ ? "True" : "False") - << ", allow_travel_arcs: " << (allow_travel_arcs_ ? "True" : "False") - << ", allow_dynamic_precision: " << (allow_dynamic_precision_ ? "True" : "False") - << ", default_xyz_precision: " << std::setprecision(0) << static_cast(current_arc_.get_xyz_precision()) - << ", default_e_precision: " << std::setprecision(0) << static_cast(current_arc_.get_e_precision()); - p_logger_->log(logger_type_, INFO, stream.str()); - - // reset tracking variables reset(); // local variable to hold the progress update return. If it's false, we will exit. @@ -236,6 +225,28 @@ arc_welder_results results; stream.str(""); stream << "Source file size: " << file_size_; p_logger_->log(logger_type_, DEBUG, stream.str()); + + // Determine if we need to overwrite the source file + bool overwrite_source_file = false; + std::string temp_file_path; + if (source_path_ == target_path_) + { + overwrite_source_file = true; + if (!utilities::get_temp_file_path_for_file(source_path_, temp_file_path)) + { + results.success = false; + results.message = "The source and target path are the same, but a temporary file path could not be created. Are the paths empty?"; + p_logger_->log_exception(logger_type_, results.message); + return results; + } + + stream.clear(); + stream.str(""); + stream << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; + p_logger_->log(logger_type_, DEBUG, stream.str()); + target_path_ = temp_file_path; + } + // Create the source file read stream and target write stream std::ifstream gcodeFile; p_logger_->log(logger_type_, DEBUG, "Opening the source file for reading."); @@ -340,6 +351,7 @@ arc_welder_results results; } p_logger_->log(logger_type_, DEBUG, "Writing all unwritten gcodes to the target file."); write_unwritten_gcodes_to_file(); + p_logger_->log(logger_type_, DEBUG, "Fetching the final progress struct."); arc_welder_progress final_progress = get_progress_(static_cast(file_size_), static_cast(start_clock)); @@ -347,13 +359,26 @@ arc_welder_results results; { p_logger_->log(logger_type_, DEBUG, "Sending final progress update message."); } - on_progress_(arc_welder_progress(final_progress)); + on_progress_(final_progress); - p_logger_->log(logger_type_, DEBUG, "Processing complete, closing source and target file."); + p_logger_->log(logger_type_, DEBUG, "Closing source and target files."); output_file_.close(); gcodeFile.close(); - const clock_t end_clock = clock(); - + + if (overwrite_source_file) + { + stream.clear(); + stream.str(""); + stream << "Deleting the original source file at '" << source_path_ << "'."; + p_logger_->log(logger_type_, DEBUG, stream.str()); + stream.clear(); + stream.str(""); + std::remove(source_path_.c_str()); + stream << "Renaming temporary file at '" << target_path_ << "' to '" << source_path_ << "'."; + p_logger_->log(0, DEBUG, stream.str()); + std::rename(target_path_.c_str(), source_path_.c_str()); + } + results.success = continue_processing; results.cancelled = !continue_processing; results.progress = final_progress; @@ -397,8 +422,14 @@ arc_welder_progress arc_welder::get_progress_(long source_file_position, double progress.compression_ratio = (static_cast(source_file_position) / static_cast(progress.target_file_size)); progress.compression_percent = (1.0 - (static_cast(progress.target_file_size) / static_cast(source_file_position))) * 100.0f; } + else { + progress.compression_ratio = 0; + progress.compression_percent = 0; + } progress.num_firmware_compensations = current_arc_.get_num_firmware_compensations(); + progress.num_gcode_length_exceptions = current_arc_.get_num_gcode_length_exceptions(); progress.segment_statistics = segment_statistics_; + progress.travel_statistics = travel_statistics_; return progress; } @@ -427,7 +458,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess bool arc_added = false; bool clear_shapes = false; double movement_length_mm = 0; - bool has_e_changed = extruder_current.is_extruding || extruder_current.is_retracting; + bool has_e_changed = extruder_current.e_relative != 0; // Update the source file statistics if (p_cur_pos->has_xy_position_changed) { @@ -441,7 +472,17 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess if (movement_length_mm > 0) { if (!is_reprocess) - segment_statistics_.update(movement_length_mm, true); + { + if (has_e_changed) + { + segment_statistics_.update(movement_length_mm, true); + } + else if (allow_3d_arcs_) + { + travel_statistics_.update(movement_length_mm, true); + } + + } } } @@ -466,8 +507,8 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position // TODO: Handle relative XYZ axis. This is possible, but maybe not so important. - bool is_g1_g2 = cmd.command == "G0" || cmd.command == "G1"; - if (allow_dynamic_precision_ && is_g1_g2) + bool is_g0_g1 = cmd.command == "G0" || cmd.command == "G1"; + if (allow_dynamic_precision_ && is_g0_g1) { for (std::vector::iterator it = cmd.parameters.begin(); it != cmd.parameters.end(); ++it) { @@ -490,7 +531,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess if ( !is_end && cmd.is_known_command && !cmd.is_empty && ( - is_g1_g2 && z_axis_ok && + is_g0_g1 && z_axis_ok && utilities::is_equal(p_cur_pos->x_offset, p_pre_pos->x_offset) && utilities::is_equal(p_cur_pos->y_offset, p_pre_pos->y_offset) && utilities::is_equal(p_cur_pos->z_offset, p_pre_pos->z_offset) && @@ -516,7 +557,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess // Record the extrusion rate previous_extrusion_rate_ = mm_extruded_per_mm_travel; - printer_point p(p_cur_pos->get_gcode_x(), p_cur_pos->get_gcode_y(), p_cur_pos->get_gcode_z(), extruder_current.e_relative, movement_length_mm); + printer_point p(p_cur_pos->get_gcode_x(), p_cur_pos->get_gcode_y(), p_cur_pos->get_gcode_z(), extruder_current.get_offset_e() ,extruder_current.e_relative, p_cur_pos->f, movement_length_mm, p_pre_pos->is_extruder_relative); if (!waiting_for_arc_) { if (debug_logging_enabled_) @@ -525,7 +566,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess } write_unwritten_gcodes_to_file(); // add the previous point as the starting point for the current arc - printer_point previous_p(p_pre_pos->get_gcode_x(), p_pre_pos->get_gcode_y(), p_pre_pos->get_gcode_z(), previous_extruder.e_relative, 0); + printer_point previous_p(p_pre_pos->get_gcode_x(), p_pre_pos->get_gcode_y(), p_pre_pos->get_gcode_z(), previous_extruder.get_offset_e(),previous_extruder.e_relative, p_pre_pos->f, 0, p_pre_pos->is_extruder_relative); // Don't add any extrusion, or you will over extrude! //std::cout << "Trying to add first point (" << p.x << "," << p.y << "," << p.z << ")..."; @@ -652,8 +693,6 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess // Reset the previous extrusion rate previous_extrusion_rate_ = 0; } - - if (!arc_added && !(cmd.is_empty && cmd.comment.length() == 0)) { @@ -677,7 +716,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess // update our statistics points_compressed_ += current_arc_.get_num_segments()-1; arcs_created_++; // increment the number of generated arcs - write_arc_gcodes(p_pre_pos->is_extruder_relative, p_pre_pos->f); + write_arc_gcodes(p_pre_pos->f); // Now clear the arc and flag the processor as not waiting for an arc waiting_for_arc_ = false; current_arc_.clear(); @@ -721,7 +760,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess // This might not work.... //position* cur_pos = p_source_position_->get_current_position_ptr(); - unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, movement_length_mm)); + unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, !has_e_changed && is_g0_g1, movement_length_mm)); } else if (!waiting_for_arc_) @@ -732,7 +771,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess return lines_written; } -void arc_welder::write_arc_gcodes(bool is_extruder_relative, double current_feedrate) +void arc_welder::write_arc_gcodes(double current_feedrate) { std::string comment = get_comment_for_arc(); @@ -742,7 +781,7 @@ void arc_welder::write_arc_gcodes(bool is_extruder_relative, double current_feed int num_segments = current_arc_.get_num_segments() - 1; for (int index = 0; index < num_segments; index++) { - while (!unwritten_commands_.pop_back().is_g1_g2); + while (!unwritten_commands_.pop_back().is_g0_g1); } // Undo the current command, since it isn't included in the arc @@ -754,16 +793,8 @@ void arc_welder::write_arc_gcodes(bool is_extruder_relative, double current_feed } // Craete the arc gcode - std::string gcode; - if (is_extruder_relative) { - gcode = get_arc_gcode_relative(current_feedrate, comment); - } - - else { - gcode = get_arc_gcode_absolute(p_source_position_->get_current_position_ptr()->get_current_extruder().get_offset_e(), current_feedrate, comment); - } - - + std::string gcode = get_arc_gcode(comment); + if (debug_logging_enabled_) { char buffer[20]; @@ -779,11 +810,16 @@ void arc_welder::write_arc_gcodes(bool is_extruder_relative, double current_feed write_unwritten_gcodes_to_file(); // Update the current extrusion statistics for the current arc gcode - segment_statistics_.update(current_arc_.get_shape_length() , false); + if (current_arc_.get_shape_e_relative() != 0) + { + segment_statistics_.update(current_arc_.get_shape_length(), false); + + } + else if (allow_3d_arcs_){ + travel_statistics_.update(current_arc_.get_shape_length(), false); + } // now write the current arc to the file write_gcode_to_file(gcode); - - } std::string arc_welder::get_comment_for_arc() @@ -830,9 +866,15 @@ int arc_welder::write_unwritten_gcodes_to_file() { // The the current unwritten position and remove it from the list unwritten_command p = unwritten_commands_.pop_front(); - if (p.extrusion_length > 0) + if(p.is_g0_g1 && p.length > 0) { - segment_statistics_.update(p.extrusion_length, false); + if (!p.is_travel) + { + segment_statistics_.update(p.length, false); + } + else if (allow_3d_arcs_) { + travel_statistics_.update(p.length, false); + } } lines_to_write.append(p.to_string()).append("\n"); } @@ -841,27 +883,12 @@ int arc_welder::write_unwritten_gcodes_to_file() return size; } -std::string arc_welder::get_arc_gcode_relative(double f, const std::string comment) -{ - // Write gcode to file - std::string gcode; - - gcode = current_arc_.get_shape_gcode_relative(f); - - if (comment.length() > 0) - { - gcode += ";" + comment; - } - return gcode; - -} - -std::string arc_welder::get_arc_gcode_absolute(double e, double f, const std::string comment) +std::string arc_welder::get_arc_gcode(const std::string comment) { // Write gcode to file std::string gcode; - gcode = current_arc_.get_shape_gcode_absolute(e, f); + gcode = current_arc_.get_shape_gcode(); if (comment.length() > 0) { diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index 98564ec..fc5bb84 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -44,9 +44,6 @@ #define _CRT_SECURE_NO_WARNINGS #endif - -#define DEFAULT_G90_G91_INFLUENCES_EXTREUDER false - static const int segment_statistic_lengths_count = 12; const double segment_statistic_lengths[] = { 0.002f, 0.005f, 0.01f, 0.05f, 0.1f, 0.5f, 1.0f, 5.0f, 10.0f, 20.0f, 50.0f, 100.0f }; @@ -346,7 +343,7 @@ private: // Struct to hold the progress, statistics, and return values struct arc_welder_progress { - arc_welder_progress() : segment_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL) { + arc_welder_progress() : segment_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL), travel_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL) { percent_complete = 0.0; seconds_elapsed = 0.0; seconds_remaining = 0.0; @@ -356,6 +353,7 @@ struct arc_welder_progress { arcs_created = 0; arcs_aborted_by_flow_rate = 0; num_firmware_compensations = 0; + num_gcode_length_exceptions = 0; source_file_size = 0; source_file_position = 0; target_file_size = 0; @@ -371,12 +369,30 @@ struct arc_welder_progress { int arcs_created; int arcs_aborted_by_flow_rate; int num_firmware_compensations; + int num_gcode_length_exceptions; double compression_ratio; double compression_percent; long source_file_position; long source_file_size; long target_file_size; source_target_segment_statistics segment_statistics; + source_target_segment_statistics travel_statistics; + + std::string simple_progress_str() const { + std::stringstream stream; + if (percent_complete == 0) { + stream << " 00.0% complete - Estimating remaining time."; + } + else if (percent_complete == 100) + { + stream << "100.0% complete - " << seconds_elapsed << " seconds total."; + } + else { + stream << " " << std::fixed << std::setprecision(1) << std::setfill('0') << std::setw(4) << percent_complete << "% complete - Estimated " << std::setprecision(0) << std::setw(-1) << seconds_remaining << " of " << seconds_elapsed + seconds_remaining << " seconds remaing."; + } + + return stream.str(); + } std::string str() const { std::stringstream stream; @@ -389,19 +405,128 @@ struct arc_welder_progress { stream << ", arcs_created: " << arcs_created; stream << ", arcs_aborted_by_flowrate: " << arcs_aborted_by_flow_rate; stream << ", num_firmware_compensations: " << num_firmware_compensations; + stream << ", num_gcode_length_exceptions: " << num_gcode_length_exceptions; stream << ", compression_ratio: " << compression_ratio; stream << ", size_reduction: " << compression_percent << "% "; return stream.str(); } std::string detail_str() const { std::stringstream stream; - stream << "\n" << "Extrusion/Retraction Counts" << "\n" << segment_statistics.str() << "\n"; + if (travel_statistics.total_count_source > 0) + { + stream << "Target File Travel Statistics:" << "\n" << travel_statistics.str() << "\n"; + } + + stream << "\n" << "Target File Extrusion Statistics:" << "\n" << segment_statistics.str() << "\n"; + return stream.str(); } }; - // define the progress callback type typedef bool(*progress_callback)(arc_welder_progress, logger* p_logger, int logger_type); +// LOGGER_NAME +#define ARC_WELDER_LOGGER_NAME "arc_welder.gcode_conversion" +// Default argument values +#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false +#define DEFAULT_GCODE_BUFFER_SIZE 10 +#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false +#define DEFAULT_ALLOW_DYNAMIC_PRECISION false +#define DEFAULT_ALLOW_TRAVEL_ARCS false +#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 0.05 +#define DEFAULT_CONVERT_TRAVEL_MOVES false + + +struct arc_welder_args +{ + arc_welder_args() : + source_path(""), + target_path(""), + log(NULL), + resolution_mm(DEFAULT_RESOLUTION_MM), + path_tolerance_percent(ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT), + max_radius_mm(DEFAULT_MAX_RADIUS_MM), + min_arc_segments(DEFAULT_MIN_ARC_SEGMENTS), + mm_per_arc_segment(DEFAULT_MM_PER_ARC_SEGMENT), + g90_g91_influences_extruder(DEFAULT_G90_G91_INFLUENCES_EXTRUDER), + allow_3d_arcs(DEFAULT_ALLOW_3D_ARCS), + allow_travel_arcs(DEFAULT_ALLOW_TRAVEL_ARCS), + allow_dynamic_precision(DEFAULT_ALLOW_DYNAMIC_PRECISION), + default_xyz_precision(DEFAULT_XYZ_PRECISION), + default_e_precision(DEFAULT_E_PRECISION), + extrusion_rate_variance_percent(DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT), + max_gcode_length(DEFAULT_MAX_GCODE_LENGTH), + buffer_size(DEFAULT_GCODE_BUFFER_SIZE), + callback(NULL){}; + + arc_welder_args(std::string source, std::string target, logger* ptr_log) : arc_welder_args() + { + source_path = source; + target_path = target; + log = ptr_log; + } + std::string source_path; + std::string target_path; + logger* log; + double resolution_mm; + double path_tolerance_percent; + double max_radius_mm; + int min_arc_segments; + double mm_per_arc_segment; + bool g90_g91_influences_extruder; + bool allow_3d_arcs; + bool allow_travel_arcs; + bool allow_dynamic_precision; + unsigned char default_xyz_precision; + unsigned char default_e_precision; + double extrusion_rate_variance_percent; + int buffer_size; + int max_gcode_length; + + progress_callback callback; + + std::string str() const { + std::string log_level_name = "NO_LOGGING"; + if (log != NULL) + { + log_level_name = log->get_log_level_name(ARC_WELDER_LOGGER_NAME); + } + std::stringstream stream; + stream << "Arc Welder Arguments\n"; + stream << std::fixed << std::setprecision(2); + stream << "\tSource File Path : " << source_path << "\n"; + if (source_path == target_path) + { + stream << "\tTarget File Path (overwrite) : " << target_path << "\n"; + } + else + { + stream << "\tTarget File Path : " << target_path << "\n"; + } + stream << "\tResolution : " << resolution_mm << "mm (+-" << std::setprecision(5) << resolution_mm / 2.0 << "mm)\n"; + stream << "\tPath Tolerance : " << std::setprecision(3) << path_tolerance_percent * 100.0 << "%\n"; + stream << "\tMaximum Arc Radius : " << std::setprecision(0) << max_radius_mm << "mm\n"; + stream << "\tMin Arc Segments : " << std::setprecision(0) << min_arc_segments << "\n"; + stream << "\tMM Per Arc Segment : " << std::setprecision(3) << mm_per_arc_segment << "\n"; + stream << "\tAllow 3D Arcs : " << (allow_3d_arcs ? "True" : "False") << "\n"; + stream << "\tAllow Travel Arcs : " << (allow_travel_arcs ? "True" : "False") << "\n"; + stream << "\tAllow Dynamic Precision : " << (allow_dynamic_precision ? "True" : "False") << "\n"; + stream << "\tDefault XYZ Precision : " << std::setprecision(0) << static_cast(default_xyz_precision) << "\n"; + stream << "\tDefault E Precision : " << std::setprecision(0) << static_cast(default_e_precision) << "\n"; + stream << "\tExtrusion Rate Variance % : " << std::setprecision(3) << extrusion_rate_variance_percent * 100.0 << "%\n"; + stream << "\tG90/G91 Influences Extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n"; + if (max_gcode_length == 0) + { + stream << "\tMax Gcode Length : Unlimited\n"; + } + else { + stream << "\tMax Gcode Length : " << std::setprecision(0) << max_gcode_length << " characters\n"; + } + stream << "\tLog Level : " << log_level_name << "\n"; + stream << "\tHide Progress Updates : " << (callback == NULL ? "True" : "False"); + return stream.str(); + }; + +}; struct arc_welder_results { arc_welder_results() : progress() @@ -415,12 +540,7 @@ struct arc_welder_results { std::string message; arc_welder_progress progress; }; -#define DEFAULT_GCODE_BUFFER_SIZE 10 -#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false -#define DEFAULT_ALLOW_DYNAMIC_PRECISION false -#define DEFAULT_ALLOW_TRAVEL_ARCS false -#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 0.05 -#define DEFAULT_CONVERT_TRAVEL_MOVES false + class arc_welder { public: @@ -440,8 +560,32 @@ public: unsigned char default_xyz_precision, unsigned char default_e_precision, double extrusion_rate_variance_percent, + int max_gcode_length, int buffer_size, progress_callback callback); + + arc_welder(arc_welder_args args) : arc_welder( + args.source_path, + args.target_path, + args.log, + args.resolution_mm, + args.path_tolerance_percent, + args.max_radius_mm, + args.min_arc_segments, + args.mm_per_arc_segment, + args.g90_g91_influences_extruder, + args.allow_3d_arcs, + args.allow_travel_arcs, + args.allow_dynamic_precision, + args.default_xyz_precision, + args.default_e_precision, + args.extrusion_rate_variance_percent, + args.max_gcode_length, + args.buffer_size, + args.callback + ){}; + + void set_logger_type(int logger_type); virtual ~arc_welder(); arc_welder_results process(); @@ -455,10 +599,9 @@ private: static gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size); progress_callback progress_callback_; int process_gcode(parsed_command cmd, bool is_end, bool is_reprocess); - void write_arc_gcodes(bool is_extruder_relative, double current_feedrate); + void write_arc_gcodes(double current_feedrate); int write_gcode_to_file(std::string gcode); - std::string get_arc_gcode_relative(double f, const std::string comment); - std::string get_arc_gcode_absolute(double e, double f, const std::string comment); + std::string get_arc_gcode(const std::string comment); std::string get_comment_for_arc(); int write_unwritten_gcodes_to_file(); std::string create_g92_e(double absolute_e); @@ -477,6 +620,7 @@ private: int arcs_created_; int arcs_aborted_by_flow_rate_; source_target_segment_statistics segment_statistics_; + source_target_segment_statistics travel_statistics_; long get_file_size(const std::string& file_path); double get_time_elapsed(double start_clock, double end_clock); double get_next_update_time() const; diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 51ef57e..531c9e5 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -38,7 +38,10 @@ segmented_arc::segmented_arc() : segmented_shape(DEFAULT_MIN_SEGMENTS, DEFAULT_M min_arc_segments_ = DEFAULT_MIN_ARC_SEGMENTS, mm_per_arc_segment_ = DEFAULT_MM_PER_ARC_SEGMENT; allow_3d_arcs_ = DEFAULT_ALLOW_3D_ARCS; + max_gcode_length_ = DEFAULT_MAX_GCODE_LENGTH; + num_gcode_length_exceptions_ = 0; num_firmware_compensations_ = 0; + } segmented_arc::segmented_arc( @@ -51,7 +54,8 @@ segmented_arc::segmented_arc( double mm_per_arc_segment, bool allow_3d_arcs, unsigned char default_xyz_precision, - unsigned char default_e_precision + unsigned char default_e_precision, + int max_gcode_length ) : segmented_shape(min_segments, max_segments, resolution_mm, path_tolerance_percent, default_xyz_precision, default_e_precision) { max_radius_mm_ = max_radius_mm; @@ -69,7 +73,15 @@ segmented_arc::segmented_arc( min_arc_segments_ = 0; } allow_3d_arcs_ = allow_3d_arcs; + max_gcode_length_ = max_gcode_length; + if (max_gcode_length_ < 1) + { + max_gcode_length_ = 0; + } num_firmware_compensations_ = 0; + num_gcode_length_exceptions_ = 0; + + } segmented_arc::~segmented_arc() @@ -108,7 +120,10 @@ int segmented_arc::get_num_firmware_compensations() const { return num_firmware_compensations_; } - +int segmented_arc::get_num_gcode_length_exceptions() const +{ + return num_gcode_length_exceptions_; +} double segmented_arc::get_mm_per_arc_segment() const { return mm_per_arc_segment_; @@ -143,7 +158,7 @@ bool segmented_arc::try_add_point(printer_point p) } // Need to separate travel moves from moves with extrusion - if (points_.count() > 2) + if (points_.count() > 1) { // We already have at least an initial point and a second point. Make cure the current point and the previous are either both // travel moves, or both extrusion @@ -217,6 +232,11 @@ bool segmented_arc::try_add_point_internal_(printer_point p) if (arc::try_create_arc(points_, current_arc_, original_shape_length_, max_radius_mm_, resolution_mm_, path_tolerance_percent_, min_arc_segments_, mm_per_arc_segment_, get_xyz_tolerance(), allow_3d_arcs_)) { bool abort_arc = false; + if (max_gcode_length_ > 0 && get_shape_gcode_length() > max_gcode_length_) + { + abort_arc = true; + num_gcode_length_exceptions_++; + } if (min_arc_segments_ > 0 && mm_per_arc_segment_ > 0) { // Apply firmware compensation @@ -252,7 +272,7 @@ bool segmented_arc::try_add_point_internal_(printer_point p) // or because both I and J == 0 current_arc_ = original_arc; } - else if (!abort_arc) + else { if (!is_shape()) { @@ -267,22 +287,12 @@ bool segmented_arc::try_add_point_internal_(printer_point p) return false; } -std::string segmented_arc::get_shape_gcode_absolute(double e, double f) -{ - bool has_e = e_relative_ != 0; - return get_shape_gcode_(has_e, e, f); -} - -std::string segmented_arc::get_shape_gcode_relative(double f) -{ - bool has_e = e_relative_ != 0; - return get_shape_gcode_(has_e, e_relative_, f); -} - -std::string segmented_arc::get_shape_gcode_(bool has_e, double e, double f) const +std::string segmented_arc::get_shape_gcode() const { std::string gcode; - // Calculate gcode size + double e = current_arc_.end_point.is_extruder_relative ? e_relative_ : current_arc_.end_point.e_offset; + double f = current_arc_.start_point.f == current_arc_.end_point.f ? 0 : current_arc_.end_point.f; + bool has_e = e_relative_ != 0; bool has_f = utilities::greater_than_or_equal(f, 1); bool has_z = allow_3d_arcs_ && !utilities::is_equal( current_arc_.start_point.z, current_arc_.end_point.z, get_xyz_tolerance() @@ -298,6 +308,9 @@ std::string segmented_arc::get_shape_gcode_(bool has_e, double e, double f) cons gcode += "G3"; } + // TODO: Limit Gcode Precision based on max_gcode_length + + // Add X, Y, I and J gcode += " X"; gcode += utilities::dtos(current_arc_.end_point.x, get_xyz_precision()); @@ -338,6 +351,40 @@ std::string segmented_arc::get_shape_gcode_(bool has_e, double e, double f) cons return gcode; +} + +int segmented_arc::get_shape_gcode_length() +{ + double e = current_arc_.end_point.is_extruder_relative ? e_relative_ : current_arc_.end_point.e_offset; + double f = current_arc_.start_point.f == current_arc_.end_point.f ? 0 : current_arc_.end_point.f; + bool has_e = e_relative_ != 0; + bool has_f = utilities::greater_than_or_equal(f, 1); + bool has_z = allow_3d_arcs_ && !utilities::is_equal( + current_arc_.start_point.z, current_arc_.end_point.z, get_xyz_tolerance() + ); + + int xyz_precision = get_xyz_precision(); + int e_precision = get_e_precision(); + + double i = current_arc_.get_i(); + double j = current_arc_.get_j(); + + int num_spaces = 3 + (has_z ? 1 : 0) + (has_e ? 1 : 0) + (has_f ? 1 : 0); + int num_decimal_points = 4 + (has_z ? 1 : 0) + (has_e ? 1 : 0); // note f has no decimal point + int num_decimals = xyz_precision * (4 + (has_z ? 1 : 0) + (has_e ? 1 : 0)); // Note f is an int + int num_digits = ( + utilities::get_num_digits(current_arc_.end_point.x) + + utilities::get_num_digits(current_arc_.end_point.y) + + (has_z ? utilities::get_num_digits(current_arc_.end_point.z) : 0) + + utilities::get_num_digits(i) + + utilities::get_num_digits(j) + + (has_f ? utilities::get_num_digits(f) : 0) + ); + // Return the length of the gcode. + return 3 + num_spaces + num_decimal_points + num_decimal_points + num_digits; + + + } /* diff --git a/ArcWelder/segmented_arc.h b/ArcWelder/segmented_arc.h index c67c071..2c7b240 100644 --- a/ArcWelder/segmented_arc.h +++ b/ArcWelder/segmented_arc.h @@ -42,14 +42,14 @@ public: double mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT, bool allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS, unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION, - unsigned char default_e_precision = DEFAULT_E_PRECISION + unsigned char default_e_precision = DEFAULT_E_PRECISION, + int max_gcode_length = DEFAULT_MAX_GCODE_LENGTH ); virtual ~segmented_arc(); virtual bool try_add_point(printer_point p); virtual double get_shape_length(); - std::string get_shape_gcode_absolute(double e, double f); - std::string get_shape_gcode_relative(double f); - + std::string get_shape_gcode() const; + int get_shape_gcode_length(); virtual bool is_shape() const; printer_point pop_front(double e_relative); printer_point pop_back(double e_relative); @@ -57,15 +57,16 @@ public: int get_min_arc_segments() const; double get_mm_per_arc_segment() const; int get_num_firmware_compensations() const; - + int get_num_gcode_length_exceptions() const; private: bool try_add_point_internal_(printer_point p); - std::string get_shape_gcode_(bool has_e, double e, double f) const; arc current_arc_; double max_radius_mm_; int min_arc_segments_; double mm_per_arc_segment_; int num_firmware_compensations_; bool allow_3d_arcs_; + int max_gcode_length_; + int num_gcode_length_exceptions_; }; diff --git a/ArcWelder/segmented_shape.cpp b/ArcWelder/segmented_shape.cpp index 4e17d3c..447cfbe 100644 --- a/ArcWelder/segmented_shape.cpp +++ b/ArcWelder/segmented_shape.cpp @@ -359,7 +359,7 @@ bool circle::is_over_deviation(const array_list& points, const do double distance = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, center.x, center.y); if (std::fabs(distance - radius) > resolution_mm) { - return true; + return true; } } } @@ -380,9 +380,9 @@ double arc::get_j() const bool arc::try_create_arc( const circle& c, - const point& start_point, - const point& mid_point, - const point& end_point, + const printer_point& start_point, + const printer_point& mid_point, + const printer_point& end_point, arc& target_arc, double approximate_length, double resolution, diff --git a/ArcWelder/segmented_shape.h b/ArcWelder/segmented_shape.h index ec4bde9..d6e7218 100644 --- a/ArcWelder/segmented_shape.h +++ b/ArcWelder/segmented_shape.h @@ -36,7 +36,7 @@ #define DEFAULT_XYZ_TOLERANCE 0.001 #define DEFAULT_E_PRECISION 5 #define ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT 0.05 // one percent - +#define DEFAULT_MAX_GCODE_LENGTH 0 // the maximum gcode length ( < 1 = unlimited) struct point { public: @@ -53,9 +53,13 @@ public: struct printer_point : point { public: - printer_point() :point(0, 0, 0), e_relative(0), distance(0) {} - printer_point(double x, double y, double z, double e_relative, double distance) :point(x,y,z), e_relative(e_relative), distance(distance) {} + printer_point() :point(0, 0, 0), e_relative(0), distance(0), is_extruder_relative(false), e_offset(0), f(0) {} + printer_point(double x, double y, double z, double e_offset, double e_relative, double f, double distance, bool is_extruder_relative) + : point(x,y,z), e_offset(e_offset), e_relative(e_relative), f(f), distance(distance), is_extruder_relative(is_extruder_relative) {} + bool is_extruder_relative; + double e_offset; double e_relative; + double f; double distance; }; @@ -164,8 +168,8 @@ struct arc : circle double polar_start_theta; double polar_end_theta; double max_deviation; - point start_point; - point end_point; + printer_point start_point; + printer_point end_point; DirectionEnum direction; double get_i() const; double get_j() const; @@ -185,9 +189,9 @@ struct arc : circle private: static bool try_create_arc( const circle& c, - const point& start_point, - const point& mid_point, - const point& end_point, + const printer_point& start_point, + const printer_point& mid_point, + const printer_point& end_point, arc& target_arc, double approximate_length, double resolution = DEFAULT_RESOLUTION_MM, diff --git a/ArcWelder/unwritten_command.h b/ArcWelder/unwritten_command.h index 8f85a50..d1ed490 100644 --- a/ArcWelder/unwritten_command.h +++ b/ArcWelder/unwritten_command.h @@ -29,34 +29,18 @@ struct unwritten_command { unwritten_command() { is_extruder_relative = false; - e_relative = 0; - offset_e = 0; - extrusion_length = 0; - is_g1_g2 = false; + length = 0; + is_g0_g1 = false; } - unwritten_command(parsed_command &cmd, bool is_relative, double command_length) { - is_extruder_relative = is_relative; - is_g1_g2 = cmd.command == "G0" || cmd.command == "G1"; - gcode = cmd.gcode; - comment = cmd.comment; - extrusion_length = command_length; + unwritten_command(parsed_command &cmd, bool is_relative, bool is_travel, double command_length) + : is_extruder_relative(is_relative), is_travel(is_travel), is_g0_g1(cmd.command == "G0" || cmd.command == "G1"), gcode(cmd.gcode), comment(cmd.comment), length(command_length) + { + } - /* - unwritten_command(position* p, double command_length) { - - e_relative = p->get_current_extruder().e_relative; - offset_e = p->get_current_extruder().get_offset_e(); - is_extruder_relative = p->is_extruder_relative; - is_g1_g2 = p->command.command == "G0" || p->command.command == "G1"; - gcode = p->command.gcode; - comment = p->command.comment; - extrusion_length = command_length; - } */ - bool is_g1_g2; + bool is_g0_g1; bool is_extruder_relative; - double e_relative; - double offset_e; - double extrusion_length; + bool is_travel; + double length; std::string gcode; std::string comment; diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp index 16a6923..e7a1ea9 100644 --- a/ArcWelderConsole/ArcWelderConsole.cpp +++ b/ArcWelderConsole/ArcWelderConsole.cpp @@ -34,27 +34,19 @@ #include #define DEFAULT_ARG_DOUBLE_PRECISION 4 +#define PROGRESS_TYPE_NONE "NONE" +#define PROGRESS_TYPE_SIMPLE "SIMPLE" +#define PROGRESS_TYPE_FULL "FULL" int main(int argc, char* argv[]) { - std::string source_file_path; - std::string target_file_path; - double resolution_mm; - double max_radius_mm; - int min_arc_segments; - double mm_per_arc_segment; - double path_tolerance_percent; - bool g90_g91_influences_extruder; - bool hide_progress; - bool overwrite_source_file = false; - bool allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS; - bool allow_travel_arcs = DEFAULT_ALLOW_TRAVEL_ARCS; - bool allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION; - unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION; - unsigned char default_e_precision = DEFAULT_E_PRECISION; - double extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; + + arc_welder_args args; std::string log_level_string; std::string log_level_string_default = "INFO"; + std::string progress_type; + std::string progress_type_default_string = PROGRESS_TYPE_SIMPLE; int log_level_value; + bool hide_progress = false; // Add info about the application std::string info = "Arc Welder: Anti-Stutter - Reduces the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3)."; @@ -154,9 +146,24 @@ int main(int argc, char* argv[]) arg_description_stream << "(experimental) - The allowed variance in extrusion rate by percent. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; TCLAP::ValueArg extrusion_rate_variance_percent_arg("v", "extrusion-rate-variance-percent", arg_description_stream.str(), false, DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, "double"); + // -l --max-gcode-length + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "The maximum length allowed for a generated G2/G3 command, not including any comments. 0 = no limit. Default Value: " << DEFAULT_MAX_GCODE_LENGTH; + TCLAP::ValueArg max_gcode_length_arg("l", "max-gcode-length", arg_description_stream.str(), false, DEFAULT_MAX_GCODE_LENGTH, "int"); - // -g --hide-progress - TCLAP::SwitchArg hide_progress_arg("p", "hide-progress", "If supplied, prevents progress updates from being displayed.", false); + // -p --progress-type + // -l --log-level + std::vector progress_type_vector; + std::string progress_type_default_string = PROGRESS_TYPE_SIMPLE; + progress_type_vector.push_back(PROGRESS_TYPE_NONE); + progress_type_vector.push_back(PROGRESS_TYPE_SIMPLE); + progress_type_vector.push_back(PROGRESS_TYPE_FULL); + TCLAP::ValuesConstraint progress_type_constraint(progress_type_vector); + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "Sets the progress type display. Default Value " << progress_type_default_string; + TCLAP::ValueArg progress_type_arg("p", "progress-type", arg_description_stream.str(), false, progress_type_default_string, &progress_type_constraint); // -l --log-level std::vector log_levels_vector; @@ -188,117 +195,125 @@ int main(int argc, char* argv[]) cmd.add(default_xyz_precision_arg); cmd.add(default_e_precision_arg); cmd.add(extrusion_rate_variance_percent_arg); + cmd.add(max_gcode_length_arg); cmd.add(g90_arg); - cmd.add(hide_progress_arg); + cmd.add(progress_type_arg); cmd.add(log_level_arg); // Parse the argv array. cmd.parse(argc, argv); // Get the value parsed by each arg. - source_file_path = source_arg.getValue(); - target_file_path = target_arg.getValue(); + args.source_path = source_arg.getValue(); + args.target_path = target_arg.getValue(); - if (target_file_path.size() == 0) + if (args.target_path.size() == 0) { - target_file_path = source_file_path; + args.target_path = args.source_path; } - resolution_mm = resolution_arg.getValue(); - max_radius_mm = max_radius_arg.getValue(); - min_arc_segments = min_arc_segments_arg.getValue(); - mm_per_arc_segment = mm_per_arc_segment_arg.getValue(); - path_tolerance_percent = path_tolerance_percent_arg.getValue(); - allow_3d_arcs = allow_3d_arcs_arg.getValue(); - allow_travel_arcs = allow_travel_arcs_arg.getValue(); - g90_g91_influences_extruder = g90_arg.getValue(); - allow_dynamic_precision = allow_dynamic_precision_arg.getValue(); - default_xyz_precision = default_xyz_precision_arg.getValue(); - default_e_precision = default_e_precision_arg.getValue(); - extrusion_rate_variance_percent = extrusion_rate_variance_percent_arg.getValue(); - - hide_progress = hide_progress_arg.getValue(); + args.resolution_mm = resolution_arg.getValue(); + args.max_radius_mm = max_radius_arg.getValue(); + args.min_arc_segments = min_arc_segments_arg.getValue(); + args.mm_per_arc_segment = mm_per_arc_segment_arg.getValue(); + args.path_tolerance_percent = path_tolerance_percent_arg.getValue(); + args.allow_3d_arcs = allow_3d_arcs_arg.getValue(); + args.allow_travel_arcs = allow_travel_arcs_arg.getValue(); + args.g90_g91_influences_extruder = g90_arg.getValue(); + args.allow_dynamic_precision = allow_dynamic_precision_arg.getValue(); + args.default_xyz_precision = default_xyz_precision_arg.getValue(); + args.default_e_precision = default_e_precision_arg.getValue(); + args.extrusion_rate_variance_percent = extrusion_rate_variance_percent_arg.getValue(); + args.max_gcode_length = max_gcode_length_arg.getValue(); + progress_type = progress_type_arg.getValue(); log_level_string = log_level_arg.getValue(); log_level_value = -1; // Check the entered values bool has_error = false; - if (resolution_mm <= 0) + if (args.resolution_mm <= 0) { - std::cerr << "error: The provided resolution of " << resolution_mm << " is negative, which is not allowed." < 1000000) + if (args.max_radius_mm > 1000000) { // warning - std::cout << "warning: The provided path max radius of " << max_radius_mm << "mm is greater than 1000000 (1km), which is not recommended." << std::endl; + std::cout << "warning: The provided path max radius of " << args.max_radius_mm << "mm is greater than 1000000 (1km), which is not recommended." << std::endl; } - if (min_arc_segments < 0) + if (args.min_arc_segments < 0) { // warning - std::cout << "warning: The provided min_arc_segments " << min_arc_segments << " is less than zero. Setting to 0." << std::endl; - min_arc_segments = 0; + std::cout << "warning: The provided min_arc_segments " << args.min_arc_segments << " is less than zero. Setting to 0." << std::endl; + args.min_arc_segments = 0; } - if (mm_per_arc_segment < 0) + if (args.mm_per_arc_segment < 0) { // warning - std::cout << "warning: The provided mm_per_arc_segment " << mm_per_arc_segment << "mm is less than zero. Setting to 0." << std::endl; - mm_per_arc_segment = 0; + std::cout << "warning: The provided mm_per_arc_segment " << args.mm_per_arc_segment << "mm is less than zero. Setting to 0." << std::endl; + args.mm_per_arc_segment = 0; } - if (path_tolerance_percent > 0.05) + if (args.path_tolerance_percent > 0.05) { // warning - std::cout << "warning: The provided path tolerance percent of " << path_tolerance_percent << " is greater than 0.05 (5%), which is not recommended." << std::endl; + std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is greater than 0.05 (5%), which is not recommended." << std::endl; } - else if (path_tolerance_percent < 0.0001 && path_tolerance_percent > 0) + else if (args.path_tolerance_percent < 0.0001 && args.path_tolerance_percent > 0) { // warning - std::cout << "warning: The provided path tolerance percent of " << path_tolerance_percent << " is less than greater than 0.001 (0.1%), which is not recommended." << std::endl; + std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is less than greater than 0.001 (0.1%), which is not recommended." << std::endl; } - if (default_xyz_precision < 3) + if (args.default_xyz_precision < 3) { // warning - std::cout << "warning: The provided default_xyz_precision " << default_xyz_precision << "mm is less than 3, with will cause issues printing arcs. A value of 3 will be used instead." << std::endl; - default_xyz_precision = 3; + std::cout << "warning: The provided default_xyz_precision " << args.default_xyz_precision << "mm is less than 3, with will cause issues printing arcs. A value of 3 will be used instead." << std::endl; + args.default_xyz_precision = 3; } - if (default_e_precision < DEFAULT_E_PRECISION) + if (args.default_e_precision < DEFAULT_E_PRECISION) { // warning - std::cout << "warning: The provided default_e_precision " << default_e_precision << "mm is less than 3, with will cause extrusion issues. A value of 3 will be used instead." << std::endl; - default_e_precision = 3; + std::cout << "warning: The provided default_e_precision " << args.default_e_precision << "mm is less than 3, with will cause extrusion issues. A value of 3 will be used instead." << std::endl; + args.default_e_precision = 3; } - if (default_xyz_precision > 6) + if (args.default_xyz_precision > 6) { // warning - std::cout << "warning: The provided default_xyz_precision " << default_xyz_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so a value of 6 will be used instead." << std::endl; - default_xyz_precision = 6; + std::cout << "warning: The provided default_xyz_precision " << args.default_xyz_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so a value of 6 will be used instead." << std::endl; + args.default_xyz_precision = 6; } - if (default_e_precision > 6) + if (args.default_e_precision > 6) { // warning - std::cout << "warning: The provided default_e_precision " << default_e_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so value of 6 will be used instead." << std::endl; - default_e_precision = 6; + std::cout << "warning: The provided default_e_precision " << args.default_e_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so value of 6 will be used instead." << std::endl; + args.default_e_precision = 6; } - if (extrusion_rate_variance_percent < 0) + if (args.extrusion_rate_variance_percent < 0) { // warning - std::cout << "warning: The provided extrusion_rate_variance_percent " << extrusion_rate_variance_percent << " is less than 0. Setting to the default." << std::endl; - extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; + std::cout << "warning: The provided extrusion_rate_variance_percent " << args.extrusion_rate_variance_percent << " is less than 0. Setting to the default." << std::endl; + args.extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; + } + + if (args.max_gcode_length < 0) + { + // warning + std::cout << "warning: The provided max_gcode_length " << args.max_gcode_length << " is less than 0. Setting to the default." << std::endl; + args.max_gcode_length = DEFAULT_MAX_GCODE_LENGTH; } if (has_error) @@ -331,95 +346,56 @@ int main(int argc, char* argv[]) // Ensure the log level name is valid std::vector log_names; - log_names.push_back("arc_welder.gcode_conversion"); + log_names.push_back(ARC_WELDER_LOGGER_NAME); std::vector log_levels; log_levels.push_back(log_levels::DEBUG); logger* p_logger = new logger(log_names, log_levels); p_logger->set_log_level_by_value(log_level_value); - - std::stringstream log_messages; - std::string temp_file_path = ""; - log_messages << std::fixed << std::setprecision(DEFAULT_ARG_DOUBLE_PRECISION); - if (source_file_path == target_file_path) + args.log = p_logger; + + arc_welder* p_arc_welder = NULL; + + if (progress_type == PROGRESS_TYPE_NONE) { - overwrite_source_file = true; - if (!utilities::get_temp_file_path_for_file(source_file_path, temp_file_path)) - { - log_messages << "The source and target path are the same, but a temporary file path could not be created. Is the path empty?"; - p_logger->log(0, INFO, log_messages.str()); - log_messages.clear(); - log_messages.str(""); - } - - // create a uuid with a tmp extension for the temporary file - log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; - p_logger->log(0, INFO, log_messages.str()); - log_messages.clear(); - log_messages.str(""); - target_file_path = temp_file_path; + p_logger->log(0, INFO, "Suppressing progress messages."); + args.callback = on_progress_suppress; } - log_messages << "Processing Gcode\n"; - log_messages << "\tSource File Path : " << source_file_path << "\n"; - if (overwrite_source_file) + else if (progress_type == PROGRESS_TYPE_FULL) { - log_messages << "\tTarget File Path (overwrite) : " << target_file_path << "\n"; - log_messages << "\tTemporary File Path : " << temp_file_path << "\n"; + p_logger->log(0, INFO, "Displaying full progress messages."); + args.callback = on_progress_full; } - else - { - log_messages << "\tTarget File File : " << target_file_path << "\n"; + else { + args.callback = on_progress_simple; } - - log_messages << "\tResolution : " << resolution_mm << "mm (+-" << std::setprecision(5) << resolution_mm/2.0 << "mm)\n"; - log_messages << "\tPath Tolerance : " << std::setprecision(3) << path_tolerance_percent*100.0 << "%\n"; - log_messages << "\tMaximum Arc Radius : " << std::setprecision(0) << max_radius_mm << "mm\n"; - log_messages << "\tMin Arc Segments : " << std::setprecision(0) << min_arc_segments << "\n"; - log_messages << "\tMM Per Arc Segment : " << std::setprecision(3) << mm_per_arc_segment << "\n"; - log_messages << "\tAllow 3D Arcs : " << (allow_3d_arcs ? "True" : "False") << "\n"; - log_messages << "\tAllow Travel Arcs : " << (allow_travel_arcs ? "True" : "False") << "\n"; - log_messages << "\tAllow Dynamic Precision : " << (allow_dynamic_precision ? "True" : "False") << "\n"; - log_messages << "\tDefault XYZ Precision : " << std::setprecision(0) << static_cast(default_xyz_precision) << "\n"; - log_messages << "\tDefault E Precision : " << std::setprecision(0) << static_cast(default_e_precision) << "\n"; - log_messages << "\tExtrusion Rate Variance % : " << std::setprecision(3) << extrusion_rate_variance_percent*100.0 << "%\n"; - log_messages << "\tG90/G91 Influences Extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n"; - log_messages << "\tLog Level : " << log_level_string << "\n"; - log_messages << "\tHide Progress Updates : " << (hide_progress ? "True" : "False"); + // Log the arguments + std::stringstream log_messages; + log_messages << "Processing GCode."; + p_logger->log(0, INFO, log_messages.str()); + log_messages.clear(); + log_messages.str(""); + log_messages << args.str(); p_logger->log(0, INFO, log_messages.str()); - arc_welder* p_arc_welder = NULL; - - if (overwrite_source_file) - { - target_file_path = temp_file_path; - } - if (!hide_progress) - p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_travel_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, on_progress); - else - p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_travel_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, extrusion_rate_variance_percent, DEFAULT_GCODE_BUFFER_SIZE, suppress_progress); + p_arc_welder = new arc_welder(args); + arc_welder_results results = p_arc_welder->process(); if (results.success) { - log_messages.clear(); - log_messages.str(""); - log_messages << "Target file at '" << target_file_path << "' created."; - - if (overwrite_source_file) + if (args.allow_travel_arcs) { log_messages.clear(); log_messages.str(""); - log_messages << "Deleting the original source file at '" << source_file_path << "'."; + log_messages << "Target File Travel Statistics:" << std::endl << results.progress.travel_statistics.str(); p_logger->log(0, INFO, log_messages.str()); - log_messages.clear(); - log_messages.str(""); - std::remove(source_file_path.c_str()); - log_messages << "Renaming temporary file at '" << target_file_path << "' to '" << source_file_path <<"'."; - p_logger->log(0, INFO, log_messages.str()); - std::rename(target_file_path.c_str(), source_file_path.c_str()); } + log_messages.clear(); log_messages.str(""); - log_messages << std::endl << results.progress.segment_statistics.str(); + log_messages << "Target File Extrusion Statistics:" << std::endl << results.progress.segment_statistics.str(); p_logger->log(0, INFO, log_messages.str() ); + + log_messages.clear(); log_messages.str(""); @@ -438,13 +414,21 @@ int main(int argc, char* argv[]) return 0; } -bool on_progress(arc_welder_progress progress, logger* p_logger, int logger_type) +bool on_progress_full(arc_welder_progress progress, logger* p_logger, int logger_type) { std::cout << "Progress: "<< progress.str() << std::endl; std::cout.flush(); return true; } -bool suppress_progress(arc_welder_progress progress, logger* p_logger, int logger_type) + +bool on_progress_simple(arc_welder_progress progress, logger* p_logger, int logger_type) +{ + std::cout << "Progress: " << progress.simple_progress_str() << std::endl; + std::cout.flush(); + return true; +} + +bool on_progress_suppress(arc_welder_progress progress, logger* p_logger, int logger_type) { return true; } diff --git a/ArcWelderConsole/ArcWelderConsole.h b/ArcWelderConsole/ArcWelderConsole.h index 64425c9..1e4762f 100644 --- a/ArcWelderConsole/ArcWelderConsole.h +++ b/ArcWelderConsole/ArcWelderConsole.h @@ -25,6 +25,8 @@ #pragma once #include "arc_welder.h" #include "version.h" -static bool on_progress(arc_welder_progress progress, logger* p_logger, int logger_type); -static bool suppress_progress(arc_welder_progress progress, logger* p_logger, int logger_type); +static bool on_progress_full(arc_welder_progress progress, logger* p_logger, int logger_type); +static bool on_progress_simple(arc_welder_progress progress, logger* p_logger, int logger_type); +static bool on_progress_suppress(arc_welder_progress progress, logger* p_logger, int logger_type); + diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 95aebc5..2ac30b1 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -266,17 +266,6 @@ static gcode_position_args get_5_extruder_position_args() static void TestAntiStutter(std::string filePath) { - //double max_resolution = DEFAULT_RESOLUTION_MM; - double max_resolution = 0.05; - double max_radius_mm = 100000; - //double max_radius_mm = 10000; - //int min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; - int min_arc_segments = 0; - double mm_per_arc_segment = 0; - - //double path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; // 1 percent - double path_tolerance_percent = 0.05; - //double path_tolerance_percent = 0.05; std::vector logger_names; logger_names.push_back("arc_welder.gcode_conversion"); std::vector logger_levels; @@ -289,29 +278,7 @@ static void TestAntiStutter(std::string filePath) logger_levels.push_back(log_levels::CRITICAL); logger* p_logger = new logger(logger_names, logger_levels); p_logger->set_log_level(INFO); - //p_logger->set_log_level(DEBUG); - //p_logger->set_log_level_by_value(5); - //arc_welder arc_welder_obj(BENCHY_0_5_MM_NO_WIPE, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, static_cast(on_progress)); - //arc_welder arc_welder_obj(SIX_SPEED_TEST, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, on_progress); - arc_welder arc_welder_obj( - BENCHY_DIFFICULT, - "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", - p_logger, - max_resolution, - path_tolerance_percent, - DEFAULT_MAX_RADIUS_MM, - min_arc_segments, - mm_per_arc_segment, - true, - true, - true, //DEFAULT_ALLOW_TRAVEL_ARCS, - DEFAULT_ALLOW_DYNAMIC_PRECISION, - DEFAULT_XYZ_PRECISION, - DEFAULT_E_PRECISION, - //DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, - 1000000, - DEFAULT_GCODE_BUFFER_SIZE, - on_progress); + //FIRMWARE_COMPENSATION_TEST_1 //BENCHY_MIN_RADIUS_TEST //BENCHY_DIFFICULT @@ -335,6 +302,17 @@ static void TestAntiStutter(std::string filePath) // BENCHY_L1_DIFFICULT // SPIRAL_TEST // SPIRAL_VASE_TEST_FUNNEL + std::string source_path = TravelWipeTest; + std::string target_path = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode"; + arc_welder_args args(source_path, target_path, p_logger); + args.callback = on_progress; + // override any arguments here; + args.allow_travel_arcs = true; + args.allow_3d_arcs = true; + args.max_radius_mm = 9999; + args.resolution_mm = 0.05; + arc_welder arc_welder_obj(args); + arc_welder_results results = arc_welder_obj.process(); p_logger->log(0, INFO, results.progress.detail_str()); p_logger->log(0, INFO, "Processing Complete."); diff --git a/ArcWelderTest/ArcWelderTest.h b/ArcWelderTest/ArcWelderTest.h index b4331e9..e854c4b 100644 --- a/ArcWelderTest/ArcWelderTest.h +++ b/ArcWelderTest/ArcWelderTest.h @@ -83,7 +83,7 @@ static std::string ISSUE_PRICKLYPEAR_LAYER_0_114 = "C:\\Users\\Brad\\Documents\\ static std::string COLINEAR_TEST_1 = "C:\\Users\\Brad\\Documents\\AntiStutter\\Sanity Checks\\G2_colinear_test.gcode"; static std::string SPIRAL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TEST.gcode"; static std::string SPIRAL_TRAVEL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TRAVEL_TEST.gcode"; - +static std::string TravelWipeTest = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\TravelWipeTest.gcode"; static std::string SPIRAL_TEST_PRECISION = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TEST_precision.gcode"; static std::string SPIRAL_VASE_TEST_DOUBLE_SPIRAL = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\SpiralVaseTest\\SpiralVaseTest_DOUBLE_SPIRAL.gcode"; static std::string SPIRAL_VASE_TEST_CYLINDER = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\SpiralVaseTest\\SpiralVaseTest_Cylinder.gcode"; diff --git a/GcodeProcessorLib/logger.cpp b/GcodeProcessorLib/logger.cpp index 5b1e4eb..3bde18e 100644 --- a/GcodeProcessorLib/logger.cpp +++ b/GcodeProcessorLib/logger.cpp @@ -57,10 +57,24 @@ void logger::set_log_level_by_value(const int level_value) logger_levels_[type_index] = log_level; } } + void logger::set_log_level(const int logger_type, const int log_level) { logger_levels_[logger_type] = log_level; } +std::string logger::get_log_level_name(std::string logger_name) +{ + std::string log_level_name = "UNKNOWN"; + for (int type_index = 0; type_index < num_loggers_; type_index++) + { + if (logger_names_[type_index] == logger_name) + { + log_level_name = log_level_names[logger_levels_[type_index]]; + break; + } + } + return log_level_name; +} void logger::set_log_level(const int log_level) { diff --git a/GcodeProcessorLib/logger.h b/GcodeProcessorLib/logger.h index 9db2df1..f345097 100644 --- a/GcodeProcessorLib/logger.h +++ b/GcodeProcessorLib/logger.h @@ -36,7 +36,7 @@ enum log_levels { NOSET, VERBOSE, DEBUG, INFO, WARNING , ERROR, CRITICAL}; static const int log_level_names_size = 7; static const char* log_level_names[] = {"NOSET", "VERBOSE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}; const static int log_level_values[LOG_LEVEL_COUNT] = { 0, 5, 10, 20, 30, 40, 50}; - +#define DEFAULT_LOG_LEVEL_VALUE 40 class logger { public: @@ -48,7 +48,8 @@ public: void set_log_level(const int logger_type, const int log_level); void set_log_level(const int log_level); - + + std::string get_log_level_name(std::string logger_name); virtual void log(const int logger_type, const int log_level, const std::string& message); virtual void log(const int logger_type, const int log_level, const std::string& message, bool is_exception); virtual void log_exception(const int logger_type, const std::string& message); diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index a723cdf..4db0f6a 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -217,7 +217,7 @@ int utilities::get_num_digits(int x) (x < 10000000 ? 7 : (x < 100000000 ? 8 : (x < 1000000000 ? 9 : - 10))))))))); + (x < 10000000000 ? 10 : -1)))))))))); } int utilities::get_num_digits(double x) diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index acbea6c..3261f91 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -55,7 +55,6 @@ public: static double get_percent_change(int v1, int v2); static double get_percent_change(double v1, double v2); static std::string get_percent_change_string(int v1, int v2, int precision); - static int get_num_digits(int x); static int get_num_digits(double x); diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp index 7fdf9bd..482c09b 100644 --- a/PyArcWelder/py_arc_welder.cpp +++ b/PyArcWelder/py_arc_welder.cpp @@ -24,105 +24,420 @@ PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, std::string guid) { - std::string segment_statistics = progress.segment_statistics.str(); - PyObject* pyGuid = gcode_arc_converter::PyUnicode_SafeFromString(guid); - if (pyGuid == NULL) - return NULL; - PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_statistics); - if (pyMessage == NULL) - return NULL; - double total_count_reduction_percent = progress.segment_statistics.get_total_count_reduction_percent(); - PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i,s:f}", - "percent_complete", - progress.percent_complete, //1 - "seconds_elapsed", - progress.seconds_elapsed, //2 - "seconds_remaining", - progress.seconds_remaining, //3 - "gcodes_processed", - progress.gcodes_processed, //4 - "lines_processed", - progress.lines_processed, //5 - "points_compressed", - progress.points_compressed, //6 - "arcs_created", - progress.arcs_created, //7 - "arcs_aborted_by_flowrate", - progress.arcs_aborted_by_flow_rate, //8 - "num_firmware_compensations", - progress.num_firmware_compensations, //9 - "source_file_position", - progress.source_file_position, //10 - "source_file_size", - progress.source_file_size, //11 - "target_file_size", - progress.target_file_size, //12 - "compression_ratio", - progress.compression_ratio, //13 - "compression_percent", - progress.compression_percent, //14 - "source_file_total_length", - progress.segment_statistics.total_length_source, //15 - "target_file_total_length", - progress.segment_statistics.total_length_target, //16 - "source_file_total_count", - progress.segment_statistics.total_count_source, //17 - "target_file_total_count", - progress.segment_statistics.total_count_target, //18 - "total_count_reduction_percent", - total_count_reduction_percent //19 - - ); + PyObject* pyGuid = gcode_arc_converter::PyUnicode_SafeFromString(guid); + if (pyGuid == NULL) + return NULL; + // Extrusion Statistics + std::string segment_statistics = progress.segment_statistics.str(); + PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_statistics); + if (pyMessage == NULL) + return NULL; + double total_count_reduction_percent = progress.segment_statistics.get_total_count_reduction_percent(); + // Travel Statistics + std::string segment_travel_statistics = progress.travel_statistics.str(); + PyObject* pyTravelMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_travel_statistics); + if (pyMessage == NULL) + return NULL; + double total_travel_count_reduction_percent = progress.travel_statistics.get_total_count_reduction_percent(); + PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i,s:f,s:f,s:f,s:i,s:i,s:f}", + "percent_complete", + progress.percent_complete, //1 + "seconds_elapsed", + progress.seconds_elapsed, //2 + "seconds_remaining", + progress.seconds_remaining, //3 + "gcodes_processed", + progress.gcodes_processed, //4 + "lines_processed", + progress.lines_processed, //5 + "points_compressed", + progress.points_compressed, //6 + "arcs_created", + progress.arcs_created, //7 + "arcs_aborted_by_flowrate", + progress.arcs_aborted_by_flow_rate, //8 + "num_firmware_compensations", + progress.num_firmware_compensations, //9 + "num_gcode_length_exceptions", + progress.num_gcode_length_exceptions, //10 + "source_file_position", + progress.source_file_position, //11 + "source_file_size", + progress.source_file_size, //12 + "target_file_size", + progress.target_file_size, //13 + "compression_ratio", + progress.compression_ratio, //14 + "compression_percent", + progress.compression_percent, //15 + "source_file_total_length", + progress.segment_statistics.total_length_source, //16 + "target_file_total_length", + progress.segment_statistics.total_length_target, //17 + "source_file_total_count", + progress.segment_statistics.total_count_source, //18 + "target_file_total_count", + progress.segment_statistics.total_count_target, //19 + "total_count_reduction_percent", + total_count_reduction_percent, //20 + "source_file_total_travel_length", + progress.travel_statistics.total_length_source, //21 + "target_file_total_travel_length", + progress.travel_statistics.total_length_target, //22 + "source_file_total_travel_count", + progress.travel_statistics.total_count_source, //23 + "target_file_total_travel_count", + progress.travel_statistics.total_count_target, //24 + "total_travel_count_reduction_percent", + total_travel_count_reduction_percent //25 - if (py_progress == NULL) - { - return NULL; - } - // Due to a CRAZY issue, I have to add this item after building the py_progress object, - // else it crashes in python 2.7. Looking forward to retiring this backwards - // compatible code... - PyDict_SetItemString(py_progress, "segment_statistics_text", pyMessage); - PyDict_SetItemString(py_progress, "guid", pyGuid); - return py_progress; + ); + + if (py_progress == NULL) + { + return NULL; + } + // Due to a CRAZY issue, I have to add this item after building the py_progress object, + // else it crashes in python 2.7. Looking forward to retiring this backwards + // compatible code... + PyDict_SetItemString(py_progress, "segment_statistics_text", pyMessage); + PyDict_SetItemString(py_progress, "segment_travel_statistics_text", pyTravelMessage); + PyDict_SetItemString(py_progress, "guid", pyGuid); + return py_progress; } bool py_arc_welder::on_progress_(const arc_welder_progress& progress) { - PyObject* py_dict = py_arc_welder::build_py_progress(progress, guid_); - if (py_dict == NULL) - { - return false; - } - PyObject* func_args = Py_BuildValue("(O)", py_dict); - if (func_args == NULL) - { - Py_DECREF(py_dict); - return false; // This was returning true, I think it was a typo. Making a note just in case. - } - - PyGILState_STATE gstate = PyGILState_Ensure(); - PyObject* pContinueProcessing = PyObject_CallObject(py_progress_callback_, func_args); - PyGILState_Release(gstate); - Py_DECREF(func_args); - Py_DECREF(py_dict); - bool continue_processing; - if (pContinueProcessing == NULL) - { - // no return value was supply, assume true, but without decrefing pContinueProcessing - continue_processing = true; - } - else - { - if (pContinueProcessing == Py_None) - { - continue_processing = true; - } - else - { - continue_processing = PyLong_AsLong(pContinueProcessing) > 0; - } - Py_DECREF(pContinueProcessing); - } - - return continue_processing; + PyObject* py_dict = py_arc_welder::build_py_progress(progress, guid_); + if (py_dict == NULL) + { + return false; + } + PyObject* func_args = Py_BuildValue("(O)", py_dict); + if (func_args == NULL) + { + Py_DECREF(py_dict); + return false; // This was returning true, I think it was a typo. Making a note just in case. + } + + PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject* pContinueProcessing = PyObject_CallObject(py_progress_callback_, func_args); + PyGILState_Release(gstate); + Py_DECREF(func_args); + Py_DECREF(py_dict); + bool continue_processing; + if (pContinueProcessing == NULL) + { + // no return value was supply, assume true, but without decrefing pContinueProcessing + continue_processing = true; + } + else + { + if (pContinueProcessing == Py_None) + { + continue_processing = true; + } + else + { + continue_processing = PyLong_AsLong(pContinueProcessing) > 0; + } + Py_DECREF(pContinueProcessing); + } + + return continue_processing; } + +bool py_gcode_arc_args::parse_args(PyObject* py_args, py_logger* p_py_logger, py_gcode_arc_args& args, PyObject** py_progress_callback) +{ + p_py_logger->log( + GCODE_CONVERSION, INFO, + "Parsing GCode Conversion Args." + ); + +#pragma region Required_Arguments +#pragma region guid + // Extract the job guid + PyObject* py_guid = PyDict_GetItemString(py_args, "guid"); + if (py_guid == NULL) + { + std::string message = "ParseArgs - Unable to retrieve required parameter 'guid' from the args."; + p_py_logger->log_exception(GCODE_CONVERSION, message); + return false; + } + args.guid = gcode_arc_converter::PyUnicode_SafeAsString(py_guid); +#pragma endregion guid +#pragma region source_path + // Extract the source file path + PyObject* py_source_path = PyDict_GetItemString(py_args, "source_path"); + if (py_source_path == NULL) + { + std::string message = "ParseArgs -Unable to retrieve required parameter 'source_path' from the args."; + p_py_logger->log_exception(GCODE_CONVERSION, message); + return false; + } + args.source_path = gcode_arc_converter::PyUnicode_SafeAsString(py_source_path); +#pragma endregion source_path +#pragma region target_path + // Extract the target file path + PyObject* py_target_path = PyDict_GetItemString(py_args, "target_path"); + if (py_target_path == NULL) + { + std::string message = "ParseArgs - Unable to retrieve required parameter 'target_path' from the args."; + p_py_logger->log_exception(GCODE_CONVERSION, message); + return false; + } + args.target_path = gcode_arc_converter::PyUnicode_SafeAsString(py_target_path); +#pragma endregion target_path +#pragma region on_progress_received + // on_progress_received + PyObject* py_on_progress_received = PyDict_GetItemString(py_args, "on_progress_received"); + if (py_on_progress_received == NULL) + { + std::string message = "ParseArgs - Unable to retrieve required parameter 'on_progress_received' from the args."; + p_py_logger->log_exception(GCODE_CONVERSION, message); + return false; + } + // need to incref this so it doesn't vanish later (borrowed reference we are saving) + Py_XINCREF(py_on_progress_received); + *py_progress_callback = py_on_progress_received; +#pragma endregion on_progress_received +#pragma endregion Required_Arguments + +#pragma region Optional_Arguments +#pragma region resolution_mm + // Extract the resolution in millimeters + PyObject* py_resolution_mm = PyDict_GetItemString(py_args, "resolution_mm"); + if (py_resolution_mm == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'resolution_mm' parameter from the args."; + p_py_logger->log(GCODE_CONVERSION, WARNING, message); + } + else { + args.resolution_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_resolution_mm); + if (args.resolution_mm <= 0) + { + args.resolution_mm = 0.05; // Set to the default if no resolution is provided, or if it is less than 0. + } + } +#pragma endregion resolution_mm +#pragma region allow_dynamic_precision + // extract allow_dynamic_precision + PyObject* py_allow_dynamic_precision = PyDict_GetItemString(py_args, "allow_dynamic_precision"); + if (py_allow_dynamic_precision == NULL) + { + std::string message = "ParseArgs - Unable to retrieve 'allow_dynamic_precision' from the args."; + p_py_logger->log(GCODE_CONVERSION, WARNING, message); + } + else { + args.allow_dynamic_precision = PyLong_AsLong(py_allow_dynamic_precision) > 0; + } +#pragma endregion allow_dynamic_precision +#pragma region default_xyz_precision + // extract default_xyz_precision + PyObject* py_default_xyz_precision = PyDict_GetItemString(py_args, "default_xyz_precision"); + if (py_default_xyz_precision == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'default_xyz_precision' parameter from the args."; + p_py_logger->log(GCODE_CONVERSION, WARNING, message); + } + else { + args.default_xyz_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision); + if (args.default_xyz_precision < 3) + { + std::string message = "ParseArgs - The default XYZ precision received was less than 3, which could cause problems printing arcs. Setting to 3."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + args.default_xyz_precision = 3; + } + else if (args.default_xyz_precision > 6) + { + std::string message = "ParseArgs - The default XYZ precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + args.default_xyz_precision = 6; + } + } +#pragma endregion default_xyz_precision +#pragma region default_e_precision + // extract default_e_precision + PyObject* py_default_e_precision = PyDict_GetItemString(py_args, "default_e_precision"); + if (py_default_e_precision == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'default_e_precision parameter' from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else { + args.default_e_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision); + if (args.default_e_precision < 3) + { + std::string message = "ParseArgs - The default E precision received was less than 3, which could cause extrusion problems. Setting to 3."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + args.default_e_precision = 3; + } + else if (args.default_e_precision > 6) + { + std::string message = "ParseArgs - The default E precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + args.default_e_precision = 6; + } + } +#pragma endregion default_e_precision +#pragma region extrusion_rate_variance_percent + // Extract the extrusion_rate_variance + PyObject* py_extrusion_rate_variance_percent = PyDict_GetItemString(py_args, "extrusion_rate_variance_percent"); + if (py_extrusion_rate_variance_percent == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'extrusion_rate_variance_percent' parameter from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.extrusion_rate_variance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_extrusion_rate_variance_percent); + if (args.extrusion_rate_variance_percent < 0) + { + args.extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; // Set to the default if no resolution is provided, or if it is less than 0. + } + } +#pragma endregion extrusion_rate_variance_percent +#pragma region path_tolerance_percent + // Extract the path tolerance_percent + PyObject* py_path_tolerance_percent = PyDict_GetItemString(py_args, "path_tolerance_percent"); + if (py_path_tolerance_percent == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'path_tolerance_percent' parameter from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.path_tolerance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_path_tolerance_percent); + if (args.path_tolerance_percent < 0) + { + args.path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; // Set to the default if no resolution is provided, or if it is less than 0. + } + } +#pragma endregion path_tolerance_percent +#pragma region max_radius_mm + // Extract the max_radius in mm + PyObject* py_max_radius_mm = PyDict_GetItemString(py_args, "max_radius_mm"); + if (py_max_radius_mm == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'max_radius_mm' parameter from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.max_radius_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_max_radius_mm); + if (args.max_radius_mm > DEFAULT_MAX_RADIUS_MM) + { + args.max_radius_mm = DEFAULT_MAX_RADIUS_MM; // Set to the default if no resolution is provided, or if it is less than 0. + } + } +#pragma endregion max_radius_mm +#pragma region mm_per_arc_segment + // Extract the mm_per_arc_segment + PyObject* py_mm_per_arc_segment = PyDict_GetItemString(py_args, "mm_per_arc_segment"); + if (py_mm_per_arc_segment == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'mm_per_arc_segment' parameter from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.mm_per_arc_segment = gcode_arc_converter::PyFloatOrInt_AsDouble(py_mm_per_arc_segment); + if (args.mm_per_arc_segment < 0) + { + args.mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; + } + } +#pragma endregion mm_per_arc_segment +#pragma region min_arc_segments + // Extract min_arc_segments + PyObject* py_min_arc_segments = PyDict_GetItemString(py_args, "min_arc_segments"); + if (py_min_arc_segments == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'min_arc_segments' parameter from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.min_arc_segments = (int)gcode_arc_converter::PyIntOrLong_AsLong(py_min_arc_segments); + if (args.min_arc_segments < 0) + { + args.min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; // Set to the default if no resolution is provided, or if it is less than 0. + } + } +#pragma endregion min_arc_segments +#pragma region max_gcode_length + // Extract max_gcode_length + PyObject* py_max_gcode_length = PyDict_GetItemString(py_args, "max_gcode_length"); + if (py_max_gcode_length == NULL) + { + std::string message = "ParseArgs - Unable to retrieve the 'max_gcode_length' parameter from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.max_gcode_length = (int)gcode_arc_converter::PyIntOrLong_AsLong(py_max_gcode_length); + if (args.max_gcode_length < 0) + { + args.max_gcode_length = DEFAULT_MAX_GCODE_LENGTH; + } + } +#pragma endregion max_gcode_length +#pragma region allow_3d_arcs + // extract allow_3d_arcs + PyObject* py_allow_3d_arcs = PyDict_GetItemString(py_args, "allow_3d_arcs"); + if (py_allow_3d_arcs == NULL) + { + std::string message = "ParseArgs - Unable to retrieve 'allow_3d_arcs' from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.allow_3d_arcs = PyLong_AsLong(py_allow_3d_arcs) > 0; + } +#pragma endregion allow_3d_arcs +#pragma region allow_travel_arcs + // extract allow_travel_arcs + PyObject* py_allow_travel_arcs = PyDict_GetItemString(py_args, "allow_travel_arcs"); + if (py_allow_travel_arcs == NULL) + { + std::string message = "ParseArgs - Unable to retrieve 'allow_travel_arcs' from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.allow_travel_arcs = PyLong_AsLong(py_allow_travel_arcs) > 0; + } +#pragma endregion allow_travel_arcs +#pragma region g90_g91_influences_extruder + // Extract G90/G91 influences extruder + // g90_influences_extruder + PyObject* py_g90_g91_influences_extruder = PyDict_GetItemString(py_args, "g90_g91_influences_extruder"); + if (py_g90_g91_influences_extruder == NULL) + { + std::string message = "ParseArgs - Unable to retrieve 'g90_g91_influences_extruder' from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + args.g90_g91_influences_extruder = PyLong_AsLong(py_g90_g91_influences_extruder) > 0; + } +#pragma endregion g90_g91_influences_extruder +#pragma region log_level + // Extract log_level + PyObject* py_log_level = PyDict_GetItemString(py_args, "log_level"); + if (py_log_level == NULL) + { + std::string message = "ParseArgs - Unable to retrieve 'log_level' from the args."; + p_py_logger->log(WARNING, GCODE_CONVERSION, message); + } + else + { + int log_level_value = static_cast(PyLong_AsLong(py_log_level)); + // determine the log level as an index rather than as a value + args.log_level = p_py_logger->get_log_level_for_value(log_level_value); + } +#pragma endregion log_level +#pragma endregion Optional_Arguments + + return true; +} \ No newline at end of file diff --git a/PyArcWelder/py_arc_welder.h b/PyArcWelder/py_arc_welder.h index 40a9cab..686a9cd 100644 --- a/PyArcWelder/py_arc_welder.h +++ b/PyArcWelder/py_arc_welder.h @@ -31,49 +31,32 @@ #else #include #endif + +struct py_gcode_arc_args : arc_welder_args { + py_gcode_arc_args() : arc_welder_args() { + log_level = INFO; + py_progress_callback = NULL; + guid = ""; + + }; + py_gcode_arc_args(std::string source_path, std::string target_path, logger* log, std::string progress_guid, PyObject* progress_callback) : arc_welder_args(source_path, target_path, log) { + guid = progress_guid; + py_progress_callback = progress_callback; + }; + + static bool parse_args(PyObject* py_args, py_logger* p_py_logger, py_gcode_arc_args& args, PyObject** py_progress_callback); + int log_level; + std::string guid; + PyObject* py_progress_callback; +}; + class py_arc_welder : public arc_welder { public: - py_arc_welder( - std::string guid, - std::string source_path, - std::string target_path, - py_logger* logger, - double resolution_mm, - double path_tolerance_percent, - double max_radius, - int min_arc_segments, - double mm_per_arc_segment, - bool g90_g91_influences_extruder, - bool allow_3d_arcs, - bool allow_travel_arcs, - bool allow_dynamic_precision, - unsigned char default_xyz_precision, - unsigned char default_e_precision, - double extrusion_rate_variance_percent, - int buffer_size, - PyObject* py_progress_callback - ): arc_welder( - source_path, - target_path, - logger, - resolution_mm, - path_tolerance_percent, - max_radius, - min_arc_segments, - mm_per_arc_segment, - g90_g91_influences_extruder, - allow_3d_arcs, - allow_travel_arcs, - allow_dynamic_precision, - default_xyz_precision, - default_e_precision, - extrusion_rate_variance_percent, - buffer_size, - NULL - ){ - guid_ = guid; - py_progress_callback_ = py_progress_callback; + py_arc_welder(py_gcode_arc_args args): arc_welder( (arc_welder_args)args) + { + guid_ = args.guid; + py_progress_callback_ = args.py_progress_callback; } virtual ~py_arc_welder() { diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp index 19a2274..b386d9f 100644 --- a/PyArcWelder/py_arc_welder_extension.cpp +++ b/PyArcWelder/py_arc_welder_extension.cpp @@ -186,7 +186,7 @@ extern "C" py_gcode_arc_args args; PyObject* py_progress_callback = NULL; - if (!ParseArgs(py_convert_file_args, args, &py_progress_callback)) + if (!py_gcode_arc_args::parse_args(py_convert_file_args, p_py_logger, args, &py_progress_callback)) { return NULL; } @@ -195,26 +195,9 @@ extern "C" std::string message = "py_gcode_arc_converter.ConvertFile - Beginning Arc Conversion."; p_py_logger->log(GCODE_CONVERSION, INFO, message); - py_arc_welder arc_welder_obj( - args.guid, - args.source_path, - args.target_path, - p_py_logger, - args.resolution_mm, - args.path_tolerance_percent, - args.max_radius_mm, - args.min_arc_segments, - args.mm_per_arc_segment, - args.g90_g91_influences_extruder, - args.allow_3d_arcs, - args.allow_travel_arcs, - args.allow_dynamic_precision, - args.default_xyz_precision, - args.default_e_precision, - args.extrusion_rate_variance_percent, - DEFAULT_GCODE_BUFFER_SIZE, - py_progress_callback - ); + args.py_progress_callback = py_progress_callback; + args.log = p_py_logger; + py_arc_welder arc_welder_obj(args); arc_welder_results results = arc_welder_obj.process(); message = "py_gcode_arc_converter.ConvertFile - Arc Conversion Complete."; p_py_logger->log(GCODE_CONVERSION, INFO, message); @@ -239,237 +222,5 @@ extern "C" } } -static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** py_progress_callback) -{ - p_py_logger->log( - GCODE_CONVERSION, INFO, - "Parsing GCode Conversion Args." - ); - - // Extract the job guid - PyObject* py_guid = PyDict_GetItemString(py_args, "guid"); - if (py_guid == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the guid parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.guid = gcode_arc_converter::PyUnicode_SafeAsString(py_guid); - - // Extract the source file path - PyObject* py_source_path = PyDict_GetItemString(py_args, "source_path"); - if (py_source_path == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the source_path parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.source_path = gcode_arc_converter::PyUnicode_SafeAsString(py_source_path); - - // Extract the target file path - PyObject* py_target_path = PyDict_GetItemString(py_args, "target_path"); - if (py_target_path == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the target_path parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.target_path = gcode_arc_converter::PyUnicode_SafeAsString(py_target_path); - - // Extract the resolution in millimeters - PyObject* py_resolution_mm = PyDict_GetItemString(py_args, "resolution_mm"); - if (py_resolution_mm == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the resolution_mm parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.resolution_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_resolution_mm); - if (args.resolution_mm <= 0) - { - args.resolution_mm = 0.05; // Set to the default if no resolution is provided, or if it is less than 0. - } - - // extract allow_dynamic_precision - PyObject* py_allow_dynamic_precision = PyDict_GetItemString(py_args, "allow_dynamic_precision"); - if (py_allow_dynamic_precision == NULL) - { - std::string message = "ParseArgs - Unable to retrieve allow_dynamic_precision from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.allow_dynamic_precision = PyLong_AsLong(py_allow_dynamic_precision) > 0; - - // extract default_xyz_precision - PyObject* py_default_xyz_precision = PyDict_GetItemString(py_args, "default_xyz_precision"); - if (py_default_xyz_precision == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the default_xyz_precision parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.default_xyz_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision); - if (args.default_xyz_precision < 3) - { - std::string message = "ParseArgs - The default XYZ precision received was less than 3, which could cause problems printing arcs. Setting to 3."; - p_py_logger->log(WARNING, GCODE_CONVERSION, message); - args.default_xyz_precision = 3; - } - else if (args.default_xyz_precision > 6) - { - std::string message = "ParseArgs - The default XYZ precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6."; - p_py_logger->log(WARNING, GCODE_CONVERSION, message); - args.default_xyz_precision = 6; - } - - // extract default_e_precision - PyObject* py_default_e_precision = PyDict_GetItemString(py_args, "default_e_precision"); - if (py_default_e_precision == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the default_e_precision parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.default_e_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision); - if (args.default_e_precision < 3) - { - std::string message = "ParseArgs - The default E precision received was less than 3, which could cause extrusion problems. Setting to 3."; - p_py_logger->log(WARNING, GCODE_CONVERSION, message); - args.default_e_precision = 3; - } - else if (args.default_e_precision > 6) - { - std::string message = "ParseArgs - The default E precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6."; - p_py_logger->log(WARNING, GCODE_CONVERSION, message); - args.default_e_precision = 6; - } - - // Extract the extrusion_rate_variance - PyObject* py_extrusion_rate_variance_percent = PyDict_GetItemString(py_args, "extrusion_rate_variance_percent"); - if (py_extrusion_rate_variance_percent == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the extrusion_rate_variance_percent parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.extrusion_rate_variance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_extrusion_rate_variance_percent); - if (args.extrusion_rate_variance_percent < 0) - { - args.extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; // Set to the default if no resolution is provided, or if it is less than 0. - } - - // Extract the path tolerance_percent - PyObject* py_path_tolerance_percent = PyDict_GetItemString(py_args, "path_tolerance_percent"); - if (py_path_tolerance_percent == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the path_tolerance_percent parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.path_tolerance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_path_tolerance_percent); - if (args.path_tolerance_percent < 0) - { - args.path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; // Set to the default if no resolution is provided, or if it is less than 0. - } - // Extract the max_radius in mm - PyObject* py_max_radius_mm = PyDict_GetItemString(py_args, "max_radius_mm"); - if (py_max_radius_mm == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the max_radius_mm parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.max_radius_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_max_radius_mm); - if (args.max_radius_mm > DEFAULT_MAX_RADIUS_MM) - { - args.max_radius_mm = DEFAULT_MAX_RADIUS_MM; // Set to the default if no resolution is provided, or if it is less than 0. - } - - // Extract the mm_per_arc_segment - PyObject* py_mm_per_arc_segment = PyDict_GetItemString(py_args, "mm_per_arc_segment"); - if (py_mm_per_arc_segment == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the mm_per_arc_segment parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.mm_per_arc_segment = gcode_arc_converter::PyFloatOrInt_AsDouble(py_mm_per_arc_segment); - if (args.mm_per_arc_segment < 0) - { - args.mm_per_arc_segment = 0; - } - - // Extract min_arc_segments - PyObject* py_min_arc_segments = PyDict_GetItemString(py_args, "min_arc_segments"); - if (py_min_arc_segments == NULL) - { - std::string message = "ParseArgs - Unable to retrieve the min_arc_segments parameter from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.min_arc_segments = (int) gcode_arc_converter::PyIntOrLong_AsLong(py_min_arc_segments); - if (args.min_arc_segments < 0) - { - args.min_arc_segments = 0; // Set to the default if no resolution is provided, or if it is less than 0. - } - - // extract allow_3d_arcs - PyObject* py_allow_3d_arcs = PyDict_GetItemString(py_args, "allow_3d_arcs"); - if (py_allow_3d_arcs == NULL) - { - std::string message = "ParseArgs - Unable to retrieve allow_3d_arcs from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.allow_3d_arcs = PyLong_AsLong(py_allow_3d_arcs) > 0; - - // extract allow_travel_arcs - PyObject* py_allow_travel_arcs = PyDict_GetItemString(py_args, "allow_travel_arcs"); - if (py_allow_travel_arcs == NULL) - { - std::string message = "ParseArgs - Unable to retrieve allow_travel_arcs from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.allow_travel_arcs = PyLong_AsLong(py_allow_travel_arcs) > 0; - - // Extract G90/G91 influences extruder - // g90_influences_extruder - PyObject* py_g90_g91_influences_extruder = PyDict_GetItemString(py_args, "g90_g91_influences_extruder"); - if (py_g90_g91_influences_extruder == NULL) - { - std::string message = "ParseArgs - Unable to retrieve g90_g91_influences_extruder from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - args.g90_g91_influences_extruder = PyLong_AsLong(py_g90_g91_influences_extruder) > 0; - - // on_progress_received - PyObject* py_on_progress_received = PyDict_GetItemString(py_args, "on_progress_received"); - if (py_on_progress_received == NULL) - { - std::string message = "ParseArgs - Unable to retrieve on_progress_received from the stabilization args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - // need to incref this so it doesn't vanish later (borrowed reference we are saving) - Py_XINCREF(py_on_progress_received); - *py_progress_callback = py_on_progress_received; - - // Extract log_level - PyObject* py_log_level = PyDict_GetItemString(py_args, "log_level"); - if (py_log_level == NULL) - { - std::string message = "ParseArgs - Unable to retrieve log_level from the args."; - p_py_logger->log_exception(GCODE_CONVERSION, message); - return false; - } - - int log_level_value = static_cast(PyLong_AsLong(py_log_level)); - // determine the log level as an index rather than as a value - args.log_level = p_py_logger->get_log_level_for_value(log_level_value); - - return true; -} diff --git a/PyArcWelder/py_arc_welder_extension.h b/PyArcWelder/py_arc_welder_extension.h index 2212748..b71f680 100644 --- a/PyArcWelder/py_arc_welder_extension.h +++ b/PyArcWelder/py_arc_welder_extension.h @@ -29,6 +29,7 @@ #include #endif #include +#include "py_arc_welder.h" #include "py_logger.h" #include "arc_welder.h" extern "C" @@ -41,76 +42,6 @@ extern "C" static PyObject* ConvertFile(PyObject* self, PyObject* args); } -struct py_gcode_arc_args { - py_gcode_arc_args() { - guid = ""; - source_path = ""; - target_path = ""; - resolution_mm = DEFAULT_RESOLUTION_MM; - path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; - max_radius_mm = DEFAULT_MAX_RADIUS_MM; - min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; - mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; - g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTREUDER; - allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS; - allow_travel_arcs = DEFAULT_ALLOW_TRAVEL_ARCS; - log_level = 0; - } - py_gcode_arc_args( - std::string guid_, - std::string source_path_, - std::string target_path_, - double resolution_mm_, - double path_tolerance_percent_, - double max_radius_mm_, - int min_arc_segments_, - double mm_per_arc_segment_, - bool g90_g91_influences_extruder_, - bool allow_3d_arcs_, - bool allow_travel_arcs_, - bool allow_dynamic_precision_, - unsigned char default_xyz_precision_, - unsigned char default_e_precision_, - double extrusion_rate_variance_percent_, - int log_level_ - ) { - guid = guid_; - source_path = source_path_; - target_path = target_path_; - resolution_mm = resolution_mm_; - path_tolerance_percent = path_tolerance_percent_; - max_radius_mm = max_radius_mm_; - min_arc_segments = min_arc_segments_; - mm_per_arc_segment = mm_per_arc_segment_; - allow_3d_arcs = allow_3d_arcs_; - allow_travel_arcs = allow_travel_arcs_; - allow_dynamic_precision = allow_dynamic_precision_; - default_xyz_precision = default_xyz_precision_; - default_e_precision = default_e_precision_; - extrusion_rate_variance_percent = extrusion_rate_variance_percent_; - g90_g91_influences_extruder = g90_g91_influences_extruder_; - log_level = log_level_; - } - std::string guid; - std::string source_path; - std::string target_path; - double resolution_mm; - double path_tolerance_percent; - bool allow_3d_arcs; - bool allow_travel_arcs; - bool allow_dynamic_precision; - unsigned char default_xyz_precision; - unsigned char default_e_precision; - double extrusion_rate_variance_percent; - bool g90_g91_influences_extruder; - double max_radius_mm; - int min_arc_segments; - double mm_per_arc_segment; - int log_level; -}; - -static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** p_py_progress_callback); - // global logger py_logger* p_py_logger = NULL; /* -- cgit v1.2.3 From 8f278fc65a922c6de47f62a95f17630bcec11071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gauthier=20Fleutot=20=C3=96stervall?= Date: Tue, 6 Jul 2021 01:59:13 +0200 Subject: Add build instructions (#68) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Build Instructions and remove old Makefile. Co-authored-by: Gauthier Östervall --- Makefile | 17 ----------------- readme.md | 13 +++++++++++++ 2 files changed, 13 insertions(+), 17 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 47ca31c..0000000 --- a/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -all: - $(MAKE) -C GcodeProcessorLib all - $(MAKE) -C ArcWelder all - $(MAKE) -C ArcWelderConsole all - $(MAKE) -C ArcWelderInverseProcessor all - -clean: - $(MAKE) -C GcodeProcessorLib clean - $(MAKE) -C ArcWelder clean - $(MAKE) -C ArcWelderConsole clean - $(MAKE) -C ArcWelderInverseProcessor clean - - -cleanall: clean - - -.PHONY: all clean diff --git a/readme.md b/readme.md index 9e5502e..b97e457 100644 --- a/readme.md +++ b/readme.md @@ -13,6 +13,19 @@ The archive will contain two folders: * **bin** - Here you will find the two console applications: ArcWelder and ArcStraightener. Just copy these to your local machine and you can run them from the command line, from a script, or directly within most slicers. See the sections below for instructions on how to use these applications. * **lib** - This folder contains pre-compiled ArcWelder libraries that can be integrated into applications. The GcodeProcessorLib library has functions for parsing gcode, tracking the printer's state and position, as well as several other goodies. The ArcWelder library (requires GcodeProcessorLib) contains the core welding algorithm. +# Building from source + +From the repository root, create a build directory, generate makefile, and build: + +``` +mkdir build +cd build +cmake .. +make +``` + +The resulting console application is located in `build/ArcWelderConsole/`. You might want to create the build directory out of the repository, or make `git` ignore it, to avoid a dirty tree. + # Arc Welder Console Application This is a multiplatform console application that can be used to run the Arc Welder algorithm from a command prompt. Binaries are available for Windows, Linux, Raspbian, and MacOs. See the [installation][#installation] section for information on how to download the console application. -- cgit v1.2.3 From 7e315a6ccf1d94802b396738bff015d98042bfa9 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Tue, 6 Jul 2021 09:23:24 -0500 Subject: Reduce the default max_radius_mm by 1mm to 9999.0 to reduce gcode length without any substantial impact on performance. --- ArcWelder/segmented_shape.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArcWelder/segmented_shape.h b/ArcWelder/segmented_shape.h index d6e7218..d5c88d5 100644 --- a/ArcWelder/segmented_shape.h +++ b/ArcWelder/segmented_shape.h @@ -104,7 +104,7 @@ struct vector : point }; -#define DEFAULT_MAX_RADIUS_MM 10000.0 // 10m +#define DEFAULT_MAX_RADIUS_MM 9999.0 // 9.999m struct circle { circle() { center.x = 0; -- cgit v1.2.3 From 56ea16df55e0e37581688effac94e78ffc10f941 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Fri, 16 Jul 2021 16:05:32 -0500 Subject: Fix xyz and e precision in console app. Fix unix->windows line endings resulting in lower compression. Fix g2/g3 length calculation within statistics. --- ArcWelder/arc_welder.cpp | 1676 ++++++++++++++-------------- ArcWelder/arc_welder.h | 4 +- ArcWelder/segmented_arc.cpp | 38 +- ArcWelder/segmented_shape.h | 1 - ArcWelder/unwritten_command.h | 4 +- ArcWelderConsole/ArcWelderConsole.cpp | 55 +- ArcWelderInverseProcessor/marlin_2_arc.cpp | 8 +- GcodeProcessorLib/array_list.h | 22 +- GcodeProcessorLib/utilities.cpp | 32 + GcodeProcessorLib/utilities.h | 3 + 10 files changed, 972 insertions(+), 871 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index 0531a67..1e601e5 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -37,903 +37,941 @@ #include arc_welder::arc_welder( - std::string source_path, - std::string target_path, - logger * log, - double resolution_mm, - double path_tolerance_percent, - double max_radius, - int min_arc_segments, - double mm_per_arc_segment, - bool g90_g91_influences_extruder, - bool allow_3d_arcs, - bool allow_travel_arcs, - bool allow_dynamic_precision, - unsigned char default_xyz_precision, - unsigned char default_e_precision, - double extrusion_rate_variance_percent, - int max_gcode_length, - int buffer_size, - progress_callback callback) : current_arc_( - DEFAULT_MIN_SEGMENTS, - buffer_size, - resolution_mm, - path_tolerance_percent, - max_radius, - min_arc_segments, - mm_per_arc_segment, - allow_3d_arcs, - default_xyz_precision, - default_e_precision, - max_gcode_length - ), - segment_statistics_( - segment_statistic_lengths, - segment_statistic_lengths_count, - log - ), - travel_statistics_( - segment_statistic_lengths, - segment_statistic_lengths_count, - log - ) + std::string source_path, + std::string target_path, + logger* log, + double resolution_mm, + double path_tolerance_percent, + double max_radius, + int min_arc_segments, + double mm_per_arc_segment, + bool g90_g91_influences_extruder, + bool allow_3d_arcs, + bool allow_travel_arcs, + bool allow_dynamic_precision, + unsigned char default_xyz_precision, + unsigned char default_e_precision, + double extrusion_rate_variance_percent, + int max_gcode_length, + int buffer_size, + progress_callback callback) : current_arc_( + DEFAULT_MIN_SEGMENTS, + buffer_size, + resolution_mm, + path_tolerance_percent, + max_radius, + min_arc_segments, + mm_per_arc_segment, + allow_3d_arcs, + default_xyz_precision, + default_e_precision, + max_gcode_length + ), + segment_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + log + ), + travel_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + log + ) { - p_logger_ = log; - debug_logging_enabled_ = false; - info_logging_enabled_ = false; - error_logging_enabled_ = false; - verbose_logging_enabled_ = false; - - logger_type_ = 0; - resolution_mm_ = resolution_mm; - progress_callback_ = callback; - verbose_output_ = false; - source_path_ = source_path; - target_path_ = target_path; - gcode_position_args_ = get_args_(g90_g91_influences_extruder, buffer_size); - allow_3d_arcs_ = allow_3d_arcs; - allow_travel_arcs_ = allow_travel_arcs; - allow_dynamic_precision_ = allow_dynamic_precision; - extrusion_rate_variance_percent_ = extrusion_rate_variance_percent; - notification_period_seconds = 1; - lines_processed_ = 0; - gcodes_processed_ = 0; - file_size_ = 0; - last_gcode_line_written_ = 0; - points_compressed_ = 0; - arcs_created_ = 0; - arcs_aborted_by_flow_rate_ = 0; - waiting_for_arc_ = false; - previous_feedrate_ = -1; - gcode_position_args_.set_num_extruders(8); - previous_extrusion_rate_ = 0; - for (int index = 0; index < 8; index++) - { - gcode_position_args_.retraction_lengths[0] = .0001; - gcode_position_args_.z_lift_heights[0] = 0.001; - gcode_position_args_.x_firmware_offsets[0] = 0.0; - gcode_position_args_.y_firmware_offsets[0] = 0.0; - } - - // We don't care about the printer settings, except for g91 influences extruder. - - p_source_position_ = new gcode_position(gcode_position_args_); + p_logger_ = log; + debug_logging_enabled_ = false; + info_logging_enabled_ = false; + error_logging_enabled_ = false; + verbose_logging_enabled_ = false; + + logger_type_ = 0; + resolution_mm_ = resolution_mm; + progress_callback_ = callback; + verbose_output_ = false; + source_path_ = source_path; + target_path_ = target_path; + gcode_position_args_ = get_args_(g90_g91_influences_extruder, buffer_size); + allow_3d_arcs_ = allow_3d_arcs; + allow_travel_arcs_ = allow_travel_arcs; + allow_dynamic_precision_ = allow_dynamic_precision; + extrusion_rate_variance_percent_ = extrusion_rate_variance_percent; + notification_period_seconds = 1; + lines_processed_ = 0; + gcodes_processed_ = 0; + file_size_ = 0; + last_gcode_line_written_ = 0; + points_compressed_ = 0; + arcs_created_ = 0; + arcs_aborted_by_flow_rate_ = 0; + waiting_for_arc_ = false; + previous_feedrate_ = -1; + gcode_position_args_.set_num_extruders(8); + previous_extrusion_rate_ = 0; + for (int index = 0; index < 8; index++) + { + gcode_position_args_.retraction_lengths[0] = .0001; + gcode_position_args_.z_lift_heights[0] = 0.001; + gcode_position_args_.x_firmware_offsets[0] = 0.0; + gcode_position_args_.y_firmware_offsets[0] = 0.0; + } + + // We don't care about the printer settings, except for g91 influences extruder. + + p_source_position_ = new gcode_position(gcode_position_args_); } gcode_position_args arc_welder::get_args_(bool g90_g91_influences_extruder, int buffer_size) { - gcode_position_args args; - // Configure gcode_position_args - args.g90_influences_extruder = g90_g91_influences_extruder; - if (buffer_size < 2) - { - buffer_size = 2; - } - args.position_buffer_size = buffer_size; - args.autodetect_position = true; - args.home_x = 0; - args.home_x_none = true; - args.home_y = 0; - args.home_y_none = true; - args.home_z = 0; - args.home_z_none = true; - args.shared_extruder = true; - args.zero_based_extruder = true; - - - args.default_extruder = 0; - args.xyz_axis_default_mode = "absolute"; - args.e_axis_default_mode = "absolute"; - args.units_default = "millimeters"; - args.location_detection_commands = std::vector(); - args.is_bound_ = false; - args.is_circular_bed = false; - args.x_min = -9999; - args.x_max = 9999; - args.y_min = -9999; - args.y_max = 9999; - args.z_min = -9999; - args.z_max = 9999; - return args; + gcode_position_args args; + // Configure gcode_position_args + args.g90_influences_extruder = g90_g91_influences_extruder; + if (buffer_size < 2) + { + buffer_size = 2; + } + args.position_buffer_size = buffer_size; + args.autodetect_position = true; + args.home_x = 0; + args.home_x_none = true; + args.home_y = 0; + args.home_y_none = true; + args.home_z = 0; + args.home_z_none = true; + args.shared_extruder = true; + args.zero_based_extruder = true; + + + args.default_extruder = 0; + args.xyz_axis_default_mode = "absolute"; + args.e_axis_default_mode = "absolute"; + args.units_default = "millimeters"; + args.location_detection_commands = std::vector(); + args.is_bound_ = false; + args.is_circular_bed = false; + args.x_min = -9999; + args.x_max = 9999; + args.y_min = -9999; + args.y_max = 9999; + args.z_min = -9999; + args.z_max = 9999; + return args; } arc_welder::~arc_welder() { - delete p_source_position_; + delete p_source_position_; } void arc_welder::set_logger_type(int logger_type) { - logger_type_ = logger_type; + logger_type_ = logger_type; } void arc_welder::reset() { - p_logger_->log(logger_type_, DEBUG, "Resetting all tracking variables."); - lines_processed_ = 0; - gcodes_processed_ = 0; - last_gcode_line_written_ = 0; - file_size_ = 0; - points_compressed_ = 0; - arcs_created_ = 0; - waiting_for_arc_ = false; + p_logger_->log(logger_type_, DEBUG, "Resetting all tracking variables."); + lines_processed_ = 0; + gcodes_processed_ = 0; + last_gcode_line_written_ = 0; + file_size_ = 0; + points_compressed_ = 0; + arcs_created_ = 0; + waiting_for_arc_ = false; } long arc_welder::get_file_size(const std::string& file_path) { - // Todo: Fix this function. This is a pretty weak implementation :( - std::ifstream file(file_path.c_str(), std::ios::in | std::ios::binary); - const long l = (long)file.tellg(); - file.seekg(0, std::ios::end); - const long m = (long)file.tellg(); - file.close(); - return (m - l); + // Todo: Fix this function. This is a pretty weak implementation :( + std::ifstream file(file_path.c_str(), std::ios::in | std::ios::binary); + const long l = (long)file.tellg(); + file.seekg(0, std::ios::end); + const long m = (long)file.tellg(); + file.close(); + return (m - l); } double arc_welder::get_next_update_time() const { - return clock() + (notification_period_seconds * CLOCKS_PER_SEC); + return clock() + (notification_period_seconds * CLOCKS_PER_SEC); } double arc_welder::get_time_elapsed(double start_clock, double end_clock) { - return static_cast(end_clock - start_clock) / CLOCKS_PER_SEC; + return static_cast(end_clock - start_clock) / CLOCKS_PER_SEC; } arc_welder_results arc_welder::process() { -arc_welder_results results; - p_logger_->log(logger_type_, DEBUG, "Configuring logging settings."); - verbose_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, VERBOSE); - debug_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, DEBUG); - info_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, INFO); - error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, ERROR); - - std::stringstream stream; - // reset tracking variables - reset(); - // local variable to hold the progress update return. If it's false, we will exit. - bool continue_processing = true; - - p_logger_->log(logger_type_, DEBUG, "Configuring progress updates."); - int read_lines_before_clock_check = 1000; - double next_update_time = get_next_update_time(); - const clock_t start_clock = clock(); - p_logger_->log(logger_type_, DEBUG, "Getting source file size."); - file_size_ = get_file_size(source_path_); - stream.clear(); - stream.str(""); - stream << "Source file size: " << file_size_; - p_logger_->log(logger_type_, DEBUG, stream.str()); - - // Determine if we need to overwrite the source file - bool overwrite_source_file = false; - std::string temp_file_path; - if (source_path_ == target_path_) - { - overwrite_source_file = true; - if (!utilities::get_temp_file_path_for_file(source_path_, temp_file_path)) - { - results.success = false; - results.message = "The source and target path are the same, but a temporary file path could not be created. Are the paths empty?"; - p_logger_->log_exception(logger_type_, results.message); - return results; - } - - stream.clear(); - stream.str(""); - stream << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; - p_logger_->log(logger_type_, DEBUG, stream.str()); - target_path_ = temp_file_path; - } - - // Create the source file read stream and target write stream - std::ifstream gcodeFile; - p_logger_->log(logger_type_, DEBUG, "Opening the source file for reading."); - gcodeFile.open(source_path_.c_str(), std::ifstream::in); - if (!gcodeFile.is_open()) - { - results.success = false; - results.message = "Unable to open the source file."; - p_logger_->log_exception(logger_type_, results.message); - return results; - } - p_logger_->log(logger_type_, DEBUG, "Source file opened successfully."); - - p_logger_->log(logger_type_, DEBUG, "Opening the target file for writing."); - - output_file_.open(target_path_.c_str(), std::ifstream::out); - if (!output_file_.is_open()) - { - results.success = false; - results.message = "Unable to open the target file."; - p_logger_->log_exception(logger_type_, results.message); - gcodeFile.close(); - return results; - } - - p_logger_->log(logger_type_, DEBUG, "Target file opened successfully."); - std::string line; - int lines_with_no_commands = 0; - parsed_command cmd; - // Communicate every second - p_logger_->log(logger_type_, DEBUG, "Sending initial progress update."); - continue_processing = on_progress_(get_progress_(static_cast(gcodeFile.tellg()), static_cast(start_clock))); - p_logger_->log(logger_type_, DEBUG, "Processing source file."); - - bool arc_Welder_comment_added = false; - while (std::getline(gcodeFile, line) && continue_processing) - { - lines_processed_++; - // Check the first line of gcode and see if it = ;FLAVOR:UltiGCode + arc_welder_results results; + p_logger_->log(logger_type_, DEBUG, "Configuring logging settings."); + verbose_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, VERBOSE); + debug_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, DEBUG); + info_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, INFO); + error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, ERROR); + + std::stringstream stream; + // reset tracking variables + reset(); + // local variable to hold the progress update return. If it's false, we will exit. + bool continue_processing = true; + + p_logger_->log(logger_type_, DEBUG, "Configuring progress updates."); + int read_lines_before_clock_check = 1000; + double next_update_time = get_next_update_time(); + const clock_t start_clock = clock(); + p_logger_->log(logger_type_, DEBUG, "Getting source file size."); + file_size_ = get_file_size(source_path_); + stream.clear(); + stream.str(""); + stream << "Source file size: " << file_size_; + p_logger_->log(logger_type_, DEBUG, stream.str()); + + // Determine if we need to overwrite the source file + bool overwrite_source_file = false; + std::string temp_file_path; + if (source_path_ == target_path_) + { + overwrite_source_file = true; + if (!utilities::get_temp_file_path_for_file(source_path_, temp_file_path)) + { + results.success = false; + results.message = "The source and target path are the same, but a temporary file path could not be created. Are the paths empty?"; + p_logger_->log_exception(logger_type_, results.message); + return results; + } + + stream.clear(); + stream.str(""); + stream << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; + p_logger_->log(logger_type_, DEBUG, stream.str()); + target_path_ = temp_file_path; + } + + // Create the source file read stream and target write stream + std::ifstream gcodeFile; + p_logger_->log(logger_type_, DEBUG, "Opening the source file for reading."); + gcodeFile.open(source_path_.c_str(), std::ifstream::in); + if (!gcodeFile.is_open()) + { + results.success = false; + results.message = "Unable to open the source file."; + p_logger_->log_exception(logger_type_, results.message); + return results; + } + p_logger_->log(logger_type_, DEBUG, "Source file opened successfully."); + + p_logger_->log(logger_type_, DEBUG, "Opening the target file for writing."); + + output_file_.open(target_path_.c_str(), std::ios_base::binary | std::ios_base::out); + if (!output_file_.is_open()) + { + results.success = false; + results.message = "Unable to open the target file."; + p_logger_->log_exception(logger_type_, results.message); + gcodeFile.close(); + return results; + } + + p_logger_->log(logger_type_, DEBUG, "Target file opened successfully."); + std::string line; + int lines_with_no_commands = 0; + parsed_command cmd; + // Communicate every second + p_logger_->log(logger_type_, DEBUG, "Sending initial progress update."); + continue_processing = on_progress_(get_progress_(static_cast(gcodeFile.tellg()), static_cast(start_clock))); + p_logger_->log(logger_type_, DEBUG, "Processing source file."); + + bool arc_Welder_comment_added = false; + while (std::getline(gcodeFile, line) && continue_processing) + { + lines_processed_++; + // Check the first line of gcode and see if it = ;FLAVOR:UltiGCode // This comment MUST be preserved as the first line for ultimakers, else things won't work - if (lines_processed_ == 1) - { - bool isUltiGCode = line == ";FLAVOR:UltiGCode"; - bool isPrusaSlicer = line.rfind("; generated by PrusaSlicer", 0) == 0; - if (isUltiGCode || isPrusaSlicer) - { - write_gcode_to_file(line); - } - add_arcwelder_comment_to_target(); - if (isUltiGCode || isPrusaSlicer) - { - lines_with_no_commands++; - continue; - } - } - - - cmd.clear(); - if (verbose_logging_enabled_) - { - stream.clear(); - stream.str(""); - stream << "Parsing: " << line; - p_logger_->log(logger_type_, VERBOSE, stream.str()); - } - parser_.try_parse_gcode(line.c_str(), cmd, true); - bool has_gcode = false; - if (cmd.gcode.length() > 0) - { - has_gcode = true; - gcodes_processed_++; - } - else - { - lines_with_no_commands++; - } - - // Always process the command through the printer, even if no command is found - // This is important so that comments can be analyzed - //std::cout << "stabilization::process_file - updating position..."; - process_gcode(cmd, false, false); - - // Only continue to process if we've found a command and either a progress_callback_ is supplied, or debug loggin is enabled. - if (has_gcode) - { - if ((lines_processed_ % read_lines_before_clock_check) == 0 && next_update_time < clock()) - { - if (verbose_logging_enabled_) - { - p_logger_->log(logger_type_, VERBOSE, "Sending progress update."); - } - continue_processing = on_progress_(get_progress_(static_cast(gcodeFile.tellg()), static_cast(start_clock))); - next_update_time = get_next_update_time(); - } - } - } - - if (current_arc_.is_shape() && waiting_for_arc_) - { - p_logger_->log(logger_type_, DEBUG, "Processing the final line."); - process_gcode(cmd, true, false); - } - p_logger_->log(logger_type_, DEBUG, "Writing all unwritten gcodes to the target file."); - write_unwritten_gcodes_to_file(); - - p_logger_->log(logger_type_, DEBUG, "Fetching the final progress struct."); - - arc_welder_progress final_progress = get_progress_(static_cast(file_size_), static_cast(start_clock)); - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Sending final progress update message."); - } - on_progress_(final_progress); - - p_logger_->log(logger_type_, DEBUG, "Closing source and target files."); - output_file_.close(); - gcodeFile.close(); - - if (overwrite_source_file) - { - stream.clear(); - stream.str(""); - stream << "Deleting the original source file at '" << source_path_ << "'."; - p_logger_->log(logger_type_, DEBUG, stream.str()); - stream.clear(); - stream.str(""); - std::remove(source_path_.c_str()); - stream << "Renaming temporary file at '" << target_path_ << "' to '" << source_path_ << "'."; - p_logger_->log(0, DEBUG, stream.str()); - std::rename(target_path_.c_str(), source_path_.c_str()); - } - - results.success = continue_processing; - results.cancelled = !continue_processing; - results.progress = final_progress; - p_logger_->log(logger_type_, DEBUG, "Returning processing results."); - - return results; + if (lines_processed_ == 1) + { + bool isUltiGCode = line == ";FLAVOR:UltiGCode"; + bool isPrusaSlicer = line.rfind("; generated by PrusaSlicer", 0) == 0; + if (isUltiGCode || isPrusaSlicer) + { + write_gcode_to_file(line); + } + add_arcwelder_comment_to_target(); + if (isUltiGCode || isPrusaSlicer) + { + lines_with_no_commands++; + continue; + } + } + + + cmd.clear(); + if (verbose_logging_enabled_) + { + stream.clear(); + stream.str(""); + stream << "Parsing: " << line; + p_logger_->log(logger_type_, VERBOSE, stream.str()); + } + parser_.try_parse_gcode(line.c_str(), cmd, true); + bool has_gcode = false; + if (cmd.gcode.length() > 0) + { + has_gcode = true; + gcodes_processed_++; + } + else + { + lines_with_no_commands++; + } + + // Always process the command through the printer, even if no command is found + // This is important so that comments can be analyzed + //std::cout << "stabilization::process_file - updating position..."; + process_gcode(cmd, false, false); + + // Only continue to process if we've found a command and either a progress_callback_ is supplied, or debug loggin is enabled. + if (has_gcode) + { + if ((lines_processed_ % read_lines_before_clock_check) == 0 && next_update_time < clock()) + { + if (verbose_logging_enabled_) + { + p_logger_->log(logger_type_, VERBOSE, "Sending progress update."); + } + continue_processing = on_progress_(get_progress_(static_cast(gcodeFile.tellg()), static_cast(start_clock))); + next_update_time = get_next_update_time(); + } + } + } + + if (current_arc_.is_shape() && waiting_for_arc_) + { + p_logger_->log(logger_type_, DEBUG, "Processing the final line."); + process_gcode(cmd, true, false); + } + p_logger_->log(logger_type_, DEBUG, "Writing all unwritten gcodes to the target file."); + write_unwritten_gcodes_to_file(); + + p_logger_->log(logger_type_, DEBUG, "Fetching the final progress struct."); + + arc_welder_progress final_progress = get_progress_(static_cast(file_size_), static_cast(start_clock)); + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, DEBUG, "Sending final progress update message."); + } + on_progress_(final_progress); + + p_logger_->log(logger_type_, DEBUG, "Closing source and target files."); + output_file_.close(); + gcodeFile.close(); + + if (overwrite_source_file) + { + stream.clear(); + stream.str(""); + stream << "Deleting the original source file at '" << source_path_ << "'."; + p_logger_->log(logger_type_, DEBUG, stream.str()); + stream.clear(); + stream.str(""); + std::remove(source_path_.c_str()); + stream << "Renaming temporary file at '" << target_path_ << "' to '" << source_path_ << "'."; + p_logger_->log(0, DEBUG, stream.str()); + std::rename(target_path_.c_str(), source_path_.c_str()); + } + + results.success = continue_processing; + results.cancelled = !continue_processing; + results.progress = final_progress; + p_logger_->log(logger_type_, DEBUG, "Returning processing results."); + + return results; } bool arc_welder::on_progress_(const arc_welder_progress& progress) { - if (progress_callback_ != NULL) - { - return progress_callback_(progress, p_logger_, logger_type_); - } - else if (info_logging_enabled_) - { - p_logger_->log(logger_type_, INFO, progress.str()); - } - - return true; + if (progress_callback_ != NULL) + { + return progress_callback_(progress, p_logger_, logger_type_); + } + else if (info_logging_enabled_) + { + p_logger_->log(logger_type_, INFO, progress.str()); + } + + return true; } arc_welder_progress arc_welder::get_progress_(long source_file_position, double start_clock) { - arc_welder_progress progress; - progress.gcodes_processed = gcodes_processed_; - progress.lines_processed = lines_processed_; - progress.points_compressed = points_compressed_; - progress.arcs_created = arcs_created_; - progress.arcs_aborted_by_flow_rate = arcs_aborted_by_flow_rate_; - progress.source_file_position = source_file_position; - progress.target_file_size = static_cast(output_file_.tellp()); - progress.source_file_size = file_size_; - long bytesRemaining = file_size_ - static_cast(source_file_position); - progress.percent_complete = static_cast(source_file_position) / static_cast(file_size_) * 100.0; - progress.seconds_elapsed = get_time_elapsed(start_clock, clock()); - double bytesPerSecond = static_cast(source_file_position) / progress.seconds_elapsed; - progress.seconds_remaining = bytesRemaining / bytesPerSecond; - - if (source_file_position > 0) { - progress.compression_ratio = (static_cast(source_file_position) / static_cast(progress.target_file_size)); - progress.compression_percent = (1.0 - (static_cast(progress.target_file_size) / static_cast(source_file_position))) * 100.0f; - } - else { - progress.compression_ratio = 0; - progress.compression_percent = 0; - } - progress.num_firmware_compensations = current_arc_.get_num_firmware_compensations(); - progress.num_gcode_length_exceptions = current_arc_.get_num_gcode_length_exceptions(); - progress.segment_statistics = segment_statistics_; - progress.travel_statistics = travel_statistics_; - return progress; - + arc_welder_progress progress; + progress.gcodes_processed = gcodes_processed_; + progress.lines_processed = lines_processed_; + progress.points_compressed = points_compressed_; + progress.arcs_created = arcs_created_; + progress.arcs_aborted_by_flow_rate = arcs_aborted_by_flow_rate_; + progress.source_file_position = source_file_position; + progress.target_file_size = static_cast(output_file_.tellp()); + progress.source_file_size = file_size_; + long bytesRemaining = file_size_ - static_cast(source_file_position); + progress.percent_complete = static_cast(source_file_position) / static_cast(file_size_) * 100.0; + progress.seconds_elapsed = get_time_elapsed(start_clock, clock()); + double bytesPerSecond = static_cast(source_file_position) / progress.seconds_elapsed; + progress.seconds_remaining = bytesRemaining / bytesPerSecond; + + if (source_file_position > 0) { + progress.compression_ratio = (static_cast(source_file_position) / static_cast(progress.target_file_size)); + progress.compression_percent = (1.0 - (static_cast(progress.target_file_size) / static_cast(source_file_position))) * 100.0f; + } + else { + progress.compression_ratio = 0; + progress.compression_percent = 0; + } + progress.num_firmware_compensations = current_arc_.get_num_firmware_compensations(); + progress.num_gcode_length_exceptions = current_arc_.get_num_gcode_length_exceptions(); + progress.segment_statistics = segment_statistics_; + progress.travel_statistics = travel_statistics_; + return progress; + } int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess) { - /* use to catch gcode for debugging since I can't set conditions equal to strings - if (cmd.gcode == "G1 X118.762 Y104.054 E0.0163") - { - std::cout << "Found it!"; - } - */ - // Update the position for the source gcode file - p_source_position_->update(cmd, lines_processed_, gcodes_processed_, -1); - position* p_cur_pos = p_source_position_->get_current_position_ptr(); - position* p_pre_pos = p_source_position_->get_previous_position_ptr(); - bool is_previous_extruder_relative = p_pre_pos->is_extruder_relative; - extruder extruder_current = p_cur_pos->get_current_extruder(); - extruder previous_extruder = p_pre_pos->get_current_extruder(); - - //std::cout << lines_processed_ << " - " << cmd.gcode << ", CurrentEAbsolute: " << cur_extruder.e <<", ExtrusionLength: " << cur_extruder.extrusion_length << ", Retraction Length: " << cur_extruder.retraction_length << ", IsExtruding: " << cur_extruder.is_extruding << ", IsRetracting: " << cur_extruder.is_retracting << ".\n"; - - int lines_written = 0; - // see if this point is an extrusion - - bool arc_added = false; - bool clear_shapes = false; - double movement_length_mm = 0; - bool has_e_changed = extruder_current.e_relative != 0; - // Update the source file statistics - if (p_cur_pos->has_xy_position_changed) - { - if (allow_3d_arcs_) { - movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z); - } - else { - movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_cur_pos->x, p_cur_pos->y); - } - - if (movement_length_mm > 0) - { - if (!is_reprocess) - { - if (has_e_changed) - { - segment_statistics_.update(movement_length_mm, true); - } - else if (allow_3d_arcs_) - { - travel_statistics_.update(movement_length_mm, true); - } - - } - } - } - - // calculate the extrusion rate (mm/mm) and see how much it changes - double mm_extruded_per_mm_travel = 0; - double extrusion_rate_change_percent = 0; - bool aborted_by_flow_rate = false; - // TODO: MAKE SURE THIS WORKS FOR TRANSITIONS FROM TRAVEL TO NON TRAVEL MOVES - if (movement_length_mm > 0 && has_e_changed) - { - mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; - if (previous_extrusion_rate_ > 0) - { - extrusion_rate_change_percent = std::fabs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); - } - } - if (previous_extrusion_rate_ != 0 && utilities::greater_than(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) - { - arcs_aborted_by_flow_rate_++; - aborted_by_flow_rate = true; - } - - // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position - // TODO: Handle relative XYZ axis. This is possible, but maybe not so important. - bool is_g0_g1 = cmd.command == "G0" || cmd.command == "G1"; - if (allow_dynamic_precision_ && is_g0_g1) - { - for (std::vector::iterator it = cmd.parameters.begin(); it != cmd.parameters.end(); ++it) - { - switch ((*it).name[0]) - { - case 'X': - case 'Y': - case 'Z': - current_arc_.update_xyz_precision((*it).double_precision); - break; - case 'E': - current_arc_.update_e_precision((*it).double_precision); - break; - } - } - } - - bool z_axis_ok = allow_3d_arcs_ || - utilities::is_equal(p_cur_pos->z, p_pre_pos->z); - - if ( - !is_end && cmd.is_known_command && !cmd.is_empty && ( - is_g0_g1 && z_axis_ok && - utilities::is_equal(p_cur_pos->x_offset, p_pre_pos->x_offset) && - utilities::is_equal(p_cur_pos->y_offset, p_pre_pos->y_offset) && - utilities::is_equal(p_cur_pos->z_offset, p_pre_pos->z_offset) && - utilities::is_equal(p_cur_pos->x_firmware_offset, p_pre_pos->x_firmware_offset) && - utilities::is_equal(p_cur_pos->y_firmware_offset, p_pre_pos->y_firmware_offset) && - utilities::is_equal(p_cur_pos->z_firmware_offset, p_pre_pos->z_firmware_offset) && - (previous_extrusion_rate_ == 0 || utilities::less_than_or_equal(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) && - !p_cur_pos->is_relative && - ( - !waiting_for_arc_ || - extruder_current.is_extruding || - // Test for travel conversion - (allow_travel_arcs_ && p_cur_pos->is_travel()) || - //(previous_extruder.is_extruding && extruder_current.is_extruding) || // Test to see if - // we can get more arcs. - (previous_extruder.is_retracting && extruder_current.is_retracting) - ) && - p_cur_pos->is_extruder_relative == is_previous_extruder_relative && - (!waiting_for_arc_ || p_pre_pos->f == p_cur_pos->f) && // might need to skip the waiting for arc check... - (!waiting_for_arc_ || p_pre_pos->feature_type_tag == p_cur_pos->feature_type_tag) - ) - ) { - - // Record the extrusion rate - previous_extrusion_rate_ = mm_extruded_per_mm_travel; - printer_point p(p_cur_pos->get_gcode_x(), p_cur_pos->get_gcode_y(), p_cur_pos->get_gcode_z(), extruder_current.get_offset_e() ,extruder_current.e_relative, p_cur_pos->f, movement_length_mm, p_pre_pos->is_extruder_relative); - if (!waiting_for_arc_) - { - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Starting new arc from Gcode:" + cmd.gcode); - } - write_unwritten_gcodes_to_file(); - // add the previous point as the starting point for the current arc - printer_point previous_p(p_pre_pos->get_gcode_x(), p_pre_pos->get_gcode_y(), p_pre_pos->get_gcode_z(), previous_extruder.get_offset_e(),previous_extruder.e_relative, p_pre_pos->f, 0, p_pre_pos->is_extruder_relative); - // Don't add any extrusion, or you will over extrude! - //std::cout << "Trying to add first point (" << p.x << "," << p.y << "," << p.z << ")..."; - - current_arc_.try_add_point(previous_p); - } - - double e_relative = extruder_current.e_relative; - int num_points = current_arc_.get_num_segments(); - arc_added = current_arc_.try_add_point(p); - if (arc_added) - { - // Make sure our position list is large enough to handle all the segments - if (current_arc_.get_num_segments()+2 > p_source_position_->get_max_positions()) - { - p_source_position_->grow_max_positions(p_source_position_->get_max_positions()*2); - } - if (!waiting_for_arc_) - { - waiting_for_arc_ = true; - previous_feedrate_ = p_pre_pos->f; - } - else - { - if (debug_logging_enabled_) - { - if (num_points+1 == current_arc_.get_num_segments()) - { - p_logger_->log(logger_type_, DEBUG, "Adding point to arc from Gcode:" + cmd.gcode); - } - - } - } - } - } - else { - - if (debug_logging_enabled_) { - if (is_end) - { - p_logger_->log(logger_type_, DEBUG, "Procesing final shape, if one exists."); - } - else if (!cmd.is_empty) - { - if (!cmd.is_known_command) - { - p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); - } - else if (cmd.command != "G0" && cmd.command != "G1") - { - p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); - } - else if (!allow_3d_arcs_ && !utilities::is_equal(p_cur_pos->z, p_pre_pos->z)) - { - p_logger_->log(logger_type_, DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); - } - else if (p_cur_pos->is_relative) - { - p_logger_->log(logger_type_, DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); - } - else if ( - waiting_for_arc_ && !( - (previous_extruder.is_extruding && extruder_current.is_extruding) || - (previous_extruder.is_retracting && extruder_current.is_retracting) - ) - ) - { - std::string message = "Extruding or retracting state changed, cannot add point to current arc: " + cmd.gcode; - if (verbose_logging_enabled_) - { - - message.append( - " - Verbose Info\n\tCurrent Position Info - Absolute E:" + utilities::to_string(extruder_current.e) + - ", Offset E:" + utilities::to_string(extruder_current.get_offset_e()) + - ", Mode:" + (p_cur_pos->is_extruder_relative_null ? "NULL" : p_cur_pos->is_extruder_relative ? "relative" : "absolute") + - ", Retraction: " + utilities::to_string(extruder_current.retraction_length) + - ", Extrusion: " + utilities::to_string(extruder_current.extrusion_length) + - ", Retracting: " + (extruder_current.is_retracting ? "True" : "False") + - ", Extruding: " + (extruder_current.is_extruding ? "True" : "False") - ); - message.append( - "\n\tPrevious Position Info - Absolute E:" + utilities::to_string(previous_extruder.e) + - ", Offset E:" + utilities::to_string(previous_extruder.get_offset_e()) + - ", Mode:" + (p_pre_pos->is_extruder_relative_null ? "NULL" : p_pre_pos->is_extruder_relative ? "relative" : "absolute") + - ", Retraction: " + utilities::to_string(previous_extruder.retraction_length) + - ", Extrusion: " + utilities::to_string(previous_extruder.extrusion_length) + - ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") + - ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False") - ); - p_logger_->log(logger_type_, VERBOSE, message); - } - else - { - p_logger_->log(logger_type_, DEBUG, message); - } - - } - else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative) - { - p_logger_->log(logger_type_, DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); - } - else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f) - { - p_logger_->log(logger_type_, DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); - } - else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag) - { - p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); - } - else if (aborted_by_flow_rate) - { - std::stringstream stream; - stream << std::fixed << std::setprecision(5); - stream << "Arc Canceled - The extrusion rate variance of " << extrusion_rate_variance_percent_ << "% exceeded by " << extrusion_rate_change_percent - extrusion_rate_variance_percent_ <<"% on line " << lines_processed_ << ". Extruded " << extruder_current.e_relative << "mm over " << movement_length_mm << "mm of travel (" << mm_extruded_per_mm_travel << "mm/mm). Previous rate: " << previous_extrusion_rate_ << "mm/mm."; - p_logger_->log(logger_type_, DEBUG, stream.str()); - } - else - { - // Todo: Add all the relevant values - p_logger_->log(logger_type_, DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); - } - } - } - - // Reset the previous extrusion rate - previous_extrusion_rate_ = 0; - } - - if (!arc_added && !(cmd.is_empty && cmd.comment.length() == 0)) - { - if (current_arc_.get_num_segments() < current_arc_.get_min_segments()) { - if (debug_logging_enabled_ && !cmd.is_empty) - { - if (current_arc_.get_num_segments() != 0) - { - p_logger_->log(logger_type_, DEBUG, "Not enough segments, resetting. Gcode:" + cmd.gcode); - } - - } - waiting_for_arc_ = false; - current_arc_.clear(); - } - else if (waiting_for_arc_) - { - - if (current_arc_.is_shape()) - { - // update our statistics - points_compressed_ += current_arc_.get_num_segments()-1; - arcs_created_++; // increment the number of generated arcs - write_arc_gcodes(p_pre_pos->f); - // Now clear the arc and flag the processor as not waiting for an arc - waiting_for_arc_ = false; - current_arc_.clear(); - p_cur_pos = NULL; - p_pre_pos = NULL; - - // Reprocess this line - if (!is_end) - { - return process_gcode(cmd, false, true); - } - else - { - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Final arc created, exiting."); - } - return 0; - } - - } - else - { - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "The current arc is not a valid arc, resetting."); - } - current_arc_.clear(); - waiting_for_arc_ = false; - } - } - else if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Could not add point to arc from gcode:" + cmd.gcode); - } - - } - - if (waiting_for_arc_ || !arc_added) - { - // This might not work.... - //position* cur_pos = p_source_position_->get_current_position_ptr(); - - unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, !has_e_changed && is_g0_g1, movement_length_mm)); - - } - else if (!waiting_for_arc_) - { - write_unwritten_gcodes_to_file(); - current_arc_.clear(); - } - return lines_written; + /* use to catch gcode for debugging since I can't set conditions equal to strings + if (cmd.gcode == "G1 X118.762 Y104.054 E0.0163") + { + std::cout << "Found it!"; + } + */ + // Update the position for the source gcode file + p_source_position_->update(cmd, lines_processed_, gcodes_processed_, -1); + position* p_cur_pos = p_source_position_->get_current_position_ptr(); + position* p_pre_pos = p_source_position_->get_previous_position_ptr(); + bool is_previous_extruder_relative = p_pre_pos->is_extruder_relative; + extruder extruder_current = p_cur_pos->get_current_extruder(); + extruder previous_extruder = p_pre_pos->get_current_extruder(); + + // Determine if this is a G0, G1, G2 or G3 + bool is_g0_g1 = cmd.command == "G0" || cmd.command == "G1"; + bool is_g2_g3 = cmd.command == "G2" || cmd.command == "G3"; + //std::cout << lines_processed_ << " - " << cmd.gcode << ", CurrentEAbsolute: " << cur_extruder.e <<", ExtrusionLength: " << cur_extruder.extrusion_length << ", Retraction Length: " << cur_extruder.retraction_length << ", IsExtruding: " << cur_extruder.is_extruding << ", IsRetracting: " << cur_extruder.is_retracting << ".\n"; + + int lines_written = 0; + // see if this point is an extrusion + + bool arc_added = false; + bool clear_shapes = false; + double movement_length_mm = 0; + bool has_e_changed = extruder_current.e_relative != 0; + // Update the source file statistics + if (p_cur_pos->has_xy_position_changed) + { + // If this is a g2/g3 command, we need to do a bit more to get the length of the arc. + // The movement_length_mm variable will contain the chord length, which we will need + if (is_g2_g3) + { + // Determine the radius of the arc, which is necessary to calculate the arc length from the chord length. + double i = 0; + double j = 0; + double r = 0; + // Iterate through the parameters and fill in I, J and R; + for (std::vector::iterator it = cmd.parameters.begin(); it != cmd.parameters.end(); ++it) + { + switch ((*it).name[0]) + { + case 'I': + i = (*it).double_precision; + break; + case 'J': + j = (*it).double_precision; + break; + // Note that the R form isn't fully implemented! + case 'R': + r = (*it).double_precision; + break; + } + } + + // Calculate R + if (r == 0) + { + r = std::sqrt(i * i + j * j); + } + // Now we know the radius and the chord length; + movement_length_mm = utilities::get_arc_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z, i, j, r, p_cur_pos->command.command == "G2"); + + } + else if (allow_3d_arcs_) { + movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z); + } + else { + movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_cur_pos->x, p_cur_pos->y); + } + + if (movement_length_mm > 0) + { + if (!is_reprocess) + { + if (has_e_changed) + { + segment_statistics_.update(movement_length_mm, true); + } + else if (allow_travel_arcs_) + { + travel_statistics_.update(movement_length_mm, true); + } + + } + } + } + + // calculate the extrusion rate (mm/mm) and see how much it changes + double mm_extruded_per_mm_travel = 0; + double extrusion_rate_change_percent = 0; + bool aborted_by_flow_rate = false; + // TODO: MAKE SURE THIS WORKS FOR TRANSITIONS FROM TRAVEL TO NON TRAVEL MOVES + if (movement_length_mm > 0 && has_e_changed) + { + mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; + if (previous_extrusion_rate_ > 0) + { + extrusion_rate_change_percent = std::fabs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); + } + } + if (previous_extrusion_rate_ != 0 && utilities::greater_than(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) + { + arcs_aborted_by_flow_rate_++; + aborted_by_flow_rate = true; + } + + // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position + // TODO: Handle relative XYZ axis. This is possible, but maybe not so important. + + if (allow_dynamic_precision_ && is_g0_g1) + { + for (std::vector::iterator it = cmd.parameters.begin(); it != cmd.parameters.end(); ++it) + { + switch ((*it).name[0]) + { + case 'X': + case 'Y': + case 'Z': + current_arc_.update_xyz_precision((*it).double_precision); + break; + case 'E': + current_arc_.update_e_precision((*it).double_precision); + break; + } + } + } + + bool z_axis_ok = allow_3d_arcs_ || + utilities::is_equal(p_cur_pos->z, p_pre_pos->z); + + if ( + !is_end && cmd.is_known_command && !cmd.is_empty && ( + is_g0_g1 && z_axis_ok && + utilities::is_equal(p_cur_pos->x_offset, p_pre_pos->x_offset) && + utilities::is_equal(p_cur_pos->y_offset, p_pre_pos->y_offset) && + utilities::is_equal(p_cur_pos->z_offset, p_pre_pos->z_offset) && + utilities::is_equal(p_cur_pos->x_firmware_offset, p_pre_pos->x_firmware_offset) && + utilities::is_equal(p_cur_pos->y_firmware_offset, p_pre_pos->y_firmware_offset) && + utilities::is_equal(p_cur_pos->z_firmware_offset, p_pre_pos->z_firmware_offset) && + (previous_extrusion_rate_ == 0 || utilities::less_than_or_equal(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) && + !p_cur_pos->is_relative && + ( + !waiting_for_arc_ || + extruder_current.is_extruding || + // Test for travel conversion + (allow_travel_arcs_ && p_cur_pos->is_travel()) || + //(previous_extruder.is_extruding && extruder_current.is_extruding) || // Test to see if + // we can get more arcs. + (previous_extruder.is_retracting && extruder_current.is_retracting) + ) && + p_cur_pos->is_extruder_relative == is_previous_extruder_relative && + (!waiting_for_arc_ || p_pre_pos->f == p_cur_pos->f) && // might need to skip the waiting for arc check... + (!waiting_for_arc_ || p_pre_pos->feature_type_tag == p_cur_pos->feature_type_tag) + ) + ) { + + // Record the extrusion rate + previous_extrusion_rate_ = mm_extruded_per_mm_travel; + printer_point p(p_cur_pos->get_gcode_x(), p_cur_pos->get_gcode_y(), p_cur_pos->get_gcode_z(), extruder_current.get_offset_e(), extruder_current.e_relative, p_cur_pos->f, movement_length_mm, p_pre_pos->is_extruder_relative); + if (!waiting_for_arc_) + { + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, DEBUG, "Starting new arc from Gcode:" + cmd.gcode); + } + write_unwritten_gcodes_to_file(); + // add the previous point as the starting point for the current arc + printer_point previous_p(p_pre_pos->get_gcode_x(), p_pre_pos->get_gcode_y(), p_pre_pos->get_gcode_z(), previous_extruder.get_offset_e(), previous_extruder.e_relative, p_pre_pos->f, 0, p_pre_pos->is_extruder_relative); + // Don't add any extrusion, or you will over extrude! + //std::cout << "Trying to add first point (" << p.x << "," << p.y << "," << p.z << ")..."; + + current_arc_.try_add_point(previous_p); + } + + double e_relative = extruder_current.e_relative; + int num_points = current_arc_.get_num_segments(); + arc_added = current_arc_.try_add_point(p); + if (arc_added) + { + // Make sure our position list is large enough to handle all the segments + if (current_arc_.get_num_segments() + 2 > p_source_position_->get_max_positions()) + { + p_source_position_->grow_max_positions(p_source_position_->get_max_positions() * 2); + } + if (!waiting_for_arc_) + { + waiting_for_arc_ = true; + previous_feedrate_ = p_pre_pos->f; + } + else + { + if (debug_logging_enabled_) + { + if (num_points + 1 == current_arc_.get_num_segments()) + { + p_logger_->log(logger_type_, DEBUG, "Adding point to arc from Gcode:" + cmd.gcode); + } + + } + } + } + } + else { + + if (debug_logging_enabled_) { + if (is_end) + { + p_logger_->log(logger_type_, DEBUG, "Procesing final shape, if one exists."); + } + else if (!cmd.is_empty) + { + if (!cmd.is_known_command) + { + p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); + } + else if (cmd.command != "G0" && cmd.command != "G1") + { + p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); + } + else if (!allow_3d_arcs_ && !utilities::is_equal(p_cur_pos->z, p_pre_pos->z)) + { + p_logger_->log(logger_type_, DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); + } + else if (p_cur_pos->is_relative) + { + p_logger_->log(logger_type_, DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); + } + else if ( + waiting_for_arc_ && !( + (previous_extruder.is_extruding && extruder_current.is_extruding) || + (previous_extruder.is_retracting && extruder_current.is_retracting) + ) + ) + { + std::string message = "Extruding or retracting state changed, cannot add point to current arc: " + cmd.gcode; + if (verbose_logging_enabled_) + { + + message.append( + " - Verbose Info\n\tCurrent Position Info - Absolute E:" + utilities::to_string(extruder_current.e) + + ", Offset E:" + utilities::to_string(extruder_current.get_offset_e()) + + ", Mode:" + (p_cur_pos->is_extruder_relative_null ? "NULL" : p_cur_pos->is_extruder_relative ? "relative" : "absolute") + + ", Retraction: " + utilities::to_string(extruder_current.retraction_length) + + ", Extrusion: " + utilities::to_string(extruder_current.extrusion_length) + + ", Retracting: " + (extruder_current.is_retracting ? "True" : "False") + + ", Extruding: " + (extruder_current.is_extruding ? "True" : "False") + ); + message.append( + "\n\tPrevious Position Info - Absolute E:" + utilities::to_string(previous_extruder.e) + + ", Offset E:" + utilities::to_string(previous_extruder.get_offset_e()) + + ", Mode:" + (p_pre_pos->is_extruder_relative_null ? "NULL" : p_pre_pos->is_extruder_relative ? "relative" : "absolute") + + ", Retraction: " + utilities::to_string(previous_extruder.retraction_length) + + ", Extrusion: " + utilities::to_string(previous_extruder.extrusion_length) + + ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") + + ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False") + ); + p_logger_->log(logger_type_, VERBOSE, message); + } + else + { + p_logger_->log(logger_type_, DEBUG, message); + } + + } + else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative) + { + p_logger_->log(logger_type_, DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); + } + else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f) + { + p_logger_->log(logger_type_, DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); + } + else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag) + { + p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); + } + else if (aborted_by_flow_rate) + { + std::stringstream stream; + stream << std::fixed << std::setprecision(5); + stream << "Arc Canceled - The extrusion rate variance of " << extrusion_rate_variance_percent_ << "% exceeded by " << extrusion_rate_change_percent - extrusion_rate_variance_percent_ << "% on line " << lines_processed_ << ". Extruded " << extruder_current.e_relative << "mm over " << movement_length_mm << "mm of travel (" << mm_extruded_per_mm_travel << "mm/mm). Previous rate: " << previous_extrusion_rate_ << "mm/mm."; + p_logger_->log(logger_type_, DEBUG, stream.str()); + } + else + { + // Todo: Add all the relevant values + p_logger_->log(logger_type_, DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); + } + } + } + + // Reset the previous extrusion rate + previous_extrusion_rate_ = 0; + } + + if (!arc_added && !(cmd.is_empty && cmd.comment.length() == 0)) + { + if (current_arc_.get_num_segments() < current_arc_.get_min_segments()) { + if (debug_logging_enabled_ && !cmd.is_empty) + { + if (current_arc_.get_num_segments() != 0) + { + p_logger_->log(logger_type_, DEBUG, "Not enough segments, resetting. Gcode:" + cmd.gcode); + } + + } + waiting_for_arc_ = false; + current_arc_.clear(); + } + else if (waiting_for_arc_) + { + + if (current_arc_.is_shape()) + { + // update our statistics + points_compressed_ += current_arc_.get_num_segments() - 1; + arcs_created_++; // increment the number of generated arcs + write_arc_gcodes(p_pre_pos->f); + // Now clear the arc and flag the processor as not waiting for an arc + waiting_for_arc_ = false; + current_arc_.clear(); + p_cur_pos = NULL; + p_pre_pos = NULL; + + // Reprocess this line + if (!is_end) + { + return process_gcode(cmd, false, true); + } + else + { + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, DEBUG, "Final arc created, exiting."); + } + return 0; + } + + } + else + { + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, DEBUG, "The current arc is not a valid arc, resetting."); + } + current_arc_.clear(); + waiting_for_arc_ = false; + } + } + else if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, DEBUG, "Could not add point to arc from gcode:" + cmd.gcode); + } + + } + + if (waiting_for_arc_ || !arc_added) + { + // This might not work.... + //position* cur_pos = p_source_position_->get_current_position_ptr(); + + unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, !has_e_changed && (is_g0_g1 || is_g2_g3), movement_length_mm)); + + } + else if (!waiting_for_arc_) + { + write_unwritten_gcodes_to_file(); + current_arc_.clear(); + } + return lines_written; } void arc_welder::write_arc_gcodes(double current_feedrate) { - std::string comment = get_comment_for_arc(); - // remove the same number of unwritten gcodes as there are arc segments, minus 1 for the start point - // Which isn't a movement - // note, skip the first point, it is the starting point - int num_segments = current_arc_.get_num_segments() - 1; - for (int index = 0; index < num_segments; index++) - { - while (!unwritten_commands_.pop_back().is_g0_g1); - } - - // Undo the current command, since it isn't included in the arc - p_source_position_->undo_update(); - - // Set the current feedrate if it is different, else set to 0 to indicate that no feedrate should be included - if (previous_feedrate_ > 0 && previous_feedrate_ == current_feedrate) { - current_feedrate = 0; - } - - // Craete the arc gcode - std::string gcode = get_arc_gcode(comment); - - if (debug_logging_enabled_) - { - char buffer[20]; - std::string message = "Arc created with "; - sprintf(buffer, "%d", current_arc_.get_num_segments()); - message += buffer; - message += " segments: "; - message += gcode; - p_logger_->log(logger_type_, DEBUG, message); - } - - // Write everything that hasn't yet been written - write_unwritten_gcodes_to_file(); - - // Update the current extrusion statistics for the current arc gcode - if (current_arc_.get_shape_e_relative() != 0) - { - segment_statistics_.update(current_arc_.get_shape_length(), false); - - } - else if (allow_3d_arcs_){ - travel_statistics_.update(current_arc_.get_shape_length(), false); - } - // now write the current arc to the file - write_gcode_to_file(gcode); + std::string comment = get_comment_for_arc(); + // remove the same number of unwritten gcodes as there are arc segments, minus 1 for the start point + // Which isn't a movement + // note, skip the first point, it is the starting point + int num_segments = current_arc_.get_num_segments() - 1; + for (int index = 0; index < num_segments; index++) + { + while (!unwritten_commands_.pop_back().is_g0_g1); + } + + // Undo the current command, since it isn't included in the arc + p_source_position_->undo_update(); + + // Set the current feedrate if it is different, else set to 0 to indicate that no feedrate should be included + if (previous_feedrate_ > 0 && previous_feedrate_ == current_feedrate) { + current_feedrate = 0; + } + + // Craete the arc gcode + std::string gcode = get_arc_gcode(comment); + + if (debug_logging_enabled_) + { + char buffer[20]; + std::string message = "Arc created with "; + sprintf(buffer, "%d", current_arc_.get_num_segments()); + message += buffer; + message += " segments: "; + message += gcode; + p_logger_->log(logger_type_, DEBUG, message); + } + + // Write everything that hasn't yet been written + write_unwritten_gcodes_to_file(); + + // Update the current extrusion statistics for the current arc gcode + if (current_arc_.get_shape_e_relative() != 0) + { + segment_statistics_.update(current_arc_.get_shape_length(), false); + + } + else if (allow_travel_arcs_) { + travel_statistics_.update(current_arc_.get_shape_length(), false); + } + // now write the current arc to the file + write_gcode_to_file(gcode); } std::string arc_welder::get_comment_for_arc() { - // build a comment string from the commands making up the arc - // We need to start with the first command entered. - int comment_index = unwritten_commands_.count() - (current_arc_.get_num_segments() - 1); - std::string comment; - for (; comment_index < unwritten_commands_.count(); comment_index++) - { - std::string old_comment = unwritten_commands_[comment_index].comment; - if (old_comment != comment && old_comment.length() > 0) - { - if (comment.length() > 0) - { - comment += " - "; - } - comment += old_comment; - } - } - return comment; + // build a comment string from the commands making up the arc + // We need to start with the first command entered. + int comment_index = unwritten_commands_.count() - (current_arc_.get_num_segments() - 1); + std::string comment; + for (; comment_index < unwritten_commands_.count(); comment_index++) + { + std::string old_comment = unwritten_commands_[comment_index].comment; + if (old_comment != comment && old_comment.length() > 0) + { + if (comment.length() > 0) + { + comment += " - "; + } + comment += old_comment; + } + } + return comment; } std::string arc_welder::create_g92_e(double absolute_e) { - std::stringstream stream; - stream << std::fixed << std::setprecision(5); - stream << "G92 E" << absolute_e; - return stream.str(); + std::stringstream stream; + stream << std::fixed << std::setprecision(5); + stream << "G92 E" << absolute_e; + return stream.str(); } int arc_welder::write_gcode_to_file(std::string gcode) { - output_file_ << gcode << "\n"; - return 1; + output_file_ << gcode << "\n"; + return 1; } int arc_welder::write_unwritten_gcodes_to_file() { - int size = unwritten_commands_.count(); - std::string lines_to_write; - - for (int index = 0; index < size; index++) - { - // The the current unwritten position and remove it from the list - unwritten_command p = unwritten_commands_.pop_front(); - if(p.is_g0_g1 && p.length > 0) - { - if (!p.is_travel) - { - segment_statistics_.update(p.length, false); - } - else if (allow_3d_arcs_) { - travel_statistics_.update(p.length, false); - } - } - lines_to_write.append(p.to_string()).append("\n"); - } - - output_file_ << lines_to_write; - return size; + int size = unwritten_commands_.count(); + std::string lines_to_write; + + for (int index = 0; index < size; index++) + { + // The the current unwritten position and remove it from the list + unwritten_command p = unwritten_commands_.pop_front(); + if ((p.is_g0_g1 || p.is_g2_g3) && p.length > 0) + { + if (!p.is_travel) + { + segment_statistics_.update(p.length, false); + } + else if (allow_travel_arcs_) { + travel_statistics_.update(p.length, false); + } + } + lines_to_write.append(p.to_string()).append("\n"); + } + + output_file_ << lines_to_write; + return size; } std::string arc_welder::get_arc_gcode(const std::string comment) { - // Write gcode to file - std::string gcode; + // Write gcode to file + std::string gcode; - gcode = current_arc_.get_shape_gcode(); + gcode = current_arc_.get_shape_gcode(); - if (comment.length() > 0) - { - gcode += ";" + comment; - } - return gcode; + if (comment.length() > 0) + { + gcode += ";" + comment; + } + return gcode; } void arc_welder::add_arcwelder_comment_to_target() { - p_logger_->log(logger_type_, DEBUG, "Adding ArcWelder comment to the target file."); - std::stringstream stream; - stream << std::fixed; - stream << "; Postprocessed by [ArcWelder](https://github.com/FormerLurker/ArcWelderLib)\n"; - stream << "; Copyright(C) 2020 - Brad Hochgesang\n"; - stream << "; Version: " << GIT_TAGGED_VERSION << ", Branch: " << GIT_BRANCH << ", BuildDate: " << BUILD_DATE << "\n"; - stream << "; resolution=" << std::setprecision(2) << resolution_mm_ << "mm\n"; - stream << "; path_tolerance=" << std::setprecision(1) << (current_arc_.get_path_tolerance_percent() * 100.0) << "%\n"; - stream << "; max_radius=" << std::setprecision(2) << (current_arc_.get_max_radius()) << "mm\n"; - if (gcode_position_args_.g90_influences_extruder) - { - stream << "; g90_influences_extruder=True\n"; - } - if (current_arc_.get_mm_per_arc_segment() > 0 && current_arc_.get_min_arc_segments() > 0) - { - stream << "; firmware_compensation=True\n"; - stream << "; mm_per_arc_segment="<< std::setprecision(2) << current_arc_.get_mm_per_arc_segment() << "mm\n"; - stream << "; min_arc_segments=" << std::setprecision(0) << current_arc_.get_min_arc_segments() << "\n"; - } - if (allow_3d_arcs_) - { - stream << "; allow_3d_arcs=True\n"; - - } - if (allow_dynamic_precision_) - { - stream << "; allow_dynamic_precision=True\n"; - } - stream << "; default_xyz_precision=" << std::setprecision(0) << static_cast(current_arc_.get_xyz_precision()) << "\n"; - stream << "; default_e_precision=" << std::setprecision(0) << static_cast(current_arc_.get_e_precision()) << "\n"; - stream << "; extrusion_rate_variance_percent=" << std::setprecision(1) << (extrusion_rate_variance_percent_ * 100.0) << "%\n\n"; - - - output_file_ << stream.str(); + p_logger_->log(logger_type_, DEBUG, "Adding ArcWelder comment to the target file."); + std::stringstream stream; + stream << std::fixed; + stream << "; Postprocessed by [ArcWelder](https://github.com/FormerLurker/ArcWelderLib)\n"; + stream << "; Copyright(C) 2020 - Brad Hochgesang\n"; + stream << "; Version: " << GIT_TAGGED_VERSION << ", Branch: " << GIT_BRANCH << ", BuildDate: " << BUILD_DATE << "\n"; + stream << "; resolution=" << std::setprecision(2) << resolution_mm_ << "mm\n"; + stream << "; path_tolerance=" << std::setprecision(1) << (current_arc_.get_path_tolerance_percent() * 100.0) << "%\n"; + stream << "; max_radius=" << std::setprecision(2) << (current_arc_.get_max_radius()) << "mm\n"; + if (gcode_position_args_.g90_influences_extruder) + { + stream << "; g90_influences_extruder=True\n"; + } + if (current_arc_.get_mm_per_arc_segment() > 0 && current_arc_.get_min_arc_segments() > 0) + { + stream << "; firmware_compensation=True\n"; + stream << "; mm_per_arc_segment=" << std::setprecision(2) << current_arc_.get_mm_per_arc_segment() << "mm\n"; + stream << "; min_arc_segments=" << std::setprecision(0) << current_arc_.get_min_arc_segments() << "\n"; + } + if (allow_3d_arcs_) + { + stream << "; allow_3d_arcs=True\n"; + + } + if (allow_dynamic_precision_) + { + stream << "; allow_dynamic_precision=True\n"; + } + stream << "; default_xyz_precision=" << std::setprecision(0) << static_cast(current_arc_.get_xyz_precision()) << "\n"; + stream << "; default_e_precision=" << std::setprecision(0) << static_cast(current_arc_.get_e_precision()) << "\n"; + stream << "; extrusion_rate_variance_percent=" << std::setprecision(1) << (extrusion_rate_variance_percent_ * 100.0) << "%\n\n"; + + + output_file_ << stream.str(); } diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index fc5bb84..ea10257 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -431,10 +431,8 @@ typedef bool(*progress_callback)(arc_welder_progress, logger* p_logger, int logg #define DEFAULT_GCODE_BUFFER_SIZE 10 #define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false #define DEFAULT_ALLOW_DYNAMIC_PRECISION false -#define DEFAULT_ALLOW_TRAVEL_ARCS false +#define DEFAULT_ALLOW_TRAVEL_ARCS true #define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 0.05 -#define DEFAULT_CONVERT_TRAVEL_MOVES false - struct arc_welder_args { diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 531c9e5..568d964 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -368,20 +368,38 @@ int segmented_arc::get_shape_gcode_length() double i = current_arc_.get_i(); double j = current_arc_.get_j(); - - int num_spaces = 3 + (has_z ? 1 : 0) + (has_e ? 1 : 0) + (has_f ? 1 : 0); + + + int num_spaces = 4 + (has_z ? 1 : 0) + (has_e ? 1 : 0) + (has_f ? 1 : 0); int num_decimal_points = 4 + (has_z ? 1 : 0) + (has_e ? 1 : 0); // note f has no decimal point - int num_decimals = xyz_precision * (4 + (has_z ? 1 : 0) + (has_e ? 1 : 0)); // Note f is an int + int num_decimals = xyz_precision * (4 + (has_z ? 1 : 0)) + e_precision * (has_e ? 1 : 0); // Note f is an int int num_digits = ( - utilities::get_num_digits(current_arc_.end_point.x) + - utilities::get_num_digits(current_arc_.end_point.y) + - (has_z ? utilities::get_num_digits(current_arc_.end_point.z) : 0) + - utilities::get_num_digits(i) + - utilities::get_num_digits(j) + - (has_f ? utilities::get_num_digits(f) : 0) + utilities::get_num_digits(current_arc_.end_point.x, xyz_precision) + + utilities::get_num_digits(current_arc_.end_point.y, xyz_precision) + + (has_z ? utilities::get_num_digits(current_arc_.end_point.z, xyz_precision) : 0) + + (has_e ? utilities::get_num_digits(e, e_precision) : 0) + + utilities::get_num_digits(i, xyz_precision) + + utilities::get_num_digits(j, xyz_precision) + + (has_f ? utilities::get_num_digits(f,0) : 0) + ); + int num_minus_signs = ( + (current_arc_.end_point.x < 0 ? 1 : 0) + + (current_arc_.end_point.y < 0 ? 1 : 0) + + (i < 0 ? 1 : 0) + + (j < 0 ? 1 : 0) + + (has_e && e < 0 ? 1 : 0) + + (has_z && current_arc_.end_point.z < 0 ? 1 : 0) ); + + int num_parameters = 4 + (has_e ? 1 : 0) + (has_z ? 1: 0) + (has_f ? 1: 0); // Return the length of the gcode. - return 3 + num_spaces + num_decimal_points + num_decimal_points + num_digits; + int gcode_length = 2 + num_spaces + num_decimal_points + num_digits + num_minus_signs + num_decimals + num_parameters; + std::string gcode = get_shape_gcode(); + if (gcode.length() != gcode_length) + { + return 10000; + } + return gcode_length; diff --git a/ArcWelder/segmented_shape.h b/ArcWelder/segmented_shape.h index d5c88d5..d27a568 100644 --- a/ArcWelder/segmented_shape.h +++ b/ArcWelder/segmented_shape.h @@ -25,7 +25,6 @@ #pragma once #include #include -#define PI_DOUBLE 3.14159265358979323846264338327950288 #include #include "utilities.h" diff --git a/ArcWelder/unwritten_command.h b/ArcWelder/unwritten_command.h index d1ed490..5acf999 100644 --- a/ArcWelder/unwritten_command.h +++ b/ArcWelder/unwritten_command.h @@ -31,13 +31,15 @@ struct unwritten_command is_extruder_relative = false; length = 0; is_g0_g1 = false; + is_g2_g3 = false; } unwritten_command(parsed_command &cmd, bool is_relative, bool is_travel, double command_length) - : is_extruder_relative(is_relative), is_travel(is_travel), is_g0_g1(cmd.command == "G0" || cmd.command == "G1"), gcode(cmd.gcode), comment(cmd.comment), length(command_length) + : is_extruder_relative(is_relative), is_travel(is_travel), is_g0_g1(cmd.command == "G0" || cmd.command == "G1"), is_g2_g3(cmd.command == "G2" || cmd.command == "G3"), gcode(cmd.gcode), comment(cmd.comment), length(command_length) { } bool is_g0_g1; + bool is_g2_g3; bool is_extruder_relative; bool is_travel; double length; diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp index e7a1ea9..37574ea 100644 --- a/ArcWelderConsole/ArcWelderConsole.cpp +++ b/ArcWelderConsole/ArcWelderConsole.cpp @@ -91,7 +91,6 @@ int main(int argc, char* argv[]) arg_description_stream << "The maximum radius of any arc in mm. Default Value: " << DEFAULT_MAX_RADIUS_MM; TCLAP::ValueArg max_radius_arg("m", "max-radius-mm", arg_description_stream.str(), false, DEFAULT_MAX_RADIUS_MM, "float"); - // -s --mm-per-arc-segment arg_description_stream.clear(); arg_description_stream.str(""); @@ -132,13 +131,13 @@ int main(int argc, char* argv[]) arg_description_stream.clear(); arg_description_stream.str(""); arg_description_stream << "The default precision of X, Y, Z, I and J output gcode parameters. The precision may be larger than this value if allow-dynamic-precision is set to true. Default Value: " << DEFAULT_XYZ_PRECISION; - TCLAP::ValueArg default_xyz_precision_arg("x", "default-xyz-precision", arg_description_stream.str(), false, DEFAULT_XYZ_PRECISION, "unsigned char"); + TCLAP::ValueArg default_xyz_precision_arg("x", "default-xyz-precision", arg_description_stream.str(), false, DEFAULT_XYZ_PRECISION, "unsigned int"); // -e --default-e-precision arg_description_stream.clear(); arg_description_stream.str(""); arg_description_stream << "The default precision of E output gcode parameters. The precision may be larger than this value if allow-dynamic-precision is set to true. Default Value: " << DEFAULT_E_PRECISION; - TCLAP::ValueArg default_e_precision_arg("e", "default-e-precision", arg_description_stream.str(), false, DEFAULT_E_PRECISION, "unsigned char"); + TCLAP::ValueArg default_e_precision_arg("e", "default-e-precision", arg_description_stream.str(), false, DEFAULT_E_PRECISION, "unsigned int"); // -v --extrusion-rate-variance arg_description_stream.clear(); @@ -146,14 +145,13 @@ int main(int argc, char* argv[]) arg_description_stream << "(experimental) - The allowed variance in extrusion rate by percent. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; TCLAP::ValueArg extrusion_rate_variance_percent_arg("v", "extrusion-rate-variance-percent", arg_description_stream.str(), false, DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, "double"); - // -l --max-gcode-length + // -c --max-gcode-length arg_description_stream.clear(); arg_description_stream.str(""); arg_description_stream << "The maximum length allowed for a generated G2/G3 command, not including any comments. 0 = no limit. Default Value: " << DEFAULT_MAX_GCODE_LENGTH; - TCLAP::ValueArg max_gcode_length_arg("l", "max-gcode-length", arg_description_stream.str(), false, DEFAULT_MAX_GCODE_LENGTH, "int"); + TCLAP::ValueArg max_gcode_length_arg("c", "max-gcode-length", arg_description_stream.str(), false, DEFAULT_MAX_GCODE_LENGTH, "int"); // -p --progress-type - // -l --log-level std::vector progress_type_vector; std::string progress_type_default_string = PROGRESS_TYPE_SIMPLE; progress_type_vector.push_back(PROGRESS_TYPE_NONE); @@ -181,6 +179,7 @@ int main(int argc, char* argv[]) arg_description_stream << "Sets console log level. Default Value: " << log_level_string_default; TCLAP::ValueArg log_level_arg("l", "log-level", arg_description_stream.str(), false, log_level_string_default, &log_levels_constraint); + // Add all arguments cmd.add(source_arg); cmd.add(target_arg); @@ -221,8 +220,8 @@ int main(int argc, char* argv[]) args.allow_travel_arcs = allow_travel_arcs_arg.getValue(); args.g90_g91_influences_extruder = g90_arg.getValue(); args.allow_dynamic_precision = allow_dynamic_precision_arg.getValue(); - args.default_xyz_precision = default_xyz_precision_arg.getValue(); - args.default_e_precision = default_e_precision_arg.getValue(); + unsigned int xyz_precision = default_xyz_precision_arg.getValue(); + unsigned int e_precision = default_e_precision_arg.getValue(); args.extrusion_rate_variance_percent = extrusion_rate_variance_percent_arg.getValue(); args.max_gcode_length = max_gcode_length_arg.getValue(); progress_type = progress_type_arg.getValue(); @@ -274,34 +273,38 @@ int main(int argc, char* argv[]) std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is less than greater than 0.001 (0.1%), which is not recommended." << std::endl; } - if (args.default_xyz_precision < 3) + if (xyz_precision < 3) { // warning - std::cout << "warning: The provided default_xyz_precision " << args.default_xyz_precision << "mm is less than 3, with will cause issues printing arcs. A value of 3 will be used instead." << std::endl; - args.default_xyz_precision = 3; + std::cout << "warning: The provided default_xyz_precision " << xyz_precision << "mm is less than 3, with will cause issues printing arcs. A value of 3 will be used instead." << std::endl; + xyz_precision = 3; } - if (args.default_e_precision < DEFAULT_E_PRECISION) + if (e_precision < DEFAULT_E_PRECISION) { // warning - std::cout << "warning: The provided default_e_precision " << args.default_e_precision << "mm is less than 3, with will cause extrusion issues. A value of 3 will be used instead." << std::endl; - args.default_e_precision = 3; + std::cout << "warning: The provided default_e_precision " << e_precision << "mm is less than 3, with will cause extrusion issues. A value of 3 will be used instead." << std::endl; + e_precision = 3; } - if (args.default_xyz_precision > 6) + if (xyz_precision > 6) { // warning - std::cout << "warning: The provided default_xyz_precision " << args.default_xyz_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so a value of 6 will be used instead." << std::endl; - args.default_xyz_precision = 6; + std::cout << "warning: The provided default_xyz_precision " << xyz_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so a value of 6 will be used instead." << std::endl; + xyz_precision = 6; } - if (args.default_e_precision > 6) + if (e_precision > 6) { // warning - std::cout << "warning: The provided default_e_precision " << args.default_e_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so value of 6 will be used instead." << std::endl; - args.default_e_precision = 6; + std::cout << "warning: The provided default_e_precision " << e_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so value of 6 will be used instead." << std::endl; + e_precision = 6; } + // Fill in the adjusted precisions + args.default_e_precision = (unsigned char)e_precision; + args.default_xyz_precision = (unsigned char)xyz_precision; + if (args.extrusion_rate_variance_percent < 0) { // warning @@ -312,7 +315,7 @@ int main(int argc, char* argv[]) if (args.max_gcode_length < 0) { // warning - std::cout << "warning: The provided max_gcode_length " << args.max_gcode_length << " is less than 0. Setting to the default." << std::endl; + std::cout << "warning: The provided max_gcode_length " << args.max_gcode_length << " is less than 0. Setting to the default (no limit)." << std::endl; args.max_gcode_length = DEFAULT_MAX_GCODE_LENGTH; } @@ -386,7 +389,15 @@ int main(int argc, char* argv[]) { log_messages.clear(); log_messages.str(""); - log_messages << "Target File Travel Statistics:" << std::endl << results.progress.travel_statistics.str(); + if (results.progress.travel_statistics.total_count_source == results.progress.travel_statistics.total_count_source) + { + log_messages << "Target File Travel Statistics: No travel arcs converted." ; + } + else + { + log_messages << "Target File Travel Statistics:" << std::endl << results.progress.travel_statistics.str(); + } + p_logger->log(0, INFO, log_messages.str()); } diff --git a/ArcWelderInverseProcessor/marlin_2_arc.cpp b/ArcWelderInverseProcessor/marlin_2_arc.cpp index b7b3246..0656015 100644 --- a/ArcWelderInverseProcessor/marlin_2_arc.cpp +++ b/ArcWelderInverseProcessor/marlin_2_arc.cpp @@ -184,18 +184,18 @@ void marlin_2_arc::process() target[Z_AXIS] = static_cast(p_cur_pos->get_gcode_z()); target[E_AXIS] = static_cast(p_cur_pos->get_current_extruder().get_offset_e()); float offset[2]; - offset[0] = 0.0; - offset[1] = 0.0; + offset[X_AXIS] = 0.0; + offset[Y_AXIS] = 0.0; for (unsigned int index = 0; index < cmd.parameters.size(); index++) { parsed_command_parameter p = cmd.parameters[index]; if (p.name == "I") { - offset[0] = static_cast(p.double_value); + offset[X_AXIS] = static_cast(p.double_value); } else if (p.name == "J") { - offset[1] = static_cast(p.double_value); + offset[Y_AXIS] = static_cast(p.double_value); } } float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc diff --git a/GcodeProcessorLib/array_list.h b/GcodeProcessorLib/array_list.h index 53ed43e..a69fc28 100644 --- a/GcodeProcessorLib/array_list.h +++ b/GcodeProcessorLib/array_list.h @@ -35,7 +35,7 @@ public: count_ = 0; items_ = new T[max_size_]; } - + array_list(int max_size) { auto_grow_ = false; @@ -44,11 +44,11 @@ public: count_ = 0; items_ = new T[max_size]; } - + virtual ~array_list() { delete[] items_; } - + void resize(int max_size) { T* new_items = new T[max_size]; @@ -71,7 +71,7 @@ public: } return index_position; } - + void push_front(T object) { if (count_ == max_size_) @@ -93,7 +93,7 @@ public: count_++; items_[front_index_] = object; } - + void push_back(T object) { if (count_ == max_size_) @@ -110,7 +110,7 @@ public: items_[pos] = object; count_++; } - + T& pop_front() { if (count_ == 0) @@ -135,7 +135,7 @@ public: { throw std::exception(); } - int pos = get_index_position(count_-1); + int pos = get_index_position(count_ - 1); count_--; return items_[pos]; } @@ -151,23 +151,23 @@ public: int opos = get_index_position(index); return items_[opos]; } - + int count() const { return count_; } - + int get_max_size() const { return max_size_; } - + void clear() { count_ = 0; front_index_ = 0; } - + void copy(const array_list& source) { if (max_size_ < source.max_size_) diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index 4db0f6a..e4b8526 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -87,6 +87,30 @@ double utilities::get_cartesian_distance(double x1, double y1, double z1, double return std::sqrt(dist_squared); } +double utilities::get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise) +{ + double center_x = x1 - i; + double center_y = y1 - j; + double radius = hypot(i, j); + double z_dist = z2-z1; + double rt_x = x2 - center_x; + double rt_y = y2 - center_y; + double angular_travel_total = std::atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y); + if (angular_travel_total < 0) { angular_travel_total += (double)(2.0 * PI_DOUBLE); } + // Adjust the angular travel if the direction is clockwise + if (is_clockwise) { angular_travel_total -= (float)(2 * PI_DOUBLE); } + // Full circle fix. + if (x1 == x2 && y1 == y2 && angular_travel_total == 0) + { + angular_travel_total += (float)(2 * PI_DOUBLE); + } + + // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are + // calculating here + return hypot(angular_travel_total * radius, std::fabs(z_dist)); + +} + std::string utilities::to_string(double value) { std::ostringstream os; @@ -220,6 +244,14 @@ int utilities::get_num_digits(int x) (x < 10000000000 ? 10 : -1)))))))))); } +int utilities::get_num_digits(double x, int precision) +{ + return get_num_digits( + (int) std::ceil(x * std::pow(10, (double)precision) - .4999999999999) + / std::pow(10, (double)precision) + ); +} + int utilities::get_num_digits(double x) { return get_num_digits((int) x); diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index 3261f91..cf3a97b 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -28,6 +28,7 @@ // Had to increase the zero tolerance because prusa slicer doesn't always // retract enough while wiping. #define ZERO_TOLERANCE 0.000005 +#define PI_DOUBLE 3.14159265358979323846264338327950288 class utilities{ public: @@ -41,6 +42,7 @@ public: static double get_cartesian_distance(double x1, double y1, double x2, double y2); static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2); + static double get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise); /* Todo: Implement for gcode comment processor static bool case_insensitive_compare_char(char& c1, char& c2); static bool case_insensitive_compare(std::string& str1, std::string& str2); @@ -57,6 +59,7 @@ public: static std::string get_percent_change_string(int v1, int v2, int precision); static int get_num_digits(int x); static int get_num_digits(double x); + static int get_num_digits(double x, int precision); static std::vector splitpath(const std::string& str); static bool get_file_path(const std::string& file_path, std::string& path); -- cgit v1.2.3 From 4b2a112c139ce7f9909ba95f27cada3ecea2405b Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 17 Jul 2021 10:13:08 -0500 Subject: Fix travel statistics print in console app. Remove gcode length calculation check for non-debug builds. --- ArcWelder/segmented_arc.cpp | 6 +++++- ArcWelderConsole/ArcWelderConsole.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 568d964..60d50cd 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -394,11 +394,15 @@ int segmented_arc::get_shape_gcode_length() int num_parameters = 4 + (has_e ? 1 : 0) + (has_z ? 1: 0) + (has_f ? 1: 0); // Return the length of the gcode. int gcode_length = 2 + num_spaces + num_decimal_points + num_digits + num_minus_signs + num_decimals + num_parameters; + + // Keep this around in case we have any future issues with the gcode length calculation + #ifdef Debug std::string gcode = get_shape_gcode(); if (gcode.length() != gcode_length) { - return 10000; + return 9999999; } + #endif return gcode_length; diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp index 37574ea..e64d9d4 100644 --- a/ArcWelderConsole/ArcWelderConsole.cpp +++ b/ArcWelderConsole/ArcWelderConsole.cpp @@ -389,7 +389,7 @@ int main(int argc, char* argv[]) { log_messages.clear(); log_messages.str(""); - if (results.progress.travel_statistics.total_count_source == results.progress.travel_statistics.total_count_source) + if (results.progress.travel_statistics.total_count_source == results.progress.travel_statistics.total_count_target) { log_messages << "Target File Travel Statistics: No travel arcs converted." ; } -- cgit v1.2.3 From 441742cfd83667fe8e2507a35cc0207687519650 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sun, 18 Jul 2021 17:30:25 -0500 Subject: Fix bytes string when logging from python. Reduce notification period from 1 sec to 0.5 sec and add an argument to control this (not added to console app yet). --- ArcWelder/arc_welder.cpp | 5 +++-- ArcWelder/arc_welder.h | 13 +++++++++++-- PyArcWelder/py_arc_welder_extension.cpp | 4 +++- PyArcWelder/py_logger.cpp | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index 1e601e5..b966b62 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -54,6 +54,7 @@ arc_welder::arc_welder( double extrusion_rate_variance_percent, int max_gcode_length, int buffer_size, + double notification_period_seconds, progress_callback callback) : current_arc_( DEFAULT_MIN_SEGMENTS, buffer_size, @@ -95,10 +96,10 @@ arc_welder::arc_welder( allow_travel_arcs_ = allow_travel_arcs; allow_dynamic_precision_ = allow_dynamic_precision; extrusion_rate_variance_percent_ = extrusion_rate_variance_percent; - notification_period_seconds = 1; lines_processed_ = 0; gcodes_processed_ = 0; file_size_ = 0; + notification_period_seconds_ = notification_period_seconds; last_gcode_line_written_ = 0; points_compressed_ = 0; arcs_created_ = 0; @@ -192,7 +193,7 @@ long arc_welder::get_file_size(const std::string& file_path) double arc_welder::get_next_update_time() const { - return clock() + (notification_period_seconds * CLOCKS_PER_SEC); + return clock() + (notification_period_seconds_ * CLOCKS_PER_SEC); } double arc_welder::get_time_elapsed(double start_clock, double end_clock) diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index ea10257..7bcff0c 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -433,6 +433,7 @@ typedef bool(*progress_callback)(arc_welder_progress, logger* p_logger, int logg #define DEFAULT_ALLOW_DYNAMIC_PRECISION false #define DEFAULT_ALLOW_TRAVEL_ARCS true #define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 0.05 +#define DEFAULT_NOTIFICATION_PERIOD_SECONDS 0.5 struct arc_welder_args { @@ -454,7 +455,9 @@ struct arc_welder_args extrusion_rate_variance_percent(DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT), max_gcode_length(DEFAULT_MAX_GCODE_LENGTH), buffer_size(DEFAULT_GCODE_BUFFER_SIZE), + notification_period_seconds(DEFAULT_NOTIFICATION_PERIOD_SECONDS), callback(NULL){}; + arc_welder_args(std::string source, std::string target, logger* ptr_log) : arc_welder_args() { @@ -479,6 +482,7 @@ struct arc_welder_args double extrusion_rate_variance_percent; int buffer_size; int max_gcode_length; + double notification_period_seconds; progress_callback callback; @@ -520,7 +524,8 @@ struct arc_welder_args stream << "\tMax Gcode Length : " << std::setprecision(0) << max_gcode_length << " characters\n"; } stream << "\tLog Level : " << log_level_name << "\n"; - stream << "\tHide Progress Updates : " << (callback == NULL ? "True" : "False"); + stream << "\tHide Progress Updates : " << (callback == NULL ? "True" : "False") << "\n"; + stream << "\tProgress Notification Period : " << std::setprecision(2) << notification_period_seconds << " seconds"; return stream.str(); }; @@ -560,6 +565,7 @@ public: double extrusion_rate_variance_percent, int max_gcode_length, int buffer_size, + double notification_period_seconds, progress_callback callback); arc_welder(arc_welder_args args) : arc_welder( @@ -580,6 +586,7 @@ public: args.extrusion_rate_variance_percent, args.max_gcode_length, args.buffer_size, + args.notification_period_seconds, args.callback ){}; @@ -587,10 +594,11 @@ public: void set_logger_type(int logger_type); virtual ~arc_welder(); arc_welder_results process(); - double notification_period_seconds; + protected: virtual bool on_progress_(const arc_welder_progress& progress); private: + arc_welder_progress get_progress_(long source_file_position, double start_clock); void add_arcwelder_comment_to_target(); void reset(); @@ -617,6 +625,7 @@ private: int points_compressed_; int arcs_created_; int arcs_aborted_by_flow_rate_; + double notification_period_seconds_; source_target_segment_statistics segment_statistics_; source_target_segment_statistics travel_statistics_; long get_file_size(const std::string& file_path); diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp index b386d9f..1e440f9 100644 --- a/PyArcWelder/py_arc_welder_extension.cpp +++ b/PyArcWelder/py_arc_welder_extension.cpp @@ -198,7 +198,9 @@ extern "C" args.py_progress_callback = py_progress_callback; args.log = p_py_logger; py_arc_welder arc_welder_obj(args); - arc_welder_results results = arc_welder_obj.process(); + arc_welder_results results; + results = arc_welder_obj.process(); + message = "py_gcode_arc_converter.ConvertFile - Arc Conversion Complete."; p_py_logger->log(GCODE_CONVERSION, INFO, message); Py_XDECREF(py_progress_callback); diff --git a/PyArcWelder/py_logger.cpp b/PyArcWelder/py_logger.cpp index 0418ef5..0ae6548 100644 --- a/PyArcWelder/py_logger.cpp +++ b/PyArcWelder/py_logger.cpp @@ -203,7 +203,9 @@ void py_logger::log(const int logger_type, const int log_level, const std::strin return; } } - PyObject* pyMessage = gcode_arc_converter::PyBytesOrString_FromString(message); + //PyObject* pyMessage = gcode_arc_converter::PyBytesOrString_FromString(message); + PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(message); + if (pyMessage == NULL) { std::cout << "Unable to convert the log message '" << message.c_str() << "' to a PyString/Unicode message.\r\n"; -- cgit v1.2.3 From b57d34b8e69d6d5680ba271d6040734899295d05 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 6 Nov 2021 14:59:27 -0500 Subject: Rewrite of ArcStraightener to support multiple firmware types and versions. Convert utilities class to namespace. --- ArcWelder/CMakeLists.txt | 2 + ArcWelder/segmented_arc.cpp | 2 - .../ArcWelderInverseProcessor.cpp | 421 ++++++++++--- .../ArcWelderInverseProcessor.h | 5 +- .../ArcWelderInverseProcessor.vcxproj | 27 +- .../ArcWelderInverseProcessor.vcxproj.filters | 42 +- ArcWelderInverseProcessor/CMakeLists.txt | 13 +- ArcWelderInverseProcessor/Makefile | 6 - ArcWelderInverseProcessor/arc_interpolation.cpp | 246 ++++++++ ArcWelderInverseProcessor/arc_interpolation.h | 56 ++ .../arc_interpolation_structs.h | 1 + ArcWelderInverseProcessor/firmware.cpp | 153 +++++ ArcWelderInverseProcessor/firmware.h | 373 ++++++++++++ ArcWelderInverseProcessor/firmware_types.cpp | 1 - ArcWelderInverseProcessor/firmware_types.h | 10 +- ArcWelderInverseProcessor/marlin_1.cpp | 357 +++++++++++ ArcWelderInverseProcessor/marlin_1.h | 101 +++ ArcWelderInverseProcessor/marlin_2.cpp | 677 +++++++++++++++++++++ ArcWelderInverseProcessor/marlin_2.h | 107 ++++ ArcWelderInverseProcessor/marlin_2_arc.cpp | 448 -------------- ArcWelderInverseProcessor/marlin_2_arc.h | 95 --- ArcWelderInverseProcessor/prusa.cpp | 466 ++++++++++++++ ArcWelderInverseProcessor/prusa.h | 83 +++ ArcWelderInverseProcessor/repetier.cpp | 476 +++++++++++++++ ArcWelderInverseProcessor/repetier.h | 39 ++ ArcWelderInverseProcessor/repiter_arc.cpp | 97 --- ArcWelderInverseProcessor/repiter_arc.h | 7 - ArcWelderInverseProcessor/smoothieware.cpp | 274 +++++++++ ArcWelderInverseProcessor/smoothieware.h | 65 ++ ArcWelderInverseProcessor/sourcelist.cmake | 20 +- ArcWelderTest/ArcWelderTest.cpp | 2 +- GcodeProcessorLib/fpconv.cpp | 4 +- GcodeProcessorLib/fpconv.h | 2 +- GcodeProcessorLib/utilities.cpp | 244 ++++++-- GcodeProcessorLib/utilities.h | 192 +++--- PyArcWelder/py_arc_welder.cpp | 4 +- 36 files changed, 4240 insertions(+), 878 deletions(-) delete mode 100644 ArcWelderInverseProcessor/Makefile create mode 100644 ArcWelderInverseProcessor/arc_interpolation.cpp create mode 100644 ArcWelderInverseProcessor/arc_interpolation.h create mode 100644 ArcWelderInverseProcessor/arc_interpolation_structs.h create mode 100644 ArcWelderInverseProcessor/firmware.cpp create mode 100644 ArcWelderInverseProcessor/firmware.h delete mode 100644 ArcWelderInverseProcessor/firmware_types.cpp create mode 100644 ArcWelderInverseProcessor/marlin_1.cpp create mode 100644 ArcWelderInverseProcessor/marlin_1.h create mode 100644 ArcWelderInverseProcessor/marlin_2.cpp create mode 100644 ArcWelderInverseProcessor/marlin_2.h delete mode 100644 ArcWelderInverseProcessor/marlin_2_arc.cpp delete mode 100644 ArcWelderInverseProcessor/marlin_2_arc.h create mode 100644 ArcWelderInverseProcessor/prusa.cpp create mode 100644 ArcWelderInverseProcessor/prusa.h create mode 100644 ArcWelderInverseProcessor/repetier.cpp create mode 100644 ArcWelderInverseProcessor/repetier.h delete mode 100644 ArcWelderInverseProcessor/repiter_arc.cpp delete mode 100644 ArcWelderInverseProcessor/repiter_arc.h create mode 100644 ArcWelderInverseProcessor/smoothieware.cpp create mode 100644 ArcWelderInverseProcessor/smoothieware.h diff --git a/ArcWelder/CMakeLists.txt b/ArcWelder/CMakeLists.txt index 738983c..f84fd23 100644 --- a/ArcWelder/CMakeLists.txt +++ b/ArcWelder/CMakeLists.txt @@ -31,3 +31,5 @@ set(${PROJECT_NAME}_DEFINITIONS ${GcodeProcessorLib_DEFINITIONS} set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/ ${GcodeProcessorLib_INCLUDE_DIRS} CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE) + + \ No newline at end of file diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 60d50cd..6f238a5 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -27,8 +27,6 @@ #include "utilities.h" #include "segmented_shape.h" #include -//#include -//#include #include #include diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp index ca64ff3..a3147eb 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp @@ -26,22 +26,31 @@ #define _CRT_SECURE_NO_DEPRECATE #endif -#include "marlin_2_arc.h" #include "ArcWelderInverseProcessor.h" -#include -#include -#include -#include -#include "gcode_position.h" +#include "arc_interpolation.h" +#include "marlin_1.h" +#include "marlin_2.h" +#include "repetier.h" +#include "prusa.h" +#include "smoothieware.h" #include "logger.h" #include "version.h" #include "utilities.h" #include #define DEFAULT_ARG_DOUBLE_PRECISION 4 - int main(int argc, char* argv[]) { - std::string info = "Arc Straightener - Converts G2/G3 commands to G1/G2 commands.."; + try { + run_arc_straightener(argc, argv); + } + catch (TCLAP::ArgException *e) { + std::cout << (*e).what() << " - " << (*e).typeDescription() << "\n"; + return -1; + } +} +int run_arc_straightener(int argc, char* argv[]) +{ + std::string info = "Arc Straightener - Converts G2/G3 commands to G1/G2 commands.."; info.append("\nVersion: ").append(GIT_TAGGED_VERSION); info.append(", Branch: ").append(GIT_BRANCH); @@ -51,20 +60,20 @@ int main(int argc, char* argv[]) std::stringstream arg_description_stream; arg_description_stream << std::fixed << std::setprecision(5); - std::string source_file_path; - std::string target_file_path; + arc_interpolation_args args; bool overwrite_source_file = false; - bool g90_g91_influences_extruder; - - ConfigurationStore cs; - double mm_per_arc_segment; - double min_mm_per_arc_segment; - int min_arc_segments; - double arc_segments_per_sec; - + std::string log_level_string; std::string log_level_string_default = "INFO"; int log_level_value; + + // Create an instance of all supported firmware types using the default args + marlin_1 marlin_1_firmware(args.firmware_args); + marlin_2 marlin_2_firmware(args.firmware_args); + repetier repetier_firmware(args.firmware_args); + prusa prusa_firmware(args.firmware_args); + smoothieware smoothieware_firmware(args.firmware_args); + // Extract arguments try { // Define the command line object @@ -77,18 +86,55 @@ int main(int argc, char* argv[]) // TCLAP::UnlabeledValueArg target_arg("target", "The target gcode file containing the converted code. If this is not supplied, the source path will be used and the source file will be overwritten.", false, "", "path to target gcode file"); - + + // -f --firmware-type + std::vector firmware_types_vector; + for (int i = 0; i < NUM_FIRMWARE_TYPES; i++) + { + firmware_types_vector.push_back(firmware_type_names[i]); + } + TCLAP::ValuesConstraint firmware_type_constraint(firmware_types_vector); + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "Sets the firmware to emulate. Default Value: " << firmware_type_names[DEFAULT_FIRMWARE_TYPE]; + TCLAP::ValueArg firmware_type_arg("f", "firmware-type", arg_description_stream.str(), false, firmware_type_names[DEFAULT_FIRMWARE_TYPE], &firmware_type_constraint); + + // -v --firmware-version + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "Sets the firmware version to use. The available versions depend on the firmware type selected. " << DEFAULT_FIRMWARE_VERSION_NAME << " will select the most recent version available.\n"; + arg_description_stream << "\tMARLIN 1 versions: " << utilities::join(marlin_1_firmware.get_version_names(), ", ") << "\n"; + arg_description_stream << "\tMARLIN 2 versions: " << utilities::join(marlin_2_firmware.get_version_names(), ", ") << "\n"; + arg_description_stream << "\tREPETIER versions: " << utilities::join(repetier_firmware.get_version_names(), ", ") << "\n"; + arg_description_stream << "\tPRUSA versions: " << utilities::join(prusa_firmware.get_version_names(), ", ") << "\n"; + arg_description_stream << "\tSMOOTHIEWARE versions: " << utilities::join(smoothieware_firmware.get_version_names(), ", ") << "\n"; + arg_description_stream << "\tDefault Value: " << DEFAULT_FIRMWARE_VERSION_NAME; + TCLAP::ValueArg firmware_version_arg("v", "firmware-version", arg_description_stream.str(), false, DEFAULT_FIRMWARE_VERSION_NAME, "string"); + // -g --g90-influences-extruder + std::string g90_g91_influences_extruder_default_value = "DEFAULT"; + std::vector g90_g91_influences_extruder_vector; + g90_g91_influences_extruder_vector.push_back("TRUE"); + g90_g91_influences_extruder_vector.push_back("FALSE"); + g90_g91_influences_extruder_vector.push_back(g90_g91_influences_extruder_default_value); + TCLAP::ValuesConstraint g90_g91_influences_extruder_constraint(g90_g91_influences_extruder_vector); arg_description_stream.clear(); arg_description_stream.str(""); - arg_description_stream << "If supplied, G90/G91 influences the extruder axis. Default Value: " << DEFAULT_G90_G91_INFLUENCES_EXTRUDER; - TCLAP::SwitchArg g90_arg("g", "g90-influences-extruder", arg_description_stream.str(), false); + arg_description_stream << "Sets the firmware's G90/G91 influences extruder axis behavior. By default this is determined by the firmware's behavior. Default Value: " << g90_g91_influences_extruder_default_value; + TCLAP::ValueArg g90_arg("g", "g90-influences-extruder", arg_description_stream.str(), false, g90_g91_influences_extruder_default_value, &g90_g91_influences_extruder_constraint); // -m --mm-per-arc-segment arg_description_stream.clear(); arg_description_stream.str(""); arg_description_stream << "The default segment length. Default Value: " << DEFAULT_MM_PER_ARC_SEGMENT; TCLAP::ValueArg mm_per_arc_segment_arg("m", "mm-per-arc-segment", arg_description_stream.str(), false, DEFAULT_MM_PER_ARC_SEGMENT, "float"); + + // max_arc_segment_mm_arg + // -d --mm-per-arc-segment + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "The maximum length of an arc segment. Default Value: " << DEFAULT_MM_PER_ARC_SEGMENT; + TCLAP::ValueArg max_arc_segment_mm_arg("d", "max-arc-segment-mm", arg_description_stream.str(), false, DEFAULT_MM_PER_ARC_SEGMENT, "float"); // -n --min-mm-per-arc-segment arg_description_stream.clear(); @@ -96,18 +142,50 @@ int main(int argc, char* argv[]) arg_description_stream << "The minimum mm per arc segment. Used to prevent unnecessarily small segments from being generated. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_MM_PER_ARC_SEGMENT; TCLAP::ValueArg min_mm_per_arc_segment_arg("n", "min-mm-per-arc-segment", arg_description_stream.str(), false, DEFAULT_MIN_MM_PER_ARC_SEGMENT, "float"); - // -s --min-arc-segments + // min_arc_segment_mm + // -b --min-arc-segment-mm + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "The minimum mm per arc segment. Used to prevent unnecessarily small segments from being generated. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_MM_PER_ARC_SEGMENT; + TCLAP::ValueArg min_arc_segment_mm_arg("b", "min-arc-segment-mm", arg_description_stream.str(), false, DEFAULT_MIN_MM_PER_ARC_SEGMENT, "float"); + + // -r --min-arc-segments arg_description_stream.clear(); arg_description_stream.str(""); arg_description_stream << "The minimum number of segments within a circle of the same radius as the arc. Can be used to increase detail on small arcs. The smallest segment generated will be no larger than min_mm_per_arc_segment. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_ARC_SEGMENTS; TCLAP::ValueArg min_arc_segments_arg("r", "min-arc-segments", arg_description_stream.str(), false, DEFAULT_MIN_ARC_SEGMENTS, "int"); + // min_circle_segments_arg + // -a --min-circle-segments-arg + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "The minimum number of segments within a circle of the same radius as the arc. Can be used to increase detail on small arcs. The smallest segment generated will be no larger than min_mm_per_arc_segment. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_ARC_SEGMENTS; + TCLAP::ValueArg min_circle_segments_arg("a", "min-circle-segments", arg_description_stream.str(), false, DEFAULT_MIN_ARC_SEGMENTS, "int"); + + // -c --n-arc-correction + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "The number of segments that will be interpolated using a small angle approximation before true sin/cos corrections are applied. A value less than or equal to 1 will disable this feature. Default Value: " << DEFAULT_N_ARC_CORRECTIONS; + TCLAP::ValueArg n_arc_correction_arg("c", "n-arc-correction", arg_description_stream.str(), false, DEFAULT_N_ARC_CORRECTIONS, "int"); + // -s --arc-segments-per-second arg_description_stream.clear(); arg_description_stream.str(""); arg_description_stream << "The number of segments per second. This will produce a constant number of arcs, clamped between mm-per-arc-segment and min-mm-per-arc-segment. Can be used to prevent stuttering when printing very quickly. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_ARC_SEGMENTS_PER_SEC; TCLAP::ValueArg arc_segments_per_sec_arg("s", "arc-segments-per-second", arg_description_stream.str(), false, DEFAULT_MIN_MM_PER_ARC_SEGMENT, "float"); + // -e --mm-max-arc-error + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "This currently is only used in Smoothieware. The maximum error for line segments that divide arcs. Set to 0 to disable. Default Value: " << DEFAULT_MM_MAX_ARC_ERROR; + TCLAP::ValueArg mm_max_arc_error_arg("e", "mm-max-arc-error", arg_description_stream.str(), false, DEFAULT_MM_MAX_ARC_ERROR, "float"); + + // -p --print-firmware-defaults + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "Prints all available settings and defaults for the provided firmware type and version. All other parameters will be ignored."; + TCLAP::SwitchArg print_firmware_defaults_arg("p", "print-firmware-defaults", arg_description_stream.str()); + // -l --log-level std::vector log_levels_vector; log_levels_vector.push_back("NOSET"); @@ -121,43 +199,213 @@ int main(int argc, char* argv[]) TCLAP::ValuesConstraint log_levels_constraint(log_levels_vector); arg_description_stream.clear(); arg_description_stream.str(""); - arg_description_stream << "Sets console log level. Default Value: " << log_level_string_default; + arg_description_stream << "Sets console log level. Possible values: Default Value: " << log_level_string_default; TCLAP::ValueArg log_level_arg("l", "log-level", arg_description_stream.str(), false, log_level_string_default, &log_levels_constraint); // Add all arguments cmd.add(source_arg); cmd.add(target_arg); + cmd.add(firmware_type_arg); + cmd.add(firmware_version_arg); cmd.add(g90_arg); - cmd.add(mm_per_arc_segment_arg); cmd.add(min_mm_per_arc_segment_arg); cmd.add(min_arc_segments_arg); + cmd.add(n_arc_correction_arg); cmd.add(arc_segments_per_sec_arg); - cmd.add(log_level_arg); + cmd.add(min_circle_segments_arg); + cmd.add(min_arc_segment_mm_arg); + cmd.add(max_arc_segment_mm_arg); + cmd.add(print_firmware_defaults_arg); // Parse the argv array. cmd.parse(argc, argv); + // Ok! Now let's see what firmware and version were selected, then we can start adding parameters + // First, Set the firmware type + std::string firmware_type_string = firmware_type_arg.getValue(); + for (int i = 0; i < NUM_FIRMWARE_TYPES; i++) + { + if (firmware_type_names[i] == firmware_type_string) + { + args.firmware_args.firmware_type = static_cast(i); + } + } + // Now set the version + // Set the firmware version, and check to make sure that the version supplied is supported. + std::string firmware_version_string = firmware_version_arg.getValue(); + switch (args.firmware_args.firmware_type) + { + case firmware_types::MARLIN_1: + if (!marlin_1_firmware.is_valid_version(firmware_version_string)) + { + throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type."); + } + break; + case firmware_types::MARLIN_2: + if (!marlin_2_firmware.is_valid_version(firmware_version_string)) + { + throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type."); + } + break; + case firmware_types::REPETIER: + if (!repetier_firmware.is_valid_version(firmware_version_string)) + { + throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type."); + } + break; + case firmware_types::PRUSA: + if (!prusa_firmware.is_valid_version(firmware_version_string)) + { + throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type."); + } + break; + case firmware_types::SMOOTHIEWARE: + if (!smoothieware_firmware.is_valid_version(firmware_version_string)) + { + throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type."); + } + break; + } + args.firmware_args.version = firmware_version_string; + + // now that we have the firmware type and version, we can extract the default arguments and override any settings that are supplied + switch (args.firmware_args.firmware_type) + { + case firmware_types::MARLIN_1: + marlin_1_firmware.set_arguments(args.firmware_args); + args.firmware_args = marlin_1_firmware.get_default_arguments_for_current_version(); + break; + case firmware_types::MARLIN_2: + marlin_2_firmware.set_arguments(args.firmware_args); + args.firmware_args = marlin_2_firmware.get_default_arguments_for_current_version(); + break; + case firmware_types::REPETIER: + repetier_firmware.set_arguments(args.firmware_args); + args.firmware_args = repetier_firmware.get_default_arguments_for_current_version(); + break; + case firmware_types::PRUSA: + prusa_firmware.set_arguments(args.firmware_args); + args.firmware_args = prusa_firmware.get_default_arguments_for_current_version(); + break; + case firmware_types::SMOOTHIEWARE: + smoothieware_firmware.set_arguments(args.firmware_args); + args.firmware_args = smoothieware_firmware.get_default_arguments_for_current_version(); + break; + } + + // see if the user want's to see the default settings + if (print_firmware_defaults_arg.getValue()) + { + std::cout << "Showing arguments and defaults for " << firmware_type_string << " ("<< firmware_version_string << ")\n"; + std::cout << "Available argument for firmware: " << get_available_arguments_string(args.firmware_args.get_available_arguments()) << "\n"; + std::cout << "Default " << args.firmware_args.get_argument_description(); + return 0; + } // Get the value parsed by each arg. - source_file_path = source_arg.getValue(); - target_file_path = target_arg.getValue(); - mm_per_arc_segment = mm_per_arc_segment_arg.getValue(); - min_mm_per_arc_segment = min_mm_per_arc_segment_arg.getValue(); - min_arc_segments = min_arc_segments_arg.getValue(); - arc_segments_per_sec = arc_segments_per_sec_arg.getValue(); - - cs.mm_per_arc_segment = (float)mm_per_arc_segment; - cs.min_mm_per_arc_segment = (float)min_mm_per_arc_segment; - cs.min_arc_segments = min_arc_segments; - cs.arc_segments_per_sec = arc_segments_per_sec; - - if (target_file_path.size() == 0) + args.source_path = source_arg.getValue(); + args.target_path = target_arg.getValue(); + if (args.target_path.size() == 0) { - target_file_path = source_file_path; + args.target_path = args.source_path; } - g90_g91_influences_extruder = g90_arg.getValue(); + // If the arguments are set, apply them. If not, don't. + if (mm_per_arc_segment_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("mm_per_arc_segment")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", mm_per_arc_segment_arg.getName(), "The argument does not apply to the " + firmware_type_string +" " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.mm_per_arc_segment = mm_per_arc_segment_arg.getValue(); + } + if (min_mm_per_arc_segment_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("min_mm_per_arc_segment")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", min_mm_per_arc_segment_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.min_mm_per_arc_segment = min_mm_per_arc_segment_arg.getValue(); + } + if (min_arc_segments_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("min_arc_segments")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", min_arc_segments_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.min_arc_segments = min_arc_segments_arg.getValue(); + } + if (arc_segments_per_sec_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("arc_segments_per_sec")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", arc_segments_per_sec_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.arc_segments_per_sec = arc_segments_per_sec_arg.getValue(); + } + if (g90_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("g90_g91_influences_extruder")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", g90_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.g90_g91_influences_extruder = g90_arg.getValue() == "TRUE"; + } + if (n_arc_correction_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("n_arc_correction")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", n_arc_correction_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.n_arc_correction = n_arc_correction_arg.getValue(); + } + if (mm_max_arc_error_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("mm_max_arc_error")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", mm_max_arc_error_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.mm_max_arc_error = mm_max_arc_error_arg.getValue(); + } + + // min_circle_segments + if (min_circle_segments_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("min_circle_segments")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", min_circle_segments_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.set_min_circle_segments(min_circle_segments_arg.getValue()); + } + // min_arc_segment_mm + if (min_arc_segment_mm_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("min_arc_segment_mm")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", min_arc_segment_mm_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.set_min_arc_segment_mm(min_arc_segment_mm_arg.getValue()); + } + // max_arc_segment_mm + if (max_arc_segment_mm_arg.isSet()) + { + // See if this argument is supported + if (!args.firmware_args.is_argument_used("max_arc_segment_mm")) + { + throw new TCLAP::ArgException("Invalid Argument For Firmware", max_arc_segment_mm_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments())); + } + args.firmware_args.set_max_arc_segment_mm(max_arc_segment_mm_arg.getValue()); + } log_level_string = log_level_arg.getValue(); log_level_value = -1; @@ -195,10 +443,10 @@ int main(int argc, char* argv[]) std::stringstream log_messages; std::string temp_file_path = ""; log_messages << std::fixed << std::setprecision(DEFAULT_ARG_DOUBLE_PRECISION); - if (source_file_path == target_file_path) + if (args.source_path == args.target_path) { overwrite_source_file = true; - if (!utilities::get_temp_file_path_for_file(source_file_path, temp_file_path)) + if (!utilities::get_temp_file_path_for_file(args.source_path, temp_file_path)) { log_messages << "The source and target path are the same, but a temporary file path could not be created. Is the path empty?"; p_logger->log(0, INFO, log_messages.str()); @@ -208,71 +456,78 @@ int main(int argc, char* argv[]) // create a uuid with a tmp extension for the temporary file - log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << target_file_path; + log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; p_logger->log(0, INFO, log_messages.str()); log_messages.clear(); log_messages.str(""); } - log_messages << "Processing Gcode\n"; - log_messages << "\tSource File Path : " << source_file_path << "\n"; + log_messages << "Arguments: \n"; + log_messages << "\tSource File Path : " << args.source_path << "\n"; if (overwrite_source_file) { - log_messages << "\tTarget File Path (overwrite) : " << target_file_path << "\n"; + log_messages << "\tTarget File Path (overwrite) : " << args.target_path << "\n"; log_messages << "\tTemporary File Path : " << temp_file_path << "\n"; } else { - log_messages << "\tTarget File File : " << target_file_path << "\n"; + log_messages << "\tTarget File File : " << args.target_path << "\n"; } log_messages << "\tLog Level : " << log_level_string << "\n"; - p_logger->log(0, INFO, log_messages.str()); - + + if (overwrite_source_file) { - target_file_path = temp_file_path; + args.target_path = temp_file_path; } - - marlin_2_arc processor(source_file_path, target_file_path, g90_g91_influences_extruder, 50, cs); - processor.process(); - // Todo: get some results! - if (true) - { - log_messages.clear(); - log_messages.str(""); - log_messages << "Target file at '" << target_file_path << "' created."; - if (overwrite_source_file) - { - log_messages.clear(); - log_messages.str(""); - log_messages << "Deleting the original source file at '" << source_file_path << "'."; - p_logger->log(0, INFO, log_messages.str()); - log_messages.clear(); - log_messages.str(""); - std::remove(source_file_path.c_str()); - log_messages << "Renaming temporary file at '" << target_file_path << "' to '" << source_file_path << "'."; - p_logger->log(0, INFO, log_messages.str()); - std::rename(target_file_path.c_str(), source_file_path.c_str()); - } - /* - log_messages.clear(); - log_messages.str(""); - log_messages << std::endl << results.progress.segment_statistics.str(); - p_logger->log(0, INFO, log_messages.str()); - */ + arc_interpolation interpolator(args); + log_messages << interpolator.get_firmware_argument_description(); + p_logger->log(0, INFO, log_messages.str()); + + p_logger->log(0, INFO, "Running interpolation..."); + interpolator.process(); + p_logger->log(0, INFO, "Interpolation Complete."); + + log_messages.clear(); + log_messages.str(""); + log_messages << "Target file at '" << args.target_path << "' created."; + + if (overwrite_source_file) + { log_messages.clear(); log_messages.str(""); - log_messages << "Process completed successfully."; + log_messages << "Deleting the original source file at '" << args.source_path << "'."; p_logger->log(0, INFO, log_messages.str()); - } - else - { log_messages.clear(); log_messages.str(""); - log_messages << "File processing failed."; + std::remove(args.source_path.c_str()); + log_messages << "Renaming temporary file at '" << args.target_path << "' to '" << args.source_path << "'."; p_logger->log(0, INFO, log_messages.str()); + std::rename(args.target_path.c_str(), args.source_path.c_str()); } + + log_messages.clear(); + log_messages.str(""); + log_messages << "Process completed successfully."; + p_logger->log(0, INFO, log_messages.str()); + return 0; } + + +std::string get_available_arguments_string(std::vector firmware_arguments) +{ + std::string available_argument_string = ""; + + for (std::vector::iterator it = firmware_arguments.begin(); it != firmware_arguments.end(); it++) + { + if (available_argument_string.size() > 0) + { + available_argument_string += ", "; + } + available_argument_string += "--" +utilities::replace(*it, "_", "-"); + } + return available_argument_string; +} \ No newline at end of file diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h index 95433f7..3d76259 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h @@ -24,8 +24,9 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma once #include -#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false - +#include +int run_arc_straightener(int argc, char* argv[]); +static std::string get_available_arguments_string(std::vector firmware_arguments); /* static void TestInverseProcessor(std::string source_path, std::string target_path); static std::string ANTI_STUTTER_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\5x5_cylinder_2000Fn_0.2mm_PLA_MK2.5MMU2_4m.gcode"; diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj index f67571c..334d94e 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj @@ -105,11 +105,11 @@ true - $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\ + $(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\ true - $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath); + $(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\ false @@ -117,7 +117,8 @@ false - $(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\ + $(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\ + $(VC_ExecutablePath_x64);$(CommonExecutablePath) @@ -202,15 +203,25 @@ + + + - - + + + + + - - - + + + + + + + diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters index bc338f9..476116b 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters @@ -18,13 +18,31 @@ Header Files - + Header Files - + Header Files - + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + Header Files @@ -32,13 +50,25 @@ Source Files - + + Source Files + + + Source Files + + + Source Files + + + Source Files + + Source Files - + Source Files - + Source Files diff --git a/ArcWelderInverseProcessor/CMakeLists.txt b/ArcWelderInverseProcessor/CMakeLists.txt index 8a9de2c..d2111d1 100644 --- a/ArcWelderInverseProcessor/CMakeLists.txt +++ b/ArcWelderInverseProcessor/CMakeLists.txt @@ -1,21 +1,19 @@ project(ArcWelderInverseProcessor C CXX) # add definitions from the GcodeProcessorLib and ArcWelder libraries -add_definitions(${GcodeProcessorLib_DEFINITIONS} ${ArcWelder_DEFINITIONS}) +add_definitions(${GcodeProcessorLib_DEFINITIONS} ) #add_definitions("-DHAS_GENERATED_VERSION") # Include the GcodeProcessorLib and ArcWelder's directories -include_directories(${GcodeProcessorLib_INCLUDE_DIRS} ${ArcWelder_INCLUDE_DIRS} ${TCLAP_INCLUDE_DIRS}) +include_directories(${GcodeProcessorLib_INCLUDE_DIRS} ${TCLAP_INCLUDE_DIRS}) # include sourcelist.cmake, which contains our source list and exposes it as the # ArcWelderConsoleSources variable include(sourcelist.cmake) # Add an executable our ArcWelderConsoleSources variable from our sourcelist file -add_executable( - ${PROJECT_NAME} - ${ArcWelderInverseProcessorSources} -) +add_executable(${PROJECT_NAME} ${ArcWelderInverseProcessorSources}) +# change the executable name to ArcWelder or ArcStraightener.exe set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "ArcStraightener") install( @@ -23,7 +21,6 @@ install( DESTINATION bin ) - # specify linking to the GcodeProcessorLib and ArcWelder libraries -target_link_libraries(${PROJECT_NAME} TCLAP GcodeProcessorLib ArcWelder) +target_link_libraries(${PROJECT_NAME} GcodeProcessorLib TCLAP) diff --git a/ArcWelderInverseProcessor/Makefile b/ArcWelderInverseProcessor/Makefile deleted file mode 100644 index a5b9b86..0000000 --- a/ArcWelderInverseProcessor/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -all: - g++ -c -I../GcodeProcessorLib/ inverse_processor.cpp - g++ -o ArcWelderInverseProcessor -I../GcodeProcessorLib/ ../GcodeProcessorLib/*.o inverse_processor.o ArcWelderInverseProcessor.cpp - -clean: - rm -f *.o ArcWelderInverseProcessor diff --git a/ArcWelderInverseProcessor/arc_interpolation.cpp b/ArcWelderInverseProcessor/arc_interpolation.cpp new file mode 100644 index 0000000..9ef4e9e --- /dev/null +++ b/ArcWelderInverseProcessor/arc_interpolation.cpp @@ -0,0 +1,246 @@ +#include "arc_interpolation.h" +#include +#include "gcode_position.h" +#include "marlin_1.h" +#include "marlin_2.h" +#include "repetier.h" +#include "prusa.h" +#include "smoothieware.h" +#include "utilities.h" + + +gcode_position_args arc_interpolation::get_args_(bool g90_g91_influences_extruder, int buffer_size) +{ + gcode_position_args args; + // Configure gcode_position_args + args.g90_influences_extruder = g90_g91_influences_extruder; + args.position_buffer_size = buffer_size; + args.autodetect_position = true; + args.home_x = 0; + args.home_x_none = true; + args.home_y = 0; + args.home_y_none = true; + args.home_z = 0; + args.home_z_none = true; + args.shared_extruder = true; + args.zero_based_extruder = true; + + + args.default_extruder = 0; + args.xyz_axis_default_mode = "absolute"; + args.e_axis_default_mode = "absolute"; + args.units_default = "millimeters"; + args.location_detection_commands = std::vector(); + args.is_bound_ = false; + args.is_circular_bed = false; + args.x_min = -9999; + args.x_max = 9999; + args.y_min = -9999; + args.y_max = 9999; + args.z_min = -9999; + args.z_max = 9999; + return args; +} + +arc_interpolation::arc_interpolation() +{ + p_current_firmware_ = NULL; +} + +arc_interpolation::arc_interpolation(arc_interpolation_args args) +{ + args_ = args; + switch (args.firmware_args.firmware_type) + { + case firmware_types::MARLIN_1: + p_current_firmware_ = new marlin_1(args.firmware_args); + break; + case firmware_types::MARLIN_2: + p_current_firmware_ = new marlin_2(args.firmware_args); + break; + case firmware_types::REPETIER: + p_current_firmware_ = new repetier(args.firmware_args); + break; + case firmware_types::PRUSA: + p_current_firmware_ = new prusa(args.firmware_args); + break; + case firmware_types::SMOOTHIEWARE: + p_current_firmware_ = new smoothieware(args.firmware_args); + } + // Initialize the source position + p_source_position_ = new gcode_position(get_args_(p_current_firmware_->get_g90_g91_influences_extruder(), DEFAULT_GCODE_BUFFER_SIZE)); +} + +arc_interpolation::~arc_interpolation() +{ + delete p_source_position_; + if (p_current_firmware_ != NULL) + { + delete p_current_firmware_; + } + +} + +void arc_interpolation::process() +{ + // Create a stringstream we can use for messaging. + std::stringstream stream; + + // Create a current and target position variable for arc processing + firmware_state state; + firmware_position current; + firmware_position target; + // I, J, and R parameter values + double i=0, j=0, r=0; + // bool values for is_clockwise and is_relative + bool is_clockwise = false, is_relative = false; + // e absolute offset, in case we are outputting absolute e + double offset_absolute_e = 0; + + int read_lines_before_clock_check = 5000; + //std::cout << "stabilization::process_file - Processing file.\r\n"; + stream << "Decompressing gcode file."; + stream << "Source File: " << args_.source_path << "\n"; + stream << "Target File: " << args_.target_path << "\n"; + std::cout << stream.str(); + const clock_t start_clock = clock(); + + // Create the source file read stream and target write stream + std::ifstream gcode_file; + gcode_file.open(args_.source_path.c_str()); + output_file_.open(args_.target_path.c_str()); + std::string line; + int lines_with_no_commands = 0; + gcode_file.sync_with_stdio(false); + output_file_.sync_with_stdio(false); + gcode_parser parser; + int gcodes_processed = 0; + if (gcode_file.is_open()) + { + if (output_file_.is_open()) + { + parsed_command cmd; + // Communicate every second + while (std::getline(gcode_file, line)) + { + lines_processed_++; + + cmd.clear(); + parser.try_parse_gcode(line.c_str(), cmd); + bool has_gcode = false; + if (cmd.gcode.length() > 0) + { + has_gcode = true; + gcodes_processed++; + } + else + { + lines_with_no_commands++; + } + + p_source_position_->update(cmd, lines_processed_, gcodes_processed, -1); + + if (cmd.command == "G2" || cmd.command == "G3") + { + // increment the number of arc commands encountered + num_arc_commands_++; + // Get the current and previous positions + position* p_cur_pos = p_source_position_->get_current_position_ptr(); + position* p_pre_pos = p_source_position_->get_previous_position_ptr(); + // create the current and target positions + current.x = p_pre_pos->get_gcode_x(); + current.y = p_pre_pos->get_gcode_y(); + current.z = p_pre_pos->get_gcode_z(); + current.e = p_pre_pos->get_current_extruder().get_offset_e(); + current.f = p_pre_pos->f; + // set the current firmware position + p_current_firmware_->set_current_position(current); + + target.x = p_cur_pos->get_gcode_x(); + target.y = p_cur_pos->get_gcode_y(); + target.z = p_cur_pos->get_gcode_z(); + target.e = p_cur_pos->get_current_extruder().get_offset_e(); + target.f = p_cur_pos->f; + + state.is_extruder_relative = p_pre_pos->is_extruder_relative; + state.is_relative = p_pre_pos->is_relative; + // set the current firmware state + p_current_firmware_->set_current_state(state); + + // get I, J, and R + i = 0; + j = 0; + r = 0; + for (unsigned int index = 0; index < cmd.parameters.size(); index++) + { + parsed_command_parameter p = cmd.parameters[index]; + if (p.name == "I") + { + i = p.double_value; + } + else if (p.name == "J") + { + j = p.double_value; + } + else if (p.name == "R") + { + r = p.double_value; + } + } + + // If r is 0, calculate the radius + if(r==0) + { + r = utilities::hypot(i, j); + } + + is_clockwise = cmd.command == "G2" ? 1 : 0; + is_relative = p_cur_pos->is_extruder_relative; + offset_absolute_e = p_pre_pos->get_current_extruder().get_offset_e(); + + // run the callback and capture any created gcode commands + std::string gcodes = p_current_firmware_->interpolate_arc(target, i, j, r, is_clockwise); + if (gcodes.length() > 0) + { + // there are gcodes to write, write them! + output_file_ << gcodes << "\n"; + } + } + else + { + // Nothing to do with the current line, just write it to disk. + output_file_ << line << "\n"; + } + + } + output_file_.close(); + } + else + { + std::cout << "Unable to open the output file for writing.\n"; + } + std::cout << "Closing the input file.\n"; + gcode_file.close(); + } + else + { + std::cout << "Unable to open the gcode file for processing.\n"; + } + + const clock_t end_clock = clock(); + const double total_seconds = (static_cast(end_clock) - static_cast(start_clock)) / CLOCKS_PER_SEC; + + stream.clear(); + stream.str(""); + stream << "Completed file processing\r\n"; + stream << "\tLines Processed : " << lines_processed_ << "\r\n"; + stream << "\tArc Commands Processed: " << num_arc_commands_ << "\r\n"; + stream << "\tArc Segments Generated: " << p_current_firmware_->get_num_arc_segments_generated() << "\r\n"; + stream << "\tTotal Seconds : " << total_seconds << "\r\n"; + std::cout << stream.str(); +} + +std::string arc_interpolation::get_firmware_argument_description() const +{ + return p_current_firmware_->get_argument_description(); +} \ No newline at end of file diff --git a/ArcWelderInverseProcessor/arc_interpolation.h b/ArcWelderInverseProcessor/arc_interpolation.h new file mode 100644 index 0000000..bdea61f --- /dev/null +++ b/ArcWelderInverseProcessor/arc_interpolation.h @@ -0,0 +1,56 @@ +#pragma once +#include "firmware.h" +#include +#include +#include "gcode_position.h" + +#define DEFAULT_GCODE_BUFFER_SIZE 50 +struct arc_interpolation_args +{ + arc_interpolation_args() + { + + source_path = ""; + target_path = ""; + } + /// + /// Firmware arguments. Not all options will apply to all firmware types. + /// + firmware_arguments firmware_args; + /// + /// Required: the path to the source file containing G2/G3 commands. + /// + std::string source_path; + /// + /// Optional: the path to the target file. If left blank the source file will be overwritten by the target. + /// + std::string target_path; + +}; + +class arc_interpolation +{ + + public: + arc_interpolation(); + arc_interpolation(arc_interpolation_args args); + virtual ~arc_interpolation(); + void process(); + /// + /// Outputs a string description of the firmware arguments. + /// + /// + std::string get_firmware_argument_description() const; + private: + arc_interpolation_args args_; + gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size); + std::string source_path_; + std::string target_path_; + gcode_position* p_source_position_; + std::ofstream output_file_; + int lines_processed_ = 0; + firmware* p_current_firmware_; + int num_arc_commands_; + +}; + diff --git a/ArcWelderInverseProcessor/arc_interpolation_structs.h b/ArcWelderInverseProcessor/arc_interpolation_structs.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/ArcWelderInverseProcessor/arc_interpolation_structs.h @@ -0,0 +1 @@ +#pragma once diff --git a/ArcWelderInverseProcessor/firmware.cpp b/ArcWelderInverseProcessor/firmware.cpp new file mode 100644 index 0000000..c8237e9 --- /dev/null +++ b/ArcWelderInverseProcessor/firmware.cpp @@ -0,0 +1,153 @@ +#include "firmware.h" +#include "utilities.h" + +firmware::firmware() { + version_index_ = -1; + num_arc_segments_generated_ = 0; +}; + +firmware::firmware(firmware_arguments args) : args_(args) { + version_index_ = -1; + num_arc_segments_generated_ = 0; +}; + +std::string firmware::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) +{ + throw "Function not yet implemented"; +} + +void firmware::apply_arguments() +{ + throw "Function not yet implemented"; +} + +void firmware::set_current_position(firmware_position& position) +{ + position_ = position; +} + +void firmware::set_current_state(firmware_state& state) +{ + state_ = state; +} + +int firmware::get_num_arc_segments_generated() +{ + return num_arc_segments_generated_; +} + +std::string firmware::g1_command(firmware_position& target) +{ + num_arc_segments_generated_++; + std::string gcode = "G1 "; + gcode.reserve(96); + + bool has_x = position_.x != target.x; + bool has_y = position_.y != target.y; + bool has_z = position_.z != target.z; + bool has_e = position_.e != target.e; + bool has_f = position_.f != target.f; + bool is_first_parameter = true; + if (has_x) + { + gcode += is_first_parameter ? "X" : " X"; + gcode += utilities::dtos(state_.is_relative ? target.x - position_.x : target.x, 3); + is_first_parameter = false; + } + + if (has_y) + { + gcode += is_first_parameter ? "Y" : " Y"; + gcode += utilities::dtos(state_.is_relative ? target.y - position_.y : target.y, 3); + is_first_parameter = false; + } + + if (has_z) + { + gcode += is_first_parameter ? "Z" : " Z"; + gcode += utilities::dtos(state_.is_relative ? target.z - position_.z : target.z, 3); + is_first_parameter = false; + } + + if (has_e) + { + gcode += is_first_parameter ? "E" : " E"; + gcode += utilities::dtos(state_.is_extruder_relative ? target.e - position_.e : target.e, 3); + is_first_parameter = false; + } + + if (has_f) + { + gcode += is_first_parameter ? "F" : " F"; + gcode += utilities::dtos(target.f, 0); + } + + return gcode; +} + +bool firmware::is_valid_version(std::string version) +{ + if (version == LATEST_FIRMWARE_VERSION_NAME) + { + return true; + } + for (std::vector::const_iterator i = version_names_.begin(); i != version_names_.end(); ++i) { + // process i + if (*i == version) + { + return true; + } + } + return false; +} + +std::vector firmware::get_version_names() +{ + return version_names_; +} + +bool firmware::get_g90_g91_influences_extruder() +{ + return args_.g90_g91_influences_extruder; +} + +std::string firmware::get_argument_description() { + + return args_.get_argument_description(); +} + +void firmware::set_versions(std::vector version_names, std::string latest_release_version_name) +{ + args_.latest_release_version = latest_release_version_name; + std::string requested_version = args_.version; + if (requested_version == LATEST_FIRMWARE_VERSION_NAME || args_.version == latest_release_version_name) + { + requested_version = latest_release_version_name; + } + version_index_ = (int)version_names.size() - 1; + for (int i = 0; i < version_names.size(); i++) + { + std::string version = version_names[i]; + version_names_.push_back(version); + if (version == requested_version) + { + version_index_ = i; + } + } +} + +firmware_arguments firmware::get_default_arguments_for_current_version() const +{ + return firmware_arguments(); +} + +void firmware::set_arguments(firmware_arguments args) +{ + args_ = args; + this->apply_arguments(); +} + +firmware_arguments firmware::arguments_changed(firmware_arguments current_args, firmware_arguments new_args) +{ + return new_args; +} \ No newline at end of file diff --git a/ArcWelderInverseProcessor/firmware.h b/ArcWelderInverseProcessor/firmware.h new file mode 100644 index 0000000..a11b057 --- /dev/null +++ b/ArcWelderInverseProcessor/firmware.h @@ -0,0 +1,373 @@ +#pragma once +#include "firmware_types.h" +#include +#include +#include +#include + +#define M_PI 3.14159265358979323846 // pi +#define DEFAULT_FIRMWARE_TYPE firmware_types::MARLIN_2 +#define LATEST_FIRMWARE_VERSION_NAME "LATEST_RELEASE" +#define DEFAULT_FIRMWARE_VERSION_NAME LATEST_FIRMWARE_VERSION_NAME +// Arc interpretation settings: +#define DEFAULT_MM_PER_ARC_SEGMENT 1.0 // REQUIRED - The enforced maximum length of an arc segment +#define DEFAULT_ARC_SEGMENTS_PER_R 0; +#define DEFAULT_MIN_MM_PER_ARC_SEGMENT 0 /* OPTIONAL - the enforced minimum length of an interpolated segment. Must be smaller than + MM_PER_ARC_SEGMENT. Only has an effect if MIN_ARC_SEGMENTS > 0 or ARC_SEGMENTS_PER_SEC > 0 */ + // If both MIN_ARC_SEGMENTS and ARC_SEGMENTS_PER_SEC is defined, the minimum calculated segment length is used. +#define DEFAULT_MIN_ARC_SEGMENTS 0 // OPTIONAL - The enforced minimum segments in a full circle of the same radius. +#define DEFAULT_ARC_SEGMENTS_PER_SEC 0 // OPTIONAL - Use feedrate to choose segment length. +// approximation will not be used for the first segment. Subsequent segments will be corrected following DEFAULT_N_ARC_CORRECTION. +#define DEFAULT_N_ARC_CORRECTIONS 24 +// This setting is for the gcode position processor to help interpret G90/G91 behavior +#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false +// This currently is only used in Smoothieware. The maximum error for line segments that divide arcs. Set to 0 to disable. +#define DEFAULT_MM_MAX_ARC_ERROR 0.01 + +struct firmware_state { + firmware_state() { + is_relative = false; + is_extruder_relative = false; + } + bool is_relative; + bool is_extruder_relative; +}; + +struct firmware_position { + firmware_position() { + x = 0; + y = 0; + z = 0; + e = 0; + f = 0; + } + double x; + double y; + double z; + double e; + double f; +}; + +struct firmware_arguments { +public: + + firmware_arguments() { + mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; + arc_segments_per_r = DEFAULT_ARC_SEGMENTS_PER_R; + min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT; + min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; + arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC; + n_arc_correction = DEFAULT_N_ARC_CORRECTIONS; + g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTRUDER; + mm_max_arc_error = DEFAULT_MM_MAX_ARC_ERROR; + version = DEFAULT_FIRMWARE_VERSION_NAME; + firmware_type = (firmware_types)DEFAULT_FIRMWARE_TYPE; + latest_release_version = LATEST_FIRMWARE_VERSION_NAME; + + // add a list of all possible arguments, including aliases + all_arguments_.clear(); + all_arguments_.push_back("mm_per_arc_segment"); + all_arguments_.push_back("arc_segments_per_r"); + all_arguments_.push_back("min_mm_per_arc_segment"); + all_arguments_.push_back("min_arc_segments"); + all_arguments_.push_back("arc_segments_per_sec"); + all_arguments_.push_back("n_arc_correction"); + all_arguments_.push_back("g90_g91_influences_extruder"); + all_arguments_.push_back("mm_max_arc_error"); + all_arguments_.push_back("min_circle_segments"); + all_arguments_.push_back("min_arc_segment_mm"); + all_arguments_.push_back("max_arc_segment_mm"); + }; + + /// + /// The maximum mm per arc segment. + /// + double mm_per_arc_segment; + /// + /// The maximum segment length + /// + double arc_segments_per_r; + /// + /// The minimum mm per arc segment. If less than or equal to 0, this is disabled + /// + double min_mm_per_arc_segment; + /// + /// The number of arc segments that will be drawn per second based on the given feedrate. + /// If less than or equal to zero, this is disabled. + /// + double arc_segments_per_sec; + /// + /// This currently is only used in Smoothieware. The maximum error for line segments that divide arcs. Set to 0 to disable. + /// + double mm_max_arc_error; + /// + /// The minimum number of arc segments in a full circle of the arc's radius. + /// If less than or equal to zero, this is disabled + /// + int min_arc_segments; + /// + /// // Number of interpolated segments before true sin and cos corrections will be applied. + /// If less than or equal to zero, true sin and cos will always be used. + /// + int n_arc_correction; + /// + /// This value will set the behavior of G90/G91. + /// + bool g90_g91_influences_extruder; + + /// + /// The type of firmware to use when interpolating. + /// + firmware_types firmware_type; + /// + /// The firmware version to use. Defaults to LATEST + /// + std::string version; + /// + /// True if the current version is the latest release. For informational purposes only + /// + std::string latest_release_version; + /// Aliases for variour parameters + int get_min_circle_segments() const + { + return min_arc_segments; + } + void set_min_circle_segments(int segments) + { + min_arc_segments = segments; + } + + double get_min_arc_segment_mm() const + { + return min_mm_per_arc_segment; + } + + void set_min_arc_segment_mm(double mm) + { + min_mm_per_arc_segment = mm; + } + + double get_max_arc_segment_mm() const + { + return mm_per_arc_segment; + } + + void set_max_arc_segment_mm(double mm) + { + mm_per_arc_segment = mm; + } + + void set_used_arguments(std::vector arguments) + { + used_arguments_ = arguments; + } + + std::vector get_unused_arguments() + { + std::vector unused_arguments; + for (std::vector::iterator it = all_arguments_.begin(); it != all_arguments_.end(); it++) + { + if (!is_argument_used(*it)) + { + unused_arguments.push_back(*it); + } + } + return unused_arguments; + } + + std::string get_unused_arguments_string() + { + std::string unusaed_argument_string = ""; + std::vector unused_argumnts = get_unused_arguments(); + for (std::vector::iterator it = unused_argumnts.begin(); it != unused_argumnts.end(); it++) + { + if (unusaed_argument_string.size() > 0) + { + unusaed_argument_string += ", "; + } + unusaed_argument_string += *it; + } + return unusaed_argument_string; + } + + std::vector get_available_arguments() + { + return used_arguments_; + } + + std::string get_argument_description() { + std::stringstream stream; + stream << "Firmware Arguments:\n"; + stream << "\tFirmware Type : " << firmware_type_names[firmware_type] << "\n"; + stream << "\tFirmware Version : " << (version == LATEST_FIRMWARE_VERSION_NAME || version == latest_release_version ? latest_release_version + " (" + LATEST_FIRMWARE_VERSION_NAME + ")" : version) <<"\n"; + stream << std::fixed << std::setprecision(0); + // Bool values + if (is_argument_used("g90_g91_influences_extruder")) + { + stream << "\tg90_g91_influences_extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n"; + } + + // Int values + if (is_argument_used("min_arc_segments")) + { + stream << "\tmin_arc_segments : " << min_arc_segments << "\n"; + } + if (is_argument_used("min_circle_segments")) + { + stream << "\tmin_circle_segments : " << get_min_circle_segments() << "\n"; + } + if (is_argument_used("n_arc_correction")) + { + stream << "\tn_arc_correction : " << n_arc_correction << "\n"; + } + + stream << std::fixed << std::setprecision(2); + // Double values + // + if (is_argument_used("mm_per_arc_segment")) + { + stream << "\tmm_per_arc_segment : " << mm_per_arc_segment << "\n"; + } + // + if (is_argument_used("arc_segments_per_r")) + { + stream << "\tarc_segments_per_r : " << arc_segments_per_r << "\n"; + } + // + if (is_argument_used("min_mm_per_arc_segment")) + { + stream << "\tmin_mm_per_arc_segment : " << min_mm_per_arc_segment << "\n"; + } + // + if (is_argument_used("arc_segments_per_sec")) + { + stream << "\tarc_segments_per_sec : " << arc_segments_per_sec << "\n"; + } + // + if (is_argument_used("mm_max_arc_error")) + { + stream << "\tmm_max_arc_error : " << mm_max_arc_error << "\n"; + } + // + if (is_argument_used("min_arc_segment_mm")) + { + stream << "\tmin_arc_segment_mm : " << get_min_arc_segment_mm() << "\n"; + } + // + if (is_argument_used("max_arc_segment_mm")) + { + stream << "\tmax_arc_segment_mm : " << get_max_arc_segment_mm() << "\n"; + } + + std::string unused_argument_string = get_unused_arguments_string(); + if (unused_argument_string.size() > 0) + { + stream << "The following parameters do not apply to this firmware version: " << unused_argument_string << "\n"; + } + return stream.str(); + + } + bool is_argument_used(std::string argument_name) + { + return (std::find(used_arguments_.begin(), used_arguments_.end(), argument_name) != used_arguments_.end()); + } + private: + std::vector all_arguments_; + std::vector used_arguments_; + +}; + +class firmware +{ +public: + + firmware(); + + firmware(firmware_arguments args); + + /// + /// Generate G1 gcode strings separated by line breaks representing the supplied G2/G3 command. + /// + /// The current printer position + /// The target printer position + /// Specifies the X offset for the arc's center. + /// Specifies the Y offset for the arc's center. + /// Specifies the radius of the arc. If r is greater than 0, this will override the i and j parameters. + /// If true, this is a G2 command. If false, this is a G3 command. + /// If this is true, the extruder is currently in relative mode. Else it is in absolute mode. + /// This is the absolute offset for absolute E coordinates if the extruder is not in relative mode. + /// + virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise); + + /// + /// Sets the current position. Should be called before interpolate_arc. + /// + /// The position to set + void set_current_position(firmware_position& position); + + /// + /// Sets firmware offsets and the xyze axis mode. + /// + /// The state to set + void set_current_state(firmware_state& state); + /// + /// Create a G1 command from the current position and offsets. + /// + /// The position of the printer after the G1 command is completed. + /// The G1 command + virtual std::string g1_command(firmware_position& target); + + /// + /// Checks a string to see if it is a valid version. + /// + /// The version to check. + /// True if the supplied version is valid + bool is_valid_version(std::string version); + + /// + /// Returns all valid versions for this firmware. + /// + /// Vector of strings, one for each supported version + std::vector get_version_names(); + + /// + /// Returns the current g90_g91_influences_extruder value for the firmware. + /// + /// + bool get_g90_g91_influences_extruder(); + + /// + /// Returns the number of arc segments that were generated from g2/g3 commands. + /// + /// + int get_num_arc_segments_generated(); + + /// + /// Outputs a string description of the firmware arguments. + /// + /// + std::string get_argument_description(); + + /// + /// Sets all available versions names and the version index based on args_.version + /// + /// + void set_versions(std::vector version_names, std::string latest_release_version_name); + + virtual firmware_arguments get_default_arguments_for_current_version()const; + + + void set_arguments(firmware_arguments args); + + virtual void apply_arguments(); + +protected: + firmware_position position_; + firmware_state state_; + firmware_arguments args_; + std::vector version_names_; + int version_index_; + int num_arc_segments_generated_; + + virtual firmware_arguments arguments_changed(firmware_arguments current_args, firmware_arguments new_args); +}; diff --git a/ArcWelderInverseProcessor/firmware_types.cpp b/ArcWelderInverseProcessor/firmware_types.cpp deleted file mode 100644 index 61820cf..0000000 --- a/ArcWelderInverseProcessor/firmware_types.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "firmware_types.h" diff --git a/ArcWelderInverseProcessor/firmware_types.h b/ArcWelderInverseProcessor/firmware_types.h index b3507ad..775a731 100644 --- a/ArcWelderInverseProcessor/firmware_types.h +++ b/ArcWelderInverseProcessor/firmware_types.h @@ -1,4 +1,12 @@ #pragma once -enum firmware_types { Marlin2, Repiter }; +#include + +enum firmware_types { MARLIN_1=0, MARLIN_2=1, REPETIER=2, PRUSA=3, SMOOTHIEWARE=4}; +#define NUM_FIRMWARE_TYPES 5 +static const std::string firmware_type_names[NUM_FIRMWARE_TYPES] = { + "MARLIN_1", "MARLIN_2", "REPETIER", "PRUSA", "SMOOTHIEWARE" +}; + + diff --git a/ArcWelderInverseProcessor/marlin_1.cpp b/ArcWelderInverseProcessor/marlin_1.cpp new file mode 100644 index 0000000..6269645 --- /dev/null +++ b/ArcWelderInverseProcessor/marlin_1.cpp @@ -0,0 +1,357 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Marlin 1 arc interpolation simulator. Please see the copyright notices in the function definitions +// starting with plan_arc_ for the original license. +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2021 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +#include "marlin_1.h" +#include "utilities.h" +marlin_1::marlin_1(firmware_arguments args) : firmware(args) { + feedrate_mm_s = 0; + current_position = new float[MARLIN_XYZE]; + apply_arguments(); +}; + +marlin_1::~marlin_1() +{ + delete current_position; +} + +void marlin_1::apply_arguments() +{ + static const std::vector marlin_1_firmware_version_names{ + "1.1.9.1" + }; + set_versions(marlin_1_firmware_version_names, "1.1.9.1"); + marlin_1_version_ = (marlin_1::marlin_1_firmware_versions)version_index_; + std::vector used_arguments; + /* Add case statement if we ever add any additional firmware versions + switch (marlin_1_version_) + { + default:*/ + plan_arc_ = &marlin_1::plan_arc_1_1_9_1; + used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" }; + //break; + //} + + args_.set_used_arguments(used_arguments); +} + +firmware_arguments marlin_1::get_default_arguments_for_current_version() const +{ + // Start off with the current args so they are set up correctly for this firmware type and version + firmware_arguments default_args = args_; + + // firmware defaults + default_args.g90_g91_influences_extruder = false; + // Add the switch in here in case we want to add more versions. + //switch (marlin_1_version_) + //{ + //default: + // Active Settings + default_args.mm_per_arc_segment = 1.0f; + default_args.min_arc_segments = 24; + default_args.n_arc_correction = 25; + // Inactive Settings + default_args.arc_segments_per_r = 0; + default_args.min_mm_per_arc_segment = 0; + default_args.arc_segments_per_sec = 0; + // Settings that do not apply + default_args.mm_max_arc_error = 0; + //break; + //} + + return default_args; +} + +std::string marlin_1::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) +{ + // Clear the current list of gcodes + gcodes_.clear(); + + // Setup the current position + current_position[X_AXIS] = static_cast(position_.x); + current_position[Y_AXIS] = static_cast(position_.y); + current_position[Z_AXIS] = static_cast(position_.z); + current_position[E_AXIS] = static_cast(position_.e); + float marlin_target[MARLIN_XYZE]; + marlin_target[X_AXIS] = static_cast(target.x); + marlin_target[Y_AXIS] = static_cast(target.y); + marlin_target[Z_AXIS] = static_cast(target.z); + marlin_target[E_AXIS] = static_cast(target.e); + float marlin_offset[2]; + marlin_offset[0] = static_cast(i); + marlin_offset[1] = static_cast(j); + // TODO: handle R form!! + + // Set the feedrate + feedrate_mm_s = static_cast(target.f); + uint8_t marlin_isclockwise = is_clockwise ? 1 : 0; + + (this->*plan_arc_)(marlin_target, marlin_offset, marlin_isclockwise); + + return gcodes_; +} + +/// +/// This function was adapted from the 1.1.9.1 release of Marlin firmware, which can be found at the following link: +/// https://github.com/MarlinFirmware/Marlin/blob/1314b31d97bba8cd74c6625c47176d4692f57790/Marlin/Marlin_main.cpp +/// Copyright Notice found on that page: +/// +/// +/// Marlin 3D Printer Firmware +/// Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] +/// +/// Based on Sprinter and grbl. +/// Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// +/// This program is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with this program. If not, see . +/// +/// The target position +/// The I and J offset +/// Is the motion clockwise or counterclockwise +void marlin_1::plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination position + const float(&offset)[2], // Center of rotation relative to current_position + const bool clockwise // Clockwise? +) +{ + // cnc workspace planes variables -- Note: This is NOT implemented, but is added for completeness in case it is in the future. + int active_extruder = 0; + AxisEnum p_axis, q_axis, l_axis; + p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS; + + + // Radius vector from center to current location + float r_P = -offset[0], r_Q = -offset[1]; + + const float radius = HYPOT(r_P, r_Q), + center_P = current_position[p_axis] - r_P, + center_Q = current_position[q_axis] - r_Q, + rt_X = cart[p_axis] - center_P, + rt_Y = cart[q_axis] - center_Q, + linear_travel = cart[l_axis] - current_position[l_axis], + extruder_travel = cart[E_CART] - current_position[E_CART]; + + // CCW angle of rotation between position and target from the circle center. Only one atan2() trig computation required. + float angular_travel = ATAN2(r_P * rt_Y - r_Q * rt_X, r_P * rt_X + r_Q * rt_Y); + if (angular_travel < 0) angular_travel += RADIANS(360); + if (clockwise) angular_travel -= RADIANS(360); + + // Make a circle if the angular rotation is 0 and the target is current position + if (angular_travel == 0 && current_position[p_axis] == cart[p_axis] && current_position[q_axis] == cart[q_axis]) + angular_travel = RADIANS(360); + + const float flat_mm = radius * angular_travel, + mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : ABS(flat_mm); + if (mm_of_travel < 0.001f) return; + + uint16_t segments = (uint16_t)FLOOR(mm_of_travel / (float)(args_.mm_per_arc_segment)); + NOLESS(segments, 1); + + /** + * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + * and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + * r_T = [cos(phi) -sin(phi); + * sin(phi) cos(phi)] * r ; + * + * For arc generation, the center of the circle is the axis of rotation and the radius vector is + * defined from the circle center to the initial position. Each line segment is formed by successive + * vector rotations. This requires only two cos() and sin() computations to form the rotation + * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + * all double numbers are single precision on the Arduino. (True double precision will not have + * round off issues for CNC applications.) Single precision error can accumulate to be greater than + * tool precision in some cases. Therefore, arc path correction is implemented. + * + * Small angle approximation may be used to reduce computation overhead further. This approximation + * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words, + * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + * issue for CNC machines with the single precision Arduino calculations. + * + * This approximation also allows plan_arc to immediately insert a line segment into the planner + * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead. + * This is important when there are successive arc motions. + */ + // Vector rotation matrix values + float raw[MARLIN_XYZE]; + const float theta_per_segment = angular_travel / segments, + linear_per_segment = linear_travel / segments, + extruder_per_segment = extruder_travel / segments, + sin_T = theta_per_segment, + cos_T = 1 - 0.5f * sq(theta_per_segment); // Small angle approximation + + // Initialize the linear axis + raw[l_axis] = current_position[l_axis]; + + // Initialize the extruder axis + raw[E_CART] = current_position[E_CART]; + + const float fr_mm_s = MMS_SCALED(feedrate_mm_s); + + int8_t arc_recalc_count = 0; + if (args_.n_arc_correction > 1) + { + arc_recalc_count = args_.n_arc_correction; + } + + + for (uint16_t i = 1; i < segments; i++) // Iterate (segments-1) times + { + + if (args_.n_arc_correction > 1 && --arc_recalc_count) + { + // Apply vector rotation matrix to previous r_P / 1 + const float r_new_Y = r_P * sin_T + r_Q * cos_T; + r_P = r_P * cos_T - r_Q * sin_T; + r_Q = r_new_Y; + } + else + { + if (args_.n_arc_correction > 1) + { + arc_recalc_count = args_.n_arc_correction; + } + + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + // To reduce stuttering, the sin and cos could be computed at different times. + // For now, compute both at the same time. + const float cos_Ti = (float)utilities::cos(i * (double)theta_per_segment), sin_Ti = (float)utilities::sin(i * (double)theta_per_segment); + r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti; + r_Q = -offset[0] * sin_Ti - offset[1] * cos_Ti; + } + + // Update raw location + raw[p_axis] = center_P + r_P; + raw[q_axis] = center_Q + r_Q; + raw[l_axis] += linear_per_segment; + raw[E_CART] += extruder_per_segment; + + clamp_to_software_endstops(raw); + + + if (!buffer_line_kinematic(raw, feedrate_mm_s, active_extruder)) + break; + } + + buffer_line_kinematic(cart, feedrate_mm_s, active_extruder); + + COPY(current_position, cart); +} + +// Marlin Function Defs +float marlin_1::HYPOT(float x, float y) +{ + return (float)utilities::hypot(x, y); +} + +float marlin_1::ATAN2(float x, float y) +{ + return (float)utilities::atan2(x, y); +} + +float marlin_1::RADIANS(float x) +{ + return (x * (float)M_PI) / 180; +} + +float marlin_1::ABS(float x) +{ + return (float)utilities::abs((double)x); +} + +float marlin_1::FLOOR(float x) +{ + return (float)utilities::floor((double)x); +} + +float marlin_1::NOLESS(uint16_t x, uint16_t y) +{ + if (x < y) + return y; + return x; +} + +float marlin_1::sq(float x) +{ + return x * x; +} + +float marlin_1::MMS_SCALED(float x) +{ + // No scaling + return x; +} + +void marlin_1::COPY(float target[MARLIN_XYZE], const float(&source)[MARLIN_XYZE]) +{ + // This is a slow copy, but speed isn't much of an issue here. + for (int i = 0; i < MARLIN_XYZE; i++) + { + target[i] = source[i]; + } +} + + +void marlin_1::clamp_to_software_endstops(const float(&raw)[MARLIN_XYZE]) +{ + // Do nothing, just added to keep mc_arc identical to the firmware version + return; +} + +//void marlin::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) +bool marlin_1::buffer_line_kinematic(const float(&cart)[MARLIN_XYZE], double fr_mm_s, int active_extruder) +{ + + // create the target position + firmware_position target; + target.x = cart[AxisEnum::X_AXIS]; + target.y = cart[AxisEnum::Y_AXIS]; + target.z = cart[AxisEnum::Z_AXIS]; + target.e = cart[AxisEnum::E_AXIS]; + target.f = fr_mm_s; + if (gcodes_.size() > 0) + { + gcodes_ += "\n"; + } + // Generate the gcode + gcodes_ += g1_command(target); + + // update the current position + set_current_position(target); + return true; +} diff --git a/ArcWelderInverseProcessor/marlin_1.h b/ArcWelderInverseProcessor/marlin_1.h new file mode 100644 index 0000000..9579bfb --- /dev/null +++ b/ArcWelderInverseProcessor/marlin_1.h @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Marlin 1 arc interpolation simulator. Please see the copyright notices in the function definitions +// starting with plan_arc_ for the original license. +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2021 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once +#include "firmware.h" + +#define NUM_MARLIN_1_FIRMWARE_VERSIONS 1 + + +#define MARLIN_XYZE 5 + +class marlin_1 : + public firmware +{ +public: + enum class marlin_1_firmware_versions { V1_1_9_1 = 0 }; + /// + /// Types and enums taken from https://github.com/MarlinFirmware/Marlin/blob/1314b31d97bba8cd74c6625c47176d4692f57790/Marlin/enum.h + /// Note that HANGPRINTER has been disabled to reduce impementation complexity + /// + enum AxisEnum : unsigned char { + X_AXIS = 0, + A_AXIS = 0, + Y_AXIS = 1, + B_AXIS = 1, + Z_AXIS = 2, + C_AXIS = 2, + E_CART = 3, +//#if ENABLED(HANGPRINTER) // Hangprinter order: A_AXIS, B_AXIS, C_AXIS, D_AXIS, E_AXIS +// D_AXIS = 3, +// E_AXIS = 4, +//#else + E_AXIS = 3, +//#endif + X_HEAD, Y_HEAD, Z_HEAD, + ALL_AXES = 0xFE, + NO_AXIS = 0xFF + }; + + marlin_1(firmware_arguments args); + virtual ~marlin_1(); + virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override; + virtual firmware_arguments get_default_arguments_for_current_version() const override; + virtual void apply_arguments() override; +private: + marlin_1_firmware_versions marlin_1_version_; + std::string gcodes_; + float* current_position; + float feedrate_mm_s; + + /// + /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name. + /// + typedef void(marlin_1::* plan_arc_func)(const float(&cart)[MARLIN_XYZE], // Destination position + const float(&offset)[2], // Center of rotation relative to current_position + const bool clockwise // Clockwise? + ); + + void plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination position + const float(&offset)[2], // Center of rotation relative to current_position + const bool clockwise // Clockwise? + ); + + plan_arc_func plan_arc_; + // Marlin Function Defs + float HYPOT(float x, float y); + float ATAN2(float x, float y); + float RADIANS(float x); + float ABS(float x); + float FLOOR(float x); + float NOLESS(uint16_t x, uint16_t y); + float sq(float x); + float MMS_SCALED(float x); + void COPY(float target[MARLIN_XYZE], const float (&source)[MARLIN_XYZE]); + bool buffer_line_kinematic(const float (&cart)[MARLIN_XYZE], double fr_mm_s, int active_extruder); + void clamp_to_software_endstops(const float (&raw)[MARLIN_XYZE]); +}; + diff --git a/ArcWelderInverseProcessor/marlin_2.cpp b/ArcWelderInverseProcessor/marlin_2.cpp new file mode 100644 index 0000000..4893dd1 --- /dev/null +++ b/ArcWelderInverseProcessor/marlin_2.cpp @@ -0,0 +1,677 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Marlin 2 arc interpolation simulator. Please see the copyright notices in the function definitions +// starting with plan_arc_ for the original license. +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2021 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "marlin_2.h" +#include "utilities.h" +marlin_2::marlin_2(firmware_arguments args) : firmware(args) { + feedrate_mm_s = 0; + current_position = new float[MARLIN_2_XYZE]; + apply_arguments(); +}; + +marlin_2::~marlin_2() +{ + delete current_position; +} + +void marlin_2::apply_arguments() +{ + static const std::vector marlin_2_firmware_version_names{ + "2.0.9.1", "2.0.9.2" + }; + set_versions(marlin_2_firmware_version_names, "2.0.9.1"); + marlin_2_version_ = (marlin_2::marlin_2_firmware_versions)version_index_; + std::vector used_arguments; + switch (marlin_2_version_) + { + case marlin_2::marlin_2_firmware_versions::V2_0_9_2: + used_arguments = {"min_arc_segment_mm", "max_arc_segment_mm", "min_circle_segments", "arc_segments_per_sec", "n_arc_correction", "g90_g91_influences_extruder" }; + plan_arc_ = &marlin_2::plan_arc_2_0_9_2; + break; + default: + used_arguments = { "mm_per_arc_segment", "arc_segments_per_r", "min_arc_segments", "arc_segments_per_sec", "n_arc_correction", "g90_g91_influences_extruder" }; + plan_arc_ = &marlin_2::plan_arc_2_0_9_1; + break; + } + args_.set_used_arguments(used_arguments); +} + +firmware_arguments marlin_2::get_default_arguments_for_current_version() const +{ + // Start off with the current args so they are set up correctly for this firmware type and version + firmware_arguments default_args = args_; + + // firmware defaults + default_args.g90_g91_influences_extruder = true; + + switch (marlin_2_version_) + { + case marlin_2::marlin_2_firmware_versions::V2_0_9_2: + // Active Settings + default_args.set_min_arc_segment_mm(0.1f); + default_args.set_max_arc_segment_mm(1.0f); + default_args.set_min_circle_segments(72); + default_args.n_arc_correction = 25; + // Inactive Settings + default_args.arc_segments_per_r = 0; + default_args.arc_segments_per_sec = 0; + // Settings that do not apply + default_args.mm_max_arc_error = 0; + break; + default: + // Active Settings + default_args.mm_per_arc_segment = 1.0f; + default_args.min_arc_segments = 24; + default_args.n_arc_correction = 25; + // Inactive Settings + default_args.arc_segments_per_r = 0; + default_args.min_mm_per_arc_segment = 0; + default_args.arc_segments_per_sec = 0; + // Settings that do not apply + default_args.mm_max_arc_error = 0; + break; + } + return default_args; +} + +std::string marlin_2::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) +{ + // Clear the current list of gcodes + gcodes_.clear(); + + // Setup the current position + current_position[X_AXIS] = static_cast(position_.x); + current_position[Y_AXIS] = static_cast(position_.y); + current_position[Z_AXIS] = static_cast(position_.z); + current_position[E_AXIS] = static_cast(position_.e); + float marlin_target[MARLIN_2_XYZE]; + marlin_target[X_AXIS] = static_cast(target.x); + marlin_target[Y_AXIS] = static_cast(target.y); + marlin_target[Z_AXIS] = static_cast(target.z); + marlin_target[E_AXIS] = static_cast(target.e); + float marlin_offset[2]; + marlin_offset[0] = static_cast(i); + marlin_offset[1] = static_cast(j); + // TODO: handle R form!! + + // Set the feedrate + feedrate_mm_s = static_cast(target.f); + uint8_t marlin_isclockwise = is_clockwise ? 1 : 0; + + (this->*plan_arc_)(marlin_target, marlin_offset, marlin_isclockwise, 0); + + return gcodes_; +} + +/// +/// This function was adapted from the 2.0.9.1 release of Marlin firmware, which can be found at the following link: +/// https://github.com/MarlinFirmware/Marlin/blob/b878127ea04cc72334eb35ce0dca39ccf7d73a68/Marlin/src/gcode/motion/G2_G3.cpp +/// Copyright Notice found on that page: +/// +/// +/// Marlin 3D Printer Firmware +/// Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] +/// +/// Based on Sprinter and grbl. +/// Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// +/// This program is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with this program. If not, see . +/// +/// The target position +/// The I and J offset +/// Is the motion clockwise or counterclockwise +void marlin_2::plan_arc_2_0_9_1( + const float(&cart)[MARLIN_2_XYZE], // Destination position + const float(&offset)[2], // Center of rotation relative to current_position + const bool clockwise, // Clockwise? + const uint8_t circles // Take the scenic route +) +{ + uint8_t p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS; + + // Radius vector from center to current location + float rvec[2]; + rvec[0] = - offset[X_AXIS]; + rvec[1] = - offset[Y_AXIS]; + + const float radius = HYPOT(rvec[0], rvec[1]), + center_P = current_position[p_axis] - rvec[0], + center_Q = current_position[q_axis] - rvec[1], + rt_X = cart[p_axis] - center_P, + rt_Y = cart[q_axis] - center_Q, + start_L = current_position[l_axis]; + + uint16_t min_segments = args_.min_arc_segments > 0 ? args_.min_arc_segments : 1; + + // Angle of rotation between position and target from the circle center. + float angular_travel; + + // Do a full circle if starting and ending positions are "identical" + if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) { + // Preserve direction for circles + angular_travel = clockwise ? -RADIANS(360) : RADIANS(360); + } + else { + // Calculate the angle + angular_travel = ATAN2(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y); + + // Angular travel too small to detect? Just return. + if (!angular_travel) return; + + // Make sure angular travel over 180 degrees goes the other way around. + switch (((angular_travel < 0) << 1) | (int)clockwise) { + case 1: angular_travel -= RADIANS(360); break; // Positive but CW? Reverse direction. + case 2: angular_travel += RADIANS(360); break; // Negative but CCW? Reverse direction. + } + + if (args_.min_arc_segments > 1) + { + min_segments = (uint16_t)CEIL(min_segments * ABS(angular_travel) / RADIANS(360)); + min_segments = (uint16_t)NOLESS(min_segments, 1U); + } + } + + + float linear_travel = cart[Z_AXIS] - start_L; + float extruder_travel = cart[E_AXIS] - current_position[E_AXIS]; + + const float flat_mm = radius * angular_travel, + mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : ABS(flat_mm); + if (mm_of_travel < 0.001f) return; + + const float scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s); + + // Start with a nominal segment length + float seg_length = (float)args_.mm_per_arc_segment; + if (args_.arc_segments_per_r > 0) + { + seg_length = constrain((float)args_.mm_per_arc_segment * radius, (float)args_.mm_per_arc_segment, (float)args_.arc_segments_per_r); + } + else if (args_.arc_segments_per_sec > 0) + { + seg_length = _MAX(scaled_fr_mm_s * RECIPROCAL((float)args_.arc_segments_per_sec), (float)args_.mm_per_arc_segment); + } + + // Divide total travel by nominal segment length + uint16_t segments = (uint16_t)FLOOR(mm_of_travel / seg_length); + //uint16_t segments = FLOOR(mm_of_travel / seg_length); + segments = (uint16_t)NOLESS(segments, min_segments); // At least some segments + seg_length = mm_of_travel / segments; + + /** + * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + * and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + * r_T = [cos(phi) -sin(phi); + * sin(phi) cos(phi)] * r ; + * + * For arc generation, the center of the circle is the axis of rotation and the radius vector is + * defined from the circle center to the initial position. Each line segment is formed by successive + * vector rotations. This requires only two cos() and sin() computations to form the rotation + * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + * all double numbers are single precision on the Arduino. (True double precision will not have + * round off issues for CNC applications.) Single precision error can accumulate to be greater than + * tool precision in some cases. Therefore, arc path correction is implemented. + * + * Small angle approximation may be used to reduce computation overhead further. This approximation + * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words, + * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + * issue for CNC machines with the single precision Arduino calculations. + * + * This approximation also allows plan_arc to immediately insert a line segment into the planner + * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead. + * This is important when there are successive arc motions. + */ + // Vector rotation matrix values + float raw[MARLIN_2_XYZE]; + const float theta_per_segment = angular_travel / segments, + sq_theta_per_segment = sq(theta_per_segment), + sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6, + cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation + + + const float linear_per_segment = linear_travel / segments; + const float extruder_per_segment = extruder_travel / segments; + + // Initialize the linear axis + raw[l_axis] = current_position[l_axis]; + + // Initialize the extruder axis + raw[E_AXIS] = current_position[E_AXIS]; + + int8_t arc_recalc_count = args_.n_arc_correction; + + for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times + + if (args_.n_arc_correction > 1 && --arc_recalc_count) + { + // Apply vector rotation matrix to previous rvec[0] / 1 + const float r_new_Y = rvec[0] * sin_T + rvec[1] * cos_T; + rvec[0] = rvec[0] * cos_T - rvec[1] * sin_T; + rvec[1] = r_new_Y; + } + else + { + if (args_.n_arc_correction > 1) + { + arc_recalc_count = args_.n_arc_correction; + } + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + // To reduce stuttering, the sin and cos could be computed at different times. + // For now, compute both at the same time. + const float cos_Ti = COS(i * theta_per_segment); + const float sin_Ti = SIN(i * theta_per_segment); + rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti; + rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti; + } + + // Update raw location + raw[p_axis] = center_P + rvec[0]; + raw[q_axis] = center_Q + rvec[1]; + raw[l_axis] = start_L, raw[l_axis] + linear_per_segment; + + raw[E_AXIS] += extruder_per_segment; + + apply_motion_limits(raw); + + if (!buffer_line(raw, scaled_fr_mm_s, 0)) + { + break; + } + } + + // Ensure last segment arrives at target location. + COPY(raw, cart); + raw[l_axis] = start_L; + + apply_motion_limits(raw); + + + buffer_line(raw, scaled_fr_mm_s, 0); + + raw[l_axis] = start_L; + COPY(current_position, raw); +} + + + +/// +/// This function was adapted from the 2.0.9.1 release of Marlin firmware, which can be found at the following link: +/// https://github.com/MarlinFirmware/Marlin/blob/b878127ea04cc72334eb35ce0dca39ccf7d73a68/Marlin/src/gcode/motion/G2_G3.cpp +/// Copyright Notice found on that page: +/// +/// +/// Marlin 3D Printer Firmware +/// Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] +/// +/// Based on Sprinter and grbl. +/// Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm +/// +/// This program is free software: you can redistribute it and/or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// +/// This program is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with this program. If not, see . +/// +/// The target position +/// The I and J offset +/// Is the motion clockwise or counterclockwise +void marlin_2::plan_arc_2_0_9_2( + const float(&cart)[MARLIN_2_XYZE], // Destination position + const float(&offset)[2], // Center of rotation relative to current_position + const bool clockwise, // Clockwise? + const uint8_t circles // Take the scenic route +) +{ + int min_circle_segments = args_.get_min_circle_segments() > 0 ? args_.get_min_circle_segments() : 1; + uint8_t p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS; + + // Radius vector from center to current location + float rvec[2]; + rvec[0] = -offset[X_AXIS]; + rvec[1] = -offset[Y_AXIS]; + + const float radius = HYPOT(rvec[0], rvec[1]), + center_P = current_position[p_axis] - rvec[0], + center_Q = current_position[q_axis] - rvec[1], + rt_X = cart[p_axis] - center_P, + rt_Y = cart[q_axis] - center_Q, + start_L = current_position[l_axis]; + + uint16_t min_segments = args_.min_arc_segments > 0 ? args_.min_arc_segments : 1; + + // Angle of rotation between position and target from the circle center. + float angular_travel, abs_angular_travel; + + // Do a full circle if starting and ending positions are "identical" + if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) { + // Preserve direction for circles + angular_travel = clockwise ? -RADIANS(360) : RADIANS(360); + abs_angular_travel = RADIANS(360); + min_segments = min_circle_segments; + } + else { + // Calculate the angle + angular_travel = ATAN2(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y); + + // Angular travel too small to detect? Just return. + if (!angular_travel) return; + + // Make sure angular travel over 180 degrees goes the other way around. + switch (((angular_travel < 0) << 1) | (int)clockwise) { + case 1: angular_travel -= RADIANS(360); break; // Positive but CW? Reverse direction. + case 2: angular_travel += RADIANS(360); break; // Negative but CCW? Reverse direction. + } + + abs_angular_travel = ABS(angular_travel); + + // Apply minimum segments to the arc + const float portion_of_circle = abs_angular_travel / RADIANS(360); // Portion of a complete circle (0 < N < 1) + min_segments = (uint16_t)CEIL((min_circle_segments)*portion_of_circle); // Minimum segments for the arc + } + + float travel_L = cart[Z_AXIS] - start_L; + float travel_E = cart[E_AXIS] - current_position[E_AXIS]; + + // Millimeters in the arc, assuming it's flat + const float flat_mm = radius * abs_angular_travel; + if (flat_mm < 0.001f + && travel_L < 0.001f + ) return; + + // Feedrate for the move, scaled by the feedrate multiplier + const float scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s); + + // Get the nominal segment length based on settings + float nominal_segment_mm; + if (args_.arc_segments_per_sec > 0) { + nominal_segment_mm = constrain(scaled_fr_mm_s * RECIPROCAL((float)args_.arc_segments_per_sec), (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm()); + } + else { + nominal_segment_mm = (float)args_.get_max_arc_segment_mm(); + } + // Number of whole segments based on the nominal segment length + const float nominal_segments = _MAX(FLOOR(flat_mm / nominal_segment_mm), min_segments); + + // A new segment length based on the required minimum + const float segment_mm = constrain(flat_mm / nominal_segments, (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm()); + + // The number of whole segments in the arc, ignoring the remainder + uint16_t segments = (uint16_t)FLOOR(flat_mm / segment_mm); + + // Are the segments now too few to reach the destination? + const float segmented_length = segment_mm * segments; + const bool tooshort = segmented_length < flat_mm - 0.0001f; + const float proportion = tooshort ? segmented_length / flat_mm : 1.0f; + + + /** + * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + * and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + * r_T = [cos(phi) -sin(phi); + * sin(phi) cos(phi)] * r ; + * + * For arc generation, the center of the circle is the axis of rotation and the radius vector is + * defined from the circle center to the initial position. Each line segment is formed by successive + * vector rotations. This requires only two cos() and sin() computations to form the rotation + * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + * all double numbers are single precision on the Arduino. (True double precision will not have + * round off issues for CNC applications.) Single precision error can accumulate to be greater than + * tool precision in some cases. Therefore, arc path correction is implemented. + * + * Small angle approximation may be used to reduce computation overhead further. This approximation + * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words, + * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + * issue for CNC machines with the single precision Arduino calculations. + * + * This approximation also allows plan_arc to immediately insert a line segment into the planner + * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead. + * This is important when there are successive arc motions. + */ + // Vector rotation matrix values + float raw[MARLIN_2_XYZE]; + const float theta_per_segment = proportion * angular_travel / segments, + sq_theta_per_segment = sq(theta_per_segment), + sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6, + cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation + + + const float per_segment_L = proportion * travel_L / segments; + const float extruder_per_segment = proportion * travel_E / segments; + + // For shortened segments, run all but the remainder in the loop + if (tooshort) segments++; + + + // Initialize the linear axis + raw[l_axis] = current_position[l_axis]; + // Initialize the extruder axis + raw[E_AXIS] = current_position[E_AXIS]; + + int8_t arc_recalc_count = args_.n_arc_correction; + + for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times + + if (args_.n_arc_correction > 1 && --arc_recalc_count) + { + // Apply vector rotation matrix to previous rvec[0] / 1 + const float r_new_Y = rvec[0] * sin_T + rvec[1] * cos_T; + rvec[0] = rvec[0] * cos_T - rvec[1] * sin_T; + rvec[1] = r_new_Y; + } + else + { + if (args_.n_arc_correction > 1) + { + arc_recalc_count = args_.n_arc_correction; + } + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + // To reduce stuttering, the sin and cos could be computed at different times. + // For now, compute both at the same time. + const float cos_Ti = COS(i * theta_per_segment); + const float sin_Ti = SIN(i * theta_per_segment); + rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti; + rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti; + } + + // Update raw location + raw[p_axis] = center_P + rvec[0]; + raw[q_axis] = center_Q + rvec[1]; + raw[l_axis] = start_L, raw[l_axis] + per_segment_L; + raw[E_AXIS] += extruder_per_segment; + + apply_motion_limits(raw); + + if (!buffer_line(raw, scaled_fr_mm_s, 0)) + { + break; + } + } + + // Ensure last segment arrives at target location. + COPY(raw, cart); + raw[l_axis] = start_L; + + apply_motion_limits(raw); + + + buffer_line(raw, scaled_fr_mm_s, 0); + + raw[l_axis] = start_L; + COPY(current_position, raw); +} +// Marlin Function Defs +float marlin_2::HYPOT(float x, float y) +{ + return (float)utilities::hypot(x, y); +} + +float marlin_2::ATAN2(float x, float y) +{ + return (float)utilities::atan2(x, y); +} + +float marlin_2::RADIANS(float x) +{ + return (x * (float)M_PI) / 180; +} + +float marlin_2::ABS(float x) +{ + return (float)utilities::abs(x); +} + +float marlin_2::FLOOR(float x) +{ + return (float)utilities::floor(x); +} + +float marlin_2::COS(float x) +{ + return (float)utilities::cos(x); +} + +float marlin_2::SIN(float x) +{ + return (float)utilities::sin(x); +} + +float marlin_2::NOLESS(uint16_t x, uint16_t y) +{ + if (x < y) + return y; + return x; +} + +float marlin_2::sq(float x) +{ + return x * x; +} + +float marlin_2::MMS_SCALED(float x) +{ + // No scaling + return x; +} + +bool marlin_2::WITHIN(float N, float L, float H) +{ + return ((N) >= (L) && (N) <= (H)); +} +bool marlin_2::NEAR_ZERO(float x) +{ + return WITHIN(x, -0.000001f, 0.000001f); +} +bool marlin_2::NEAR(float x, float y) +{ + return NEAR_ZERO((x)-(y)); +} + +float marlin_2::CEIL(float x) +{ + return (float)utilities::ceil(x); +} + +float marlin_2::constrain(float value, float arg_min, float arg_max) +{ + return ((value) < (arg_min) ? (arg_min) : ((value) > (arg_max) ? (arg_max) : (value))); +} + +float marlin_2::_MAX(float x, float y) +{ + if (x>y) return x; + return y; +} +float marlin_2::_MIN(float x, float y) +{ + if (x < y) return x; + return y; +} + +float marlin_2::RECIPROCAL(float x) +{ + return (float)1.0/x; +} +void marlin_2::COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE]) +{ + // This is a slow copy, but speed isn't much of an issue here. + for (int i = 0; i < MARLIN_2_XYZE; i++) + { + target[i] = source[i]; + } +} + +void marlin_2::apply_motion_limits(float (&pos)[MARLIN_2_XYZE]) +{ + // do nothing + return; +} + +//void marlin::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) +bool marlin_2::buffer_line(const float(&cart)[MARLIN_2_XYZE], double fr_mm_s, int active_extruder) +{ + + // create the target position + firmware_position target; + target.x = cart[AxisEnum::X_AXIS]; + target.y = cart[AxisEnum::Y_AXIS]; + target.z = cart[AxisEnum::Z_AXIS]; + target.e = cart[AxisEnum::E_AXIS]; + target.f = fr_mm_s; + if (gcodes_.size() > 0) + { + gcodes_ += "\n"; + } + // Generate the gcode + gcodes_ += g1_command(target); + + return true; +} diff --git a/ArcWelderInverseProcessor/marlin_2.h b/ArcWelderInverseProcessor/marlin_2.h new file mode 100644 index 0000000..f9735ec --- /dev/null +++ b/ArcWelderInverseProcessor/marlin_2.h @@ -0,0 +1,107 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Marlin 2 arc interpolation simulator. Please see the copyright notices in the function definitions +// starting with plan_arc_ for the original license. +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2021 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once +#include "firmware.h" + + +#define MARLIN_2_XYZE 4 + +class marlin_2 : + public firmware +{ +public: + enum class marlin_2_firmware_versions { V2_0_9_1 = 0, V2_0_9_2 = 1}; + /// + /// Types and enums taken from https://github.com/MarlinFirmware/Marlin/blob/cce585f6ca2235d0a534e8f3043d6d502b3bd93b/Marlin/src/core/types.h + /// + // This enum was simplified + enum AxisEnum : uint8_t { + X_AXIS=0, + Y_AXIS=1, + Z_AXIS=2, + E_AXIS=3, + }; + + marlin_2(firmware_arguments args); + virtual ~marlin_2(); + virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override; + virtual firmware_arguments get_default_arguments_for_current_version() const override; + virtual void apply_arguments() override; +private: + marlin_2_firmware_versions marlin_2_version_; + std::string gcodes_; + float* current_position; + float feedrate_mm_s; + /// + /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name. + /// + typedef void(marlin_2::* plan_arc_func)( + const float(&cart)[MARLIN_2_XYZE], // Destination position + const float(&offset)[2], // Center of rotation relative to current_position + const bool clockwise, // Clockwise? + const uint8_t circles // Take the scenic route + ); + + void plan_arc_2_0_9_1( + const float (&cart)[MARLIN_2_XYZE], // Destination position + const float (&offset)[2], // Center of rotation relative to current_position + const bool clockwise, // Clockwise? + const uint8_t circles // Take the scenic route + ); + void plan_arc_2_0_9_2( + const float(&cart)[MARLIN_2_XYZE], // Destination position + const float(&offset)[2], // Center of rotation relative to current_position + const bool clockwise, // Clockwise? + const uint8_t circles // Take the scenic route + ); + + bool buffer_line(const float(&cart)[MARLIN_2_XYZE], double fr_mm_s, int active_extruder); + void apply_motion_limits(float (&pos)[MARLIN_2_XYZE]); + plan_arc_func plan_arc_; + + // Marlin Function Defs + float HYPOT(float x, float y); + float ATAN2(float x, float y); + float RADIANS(float x); + float COS(float x); + float SIN(float s); + float ABS(float x); + float FLOOR(float x); + float NOLESS(uint16_t x, uint16_t y); + float sq(float x); + float MMS_SCALED(float x); + bool NEAR_ZERO(float x); + bool NEAR(float x, float y); + bool WITHIN(float N, float L, float H); + float CEIL(float x); + float constrain(float value, float arg_min, float arg_max); + float _MAX(float x, float y); + float _MIN(float x, float y); + float RECIPROCAL(float x); + void COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE]); +}; + diff --git a/ArcWelderInverseProcessor/marlin_2_arc.cpp b/ArcWelderInverseProcessor/marlin_2_arc.cpp deleted file mode 100644 index 0656015..0000000 --- a/ArcWelderInverseProcessor/marlin_2_arc.cpp +++ /dev/null @@ -1,448 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Arc Welder: Inverse Processor Console Application -// -// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. -// This reduces file size and the number of gcodes per second. -// -// Built using the 'Arc Welder: Anti Stutter' library -// -// Copyright(C) 2020 - Brad Hochgesang -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// This program is free software : you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -// GNU Affero General Public License for more details. -// -// -// You can contact the author at the following email address: -// FormerLurker@pm.me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// This file includes portions from Marlin's motion_control.c file since it is intended to test some firmware modifications. -// This file was included in the AntiStutter project for convenience, and will not be included within the final version. -/* - motion_control.c - high level interface for issuing motion commands - Part of Grbl - - Copyright (c) 2009-2011 Simen Svale Skogsrud - Copyright (c) 2011 Sungeun K. Jeon - Copyright (c) 2020 Brad Hochgesang - - Grbl is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Grbl is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Grbl. If not, see . -*/ - -#include "marlin_2_arc.h" -#include - -//#include "Marlin.h" -//#include "stepper.h" -//#include "planner.h" - -marlin_2_arc::marlin_2_arc(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs) -{ - source_path_ = source_path; - target_path_ = target_path; - p_source_position_ = new gcode_position(get_args_(g90_g91_influences_extruder, buffer_size)); - cs_ = cs; - offset_absolute_e_ = 0; - // ** Gloabal Variable Definition ** - // 20200417 - FormerLurker - Declare two globals and pre-calculate some values that will reduce the - // amount of trig funcitons we need to call while printing. For the price of having two globals we - // save one trig calc per G2/G3 for both MIN_ARC_SEGMENTS and MIN_MM_PER_ARC_SEGMENT. This is a good trade IMHO. - - -#ifdef MIN_ARC_SEGMENTS -// Determines the radius at which the transition from using MM_PER_ARC_SEGMENT to MIN_ARC_SEGMENTS - arc_max_radius_threshold = MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS)); -#endif -/* -#if defined(MIN_ARC_SEGMENTS) && defined(MIN_MM_PER_ARC_SEGMENT) - // Determines the radius at which the transition from using MIN_ARC_SEGMENTS to MIN_MM_PER_ARC_SEGMENT. - arc_min_radius_threshold = MIN_MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS)); -#endif -*/ -} - -gcode_position_args marlin_2_arc::get_args_(bool g90_g91_influences_extruder, int buffer_size) -{ - gcode_position_args args; - // Configure gcode_position_args - args.g90_influences_extruder = g90_g91_influences_extruder; - args.position_buffer_size = buffer_size; - args.autodetect_position = true; - args.home_x = 0; - args.home_x_none = true; - args.home_y = 0; - args.home_y_none = true; - args.home_z = 0; - args.home_z_none = true; - args.shared_extruder = true; - args.zero_based_extruder = true; - - - args.default_extruder = 0; - args.xyz_axis_default_mode = "absolute"; - args.e_axis_default_mode = "absolute"; - args.units_default = "millimeters"; - args.location_detection_commands = std::vector(); - args.is_bound_ = false; - args.is_circular_bed = false; - args.x_min = -9999; - args.x_max = 9999; - args.y_min = -9999; - args.y_max = 9999; - args.z_min = -9999; - args.z_max = 9999; - return args; -} - -marlin_2_arc::~marlin_2_arc() -{ - delete p_source_position_; -} - -void marlin_2_arc::process() -{ - // Create a stringstream we can use for messaging. - std::stringstream stream; - - int read_lines_before_clock_check = 5000; - //std::cout << "stabilization::process_file - Processing file.\r\n"; - stream << "Decompressing gcode file."; - stream << "Source File: " << source_path_ << "\n"; - stream << "Target File: " << target_path_ << "\n"; - std::cout << stream.str(); - const clock_t start_clock = clock(); - - // Create the source file read stream and target write stream - std::ifstream gcode_file; - gcode_file.open(source_path_.c_str()); - output_file_.open(target_path_.c_str()); - std::string line; - int lines_with_no_commands = 0; - gcode_file.sync_with_stdio(false); - output_file_.sync_with_stdio(false); - gcode_parser parser; - int gcodes_processed = 0; - if (gcode_file.is_open()) - { - if (output_file_.is_open()) - { - //stream.clear(); - //stream.str(""); - //stream << "Opened file for reading. File Size: " << file_size_ << "\n"; - //std::cout << stream.str(); - parsed_command cmd; - // Communicate every second - while (std::getline(gcode_file, line)) - { - lines_processed_++; - - cmd.clear(); - parser.try_parse_gcode(line.c_str(), cmd); - bool has_gcode = false; - if (cmd.gcode.length() > 0) - { - has_gcode = true; - gcodes_processed++; - } - else - { - lines_with_no_commands++; - } - - p_source_position_->update(cmd, lines_processed_, gcodes_processed, -1); - - if (cmd.command == "G2" || cmd.command == "G3") - { - position* p_cur_pos = p_source_position_->get_current_position_ptr(); - position* p_pre_pos = p_source_position_->get_previous_position_ptr(); - float position[4]; - position[X_AXIS] = static_cast(p_pre_pos->get_gcode_x()); - position[Y_AXIS] = static_cast(p_pre_pos->get_gcode_y()); - position[Z_AXIS] = static_cast(p_pre_pos->get_gcode_z()); - position[E_AXIS] = static_cast(p_pre_pos->get_current_extruder().get_offset_e()); - float target[4]; - target[X_AXIS] = static_cast(p_cur_pos->get_gcode_x()); - target[Y_AXIS] = static_cast(p_cur_pos->get_gcode_y()); - target[Z_AXIS] = static_cast(p_cur_pos->get_gcode_z()); - target[E_AXIS] = static_cast(p_cur_pos->get_current_extruder().get_offset_e()); - float offset[2]; - offset[X_AXIS] = 0.0; - offset[Y_AXIS] = 0.0; - for (unsigned int index = 0; index < cmd.parameters.size(); index++) - { - parsed_command_parameter p = cmd.parameters[index]; - if (p.name == "I") - { - offset[X_AXIS] = static_cast(p.double_value); - } - else if (p.name == "J") - { - offset[Y_AXIS] = static_cast(p.double_value); - } - } - float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc - uint8_t isclockwise = cmd.command == "G2" ? 1 : 0; - output_relative_ = p_cur_pos->is_extruder_relative; - offset_absolute_e_ = p_pre_pos->get_current_extruder().get_offset_e(); - mc_arc(position, target, offset, static_cast(p_cur_pos->f), radius, isclockwise, 0); - } - else - { - output_file_ << line << "\n"; - } - - } - output_file_.close(); - } - else - { - std::cout << "Unable to open the output file for writing.\n"; - } - std::cout << "Closing the input file.\n"; - gcode_file.close(); - } - else - { - std::cout << "Unable to open the gcode file for processing.\n"; - } - - const clock_t end_clock = clock(); - const double total_seconds = (static_cast(end_clock) - static_cast(start_clock)) / CLOCKS_PER_SEC; - - stream.clear(); - stream.str(""); - stream << "Completed file processing\r\n"; - stream << "\tLines Processed : " << lines_processed_ << "\r\n"; - stream << "\tTotal Seconds : " << total_seconds << "\r\n"; - stream << "\tExtra Trig Count : " << trig_calc_count << "\r\n"; - stream << "\tTotal E Adjustment : " << total_e_adjustment << "\r\n"; - std::cout << stream.str(); -} - -// The arc is approximated by generating a huge number of tiny, linear segments. The length of each -// segment is configured in settings.mm_per_arc_segment. -void marlin_2_arc::mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder) -{ - // Extract the position to reduce indexing at the cost of a few bytes of mem - float p_x = position[X_AXIS]; - float p_y = position[Y_AXIS]; - float p_z = position[Z_AXIS]; - float p_e = position[E_AXIS]; - - float t_x = target[X_AXIS]; - float t_y = target[Y_AXIS]; - float t_z = target[Z_AXIS]; - float t_e = target[E_AXIS]; - - float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location - float r_axis_y = -offset[Y_AXIS]; - float center_axis_x = p_x - r_axis_x; - float center_axis_y = p_y - r_axis_y; - float travel_z = t_z - p_z; - float extruder_travel_total = t_e - p_e; - - float rt_x = t_x - center_axis_x; - float rt_y = t_y - center_axis_y; - // 20200419 - Add a variable that will be used to hold the arc segment length - float mm_per_arc_segment = cs_.mm_per_arc_segment; - - // CCW angle between position and target from circle center. Only one atan2() trig computation required. - float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y); - if (angular_travel_total < 0) { angular_travel_total += (float)(2 * M_PI); } - - bool check_mm_per_arc_segment_max = false; - if (cs_.min_arc_segments > 0) - { - // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation - // Do this before converting the angular travel for clockwise rotation - mm_per_arc_segment = (float)(radius * ((2.0f * M_PI) / cs_.min_arc_segments)); - check_mm_per_arc_segment_max = true; - } - - if (cs_.arc_segments_per_sec > 0) - { - // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation - float mm_per_arc_segment_sec = (float)((feed_rate / 60.0f) * (1.0f / cs_.arc_segments_per_sec)); - if (mm_per_arc_segment_sec < mm_per_arc_segment) - mm_per_arc_segment = mm_per_arc_segment_sec; - check_mm_per_arc_segment_max = true; - } - - if (cs_.min_mm_per_arc_segment > 0) - { - check_mm_per_arc_segment_max = true; - // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined - // This prevents a very high number of segments from being generated for curves of a short radius - if (mm_per_arc_segment < cs_.min_mm_per_arc_segment) mm_per_arc_segment = cs_.min_mm_per_arc_segment; - } - - if (check_mm_per_arc_segment_max && mm_per_arc_segment > cs_.mm_per_arc_segment) mm_per_arc_segment = cs_.mm_per_arc_segment; - - - - // Adjust the angular travel if the direction is clockwise - if (isclockwise) { angular_travel_total -= (float)(2 * M_PI); } - - //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving - //to compensate when start pos = target pos && angle is zero -> angle = 2Pi - if (p_x == t_x && p_y == t_y && angular_travel_total == 0) - { - angular_travel_total += (float)(2 * M_PI); - } - //end fix G03 - - // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are - // calculating here - float millimeters_of_travel_arc = hypot(angular_travel_total * radius, std::fabs(travel_z)); - if (millimeters_of_travel_arc < 0.001) { return; } - // Calculate the total travel per segment - // Calculate the number of arc segments - uint16_t segments = static_cast(ceil(millimeters_of_travel_arc / mm_per_arc_segment)); - - - // Calculate theta per segments and linear (z) travel per segment - float theta_per_segment = angular_travel_total / segments; - float linear_per_segment = travel_z / (segments); - // Calculate the extrusion amount per segment - float segment_extruder_travel = extruder_travel_total / (segments); - /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, - and phi is the angle of rotation. Based on the solution approach by Jens Geisler. - r_T = [cos(phi) -sin(phi); - sin(phi) cos(phi] * r ; - - For arc generation, the center of the circle is the axis of rotation and the radius vector is - defined from the circle center to the initial position. Each line segment is formed by successive - vector rotations. This requires only two cos() and sin() computations to form the rotation - matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since - all double numbers are single precision on the Arduino. (True double precision will not have - round off issues for CNC applications.) Single precision error can accumulate to be greater than - tool precision in some cases. Therefore, arc path correction is implemented. - - The small angle approximation was removed because of excessive errors for small circles (perhaps unique to - 3d printing applications, causing significant path deviation and extrusion issues). - Now there will be no corrections applied, but an accurate initial sin and cos will be calculated. - This seems to work with a very high degree of accuracy and results in much simpler code. - - Finding a faster way to approximate sin, knowing that there can be substantial deviations from the true - arc when using the previous approximation, would be beneficial. - */ - - // Don't bother calculating cot_T or sin_T if there is only 1 segment. - if (segments > 1) - { - // Initialize the extruder axis - float cos_T, sin_T, sin_Ti, cos_Ti; - //float cos_T = cos(theta_per_segment); - //float sin_T = sin(theta_per_segment); - float sq_theta_per_segment = theta_per_segment * theta_per_segment; - sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6; - cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation - - //cos_T = 1 - 0.5 * theta_per_segment * theta_per_segment; // Small angle approximation - //sin_T = theta_per_segment; - float r_axisi; - uint16_t i; - int8_t count = 0; - for (i = 1; i < segments; i++) { // Increment (segments-1) - - if (count < cs_.n_arc_correction /*&& theta_per_segment > 0.001 */) { - // Apply vector rotation matrix - r_axisi = r_axis_x * sin_T + r_axis_y * cos_T; - r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T; - r_axis_y = r_axisi; - count++; - } - else { - // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. - // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = cos(i * theta_per_segment); - sin_Ti = sin(i * theta_per_segment); - r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti; - r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti; - count = 0; - - } - - - - // Update arc_target location - p_x = center_axis_x + r_axis_x; - p_y = center_axis_y + r_axis_y; - p_z += linear_per_segment; - p_e += segment_extruder_travel; - // We can't clamp to the target because we are interpolating! We would need to update a position, clamp to it - // after updating from calculated values. - //clamp_to_software_endstops(position); - plan_buffer_line(p_x, p_y, travel_z > 0, p_z, p_e, feed_rate, extruder); - } - } - // Ensure last segment arrives at target location. - // Here we could clamp, but why bother. We would need to update our current position, clamp to it - //clamp_to_software_endstops(target); - plan_buffer_line(t_x, t_y, travel_z> 0, t_z, t_e, feed_rate, extruder); - position[X_AXIS] = t_x; - position[Y_AXIS] = t_y; - position[Z_AXIS] = t_z; - position[E_AXIS] = t_e; -} - -void marlin_2_arc::clamp_to_software_endstops(float* target) -{ - // Do nothing, just added to keep mc_arc identical to the firmware version - return; -} - -void marlin_2_arc::plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) -{ - std::stringstream stream; - stream << std::fixed; - - position * previous_pos = p_source_position_->get_previous_position_ptr(); - position* current_pos = p_source_position_->get_current_position_ptr(); - - stream << "G1 X" << std::setprecision(3) << x << " Y" << y; - if (has_z) - { - stream << " Z" << z; - } - - double output_e = e; - if (previous_pos->is_extruder_relative) - { - output_e = e - offset_absolute_e_; - offset_absolute_e_ = e; - } - - stream << std::setprecision(5) << " E" << output_e; - - - if (feed_rate != previous_pos->f) - { - stream << std::setprecision(0) << " F" << feed_rate; - } - - if (!current_pos->command.comment.empty()) - { - stream << ";" << current_pos->command.comment; - } - stream << "\n"; - output_file_ << stream.str(); -} diff --git a/ArcWelderInverseProcessor/marlin_2_arc.h b/ArcWelderInverseProcessor/marlin_2_arc.h deleted file mode 100644 index a7a2ac3..0000000 --- a/ArcWelderInverseProcessor/marlin_2_arc.h +++ /dev/null @@ -1,95 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Arc Welder: Inverse Processor Console Application -// -// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. -// This reduces file size and the number of gcodes per second. -// -// Built using the 'Arc Welder: Anti Stutter' library -// -// Copyright(C) 2020 - Brad Hochgesang -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// This program is free software : you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -// GNU Affero General Public License for more details. -// -// -// You can contact the author at the following email address: -// FormerLurker@pm.me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma once - -#include -#include "gcode_position.h" -#include -#include -#include -#include -#include - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef signed char int8_t; -#define M_PI 3.14159265358979323846 // pi -enum AxisEnum { X_AXIS = 0, Y_AXIS= 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 }; -// Arc interpretation settings: -#define DEFAULT_MM_PER_ARC_SEGMENT 1.0 // REQUIRED - The enforced maximum length of an arc segment -#define DEFAULT_MIN_MM_PER_ARC_SEGMENT 0 /* OPTIONAL - the enforced minimum length of an interpolated segment. Must be smaller than - MM_PER_ARC_SEGMENT. Only has an effect if MIN_ARC_SEGMENTS > 0 or ARC_SEGMENTS_PER_SEC > 0 */ - // If both MIN_ARC_SEGMENTS and ARC_SEGMENTS_PER_SEC is defined, the minimum calculated segment length is used. -#define DEFAULT_MIN_ARC_SEGMENTS 0 // OPTIONAL - The enforced minimum segments in a full circle of the same radius. -#define DEFAULT_ARC_SEGMENTS_PER_SEC 0 // OPTIONAL - Use feedrate to choose segment length. -// approximation will not be used for the first segment. Subsequent segments will be corrected following DEFAULT_N_ARC_CORRECTION. -#define DEFAULT_N_ARC_CORRECTIONS 24 - -struct ConfigurationStore { - ConfigurationStore() { - mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; - min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT; - min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; - arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC; - n_arc_correction = DEFAULT_N_ARC_CORRECTIONS; - } - float mm_per_arc_segment; // This value is ALWAYS used. - float min_mm_per_arc_segment; // if less than or equal to 0, this is disabled - int min_arc_segments; // If less than or equal to zero, this is disabled - double arc_segments_per_sec; // If less than or equal to zero, this is disabled - int n_arc_correction; - -}; -class marlin_2_arc { -public: - marlin_2_arc(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs = ConfigurationStore()); - virtual ~marlin_2_arc(); - void process(); - void mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); - -private: - ConfigurationStore cs_; - gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size); - std::string source_path_; - std::string target_path_; - gcode_position* p_source_position_; - std::ofstream output_file_; - bool output_relative_; - double offset_absolute_e_; - float arc_max_radius_threshold; - //float arc_min_radius_threshold; - float total_e_adjustment; - int trig_calc_count = 0; - int lines_processed_ = 0; - void clamp_to_software_endstops(float* target); - - void plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target=NULL); - -}; - - - - - diff --git a/ArcWelderInverseProcessor/prusa.cpp b/ArcWelderInverseProcessor/prusa.cpp new file mode 100644 index 0000000..ddd8a53 --- /dev/null +++ b/ArcWelderInverseProcessor/prusa.cpp @@ -0,0 +1,466 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Prusa arc interpolation simulator. Please see the copyright notices in the function definitions +// starting with mc_arc_ for the original license. +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2021 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +#include "prusa.h" +#include +#include "utilities.h" +prusa::prusa(firmware_arguments args) : firmware(args) { + apply_arguments(); +}; + +void prusa::apply_arguments() +{ + const std::vector prusa_firmware_version_names{ + "3.10.0", "3.11.0" + }; + set_versions(prusa_firmware_version_names, "3.10.0"); + prusa_version_ = (prusa::prusa_firmware_versions)version_index_; + std::vector used_arguments; + + switch (prusa_version_) + { + case prusa::prusa_firmware_versions::V3_11_0: + mc_arc_ = &prusa::mc_arc_3_11_0; + used_arguments = { "mm_per_arc_segment", "min_arc_segments", "min_mm_per_arc_segment", "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" }; + break; + default: + mc_arc_ = &prusa::mc_arc_3_10_0; + used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" }; + break; + } + args_.set_used_arguments(used_arguments); + cs.arc_segments_per_sec = args_.arc_segments_per_sec; + cs.min_arc_segments = args_.min_arc_segments; + cs.min_mm_per_arc_segment = (float)args_.min_mm_per_arc_segment; + cs.mm_per_arc_segment = (float)args_.mm_per_arc_segment; + cs.n_arc_correction = args_.n_arc_correction; + +} + + +firmware_arguments prusa::get_default_arguments_for_current_version() const +{ + // Start off with the current args so they are set up correctly for this firmware type and version + firmware_arguments default_args = args_; + + // firmware defaults + default_args.g90_g91_influences_extruder = false; + // Leave the switch in here in case we want to add more versions. + switch (prusa_version_) + { + case prusa::prusa_firmware_versions::V3_11_0: + // Active Settings + default_args.mm_per_arc_segment = 1.0f; + default_args.n_arc_correction = 25; + default_args.min_arc_segments = 24; + // Inactive Settings + default_args.arc_segments_per_r = 0; + default_args.min_mm_per_arc_segment = 0; + default_args.arc_segments_per_sec = 0; + break; + default: + // Active Settings + default_args.mm_per_arc_segment = 1.0f; + default_args.min_arc_segments = 24; + default_args.n_arc_correction = 25; + // Inactive Settings + default_args.arc_segments_per_r = 0; + default_args.min_mm_per_arc_segment = 0; + default_args.arc_segments_per_sec = 0; + break; + } + return default_args; +} + +std::string prusa::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) +{ + // Clear the current list of gcodes + gcodes_.clear(); + // Set up the necessary values to call mc_arc + float prusa_position[4]; + prusa_position[X_AXIS] = static_cast(position_.x); + prusa_position[Y_AXIS] = static_cast(position_.y); + prusa_position[Z_AXIS] = static_cast(position_.z); + prusa_position[E_AXIS] = static_cast(position_.e); + float prusa_target[4]; + prusa_target[X_AXIS] = static_cast(target.x); + prusa_target[Y_AXIS] = static_cast(target.y); + prusa_target[Z_AXIS] = static_cast(target.z); + prusa_target[E_AXIS] = static_cast(target.e); + float prusa_offset[2]; + prusa_offset[0] = static_cast(i); + prusa_offset[1] = static_cast(j); + float prusa_radius = static_cast(r); + if (prusa_radius != 0) + { + prusa_radius = (float)utilities::hypot(prusa_offset[X_AXIS], prusa_offset[Y_AXIS]); // Compute arc radius for mc_arc + } + float prusa_f = static_cast(target.f); + + uint8_t prusa_isclockwise = is_clockwise ? 1 : 0; + + (this->*mc_arc_)(prusa_position, prusa_target, prusa_offset, prusa_f, prusa_radius, prusa_isclockwise, 0); + + return gcodes_; +} + +/// +/// This function was adapted from the 3.10.0 release of Prusa's firmware, which can be found at the following link: +/// https://github.com/prusa3d/Prusa-Firmware/blob/04de9c0c8a49a4bee11b8c4789be3f18c1e1ccfe/Firmware/motion_control.cpp +/// Copyright Notice found on that page: +/// +/// motion_control.c - high level interface for issuing motion commands +/// Part of Grbl +/// Copyright(c) 2009 - 2011 Simen Svale Skogsrud +/// Copyright(c) 2011 Sungeun K.Jeon +/// +/// Grbl is free software : you can redistribute it and /or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// Grbl is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +/// GNU General Public License for more details. +/// You should have received a copy of the GNU General Public License +/// along with Grbl.If not, see < http://www.gnu.org/licenses/>. +/// +/// The current position +/// The target position +/// The I and J offset +/// The target feedrate +/// The radius of the arc. +/// Is the motion clockwise or counterclockwise +/// unknown +void prusa::mc_arc_3_10_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder) +{ + /// Setup * This code is NOT in the original source, and is added for compatibility with the function definition + uint8_t axis_0 = X_AXIS; + uint8_t axis_1 = Y_AXIS; + uint8_t axis_linear = Z_AXIS; + + // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled(); + // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc + float center_axis0 = position[axis_0] + offset[axis_0]; + float center_axis1 = position[axis_1] + offset[axis_1]; + float linear_travel = target[axis_linear] - position[axis_linear]; + float extruder_travel = target[E_AXIS] - position[E_AXIS]; + float r_axis0 = -offset[axis_0]; // Radius vector from center to current location + float r_axis1 = -offset[axis_1]; + float rt_axis0 = target[axis_0] - center_axis0; + float rt_axis1 = target[axis_1] - center_axis1; + + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1); + if (angular_travel < 0) { angular_travel += 2 * (float)M_PI; } + if (isclockwise) { angular_travel -= 2 * (float)M_PI; } + + //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving + //to compensate when start pos = target pos && angle is zero -> angle = 2Pi + if (position[axis_0] == target[axis_0] && position[axis_1] == target[axis_1] && angular_travel == 0) + { + angular_travel += 2 * (float)M_PI; + } + //end fix G03 + + float millimeters_of_travel = (float)utilities::hypot(angular_travel * radius, fabs(linear_travel)); + if (millimeters_of_travel < 0.001) { return; } + uint16_t segments = (uint16_t)floor(millimeters_of_travel / cs.mm_per_arc_segment); + if (segments == 0) segments = 1; + + /* + // Multiply inverse feed_rate to compensate for the fact that this movement is approximated + // by a number of discrete segments. The inverse feed_rate should be correct for the sum of + // all segments. + if (invert_feed_rate) { feed_rate *= segments; } + */ + float theta_per_segment = angular_travel / segments; + float linear_per_segment = linear_travel / segments; + float extruder_per_segment = extruder_travel / segments; + + /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + r_T = [cos(phi) -sin(phi); + sin(phi) cos(phi] * r ; + + For arc generation, the center of the circle is the axis of rotation and the radius vector is + defined from the circle center to the initial position. Each line segment is formed by successive + vector rotations. This requires only two cos() and sin() computations to form the rotation + matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + all double numbers are single precision on the Arduino. (True double precision will not have + round off issues for CNC applications.) Single precision error can accumulate to be greater than + tool precision in some cases. Therefore, arc path correction is implemented. + Small angle approximation may be used to reduce computation overhead further. This approximation + holds for everything, but very small circles and large mm_per_arc_segment values. In other words, + theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + issue for CNC machines with the single precision Arduino calculations. + + This approximation also allows mc_arc to immediately insert a line segment into the planner + without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. + This is important when there are successive arc motions. + */ + // Vector rotation matrix values + float cos_T = 1 - (float)0.5 * theta_per_segment * theta_per_segment; // Small angle approximation + float sin_T = theta_per_segment; + + float arc_target[4]; + float sin_Ti; + float cos_Ti; + float r_axisi; + uint16_t i; + int8_t count = 0; + + // Initialize the linear axis + arc_target[axis_linear] = position[axis_linear]; + + // Initialize the extruder axis + arc_target[E_AXIS] = position[E_AXIS]; + + for (i = 1; i < segments; i++) { // Increment (segments-1) + + if (count < cs.n_arc_correction) { + // Apply vector rotation matrix + r_axisi = r_axis0 * sin_T + r_axis1 * cos_T; + r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T; + r_axis1 = r_axisi; + count++; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + cos_Ti = cos(i * theta_per_segment); + sin_Ti = sin(i * theta_per_segment); + r_axis0 = -offset[axis_0] * cos_Ti + offset[axis_1] * sin_Ti; + r_axis1 = -offset[axis_0] * sin_Ti - offset[axis_1] * cos_Ti; + count = 0; + } + + // Update arc_target location + arc_target[axis_0] = center_axis0 + r_axis0; + arc_target[axis_1] = center_axis1 + r_axis1; + arc_target[axis_linear] += linear_per_segment; + arc_target[E_AXIS] += extruder_per_segment; + + clamp_to_software_endstops(arc_target); + plan_buffer_line(arc_target[X_AXIS], arc_target[Y_AXIS], arc_target[Z_AXIS], arc_target[E_AXIS], feed_rate, extruder, NULL); + + } + // Ensure last segment arrives at target location. + plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, NULL); + + // plan_set_acceleration_manager_enabled(acceleration_manager_was_enabled); +} + +/// +/// This function was taken from a pull request I submitted for the 3.11.0 release of Prusa's firmware, which can be found at the following link: +/// https://github.com/FormerLurker/Prusa-Firmware/blob/MK3/Firmware/motion_control.cpp +/// +/// It was forked from the original at: https://github.com/prusa3d/Prusa-Firmware +/// +/// Copyright Notice found on that page: +/// +/// motion_control.c - high level interface for issuing motion commands +/// Part of Grbl +/// Copyright(c) 2009 - 2011 Simen Svale Skogsrud +/// Copyright(c) 2011 Sungeun K.Jeon +/// Copyright(c) 2020 Brad Hochgesang +/// Grbl is free software : you can redistribute it and /or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// Grbl is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +/// GNU General Public License for more details. +/// You should have received a copy of the GNU General Public License +/// along with Grbl.If not, see < http://www.gnu.org/licenses/>. +/// +/// The current position +/// The target position +/// The I and J offset +/// The target feedrate +/// The radius of the arc. +/// Is the motion clockwise or counterclockwise +/// unknown +void prusa::mc_arc_3_11_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder) +{ + + float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location + float r_axis_y = -offset[Y_AXIS]; + float center_axis_x = position[X_AXIS] - r_axis_x; + float center_axis_y = position[Y_AXIS] - r_axis_y; + float travel_z = target[Z_AXIS] - position[Z_AXIS]; + float rt_x = target[X_AXIS] - center_axis_x; + float rt_y = target[Y_AXIS] - center_axis_y; + // 20200419 - Add a variable that will be used to hold the arc segment length + float mm_per_arc_segment = cs.mm_per_arc_segment; + // 20210109 - Add a variable to hold the n_arc_correction value + uint8_t n_arc_correction = cs.n_arc_correction; + + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y); + if (angular_travel_total < 0) { angular_travel_total += 2 * (float)M_PI; } + + if (cs.min_arc_segments > 0) + { + // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation + // Do this before converting the angular travel for clockwise rotation + mm_per_arc_segment = radius * ((2.0f * (float)M_PI) / cs.min_arc_segments); + } + if (cs.arc_segments_per_sec > 0) + { + // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation + float mm_per_arc_segment_sec = (feed_rate / 60.0f) * (1.0f / (float)cs.arc_segments_per_sec); + if (mm_per_arc_segment_sec < mm_per_arc_segment) + mm_per_arc_segment = mm_per_arc_segment_sec; + } + + // Note: no need to check to see if min_mm_per_arc_segment is enabled or not (i.e. = 0), since mm_per_arc_segment can never be below 0. + if (mm_per_arc_segment < cs.min_mm_per_arc_segment) + { + // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined + // This prevents a very high number of segments from being generated for curves of a short radius + mm_per_arc_segment = cs.min_mm_per_arc_segment; + } + else if (mm_per_arc_segment > cs.mm_per_arc_segment) { + // 20210113 - This can be implemented in an else if since we can't be below the min AND above the max at the same time. + // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined + mm_per_arc_segment = cs.mm_per_arc_segment; + } + + // Adjust the angular travel if the direction is clockwise + if (isclockwise) { angular_travel_total -= 2 * (float)M_PI; } + + //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving + //to compensate when start pos = target pos && angle is zero -> angle = 2Pi + if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0) + { + angular_travel_total += 2 * (float)M_PI; + } + //end fix G03 + + // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are + // calculating here + const float millimeters_of_travel_arc = hypot(angular_travel_total * radius, fabs(travel_z)); + if (millimeters_of_travel_arc < 0.001) { return; } + + // Calculate the number of arc segments + uint16_t segments = static_cast(ceil(millimeters_of_travel_arc / mm_per_arc_segment)); + + /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + r_T = [cos(phi) -sin(phi); + sin(phi) cos(phi] * r ; + For arc generation, the center of the circle is the axis of rotation and the radius vector is + defined from the circle center to the initial position. Each line segment is formed by successive + vector rotations. This requires only two cos() and sin() computations to form the rotation + matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + all double numbers are single precision on the Arduino. (True double precision will not have + round off issues for CNC applications.) Single precision error can accumulate to be greater than + tool precision in some cases. Therefore, arc path correction is implemented. + The small angle approximation was removed because of excessive errors for small circles (perhaps unique to + 3d printing applications, causing significant path deviation and extrusion issues). + Now there will be no corrections applied, but an accurate initial sin and cos will be calculated. + This seems to work with a very high degree of accuracy and results in much simpler code. + Finding a faster way to approximate sin, knowing that there can be substantial deviations from the true + arc when using the previous approximation, would be beneficial. + */ + + // If there is only one segment, no need to do a bunch of work since this is a straight line! + if (segments > 1) + { + // Calculate theta per segments, and linear (z) travel per segment, e travel per segment + // as well as the small angle approximation for sin and cos. + const float theta_per_segment = angular_travel_total / segments, + linear_per_segment = travel_z / (segments), + segment_extruder_travel = (target[E_AXIS] - position[E_AXIS]) / (segments), + sq_theta_per_segment = theta_per_segment * theta_per_segment, + sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6, + cos_T = 1 - 0.5f * sq_theta_per_segment; + // Loop through all but one of the segments. The last one can be done simply + // by moving to the target. + for (uint16_t i = 1; i < segments; i++) { + if (n_arc_correction-- == 0) { + // Calculate the actual position for r_axis_x and r_axis_y + const float cos_Ti = cos(i * theta_per_segment), sin_Ti = sin(i * theta_per_segment); + r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti; + r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti; + // reset n_arc_correction + n_arc_correction = cs.n_arc_correction; + } + else { + // Calculate X and Y using the small angle approximation + const float r_axisi = r_axis_x * sin_T + r_axis_y * cos_T; + r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T; + r_axis_y = r_axisi; + } + + // Update Position + position[X_AXIS] = center_axis_x + r_axis_x; + position[Y_AXIS] = center_axis_y + r_axis_y; + position[Z_AXIS] += linear_per_segment; + position[E_AXIS] += segment_extruder_travel; + // Clamp to the calculated position. + clamp_to_software_endstops(position); + // Insert the segment into the buffer + plan_buffer_line(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS], feed_rate, extruder, position); + } + } + // Clamp to the target position. + clamp_to_software_endstops(target); + // Ensure last segment arrives at target location. + plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, target); +} + +void prusa::clamp_to_software_endstops(float* target) +{ + // Do nothing, just added to keep mc_arc identical to the firmware version + return; +} + +void prusa::plan_buffer_line(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) +{ + // create the target position + firmware_position target; + target.x = x; + target.y = y; + target.z = z; + target.e = e; + target.f = feed_rate; + if (gcodes_.size() > 0) + { + gcodes_ += "\n"; + } + // Generate the gcode + gcodes_ += g1_command(target); + + // update the current position + set_current_position(target); +} diff --git a/ArcWelderInverseProcessor/prusa.h b/ArcWelderInverseProcessor/prusa.h new file mode 100644 index 0000000..f2b3d7e --- /dev/null +++ b/ArcWelderInverseProcessor/prusa.h @@ -0,0 +1,83 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Inverse Processor Console Application +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2020 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include "gcode_position.h" +#include "firmware.h" +#include "arc_interpolation_structs.h" +#include +#include +#include +#include +#include + +struct ConfigurationStore { + ConfigurationStore() { + mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT; + min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT; + min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; + arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC; + n_arc_correction = DEFAULT_N_ARC_CORRECTIONS; + } + float mm_per_arc_segment; // This value is ALWAYS used. + float min_mm_per_arc_segment; // if less than or equal to 0, this is disabled + int min_arc_segments; // If less than or equal to zero, this is disabled + double arc_segments_per_sec; // If less than or equal to zero, this is disabled + int n_arc_correction; + +}; +class prusa : + public firmware +{ +public: + enum class prusa_firmware_versions { V3_10_0 = 0, V3_11_0 = 1 }; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef signed char int8_t; + enum AxisEnum { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 }; + prusa(firmware_arguments args); + virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override; + virtual firmware_arguments get_default_arguments_for_current_version() const override; + virtual void apply_arguments() override; +private: + std::string gcodes_; + /// + /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name. + /// + ConfigurationStore cs; + typedef void(prusa::*mc_arc_func)(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); + void mc_arc_3_10_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); + void mc_arc_3_11_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); + void clamp_to_software_endstops(float* target); + void plan_buffer_line(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target); + mc_arc_func mc_arc_; + prusa::prusa_firmware_versions prusa_version_; +}; + + + + + diff --git a/ArcWelderInverseProcessor/repetier.cpp b/ArcWelderInverseProcessor/repetier.cpp new file mode 100644 index 0000000..7d5798c --- /dev/null +++ b/ArcWelderInverseProcessor/repetier.cpp @@ -0,0 +1,476 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Repetier arc interpolation simulator. Please see the copyright notices in the function definitions +// starting with plan_arc_ for the original license. +// +// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support. +// This reduces file size and the number of gcodes per second. +// +// Built using the 'Arc Welder: Anti Stutter' library +// +// Copyright(C) 2021 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +#include "repetier.h" +#include "utilities.h" +repetier::repetier(firmware_arguments args) : firmware(args) { + + feedrate = 0; + apply_arguments(); +}; + +void repetier::apply_arguments() +{ + static const std::vector repetier_firmware_version_names{ "1.0.4", "1.0.5" }; + set_versions(repetier_firmware_version_names, "1.0.4"); + repetier_version_ = (repetier::repetier_firmware_versions)version_index_; + std::vector used_arguments; + switch (repetier_version_) + { + case repetier::repetier_firmware_versions::V1_0_5: + used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" }; + arc_ = &repetier::arc_1_0_5; + break; + default: + used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" }; + arc_ = &repetier::arc_1_0_4; + break; + } + + args_.set_used_arguments(used_arguments); + +} +firmware_arguments repetier::get_default_arguments_for_current_version() const +{ + // Start off with the current args so they are set up correctly for this firmware type and version + firmware_arguments default_args = args_; + + + // firmware defaults + default_args.g90_g91_influences_extruder = false; + // Leave the switch in here in case we want to add more versions. + switch (repetier_version_) + { + case repetier::repetier_firmware_versions::V1_0_5: + // Active Settings + default_args.mm_per_arc_segment = 1.0f; + default_args.n_arc_correction = 25; + default_args.min_arc_segments = 24; + // Inactive Settings + default_args.min_arc_segments = 0; + default_args.arc_segments_per_r = 0; + default_args.min_mm_per_arc_segment = 0; + default_args.arc_segments_per_sec = 0; + // Settings that do not apply + default_args.mm_max_arc_error = 0; + break; + default: + // Active Settings + default_args.mm_per_arc_segment = 1.0f; + default_args.n_arc_correction = 25; + // Inactive Settings + default_args.min_arc_segments = 0; + default_args.arc_segments_per_r = 0; + default_args.min_mm_per_arc_segment = 0; + default_args.arc_segments_per_sec = 0; + // Settings that do not apply + default_args.mm_max_arc_error = 0; + break; + } + return default_args; +} + +repetier::~repetier() +{ +} + +std::string repetier::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) +{ + // Clear the current list of gcodes + gcodes_.clear(); + // Set up the necessary values to call mc_arc + float repetier_position[4]; + repetier_position[X_AXIS] = static_cast(position_.x); + repetier_position[Y_AXIS] = static_cast(position_.y); + repetier_position[Z_AXIS] = static_cast(position_.z); + repetier_position[E_AXIS] = static_cast(position_.e); + float repetier_target[4]; + repetier_target[X_AXIS] = static_cast(target.x); + repetier_target[Y_AXIS] = static_cast(target.y); + repetier_target[Z_AXIS] = static_cast(target.z); + repetier_target[E_AXIS] = static_cast(target.e); + float repetier_offset[2]; + repetier_offset[0] = static_cast(i); + repetier_offset[1] = static_cast(j); + float repetier_radius = static_cast(r); + if (repetier_radius != 0) + { + repetier_radius = (float)utilities::hypot(repetier_offset[X_AXIS], repetier_offset[Y_AXIS]); // Compute arc radius for mc_arc + } + float repetier_f = static_cast(target.f); + + uint8_t repetier_isclockwise = is_clockwise ? 1 : 0; + + feedrate = repetier_f; + (this->*arc_)(repetier_position, repetier_target, repetier_offset, repetier_radius, repetier_isclockwise); + + return gcodes_; +} + +/// +/// This is intended as a possible replacement for the arc function in 1.0.4 (1.0.5?), which can be found at the following link: +/// https://github.com/repetier/Repetier-Firmware/blob/2bbda51eb6407faf29a09987fd635c86818d32db/src/ArduinoAVR/Repetier/motion.cpp +/// This copyright notice was taken from the link above: +/// +/// This file is part of Repetier-Firmware. +/// Repetier-Firmware is free software: you can redistribute it and/or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// Repetier-Firmware is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// You should have received a copy of the GNU General Public License +/// along with Repetier-Firmware. If not, see . +/// This firmware is a nearly complete rewrite of the sprinter firmware +/// by kliment (https://github.com/kliment/Sprinter) +/// which based on Tonokip RepRap firmware rewrite based off of Hydra-mmm firmware. +/// Functions in this file are used to communicate using ascii or repetier protocol. +/// +/// The target position +/// The I and J offset +/// Is the motion clockwise or counterclockwise +void repetier::arc_1_0_5(float* position, float* target, float* offset, float radius, uint8_t isclockwise) +{ + // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled(); + // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc + float center_axis0 = position[X_AXIS] + offset[X_AXIS]; + float center_axis1 = position[Y_AXIS] + offset[Y_AXIS]; + float linear_travel = target[Z_AXIS] - position[Z_AXIS]; + float extruder_travel = (target[E_AXIS] - position[E_AXIS]); // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does... + float r_axis0 = -offset[0]; // Radius vector from center to current location + float r_axis1 = -offset[1]; + float rt_axis0 = target[0] - center_axis0; + float rt_axis1 = target[1] - center_axis1; + + float angular_travel; + // First determine if we have a full circle by checking to see if the start and ending + // XY position is the same before and after the arc is drawn + //if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS]) + if (repetier_is_close(position[X_AXIS], target[X_AXIS]) && repetier_is_close(position[Y_AXIS], target[Y_AXIS])) + { + // Preserve direction for circles + angular_travel = isclockwise ? -2.0f * (float)M_PI : 2.0f * (float)M_PI; + } + else + { + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + angular_travel = (float)utilities::atan2((double)(r_axis0 * rt_axis1) - (double)(r_axis1 * rt_axis0), (double)(r_axis0 * rt_axis0) + (double)(r_axis1 * rt_axis1)); + + // No need to draw an arc if there is no angular travel + if (!angular_travel) return; + + // Make sure angular travel over 180 degrees goes the other way around. + if (angular_travel > 0) + { + if (isclockwise) + { + angular_travel -= 2.0f * (float)M_PI; + } + } + else if (!isclockwise) + { + angular_travel += 2.0f * (float)M_PI; + } + } + + // Determine the number of mm of total travel + float millimeters_of_travel = abs(angular_travel) * radius; + if (linear_travel) + { + // If we have any Z motion, add this to the total mm of travel. + millimeters_of_travel = (float)utilities::hypot(millimeters_of_travel, linear_travel); + } + + if (millimeters_of_travel < 0.001f) { + return; // treat as succes because there is nothing to do; + } + + // The speed restrictions will be based on some new parameters. Removing the hard coded values + // Increase segment size if printing faster then computation speed allows + //uint16_t segments = (feedrate > 60.0f ? floor(millimeters_of_travel / min(static_cast(args_.mm_per_arc_segment), feedrate * 0.01666f * static_cast(args_.mm_per_arc_segment))) : floor(millimeters_of_travel / static_cast(args_.mm_per_arc_segment))); + + // Use ceil here since the final segment could be nearly 2x as long as the rest. + // Example, assume millimeters_of_travel = 1.999. In this case segments will be 1, + // the interpolation loop will be skipped, and the segment drawn will be of length + // 1.999, which is not great. + uint16_t segments = (uint16_t)utilities::ceil(millimeters_of_travel / (float)args_.mm_per_arc_segment); + + if (segments == 0) + segments = 1; + /* + // Multiply inverse feed_rate to compensate for the fact that this movement is approximated + // by a number of discrete segments. The inverse feed_rate should be correct for the sum of + // all segments. + if (invert_feed_rate) { feed_rate *= segments; } + */ + float theta_per_segment = angular_travel / segments; + float linear_per_segment = linear_travel/segments; + float extruder_per_segment = extruder_travel / segments; + + /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + r_T = [cos(phi) -sin(phi); + sin(phi) cos(phi] * r ; + For arc generation, the center of the circle is the axis of rotation and the radius vector is + defined from the circle center to the initial position. Each line segment is formed by successive + vector rotations. This requires only two cos() and sin() computations to form the rotation + matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + all double numbers are single precision on the Arduino. (True double precision will not have + round off issues for CNC applications.) Single precision error can accumulate to be greater than + tool precision in some cases. Therefore, arc path correction is implemented. + Small angle approximation may be used to reduce computation overhead further. This approximation + holds for everything, but very small circles and large mm_per_arc_segment values. In other words, + theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + issue for CNC machines with the single precision Arduino calculations. + This approximation also allows mc_arc to immediately insert a line segment into the planner + without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. + This is important when there are successive arc motions. + */ + // Vector rotation matrix values + // Use two terms for better accuracy + float sq_theta_per_segment = theta_per_segment * theta_per_segment; + float cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation + float sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6; + + float arc_target[4]; + float sin_Ti; + float cos_Ti; + float r_axisi; + uint16_t i; + int8_t count = 0; + + // Initialize the linear axis + //arc_target[axis_linear] = position[axis_linear]; + + // Initialize the extruder axis + arc_target[E_AXIS] = position[E_AXIS]; // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does + + for (i = 1; i < segments; i++) { + // Increment (segments-1) + + if (count < args_.n_arc_correction) { //25 pieces + // Apply vector rotation matrix + r_axisi = r_axis0 * sin_T + r_axis1 * cos_T; + r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T; + r_axis1 = r_axisi; + count++; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + cos_Ti = (float)utilities::cos(i * theta_per_segment); + sin_Ti = (float)utilities::sin(i * theta_per_segment); + r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti; + r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti; + count = 0; + } + + // Update arc_target location + arc_target[X_AXIS] = center_axis0 + r_axis0; + arc_target[Y_AXIS] = center_axis1 + r_axis1; + arc_target[Z_AXIS] += linear_per_segment; + arc_target[E_AXIS] += extruder_per_segment; + moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], position[Z_AXIS], arc_target[E_AXIS]); + } + // Ensure last segment arrives at target location. + moveToReal(target[X_AXIS], target[Y_AXIS], position[Z_AXIS], target[E_AXIS]); +} + +/// +/// This function was adapted from the 1.0.4 release of Repetier firmware, which can be found at the following link: +/// https://github.com/repetier/Repetier-Firmware/blob/2bbda51eb6407faf29a09987fd635c86818d32db/src/ArduinoAVR/Repetier/motion.cpp +/// This copyright notice was taken from the link above: +/// +/// This file is part of Repetier-Firmware. +/// Repetier-Firmware is free software: you can redistribute it and/or modify +/// it under the terms of the GNU General Public License as published by +/// the Free Software Foundation, either version 3 of the License, or +/// (at your option) any later version. +/// Repetier-Firmware is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// You should have received a copy of the GNU General Public License +/// along with Repetier-Firmware. If not, see . +/// This firmware is a nearly complete rewrite of the sprinter firmware +/// by kliment (https://github.com/kliment/Sprinter) +/// which based on Tonokip RepRap firmware rewrite based off of Hydra-mmm firmware. +/// Functions in this file are used to communicate using ascii or repetier protocol. +/// +/// The target position +/// The I and J offset +/// Is the motion clockwise or counterclockwise +void repetier::arc_1_0_4(float* position, float* target, float* offset, float radius, uint8_t isclockwise) +{ + // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled(); + // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc + float center_axis0 = position[X_AXIS] + offset[X_AXIS]; + float center_axis1 = position[Y_AXIS] + offset[Y_AXIS]; + //float linear_travel = 0; //target[axis_linear] - position[axis_linear]; + float extruder_travel = (target[E_AXIS] - position[E_AXIS]); // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does... + float r_axis0 = -offset[0]; // Radius vector from center to current location + float r_axis1 = -offset[1]; + float rt_axis0 = target[0] - center_axis0; + float rt_axis1 = target[1] - center_axis1; + /*long xtarget = Printer::destinationSteps[X_AXIS]; + long ytarget = Printer::destinationSteps[Y_AXIS]; + long ztarget = Printer::destinationSteps[Z_AXIS]; + long etarget = Printer::destinationSteps[E_AXIS]; + */ + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + float angular_travel = (float)utilities::atan2((double)(r_axis0 * rt_axis1) - (double)(r_axis1 * rt_axis0), (double)(r_axis0 * rt_axis0) + (double)(r_axis1 * rt_axis1)); + if ((!isclockwise && angular_travel <= 0.00001) || (isclockwise && angular_travel < -0.000001)) { + angular_travel += 2.0f * (float)M_PI; + } + if (isclockwise) { + angular_travel -= 2.0f * (float)M_PI; + } + + float millimeters_of_travel = (float)utilities::fabs(angular_travel) * radius; //hypot(angular_travel*radius, fabs(linear_travel)); + if (millimeters_of_travel < 0.001f) { + return; // treat as succes because there is nothing to do; + } + //uint16_t segments = (radius>=BIG_ARC_RADIUS ? floor(millimeters_of_travel/MM_PER_ARC_SEGMENT_BIG) : floor(millimeters_of_travel/MM_PER_ARC_SEGMENT)); + // Increase segment size if printing faster then computation speed allows + uint16_t segments = (uint16_t)(feedrate > 60.0f ? utilities::floor(millimeters_of_travel / utilities::min(static_cast(args_.mm_per_arc_segment), feedrate * 0.01666f * static_cast(args_.mm_per_arc_segment))) : utilities::floor(millimeters_of_travel / static_cast(args_.mm_per_arc_segment))); + if (segments == 0) + segments = 1; + /* + // Multiply inverse feed_rate to compensate for the fact that this movement is approximated + // by a number of discrete segments. The inverse feed_rate should be correct for the sum of + // all segments. + if (invert_feed_rate) { feed_rate *= segments; } + */ + float theta_per_segment = angular_travel / segments; + //float linear_per_segment = linear_travel/segments; + float extruder_per_segment = extruder_travel / segments; + + /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + r_T = [cos(phi) -sin(phi); + sin(phi) cos(phi] * r ; + For arc generation, the center of the circle is the axis of rotation and the radius vector is + defined from the circle center to the initial position. Each line segment is formed by successive + vector rotations. This requires only two cos() and sin() computations to form the rotation + matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + all double numbers are single precision on the Arduino. (True double precision will not have + round off issues for CNC applications.) Single precision error can accumulate to be greater than + tool precision in some cases. Therefore, arc path correction is implemented. + Small angle approximation may be used to reduce computation overhead further. This approximation + holds for everything, but very small circles and large mm_per_arc_segment values. In other words, + theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + issue for CNC machines with the single precision Arduino calculations. + This approximation also allows mc_arc to immediately insert a line segment into the planner + without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. + This is important when there are successive arc motions. + */ + // Vector rotation matrix values + float cos_T = 1 - 0.5f * theta_per_segment * theta_per_segment; // Small angle approximation + float sin_T = theta_per_segment; + + float arc_target[4]; + float sin_Ti; + float cos_Ti; + float r_axisi; + uint16_t i; + int8_t count = 0; + + // Initialize the linear axis + //arc_target[axis_linear] = position[axis_linear]; + + // Initialize the extruder axis + arc_target[E_AXIS] = position[E_AXIS]; // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does + + for (i = 1; i < segments; i++) { + // Increment (segments-1) + + if (count < args_.n_arc_correction) { //25 pieces + // Apply vector rotation matrix + r_axisi = r_axis0 * sin_T + r_axis1 * cos_T; + r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T; + r_axis1 = r_axisi; + count++; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + cos_Ti = (float)utilities::cos(i * theta_per_segment); + sin_Ti = (float)utilities::sin(i * theta_per_segment); + r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti; + r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti; + count = 0; + } + + // Update arc_target location + arc_target[X_AXIS] = center_axis0 + r_axis0; + arc_target[Y_AXIS] = center_axis1 + r_axis1; + //arc_target[axis_linear] += linear_per_segment; + arc_target[E_AXIS] += extruder_per_segment; + moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], position[Z_AXIS], arc_target[E_AXIS]); + } + // Ensure last segment arrives at target location. + moveToReal(target[X_AXIS], target[Y_AXIS], position[Z_AXIS], target[E_AXIS]); +} + +float repetier::min(float x, float y) +{ + if (x < y) + { + return x; + } + return y; +} + +//void repetier::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) +void repetier::moveToReal(float x, float y, float z, float e) +{ + + // create the target position + firmware_position target; + target.x = x; + target.y = y; + target.z = z; + target.e = e; + target.f = feedrate; + if (gcodes_.size() > 0) + { + gcodes_ += "\n"; + } + // Generate the gcode + gcodes_ += g1_command(target); + + // update the current position + set_current_position(target); +} diff --git a/ArcWelderInverseProcessor/repetier.h b/ArcWelderInverseProcessor/repetier.h new file mode 100644 index 0000000..6b4bcb3 --- /dev/null +++ b/ArcWelderInverseProcessor/repetier.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include "firmware.h" +#include "utilities.h" +#define repetier_is_close_value 0.001f +#define repetier_is_close(x,y) ( repetier_is_close_value > utilities::fabs(x-y) ) +class repetier : + public firmware +{ +public: + enum class repetier_firmware_versions { V1_0_4 = 0, V1_0_5}; + + repetier(firmware_arguments args); + virtual ~repetier(); + virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override; + virtual firmware_arguments get_default_arguments_for_current_version() const override; + virtual void apply_arguments() override; +private: + repetier_firmware_versions repetier_version_; + std::string gcodes_; + const static int REPETIER_XYZE = 4; + enum AxisEnum { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E_AXIS = 3}; + /// + /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name. + /// + typedef void(repetier::* arc_func)(float* position, float* target, float* offset, float radius, uint8_t isclockwise); + + void arc_1_0_4(float* position, float* target, float* offset, float radius, uint8_t isclockwise); + void arc_1_0_5(float* position, float* target, float* offset, float radius, uint8_t isclockwise); + + arc_func arc_; + + // Note that trailing underscore are sometimes dropped to keep the ported function as close as possible to the original + float feedrate; + // Repetier Function Defs + float min(float x, float y); + void moveToReal(float x, float y, float z, float e); +}; + diff --git a/ArcWelderInverseProcessor/repiter_arc.cpp b/ArcWelderInverseProcessor/repiter_arc.cpp deleted file mode 100644 index 83818a0..0000000 --- a/ArcWelderInverseProcessor/repiter_arc.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "repiter_arc.h" - - -/* -// Arc function taken from grbl -// The arc is approximated by generating a huge number of tiny, linear segments. The length of each -// segment is configured in settings.mm_per_arc_segment. -void repiter_arc::arc(float* position, float* target, float* offset, float radius, uint8_t isclockwise) { - // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled(); - // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc - float center_axis0 = position[X_AXIS] + offset[X_AXIS]; - float center_axis1 = position[Y_AXIS] + offset[Y_AXIS]; - //float linear_travel = 0; //target[axis_linear] - position[axis_linear]; - float extruder_travel = (Printer::destinationSteps[E_AXIS] - Printer::currentPositionSteps[E_AXIS]) * Printer::invAxisStepsPerMM[E_AXIS]; - float r_axis0 = -offset[0]; // Radius vector from center to current location - float r_axis1 = -offset[1]; - float rt_axis0 = target[0] - center_axis0; - float rt_axis1 = target[1] - center_axis1; - // CCW angle between position and target from circle center. Only one atan2() trig computation required. - float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1); - if ((!isclockwise && angular_travel <= 0.00001) || (isclockwise && angular_travel < -0.000001)) { - angular_travel += 2.0f * M_PI; - } - if (isclockwise) { - angular_travel -= 2.0f * M_PI; - } - - float millimeters_of_travel = fabs(angular_travel) * radius; //hypot(angular_travel*radius, fabs(linear_travel)); - if (millimeters_of_travel < 0.001f) { - return; // treat as succes because there is nothing to do; - } - //uint16_t segments = (radius>=BIG_ARC_RADIUS ? floor(millimeters_of_travel/MM_PER_ARC_SEGMENT_BIG) : floor(millimeters_of_travel/MM_PER_ARC_SEGMENT)); - // Increase segment size if printing faster then computation speed allows - uint16_t segments = (Printer::feedrate > 60.0f ? floor(millimeters_of_travel / RMath::min(static_cast(MM_PER_ARC_SEGMENT_BIG), Printer::feedrate * 0.01666f * static_cast(MM_PER_ARC_SEGMENT))) : floor(millimeters_of_travel / static_cast(MM_PER_ARC_SEGMENT))); - if (segments == 0) - segments = 1; - - float theta_per_segment = angular_travel / segments; - //float linear_per_segment = linear_travel/segments; - float extruder_per_segment = extruder_travel / segments; - - - // Vector rotation matrix values - float cos_T = 1 - 0.5 * theta_per_segment * theta_per_segment; // Small angle approximation - float sin_T = theta_per_segment; - - float arc_target[4]; - float sin_Ti; - float cos_Ti; - float r_axisi; - uint16_t i; - int8_t count = 0; - - // Initialize the linear axis - //arc_target[axis_linear] = position[axis_linear]; - - // Initialize the extruder axis - arc_target[E_AXIS] = Printer::currentPositionSteps[E_AXIS] * Printer::invAxisStepsPerMM[E_AXIS]; - - for (i = 1; i < segments; i++) { - // Increment (segments-1) - - if ((count & 3) == 0) { - //GCode::readFromSerial(); - Commands::checkForPeriodicalActions(false); - UI_MEDIUM; // do check encoder - } - - if (count < N_ARC_CORRECTION) { //25 pieces - // Apply vector rotation matrix - r_axisi = r_axis0 * sin_T + r_axis1 * cos_T; - r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T; - r_axis1 = r_axisi; - count++; - } - else { - // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. - // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = cos(i * theta_per_segment); - sin_Ti = sin(i * theta_per_segment); - r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti; - r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti; - count = 0; - } - - // Update arc_target location - arc_target[X_AXIS] = center_axis0 + r_axis0; - arc_target[Y_AXIS] = center_axis1 + r_axis1; - //arc_target[axis_linear] += linear_per_segment; - arc_target[E_AXIS] += extruder_per_segment; - Printer::moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], IGNORE_COORDINATE, arc_target[E_AXIS], IGNORE_COORDINATE); - } - // Ensure last segment arrives at target location. - Printer::moveToReal(target[X_AXIS], target[Y_AXIS], IGNORE_COORDINATE, target[E_AXIS], IGNORE_COORDINATE); -} - -*/ \ No newline at end of file diff --git a/ArcWelderInverseProcessor/repiter_arc.h b/ArcWelderInverseProcessor/repiter_arc.h deleted file mode 100644 index 353c040..0000000 --- a/ArcWelderInverseProcessor/repiter_arc.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include -class repiter_arc -{ - void arc(float* position, float* target, float* offset, float radius, uint8_t isclockwise); -}; - diff --git a/ArcWelderInverseProcessor/smoothieware.cpp b/ArcWelderInverseProcessor/smoothieware.cpp new file mode 100644 index 0000000..bf17934 --- /dev/null +++ b/ArcWelderInverseProcessor/smoothieware.cpp @@ -0,0 +1,274 @@ +#include "smoothieware.h" +#include "utilities.h" +smoothieware::smoothieware(firmware_arguments args) : firmware(args) { + feed_rate = 0; + for (int i = 0; i < REPETIER_XYZE; i++) + { + machine_position[i] = 0; + } + THEKERNEL = new SmoothiewareKernel(); + apply_arguments(); +}; + +void smoothieware::apply_arguments() +{ + static const std::vector smoothieware_firmware_version_names{ "2021-06-19" }; + set_versions(smoothieware_firmware_version_names, "2021-06-19"); + smoothieware_version_ = (smoothieware::smoothieware_firmware_versions)version_index_; + std::vector used_arguments; + // Add switch back in if we ever add more versions + //switch (smoothieware_version_) + //{ + //default: + append_arc_ = &smoothieware::append_arc_2021_06_19; + used_arguments = { "mm_per_arc_segment", "mm_max_arc_error", "n_arc_correction", "g90_g91_influences_extruder" }; + //break; + //} + + args_.set_used_arguments(used_arguments); + +} + +smoothieware::~smoothieware() +{ + delete THEKERNEL; +} + +firmware_arguments smoothieware::get_default_arguments_for_current_version() const +{ + // Start off with the current args so they are set up correctly for this firmware type and version + firmware_arguments default_args = args_; + // firmware defaults + default_args.g90_g91_influences_extruder = true; + // Add the switch back if we ever need to add more versions + //switch (smoothieware_version_) + //{ + //default: + // Active Settings + default_args.mm_per_arc_segment = 0.0f; + default_args.mm_max_arc_error = 0.01; + default_args.n_arc_correction = 5; + //break; + //} + return default_args; +} + +std::string smoothieware::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) +{ + // Clear the current list of gcodes + gcodes_.clear(); + + // Setup the current position + machine_position[X_AXIS] = static_cast(position_.x); + machine_position[Y_AXIS] = static_cast(position_.y); + machine_position[Z_AXIS] = static_cast(position_.z); + machine_position[E_AXIS] = static_cast(position_.e); + float smoothieware_target[k_max_actuators]; + smoothieware_target[X_AXIS] = static_cast(target.x); + smoothieware_target[Y_AXIS] = static_cast(target.y); + smoothieware_target[Z_AXIS] = static_cast(target.z); + smoothieware_target[E_AXIS] = static_cast(target.e); + float smoothieware_offset[2]; + smoothieware_offset[0] = static_cast(i); + smoothieware_offset[1] = static_cast(j); + float radius = static_cast(r); + + + // Set the feedrate + feed_rate = static_cast(target.f); + uint8_t smoothieware_isclockwise = is_clockwise ? 1 : 0; + + (this->*append_arc_)(&gcode_, smoothieware_target, smoothieware_offset, radius, smoothieware_isclockwise); + + return gcodes_; +} +// Append an arc to the queue ( cutting it into segments as needed ) +bool smoothieware::append_arc_2021_06_19(SmoothiewareGcode* gcode, const float target[], const float offset[], float radius, bool is_clockwise) +{ + float rate_mm_s = this->feed_rate / seconds_per_minute; + // catch negative or zero feed rates and return the same error as GRBL does + if (rate_mm_s <= 0.0F) { + gcode->is_error = true; + gcode->txt_after_ok = (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0"); + return false; + } + + // Scary math. + float center_axis0 = this->machine_position[this->plane_axis_0] + offset[this->plane_axis_0]; + float center_axis1 = this->machine_position[this->plane_axis_1] + offset[this->plane_axis_1]; + float linear_travel = target[this->plane_axis_2] - this->machine_position[this->plane_axis_2]; + float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to start position + float r_axis1 = -offset[this->plane_axis_1]; + float rt_axis0 = target[this->plane_axis_0] - this->machine_position[this->plane_axis_0] - offset[this->plane_axis_0]; // Radius vector from center to target position + float rt_axis1 = target[this->plane_axis_1] - this->machine_position[this->plane_axis_1] - offset[this->plane_axis_1]; + float angular_travel = 0; + //check for condition where atan2 formula will fail due to everything canceling out exactly + if ((this->machine_position[this->plane_axis_0] == target[this->plane_axis_0]) && (this->machine_position[this->plane_axis_1] == target[this->plane_axis_1])) { + if (is_clockwise) { // set angular_travel to -2pi for a clockwise full circle + angular_travel = (-2 * (float)PI); + } + else { // set angular_travel to 2pi for a counterclockwise full circle + angular_travel = (2 * (float)PI); + } + } + else { + // Patch from GRBL Firmware - Christoph Baumann 04072015 + // CCW angle between position and target from circle center. Only one atan2() trig computation required. + // Only run if not a full circle or angular travel will incorrectly result in 0.0f + angular_travel = atan2f(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1); + if (plane_axis_2 == Y_AXIS) { is_clockwise = !is_clockwise; } //Math for XZ plane is reverse of other 2 planes + if (is_clockwise) { // adjust angular_travel to be in the range of -2pi to 0 for clockwise arcs + if (angular_travel > 0) { angular_travel -= (2 * (float)PI); } + } + else { // adjust angular_travel to be in the range of 0 to 2pi for counterclockwise arcs + if (angular_travel < 0) { angular_travel += (2 * (float)PI); } + } + } + + // initialize linear travel for ABC +#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3 + float abc_travel[n_motors - 3]; + for (int i = A_AXIS; i < n_motors; i++) { + abc_travel[i - 3] = target[i] - this->machine_position[i]; + } +#endif + + + // Find the distance for this gcode + float millimeters_of_travel = (float)utilities::hypot(angular_travel * radius, utilities::fabsf(linear_travel)); + + // We don't care about non-XYZ moves ( for example the extruder produces some of those ) + if (millimeters_of_travel < 0.000001F) { + return false; + } + + // limit segments by maximum arc error + float arc_segment = (float)args_.mm_per_arc_segment; + if ((args_.mm_max_arc_error > 0) && (2 * radius > args_.mm_max_arc_error)) { + float min_err_segment = 2 * (float)utilities::sqrtf((args_.mm_max_arc_error * (2 * radius - args_.mm_max_arc_error))); + if (args_.mm_per_arc_segment < min_err_segment) { + arc_segment = min_err_segment; + } + } + + // catch fall through on above + if (arc_segment < 0.0001F) { + arc_segment = 0.5F; /// the old default, so we avoid the divide by zero + } + + // Figure out how many segments for this gcode + // TODO for deltas we need to make sure we are at least as many segments as requested, also if mm_per_line_segment is set we need to use the + uint16_t segments = (uint16_t)floorf(millimeters_of_travel / arc_segment); + bool moved = false; + + if (segments > 1) { + float theta_per_segment = angular_travel / segments; + float linear_per_segment = linear_travel / segments; +#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3 + float abc_per_segment[n_motors - 3]; + for (int i = 0; i < n_motors - 3; i++) { + abc_per_segment[i] = abc_travel[i] / segments; + } +#endif + + /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + r_T = [cos(phi) -sin(phi); + sin(phi) cos(phi] * r ; + For arc generation, the center of the circle is the axis of rotation and the radius vector is + defined from the circle center to the initial position. Each line segment is formed by successive + vector rotations. This requires only two cos() and sin() computations to form the rotation + matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + all float numbers are single precision on the Arduino. (True float precision will not have + round off issues for CNC applications.) Single precision error can accumulate to be greater than + tool precision in some cases. Therefore, arc path correction is implemented. + Small angle approximation may be used to reduce computation overhead further. This approximation + holds for everything, but very small circles and large mm_per_arc_segment values. In other words, + theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + issue for CNC machines with the single precision Arduino calculations. + This approximation also allows mc_arc to immediately insert a line segment into the planner + without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. + This is important when there are successive arc motions. + */ + // Vector rotation matrix values + float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation + float sin_T = theta_per_segment; + + float arc_target[n_motors]; + float sin_Ti; + float cos_Ti; + float r_axisi; + uint16_t i; + int8_t count = 0; + + // init array for all axis + utilities::memcpy(arc_target, machine_position, n_motors * sizeof(float)); + + // Initialize the linear axis + arc_target[this->plane_axis_2] = this->machine_position[this->plane_axis_2]; + + for (i = 1; i < segments; i++) { // Increment (segments-1) + if (THEKERNEL->is_halted()) return false; // don't queue any more segments + + if (count < args_.n_arc_correction) { + // Apply vector rotation matrix + r_axisi = r_axis0 * sin_T + r_axis1 * cos_T; + r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T; + r_axis1 = r_axisi; + count++; + } + else { + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + cos_Ti = (float)utilities::cosf(i * theta_per_segment); + sin_Ti = (float)utilities::sinf(i * theta_per_segment); + r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti; + r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti; + count = 0; + } + + // Update arc_target location + arc_target[this->plane_axis_0] = center_axis0 + r_axis0; + arc_target[this->plane_axis_1] = center_axis1 + r_axis1; + arc_target[this->plane_axis_2] += linear_per_segment; +#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3 + for (int a = A_AXIS; a < n_motors; a++) { + arc_target[a] += abc_per_segment[a - 3]; + } +#endif + + // Append this segment to the queue + bool b = this->append_milestone(arc_target, rate_mm_s); + moved = moved || b; + } + } + + // Ensure last segment arrives at target location. + if (this->append_milestone(target, rate_mm_s)) moved = true; + + return moved; +} + +bool smoothieware::append_milestone(const float target[], double rate_mm_s) +{ + double rate_mm_min = rate_mm_s * 60; + // create the target position + firmware_position gcode_target; + gcode_target.x = target[AxisEnum::X_AXIS]; + gcode_target.y = target[AxisEnum::Y_AXIS]; + gcode_target.z = target[AxisEnum::Z_AXIS]; + gcode_target.e = target[AxisEnum::E_AXIS]; + gcode_target.f = rate_mm_min; + if (gcodes_.size() > 0) + { + gcodes_ += "\n"; + } + // Generate the gcode + gcodes_ += g1_command(gcode_target); + + return true; + return true; +} diff --git a/ArcWelderInverseProcessor/smoothieware.h b/ArcWelderInverseProcessor/smoothieware.h new file mode 100644 index 0000000..61461c6 --- /dev/null +++ b/ArcWelderInverseProcessor/smoothieware.h @@ -0,0 +1,65 @@ +#pragma once +#include "firmware.h" + +#define SMOOTHIEWARE_MAX_ROBOT_ACTUATORS 4 +struct SmoothiewareGcode { + SmoothiewareGcode() { + is_error = false; + txt_after_ok = ""; + } + bool is_error; + std::string txt_after_ok; + +}; +struct SmoothiewareKernel +{ + bool is_halted() {return false;} +}; +class smoothieware : + public firmware +{ +public: + enum class smoothieware_firmware_versions { V2021_06_19 = 0 }; + smoothieware(firmware_arguments args); + virtual ~smoothieware(); + virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override; + virtual firmware_arguments get_default_arguments_for_current_version() const override; + virtual void apply_arguments() override; +private: + smoothieware::smoothieware_firmware_versions smoothieware_version_; + enum MOTION_MODE_T { + NONE, + SEEK, // G0 + LINEAR, // G1 + CW_ARC, // G2 + CCW_ARC // G3 + }; + std::string gcodes_; + const static int REPETIER_XYZE = 4; + enum AxisEnum { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E_AXIS = 3, A_AXIS = 3 }; // A axis is the same as the E axis. + /// + /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name. + /// + typedef bool(smoothieware::* append_arc_func)(SmoothiewareGcode* gcode, const float target[], const float offset[], float radius, bool is_clockwise); + + bool append_arc_2021_06_19(SmoothiewareGcode* gcode, const float target[], const float offset[], float radius, bool is_clockwise); + + append_arc_func append_arc_; + + // Note that trailing underscore are sometimes dropped to keep the ported function as close as possible to the original + // Repetier Function Defs + bool append_milestone(const float target[], double rate_mm_s); + static const int seconds_per_minute = 60; + static const int k_max_actuators = SMOOTHIEWARE_MAX_ROBOT_ACTUATORS; + static const int n_motors = SMOOTHIEWARE_MAX_ROBOT_ACTUATORS; + float machine_position[k_max_actuators]; + static const int plane_axis_0 = AxisEnum::X_AXIS; + static const int plane_axis_1 = AxisEnum::Y_AXIS; + static const int plane_axis_2 = AxisEnum::Z_AXIS; + static const int plane_axis_3 = AxisEnum::E_AXIS; + const double PI = M_PI; + SmoothiewareGcode gcode_; + SmoothiewareKernel *THEKERNEL; + float feed_rate; +}; + diff --git a/ArcWelderInverseProcessor/sourcelist.cmake b/ArcWelderInverseProcessor/sourcelist.cmake index d5f94b2..6ba1376 100644 --- a/ArcWelderInverseProcessor/sourcelist.cmake +++ b/ArcWelderInverseProcessor/sourcelist.cmake @@ -1,10 +1,20 @@ set(ArcWelderInverseProcessorSources ${ArcWelderInverseProcessorSources} + arc_interpolation.h + arc_interpolation.cpp + arc_interpolation_structs.h ArcWelderInverseProcessor.h ArcWelderInverseProcessor.cpp + firmware.cpp + firmware.h firmware_types.h - firmware_types.cpp - marlin_2_arc.h - marlin_2_arc.cpp - repiter_arc.h - repiter_arc.cpp + marlin_1.cpp + marlin_1.h + marlin_2.cpp + marlin_2.h + prusa.cpp + prusa.h + repetier.cpp + repetier.h + smoothieware.cpp + smoothieware.h ) \ No newline at end of file diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 2ac30b1..23588fd 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -24,7 +24,7 @@ #include "ArcWelderTest.h" #include "logger.h" #include -#include "utilities.h" + int main(int argc, char* argv[]) { diff --git a/GcodeProcessorLib/fpconv.cpp b/GcodeProcessorLib/fpconv.cpp index e22731a..9c2b8ee 100644 --- a/GcodeProcessorLib/fpconv.cpp +++ b/GcodeProcessorLib/fpconv.cpp @@ -190,7 +190,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K) for (divp = tens + 10; kappa > 0; divp++) { unsigned long long div = *divp; - unsigned digit = part1 / div; + unsigned digit = (unsigned int)(part1 / div); if (digit || idx) { digits[idx++] = digit + '0'; @@ -216,7 +216,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K) delta *= 10; kappa--; - unsigned digit = part2 >> -one.exp; + unsigned digit = (unsigned int)(part2 >> -one.exp); if (digit || idx) { digits[idx++] = digit + '0'; } diff --git a/GcodeProcessorLib/fpconv.h b/GcodeProcessorLib/fpconv.h index 6896a3a..8afcb21 100644 --- a/GcodeProcessorLib/fpconv.h +++ b/GcodeProcessorLib/fpconv.h @@ -149,7 +149,7 @@ static Fp find_cachedpow10(int exp, int* k) { const double one_log_ten = 0.30102999566398114; - int approx = -(exp + npowers) * one_log_ten; + int approx = (int)(-(exp + npowers) * one_log_ten); int idx = (approx - firstpower) / steppowers; while (1) { diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index e4b8526..c1ac9fc 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -19,21 +19,30 @@ // You can contact the author at the following email address: // FormerLurker@pm.me //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + #include "utilities.h" -#include -#include -#include -#include -#include -#include "fpconv.h" -const std::string utilities::WHITESPACE_ = " \n\r\t\f\v"; -const char utilities::GUID_RANGE[] = "0123456789abcdef"; -const bool utilities::GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 }; +namespace utilities { + const std::string WHITESPACE_ = " \n\r\t\f\v"; + const char GUID_RANGE[] = "0123456789abcdef"; + const bool GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 }; + + extern const char PATH_SEPARATOR_ = +#ifdef _WIN32 + '\\'; +#else + '/'; +#endif + +} bool utilities::is_zero(double x, double tolerance) { - return std::fabs(x) < tolerance; + return fabs(x) < tolerance; +} +bool utilities::is_zero(double x) +{ + return fabs(x) < ZERO_TOLERANCE; } int utilities::round_up_to_int(double x, double tolerance) @@ -41,32 +50,63 @@ int utilities::round_up_to_int(double x, double tolerance) return int(x + tolerance); } +int utilities::round_up_to_int(double x) +{ + return int(x + ZERO_TOLERANCE); +} + bool utilities::is_equal(double x, double y, double tolerance) { - double abs_difference = std::fabs(x - y); + double abs_difference = fabs(x - y); return abs_difference < tolerance; } +bool utilities::is_equal(double x, double y) +{ + double abs_difference = fabs(x - y); + return abs_difference < ZERO_TOLERANCE; +} + bool utilities::greater_than(double x, double y, double tolerance) { return x > y && !is_equal(x, y, tolerance); } +bool utilities::greater_than(double x, double y) +{ + return x > y && !is_equal(x, y); +} + bool utilities::greater_than_or_equal(double x, double y, double tolerance) { return x > y || is_equal(x, y, tolerance); } +bool utilities::greater_than_or_equal(double x, double y) +{ + return x > y || is_equal(x, y); +} + bool utilities::less_than(double x, double y, double tolerance) { return x < y && !is_equal(x, y, tolerance); } +bool utilities::less_than(double x, double y) +{ + return x < y && !is_equal(x, y); +} + bool utilities::less_than_or_equal(double x, double y, double tolerance) { return x < y || is_equal(x, y, tolerance); } +bool utilities::less_than_or_equal(double x, double y) +{ + return x < y || is_equal(x, y); +} + double utilities::get_cartesian_distance(double x1, double y1, double x2, double y2) { @@ -74,7 +114,7 @@ double utilities::get_cartesian_distance(double x1, double y1, double x2, double double xdif = x1 - x2; double ydif = y1 - y2; double dist_squared = xdif * xdif + ydif * ydif; - return std::sqrt(dist_squared); + return sqrt(dist_squared); } double utilities::get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2) @@ -84,7 +124,7 @@ double utilities::get_cartesian_distance(double x1, double y1, double z1, double double ydif = y1 - y2; double zdif = z1 - z2; double dist_squared = xdif * xdif + ydif * ydif + zdif * zdif; - return std::sqrt(dist_squared); + return sqrt(dist_squared); } double utilities::get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise) @@ -92,10 +132,10 @@ double utilities::get_arc_distance(double x1, double y1, double z1, double x2, d double center_x = x1 - i; double center_y = y1 - j; double radius = hypot(i, j); - double z_dist = z2-z1; + double z_dist = z2 - z1; double rt_x = x2 - center_x; double rt_y = y2 - center_y; - double angular_travel_total = std::atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y); + double angular_travel_total = atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y); if (angular_travel_total < 0) { angular_travel_total += (double)(2.0 * PI_DOUBLE); } // Adjust the angular travel if the direction is clockwise if (is_clockwise) { angular_travel_total -= (float)(2 * PI_DOUBLE); } @@ -104,10 +144,10 @@ double utilities::get_arc_distance(double x1, double y1, double z1, double x2, d { angular_travel_total += (float)(2 * PI_DOUBLE); } - + // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are // calculating here - return hypot(angular_travel_total * radius, std::fabs(z_dist)); + return hypot(angular_travel_total * radius, fabs(z_dist)); } @@ -142,6 +182,33 @@ std::string utilities::trim(const std::string& s) return rtrim(ltrim(s)); } +std::string utilities::join(const std::string* strings, size_t length, std::string sep) +{ + std::string output; + for (int i = 0; i < length; i++) + { + if (i > 0) + { + output += sep; + } + output += strings[i]; + } + return output; +} + +std::string utilities::join(const std::vector strings, std::string sep) +{ + std::string output; + + for (std::vector::const_iterator p = strings.begin(); + p != strings.end(); ++p) { + output += *p; + if (p != strings.end() - 1) + output += sep; + } + return output; +} + std::istream& utilities::safe_get_line(std::istream& is, std::string& t) { t.clear(); @@ -174,7 +241,7 @@ std::istream& utilities::safe_get_line(std::istream& is, std::string& t) } } -std::string utilities::center(std::string input, int width) +std::string utilities::center(std::string input, int width) { int input_width = (int)input.length(); int difference = width - input_width; @@ -182,7 +249,7 @@ std::string utilities::center(std::string input, int width) { return input; } - int left_padding = difference /2; + int left_padding = difference / 2; int right_padding = width - left_padding - input_width; return std::string(left_padding, ' ') + input + std::string(right_padding, ' '); } @@ -231,7 +298,7 @@ std::string utilities::get_percent_change_string(int v1, int v2, int precision) int utilities::get_num_digits(int x) { - x = abs(x); + x = (int)abs(x); return (x < 10 ? 1 : (x < 100 ? 2 : (x < 1000 ? 3 : @@ -247,14 +314,14 @@ int utilities::get_num_digits(int x) int utilities::get_num_digits(double x, int precision) { return get_num_digits( - (int) std::ceil(x * std::pow(10, (double)precision) - .4999999999999) - / std::pow(10, (double)precision) + (int)ceil(x * pow(10, (double)precision) - .4999999999999) + / pow(10, (double)precision) ); } int utilities::get_num_digits(double x) { - return get_num_digits((int) x); + return get_num_digits((int)x); } // Nice utility function found here: https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path @@ -285,7 +352,7 @@ std::vector utilities::splitpath(const std::string& str) return result; } -bool utilities::get_file_path(const std::string& file_path, std::string & path) +bool utilities::get_file_path(const std::string& file_path, std::string& path) { std::vector file_parts = splitpath(file_path); if (file_parts.size() == 0) @@ -311,12 +378,12 @@ std::string utilities::create_uuid() { bool utilities::get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path) { temp_file_path = ""; - if (!utilities::get_file_path(file_path, temp_file_path)) + if (!get_file_path(file_path, temp_file_path)) { return false; } temp_file_path = temp_file_path; - temp_file_path += utilities::create_uuid(); + temp_file_path += create_uuid(); temp_file_path += ".tmp"; return true; } @@ -331,7 +398,92 @@ double utilities::hypot(double x, double y) } if (y == 0.0) return x; y /= x; - return x * std::sqrt(1.0 + y * y); + return x * sqrt(1.0 + y * y); +} + +double utilities::atan2(double y, double x) +{ + return std::atan2(y, x); +} + +double utilities::atan2f(double y, double x) +{ + return std::atan2(y, x); +} + +double utilities::floor(double x) +{ + return std::floor(x); +} + +double utilities::floorf(double x) +{ + return std::floor(x); +} + +double utilities::ceil(double x) +{ + return std::ceil(x); +} + +double utilities::cos(double x) +{ + return std::cos(x); +} + +double utilities::sin(double x) +{ + return std::sin(x); +} + +double utilities::cosf(double x) +{ + return std::cos(x); +} + +double utilities::sinf(double x) +{ + return std::sin(x); +} + +double utilities::abs(double x) +{ + return std::abs(x); +} + +double utilities::fabs(double x) +{ + return std::fabs(x); +} + +double utilities::fabsf(double x) +{ + return std::fabs(x); +} + +double utilities::sqrt(double x) +{ + return std::sqrt(x); +} + +double utilities::sqrtf(double x) +{ + return std::sqrt(x); +} + +double utilities::pow(int e, double x) +{ + return std::pow(e, x); +} + +double utilities::min(float x, float y) +{ + return std::min(x, y); +} + +void* utilities::memcpy(void* dest, const void* src, size_t n) +{ + return std::memcpy(dest, src, n); } std::string utilities::dtos(double x, unsigned char precision) @@ -339,14 +491,14 @@ std::string utilities::dtos(double x, unsigned char precision) static char buffer[FPCONV_BUFFER_LENGTH]; char* p = buffer; buffer[fpconv_dtos(x, buffer, precision)] = '\0'; - /* This is code that can be used to compare the output of the + /* This is code that can be used to compare the output of the modified fpconv_dtos function to the ofstream output Note: It currently only fails for some checks where the original double does not store perfectly. In this case I actually think the dtos output is better than ostringstream! std::ostringstream stream; stream << std::fixed; stream << std::setprecision(precision) << x; - + if (std::string(buffer) != stream.str()) { std::cout << std::fixed << "Failed to convert: " << std::setprecision(24) << x << " Precision:" << std::setprecision(0) << static_cast (precision) << " String:" << std::string(buffer) << " Stream:" << stream.str() << std::endl; @@ -355,7 +507,7 @@ std::string utilities::dtos(double x, unsigned char precision) return buffer; } /* -bool utilities::case_insensitive_compare_char(char& c1, char& c2) +bool case_insensitive_compare_char(char& c1, char& c2) { if (c1 == c2) return true; @@ -366,10 +518,34 @@ bool utilities::case_insensitive_compare_char(char& c1, char& c2) /* * Case Insensitive String Comparision - -bool utilities::case_insensitive_compare(std::string& str1, std::string& str2) + +bool case_insensitive_compare(std::string& str1, std::string& str2) { - return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), &utilities::case_insensitive_compare_char)); + return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), &case_insensitive_compare_char)); +} + +*/ + +std::string utilities::replace(std::string subject, const std::string& search, const std::string& replace) { + size_t pos = 0; + while ((pos = subject.find(search, pos)) != std::string::npos) { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + return subject; +} + +double utilities::rand_range(double min, double max) { + double f = (double)std::rand() / RAND_MAX; + return min + f * (max - min); +} + +unsigned char utilities::rand_range(unsigned char min, unsigned char max) { + double f = (double)std::rand() / RAND_MAX; + return static_cast(static_cast(min) + f * (static_cast(max) - static_cast(min))); } -*/ \ No newline at end of file +int utilities::rand_range(int min, int max) { + double f = (double)std::rand() / RAND_MAX; + return static_cast(static_cast(min) + f * (static_cast(max) - static_cast(min))); +} \ No newline at end of file diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index cf3a97b..7e2f80b 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -20,83 +20,137 @@ // FormerLurker@pm.me //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma once +#ifndef UTILITIES_H +#define UTILITIES_H #include #include #include +#include +#include +#include +#include +#include +#include +#include "fpconv.h" + #define FPCONV_BUFFER_LENGTH 25 // Had to increase the zero tolerance because prusa slicer doesn't always // retract enough while wiping. #define ZERO_TOLERANCE 0.000005 #define PI_DOUBLE 3.14159265358979323846264338327950288 -class utilities{ -public: - static bool is_zero(double x, double tolerance = ZERO_TOLERANCE); - static int round_up_to_int(double x, double tolerance = ZERO_TOLERANCE); - static bool is_equal(double x, double y, double tolerance = ZERO_TOLERANCE); - static bool greater_than(double x, double y, double tolerance = ZERO_TOLERANCE); - static bool greater_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE); - static bool less_than(double x, double y, double tolerance = ZERO_TOLERANCE); - static bool less_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE); - - static double get_cartesian_distance(double x1, double y1, double x2, double y2); - static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2); - static double get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise); - /* Todo: Implement for gcode comment processor - static bool case_insensitive_compare_char(char& c1, char& c2); - static bool case_insensitive_compare(std::string& str1, std::string& str2); - */ - static std::string to_string(double value); - static std::string to_string(int value); - static std::string ltrim(const std::string& s); - static std::string rtrim(const std::string& s); - static std::string trim(const std::string& s); - static std::istream& safe_get_line(std::istream& is, std::string& t); - static std::string center(std::string input, int width); - static double get_percent_change(int v1, int v2); - static double get_percent_change(double v1, double v2); - static std::string get_percent_change_string(int v1, int v2, int precision); - static int get_num_digits(int x); - static int get_num_digits(double x); - static int get_num_digits(double x, int precision); - - static std::vector splitpath(const std::string& str); - static bool get_file_path(const std::string& file_path, std::string& path); - static bool get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path); - static std::string create_uuid(); - // Man I can't wait till I can drop python 2.7 support so I can stop doing everything myself. s - // td::hypot doesn't work for msvc for python 2.7.... - static double hypot(double x, double y); - static std::string dtos(double x, unsigned char precision); - - static double rand_range(double min, double max) { - double f = (double)std::rand() / RAND_MAX; - return min + f * (max - min); - } - - static unsigned char rand_range(unsigned char min, unsigned char max) { - double f = (double)std::rand() / RAND_MAX; - return static_cast(static_cast(min) + f * (static_cast(max) - static_cast(min))); - } - - static int rand_range(int min, int max) { - double f = (double)std::rand() / RAND_MAX; - return static_cast(static_cast(min) + f * (static_cast(max) - static_cast(min))); - } +namespace utilities{ + extern const std::string WHITESPACE_; + extern const char GUID_RANGE[]; + extern const bool GUID_DASHES[]; + + extern const char PATH_SEPARATOR_; + bool is_zero(double x, double tolerance); + bool is_zero(double x); + + int round_up_to_int(double x, double tolerance); + int round_up_to_int(double x); + + bool is_equal(double x, double y, double tolerance); + + bool is_equal(double x, double y); + + bool greater_than(double x, double y, double tolerance); + + bool greater_than(double x, double y); + + bool greater_than_or_equal(double x, double y, double tolerance); + bool greater_than_or_equal(double x, double y); + + bool less_than(double x, double y, double tolerance); + bool less_than(double x, double y); + + bool less_than_or_equal(double x, double y, double tolerance); + + bool less_than_or_equal(double x, double y); + + double get_cartesian_distance(double x1, double y1, double x2, double y2); + + double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2); + + double get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise); + std::string to_string(double value); + + std::string to_string(int value); + std::string ltrim(const std::string& s); + + std::string rtrim(const std::string& s); + + std::string trim(const std::string& s); + std::string join(const std::string* strings, size_t length, std::string sep); + + std::string join(const std::vector strings, std::string sep); + + std::istream& safe_get_line(std::istream& is, std::string& t); + + std::string center(std::string input, int width); + double get_percent_change(int v1, int v2); + double get_percent_change(double v1, double v2); + std::string get_percent_change_string(int v1, int v2, int precision); + + int get_num_digits(int x); + int get_num_digits(double x, int precision); + + int get_num_digits(double x); + // Nice utility function found here: https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path + std::vector splitpath(const std::string& str); + + bool get_file_path(const std::string& file_path, std::string& path); + std::string create_uuid(); + + bool get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path); + + double hypot(double x, double y); + + double atan2(double y, double x); + + double atan2f(double y, double x); + + double floor(double x); + + double floorf(double x); + + double ceil(double x); + + double cos(double x); + + double sin(double x); + + double cosf(double x); + + double sinf(double x); + + double abs(double x); + + double fabs(double x); + + double fabsf(double x); + + double sqrt(double x); + + double sqrtf(double x); + + double pow(int e, double x); + + double min(float x, float y); + + void* memcpy(void* dest, const void* src, size_t n); + + std::string dtos(double x, unsigned char precision); -protected: - static const std::string WHITESPACE_; - static const char PATH_SEPARATOR_ = -#ifdef _WIN32 - '\\'; -#else - '/'; -#endif - static const char GUID_RANGE[]; - static const bool GUID_DASHES[]; -private: - utilities(); - -}; + std::string replace(std::string subject, const std::string& search, const std::string& replace); + + double rand_range(double min, double max); + unsigned char rand_range(unsigned char min, unsigned char max); + int rand_range(int min, int max); + + + +} +#endif \ No newline at end of file diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp index 482c09b..fcb4b71 100644 --- a/PyArcWelder/py_arc_welder.cpp +++ b/PyArcWelder/py_arc_welder.cpp @@ -241,7 +241,7 @@ bool py_gcode_arc_args::parse_args(PyObject* py_args, py_logger* p_py_logger, py p_py_logger->log(GCODE_CONVERSION, WARNING, message); } else { - args.default_xyz_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision); + args.default_xyz_precision = (unsigned char)gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision); if (args.default_xyz_precision < 3) { std::string message = "ParseArgs - The default XYZ precision received was less than 3, which could cause problems printing arcs. Setting to 3."; @@ -265,7 +265,7 @@ bool py_gcode_arc_args::parse_args(PyObject* py_args, py_logger* p_py_logger, py p_py_logger->log(WARNING, GCODE_CONVERSION, message); } else { - args.default_e_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision); + args.default_e_precision = (unsigned char)gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision); if (args.default_e_precision < 3) { std::string message = "ParseArgs - The default E precision received was less than 3, which could cause extrusion problems. Setting to 3."; -- cgit v1.2.3 From 00df4c1dd984e294108fd77359af6f8771143fc7 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 10:49:44 -0600 Subject: Continue moving math calls to utilities --- ArcWelder/arc_welder.cpp | 4 +- ArcWelder/segmented_arc.cpp | 6 +- ArcWelder/segmented_shape.cpp | 26 ++--- ArcWelderInverseProcessor/firmware.h | 1 - ArcWelderInverseProcessor/marlin_1.cpp | 52 ++-------- ArcWelderInverseProcessor/marlin_1.h | 8 +- ArcWelderInverseProcessor/marlin_2.cpp | 143 +++++++-------------------- ArcWelderInverseProcessor/marlin_2.h | 16 +-- ArcWelderInverseProcessor/prusa.cpp | 34 +++---- ArcWelderInverseProcessor/repetier.cpp | 38 +++----- ArcWelderInverseProcessor/repetier.h | 1 - ArcWelderInverseProcessor/smoothieware.cpp | 22 ++--- ArcWelderInverseProcessor/smoothieware.h | 1 - ArcWelderTest/ArcWelderTest.cpp | 10 +- GcodeProcessorLib/gcode_position.cpp | 2 +- GcodeProcessorLib/utilities.cpp | 150 ++++++++++++++++++++++++----- GcodeProcessorLib/utilities.h | 53 ++++++++-- 17 files changed, 287 insertions(+), 280 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index b966b62..fe129b4 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -495,7 +495,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess // Calculate R if (r == 0) { - r = std::sqrt(i * i + j * j); + r = utilities::sqrt(i * i + j * j); } // Now we know the radius and the chord length; movement_length_mm = utilities::get_arc_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z, i, j, r, p_cur_pos->command.command == "G2"); @@ -535,7 +535,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; if (previous_extrusion_rate_ > 0) { - extrusion_rate_change_percent = std::fabs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); + extrusion_rate_change_percent = utilities::abs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); } } if (previous_extrusion_rate_ != 0 && utilities::greater_than(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 6f238a5..527ad3a 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -240,10 +240,12 @@ bool segmented_arc::try_add_point_internal_(printer_point p) // Apply firmware compensation // See how many arcs will be interpolated double circumference = 2.0 * PI_DOUBLE * current_arc_.radius; - int num_segments = (int)std::floor(circumference / min_arc_segments_); + // TODO: Should this be ceil? + int num_segments = (int)utilities::floor(circumference / min_arc_segments_); if (num_segments < min_arc_segments_) { //num_segments = (int)std::ceil(circumference/approximate_length) * (int)std::ceil(approximate_length / mm_per_arc_segment); - num_segments = (int)std::floor(circumference / original_shape_length_); + // TODO: Should this be ceil? + num_segments = (int)utilities::floor(circumference / original_shape_length_); if (num_segments < min_arc_segments_) { abort_arc = true; num_firmware_compensations_++; diff --git a/ArcWelder/segmented_shape.cpp b/ArcWelder/segmented_shape.cpp index 447cfbe..dd422a4 100644 --- a/ArcWelder/segmented_shape.cpp +++ b/ArcWelder/segmented_shape.cpp @@ -83,7 +83,7 @@ point point::get_midpoint(point p1, point p2) bool point::is_near_collinear(const point& p1, const point& p2, const point& p3, double tolerance) { - return fabs((p1.y - p2.y) * (p1.x - p3.x) - (p1.y - p3.y) * (p1.x - p2.x)) <= 1e-9; + return utilities::abs((p1.y - p2.y) * (p1.x - p3.x) - (p1.y - p3.y) * (p1.x - p2.x)) <= 1e-9; } double point::cartesian_distance(const point& p1, const point& p2) @@ -124,7 +124,7 @@ bool segment::get_closest_perpendicular_point(const point& p1, const point& p2, #pragma region Vector Functions double vector::get_magnitude() { - return sqrt(x * x + y * y + z * z); + return utilities::sqrt(x * x + y * y + z * z); } double vector::cross_product_magnitude(vector v1, vector v2) @@ -144,9 +144,9 @@ double vector::cross_product_magnitude(vector v1, vector v2) // Users of this code must verify correctness for their application. // dot product (3D) which allows vector operations in arguments #define dot(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) -#define dotxy(u,v) ((u).x * (v).x + (u).y * (v).y) -#define norm(v) sqrt(dot(v,v)) // norm = length of vector -#define d(u,v) norm(u-v) // distance = norm of difference +//#define dotxy(u,v) ((u).x * (v).x + (u).y * (v).y) +//#define norm(v) utilities::sqrt(dot(v,v)) // norm = length of vector +//#define d(u,v) norm(u-v) // distance = norm of difference #pragma endregion Distance Calculation Source @@ -259,7 +259,7 @@ bool circle::try_create_circle(const array_list& points, const do double circle::get_polar_radians(const point& p1) const { - double polar_radians = atan2(p1.y - center.y, p1.x - center.x); + double polar_radians = utilities::atan2(p1.y - center.y, p1.x - center.x); if (polar_radians < 0) polar_radians = (2.0 * PI_DOUBLE) + polar_radians; return polar_radians; @@ -290,7 +290,7 @@ bool circle::get_deviation_sum_squared(const array_list& points, return false; } } - double deviation = std::fabs(distance_from_center - radius); + double deviation = utilities::abs(distance_from_center - radius); total_deviation += deviation * deviation; if (deviation > resolution_mm) { @@ -305,7 +305,7 @@ bool circle::get_deviation_sum_squared(const array_list& points, if (segment::get_closest_perpendicular_point(points[index], points[index + 1], center, point_to_test)) { double distance = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, center.x, center.y); - double deviation = std::fabs(distance - radius); + double deviation = utilities::abs(distance - radius); total_deviation += deviation * deviation; if (deviation > resolution_mm) { @@ -346,7 +346,7 @@ bool circle::is_over_deviation(const array_list& points, const do return true; } } - if (std::fabs(distance_from_center - radius) > resolution_mm) + if (utilities::abs(distance_from_center - radius) > resolution_mm) { return true; } @@ -357,7 +357,7 @@ bool circle::is_over_deviation(const array_list& points, const do if (segment::get_closest_perpendicular_point(current_point, points[index + 1], center, point_to_test)) { double distance = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, center.x, center.y); - if (std::fabs(distance - radius) > resolution_mm) + if (utilities::abs(distance - radius) > resolution_mm) { return true; } @@ -457,7 +457,7 @@ bool arc::try_create_arc( // see if an arc moving in the opposite direction had the correct length. // Find the rest of the angle across the circle - double test_radians = std::fabs(angle_radians - 2 * PI_DOUBLE); + double test_radians = utilities::abs(angle_radians - 2 * PI_DOUBLE); // Calculate the length of that arc double test_arc_length = c.radius * test_radians; if (allow_3d_arcs) @@ -667,7 +667,7 @@ bool arc::ray_intersects_segment(const point rayOrigin, const point rayDirection vector v3 = vector(-rayDirection.y, rayDirection.x, 0); double dot = dot(v2, v3); - if (std::fabs(dot) < 0.000001) + if (utilities::abs(dot) < 0.000001) return false; double t1 = vector::cross_product_magnitude(v2, v1) / dot; @@ -727,7 +727,7 @@ void segmented_shape::set_xyz_precision(unsigned char precision) void segmented_shape::set_xyz_tolerance_from_precision() { - xyz_tolerance_ = std::pow(10.0, -1.0 * static_cast(xyz_precision_)); + xyz_tolerance_ = utilities::pow(10, -1.0 * static_cast(xyz_precision_)); } void segmented_shape::reset_precision() diff --git a/ArcWelderInverseProcessor/firmware.h b/ArcWelderInverseProcessor/firmware.h index a11b057..80d5d07 100644 --- a/ArcWelderInverseProcessor/firmware.h +++ b/ArcWelderInverseProcessor/firmware.h @@ -5,7 +5,6 @@ #include #include -#define M_PI 3.14159265358979323846 // pi #define DEFAULT_FIRMWARE_TYPE firmware_types::MARLIN_2 #define LATEST_FIRMWARE_VERSION_NAME "LATEST_RELEASE" #define DEFAULT_FIRMWARE_VERSION_NAME LATEST_FIRMWARE_VERSION_NAME diff --git a/ArcWelderInverseProcessor/marlin_1.cpp b/ArcWelderInverseProcessor/marlin_1.cpp index 6269645..0cca4e9 100644 --- a/ArcWelderInverseProcessor/marlin_1.cpp +++ b/ArcWelderInverseProcessor/marlin_1.cpp @@ -156,7 +156,7 @@ void marlin_1::plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination // Radius vector from center to current location float r_P = -offset[0], r_Q = -offset[1]; - const float radius = HYPOT(r_P, r_Q), + const float radius = utilities::hypotf(r_P, r_Q), center_P = current_position[p_axis] - r_P, center_Q = current_position[q_axis] - r_Q, rt_X = cart[p_axis] - center_P, @@ -165,19 +165,19 @@ void marlin_1::plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination extruder_travel = cart[E_CART] - current_position[E_CART]; // CCW angle of rotation between position and target from the circle center. Only one atan2() trig computation required. - float angular_travel = ATAN2(r_P * rt_Y - r_Q * rt_X, r_P * rt_X + r_Q * rt_Y); - if (angular_travel < 0) angular_travel += RADIANS(360); - if (clockwise) angular_travel -= RADIANS(360); + float angular_travel = (float)utilities::atan2((double)r_P * rt_Y - (double)r_Q * rt_X, (double)r_P * rt_X + (double)r_Q * rt_Y); + if (angular_travel < 0) angular_travel += utilities::radiansf(360.0f); + if (clockwise) angular_travel -= utilities::radiansf(360.0f); // Make a circle if the angular rotation is 0 and the target is current position if (angular_travel == 0 && current_position[p_axis] == cart[p_axis] && current_position[q_axis] == cart[q_axis]) - angular_travel = RADIANS(360); + angular_travel = utilities::radiansf(360.0f); const float flat_mm = radius * angular_travel, - mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : ABS(flat_mm); + mm_of_travel = linear_travel ? utilities::hypotf(flat_mm, linear_travel) : utilities::absf(flat_mm); if (mm_of_travel < 0.001f) return; - uint16_t segments = (uint16_t)FLOOR(mm_of_travel / (float)(args_.mm_per_arc_segment)); + uint16_t segments = (uint16_t)utilities::floorf(mm_of_travel / (float)(args_.mm_per_arc_segment)); NOLESS(segments, 1); /** @@ -212,7 +212,7 @@ void marlin_1::plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination linear_per_segment = linear_travel / segments, extruder_per_segment = extruder_travel / segments, sin_T = theta_per_segment, - cos_T = 1 - 0.5f * sq(theta_per_segment); // Small angle approximation + cos_T = 1 - 0.5f * utilities::sqf(theta_per_segment); // Small angle approximation // Initialize the linear axis raw[l_axis] = current_position[l_axis]; @@ -273,42 +273,10 @@ void marlin_1::plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination COPY(current_position, cart); } -// Marlin Function Defs -float marlin_1::HYPOT(float x, float y) -{ - return (float)utilities::hypot(x, y); -} - -float marlin_1::ATAN2(float x, float y) -{ - return (float)utilities::atan2(x, y); -} - -float marlin_1::RADIANS(float x) -{ - return (x * (float)M_PI) / 180; -} - -float marlin_1::ABS(float x) -{ - return (float)utilities::abs((double)x); -} - -float marlin_1::FLOOR(float x) -{ - return (float)utilities::floor((double)x); -} - -float marlin_1::NOLESS(uint16_t x, uint16_t y) +void marlin_1::NOLESS(uint16_t &x, uint16_t y) { if (x < y) - return y; - return x; -} - -float marlin_1::sq(float x) -{ - return x * x; + x = y; } float marlin_1::MMS_SCALED(float x) diff --git a/ArcWelderInverseProcessor/marlin_1.h b/ArcWelderInverseProcessor/marlin_1.h index 9579bfb..0fc11c4 100644 --- a/ArcWelderInverseProcessor/marlin_1.h +++ b/ArcWelderInverseProcessor/marlin_1.h @@ -86,13 +86,7 @@ private: plan_arc_func plan_arc_; // Marlin Function Defs - float HYPOT(float x, float y); - float ATAN2(float x, float y); - float RADIANS(float x); - float ABS(float x); - float FLOOR(float x); - float NOLESS(uint16_t x, uint16_t y); - float sq(float x); + void NOLESS(uint16_t& x, uint16_t y); float MMS_SCALED(float x); void COPY(float target[MARLIN_XYZE], const float (&source)[MARLIN_XYZE]); bool buffer_line_kinematic(const float (&cart)[MARLIN_XYZE], double fr_mm_s, int active_extruder); diff --git a/ArcWelderInverseProcessor/marlin_2.cpp b/ArcWelderInverseProcessor/marlin_2.cpp index 4893dd1..afd17a6 100644 --- a/ArcWelderInverseProcessor/marlin_2.cpp +++ b/ArcWelderInverseProcessor/marlin_2.cpp @@ -168,7 +168,7 @@ void marlin_2::plan_arc_2_0_9_1( rvec[0] = - offset[X_AXIS]; rvec[1] = - offset[Y_AXIS]; - const float radius = HYPOT(rvec[0], rvec[1]), + const float radius = utilities::hypotf(rvec[0], rvec[1]), center_P = current_position[p_axis] - rvec[0], center_Q = current_position[q_axis] - rvec[1], rt_X = cart[p_axis] - center_P, @@ -183,25 +183,25 @@ void marlin_2::plan_arc_2_0_9_1( // Do a full circle if starting and ending positions are "identical" if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) { // Preserve direction for circles - angular_travel = clockwise ? -RADIANS(360) : RADIANS(360); + angular_travel = clockwise ? -utilities::radiansf(360.0f) : utilities::radiansf(360.0f); } else { // Calculate the angle - angular_travel = ATAN2(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y); + angular_travel = utilities::atan2f(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y); // Angular travel too small to detect? Just return. if (!angular_travel) return; // Make sure angular travel over 180 degrees goes the other way around. switch (((angular_travel < 0) << 1) | (int)clockwise) { - case 1: angular_travel -= RADIANS(360); break; // Positive but CW? Reverse direction. - case 2: angular_travel += RADIANS(360); break; // Negative but CCW? Reverse direction. + case 1: angular_travel -= utilities::radiansf(360.0f); break; // Positive but CW? Reverse direction. + case 2: angular_travel += utilities::radiansf(360.0f); break; // Negative but CCW? Reverse direction. } if (args_.min_arc_segments > 1) { - min_segments = (uint16_t)CEIL(min_segments * ABS(angular_travel) / RADIANS(360)); - min_segments = (uint16_t)NOLESS(min_segments, 1U); + min_segments = (uint16_t)utilities::ceilf(min_segments * utilities::absf(angular_travel) / utilities::radiansf(360.0f)); + NOLESS(min_segments, 1U); } } @@ -210,7 +210,7 @@ void marlin_2::plan_arc_2_0_9_1( float extruder_travel = cart[E_AXIS] - current_position[E_AXIS]; const float flat_mm = radius * angular_travel, - mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : ABS(flat_mm); + mm_of_travel = linear_travel ? utilities::hypotf(flat_mm, linear_travel) : utilities::absf(flat_mm); if (mm_of_travel < 0.001f) return; const float scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s); @@ -219,17 +219,17 @@ void marlin_2::plan_arc_2_0_9_1( float seg_length = (float)args_.mm_per_arc_segment; if (args_.arc_segments_per_r > 0) { - seg_length = constrain((float)args_.mm_per_arc_segment * radius, (float)args_.mm_per_arc_segment, (float)args_.arc_segments_per_r); + seg_length = utilities::constrainf((float)args_.mm_per_arc_segment * radius, (float)args_.mm_per_arc_segment, (float)args_.arc_segments_per_r); } else if (args_.arc_segments_per_sec > 0) { - seg_length = _MAX(scaled_fr_mm_s * RECIPROCAL((float)args_.arc_segments_per_sec), (float)args_.mm_per_arc_segment); + seg_length = utilities::maxf(scaled_fr_mm_s * utilities::reciprocalf((float)args_.arc_segments_per_sec), (float)args_.mm_per_arc_segment); } // Divide total travel by nominal segment length - uint16_t segments = (uint16_t)FLOOR(mm_of_travel / seg_length); - //uint16_t segments = FLOOR(mm_of_travel / seg_length); - segments = (uint16_t)NOLESS(segments, min_segments); // At least some segments + uint16_t segments = (uint16_t)utilities::floorf(mm_of_travel / seg_length); + //uint16_t segments = utilities::floorf(mm_of_travel / seg_length); + NOLESS(segments, min_segments); // At least some segments seg_length = mm_of_travel / segments; /** @@ -261,7 +261,7 @@ void marlin_2::plan_arc_2_0_9_1( // Vector rotation matrix values float raw[MARLIN_2_XYZE]; const float theta_per_segment = angular_travel / segments, - sq_theta_per_segment = sq(theta_per_segment), + sq_theta_per_segment = utilities::sqf(theta_per_segment), sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6, cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation @@ -296,8 +296,8 @@ void marlin_2::plan_arc_2_0_9_1( // Compute exact location by applying transformation matrix from initial radius vector(=-offset). // To reduce stuttering, the sin and cos could be computed at different times. // For now, compute both at the same time. - const float cos_Ti = COS(i * theta_per_segment); - const float sin_Ti = SIN(i * theta_per_segment); + const float cos_Ti = (float)utilities::cos((i * (double)theta_per_segment)); + const float sin_Ti = (float)utilities::sin((i * (double)theta_per_segment)); rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti; rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti; } @@ -375,7 +375,7 @@ void marlin_2::plan_arc_2_0_9_2( rvec[0] = -offset[X_AXIS]; rvec[1] = -offset[Y_AXIS]; - const float radius = HYPOT(rvec[0], rvec[1]), + const float radius = utilities::hypotf(rvec[0], rvec[1]), center_P = current_position[p_axis] - rvec[0], center_Q = current_position[q_axis] - rvec[1], rt_X = cart[p_axis] - center_P, @@ -390,28 +390,28 @@ void marlin_2::plan_arc_2_0_9_2( // Do a full circle if starting and ending positions are "identical" if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) { // Preserve direction for circles - angular_travel = clockwise ? -RADIANS(360) : RADIANS(360); - abs_angular_travel = RADIANS(360); + angular_travel = clockwise ? -utilities::radiansf(360.0f) : utilities::radiansf(360.0f); + abs_angular_travel = utilities::radiansf(360.0f); min_segments = min_circle_segments; } else { // Calculate the angle - angular_travel = ATAN2(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y); + angular_travel = utilities::atan2f(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y); // Angular travel too small to detect? Just return. if (!angular_travel) return; // Make sure angular travel over 180 degrees goes the other way around. switch (((angular_travel < 0) << 1) | (int)clockwise) { - case 1: angular_travel -= RADIANS(360); break; // Positive but CW? Reverse direction. - case 2: angular_travel += RADIANS(360); break; // Negative but CCW? Reverse direction. + case 1: angular_travel -= utilities::radiansf(360.0f); break; // Positive but CW? Reverse direction. + case 2: angular_travel += utilities::radiansf(360.0f); break; // Negative but CCW? Reverse direction. } - abs_angular_travel = ABS(angular_travel); + abs_angular_travel = utilities::absf(angular_travel); // Apply minimum segments to the arc - const float portion_of_circle = abs_angular_travel / RADIANS(360); // Portion of a complete circle (0 < N < 1) - min_segments = (uint16_t)CEIL((min_circle_segments)*portion_of_circle); // Minimum segments for the arc + const float portion_of_circle = abs_angular_travel / utilities::radiansf(360.0f); // Portion of a complete circle (0 < N < 1) + min_segments = (uint16_t)utilities::ceilf((min_circle_segments)*portion_of_circle); // Minimum segments for the arc } float travel_L = cart[Z_AXIS] - start_L; @@ -429,19 +429,19 @@ void marlin_2::plan_arc_2_0_9_2( // Get the nominal segment length based on settings float nominal_segment_mm; if (args_.arc_segments_per_sec > 0) { - nominal_segment_mm = constrain(scaled_fr_mm_s * RECIPROCAL((float)args_.arc_segments_per_sec), (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm()); + nominal_segment_mm = utilities::constrainf(scaled_fr_mm_s * utilities::reciprocalf((float)args_.arc_segments_per_sec), (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm()); } else { nominal_segment_mm = (float)args_.get_max_arc_segment_mm(); } // Number of whole segments based on the nominal segment length - const float nominal_segments = _MAX(FLOOR(flat_mm / nominal_segment_mm), min_segments); + const float nominal_segments = utilities::maxf(utilities::floorf(flat_mm / nominal_segment_mm), min_segments); // A new segment length based on the required minimum - const float segment_mm = constrain(flat_mm / nominal_segments, (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm()); + const float segment_mm = utilities::constrainf(flat_mm / nominal_segments, (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm()); // The number of whole segments in the arc, ignoring the remainder - uint16_t segments = (uint16_t)FLOOR(flat_mm / segment_mm); + uint16_t segments = (uint16_t)utilities::floorf(flat_mm / segment_mm); // Are the segments now too few to reach the destination? const float segmented_length = segment_mm * segments; @@ -478,7 +478,7 @@ void marlin_2::plan_arc_2_0_9_2( // Vector rotation matrix values float raw[MARLIN_2_XYZE]; const float theta_per_segment = proportion * angular_travel / segments, - sq_theta_per_segment = sq(theta_per_segment), + sq_theta_per_segment = utilities::sqf(theta_per_segment), sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6, cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation @@ -516,8 +516,8 @@ void marlin_2::plan_arc_2_0_9_2( // Compute exact location by applying transformation matrix from initial radius vector(=-offset). // To reduce stuttering, the sin and cos could be computed at different times. // For now, compute both at the same time. - const float cos_Ti = COS(i * theta_per_segment); - const float sin_Ti = SIN(i * theta_per_segment); + const float cos_Ti = (float)utilities::cos(i * (double)theta_per_segment); + const float sin_Ti = (float)utilities::sin(i * (double)theta_per_segment); rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti; rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti; } @@ -549,97 +549,26 @@ void marlin_2::plan_arc_2_0_9_2( COPY(current_position, raw); } // Marlin Function Defs -float marlin_2::HYPOT(float x, float y) +void marlin_2::NOLESS(uint16_t& x, uint16_t y) { - return (float)utilities::hypot(x, y); + if (x < y) + x = y; } - -float marlin_2::ATAN2(float x, float y) -{ - return (float)utilities::atan2(x, y); -} - -float marlin_2::RADIANS(float x) -{ - return (x * (float)M_PI) / 180; -} - -float marlin_2::ABS(float x) -{ - return (float)utilities::abs(x); -} - -float marlin_2::FLOOR(float x) -{ - return (float)utilities::floor(x); -} - -float marlin_2::COS(float x) -{ - return (float)utilities::cos(x); -} - -float marlin_2::SIN(float x) -{ - return (float)utilities::sin(x); -} - -float marlin_2::NOLESS(uint16_t x, uint16_t y) -{ - if (x < y) - return y; - return x; -} - -float marlin_2::sq(float x) -{ - return x * x; -} - float marlin_2::MMS_SCALED(float x) { // No scaling return x; } -bool marlin_2::WITHIN(float N, float L, float H) -{ - return ((N) >= (L) && (N) <= (H)); -} bool marlin_2::NEAR_ZERO(float x) { - return WITHIN(x, -0.000001f, 0.000001f); + return utilities::withinf(x, -0.000001f, 0.000001f); } bool marlin_2::NEAR(float x, float y) { return NEAR_ZERO((x)-(y)); } -float marlin_2::CEIL(float x) -{ - return (float)utilities::ceil(x); -} - -float marlin_2::constrain(float value, float arg_min, float arg_max) -{ - return ((value) < (arg_min) ? (arg_min) : ((value) > (arg_max) ? (arg_max) : (value))); -} - -float marlin_2::_MAX(float x, float y) -{ - if (x>y) return x; - return y; -} -float marlin_2::_MIN(float x, float y) -{ - if (x < y) return x; - return y; -} - -float marlin_2::RECIPROCAL(float x) -{ - return (float)1.0/x; -} void marlin_2::COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE]) { // This is a slow copy, but speed isn't much of an issue here. diff --git a/ArcWelderInverseProcessor/marlin_2.h b/ArcWelderInverseProcessor/marlin_2.h index f9735ec..2abea56 100644 --- a/ArcWelderInverseProcessor/marlin_2.h +++ b/ArcWelderInverseProcessor/marlin_2.h @@ -84,24 +84,10 @@ private: plan_arc_func plan_arc_; // Marlin Function Defs - float HYPOT(float x, float y); - float ATAN2(float x, float y); - float RADIANS(float x); - float COS(float x); - float SIN(float s); - float ABS(float x); - float FLOOR(float x); - float NOLESS(uint16_t x, uint16_t y); - float sq(float x); + void NOLESS(uint16_t& x, uint16_t y); float MMS_SCALED(float x); bool NEAR_ZERO(float x); bool NEAR(float x, float y); - bool WITHIN(float N, float L, float H); - float CEIL(float x); - float constrain(float value, float arg_min, float arg_max); - float _MAX(float x, float y); - float _MIN(float x, float y); - float RECIPROCAL(float x); void COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE]); }; diff --git a/ArcWelderInverseProcessor/prusa.cpp b/ArcWelderInverseProcessor/prusa.cpp index ddd8a53..34ee82b 100644 --- a/ArcWelderInverseProcessor/prusa.cpp +++ b/ArcWelderInverseProcessor/prusa.cpp @@ -117,7 +117,7 @@ std::string prusa::interpolate_arc(firmware_position& target, double i, double j float prusa_radius = static_cast(r); if (prusa_radius != 0) { - prusa_radius = (float)utilities::hypot(prusa_offset[X_AXIS], prusa_offset[Y_AXIS]); // Compute arc radius for mc_arc + prusa_radius = utilities::hypotf(prusa_offset[X_AXIS], prusa_offset[Y_AXIS]); // Compute arc radius for mc_arc } float prusa_f = static_cast(target.f); @@ -175,21 +175,21 @@ void prusa::mc_arc_3_10_0(float* position, float* target, float* offset, float f float rt_axis1 = target[axis_1] - center_axis1; // CCW angle between position and target from circle center. Only one atan2() trig computation required. - float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1); - if (angular_travel < 0) { angular_travel += 2 * (float)M_PI; } - if (isclockwise) { angular_travel -= 2 * (float)M_PI; } + float angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1); + if (angular_travel < 0) { angular_travel += 2.0f * PI_FLOAT; } + if (isclockwise) { angular_travel -= 2.0f * PI_FLOAT; } //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving //to compensate when start pos = target pos && angle is zero -> angle = 2Pi if (position[axis_0] == target[axis_0] && position[axis_1] == target[axis_1] && angular_travel == 0) { - angular_travel += 2 * (float)M_PI; + angular_travel += 2.0f * PI_FLOAT; } //end fix G03 - float millimeters_of_travel = (float)utilities::hypot(angular_travel * radius, fabs(linear_travel)); + float millimeters_of_travel = (float)utilities::hypot((double)angular_travel * radius, utilities::absf(linear_travel)); if (millimeters_of_travel < 0.001) { return; } - uint16_t segments = (uint16_t)floor(millimeters_of_travel / cs.mm_per_arc_segment); + uint16_t segments = (uint16_t)utilities::floorf(millimeters_of_travel / cs.mm_per_arc_segment); if (segments == 0) segments = 1; /* @@ -255,8 +255,8 @@ void prusa::mc_arc_3_10_0(float* position, float* target, float* offset, float f else { // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = cos(i * theta_per_segment); - sin_Ti = sin(i * theta_per_segment); + cos_Ti = (float)utilities::cos(i * (double)theta_per_segment); + sin_Ti = (float)utilities::sin(i * (double)theta_per_segment); r_axis0 = -offset[axis_0] * cos_Ti + offset[axis_1] * sin_Ti; r_axis1 = -offset[axis_0] * sin_Ti - offset[axis_1] * cos_Ti; count = 0; @@ -325,14 +325,14 @@ void prusa::mc_arc_3_11_0(float* position, float* target, float* offset, float f uint8_t n_arc_correction = cs.n_arc_correction; // CCW angle between position and target from circle center. Only one atan2() trig computation required. - float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y); - if (angular_travel_total < 0) { angular_travel_total += 2 * (float)M_PI; } + float angular_travel_total = (float)utilities::atan2((double)r_axis_x * rt_y - (double)r_axis_y * rt_x, (double)r_axis_x * rt_x + (double)r_axis_y * rt_y); + if (angular_travel_total < 0) { angular_travel_total += 2.0f * PI_FLOAT; } if (cs.min_arc_segments > 0) { // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation // Do this before converting the angular travel for clockwise rotation - mm_per_arc_segment = radius * ((2.0f * (float)M_PI) / cs.min_arc_segments); + mm_per_arc_segment = radius * ((2.0f * PI_FLOAT) / cs.min_arc_segments); } if (cs.arc_segments_per_sec > 0) { @@ -356,23 +356,23 @@ void prusa::mc_arc_3_11_0(float* position, float* target, float* offset, float f } // Adjust the angular travel if the direction is clockwise - if (isclockwise) { angular_travel_total -= 2 * (float)M_PI; } + if (isclockwise) { angular_travel_total -= 2.0f * PI_FLOAT; } //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving //to compensate when start pos = target pos && angle is zero -> angle = 2Pi if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0) { - angular_travel_total += 2 * (float)M_PI; + angular_travel_total += 2.0f * PI_FLOAT; } //end fix G03 // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are // calculating here - const float millimeters_of_travel_arc = hypot(angular_travel_total * radius, fabs(travel_z)); + const float millimeters_of_travel_arc = (float)utilities::hypot(angular_travel_total * (double)radius, utilities::fabs(travel_z)); if (millimeters_of_travel_arc < 0.001) { return; } // Calculate the number of arc segments - uint16_t segments = static_cast(ceil(millimeters_of_travel_arc / mm_per_arc_segment)); + uint16_t segments = static_cast(utilities::ceilf(millimeters_of_travel_arc / mm_per_arc_segment)); /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, and phi is the angle of rotation. Based on the solution approach by Jens Geisler. @@ -409,7 +409,7 @@ void prusa::mc_arc_3_11_0(float* position, float* target, float* offset, float f for (uint16_t i = 1; i < segments; i++) { if (n_arc_correction-- == 0) { // Calculate the actual position for r_axis_x and r_axis_y - const float cos_Ti = cos(i * theta_per_segment), sin_Ti = sin(i * theta_per_segment); + const float cos_Ti = (float)utilities::cos(i * (double)theta_per_segment), sin_Ti = (float)utilities::sin(i * (double)theta_per_segment); r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti; r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti; // reset n_arc_correction diff --git a/ArcWelderInverseProcessor/repetier.cpp b/ArcWelderInverseProcessor/repetier.cpp index 7d5798c..e5c34ec 100644 --- a/ArcWelderInverseProcessor/repetier.cpp +++ b/ArcWelderInverseProcessor/repetier.cpp @@ -119,7 +119,7 @@ std::string repetier::interpolate_arc(firmware_position& target, double i, doubl float repetier_radius = static_cast(r); if (repetier_radius != 0) { - repetier_radius = (float)utilities::hypot(repetier_offset[X_AXIS], repetier_offset[Y_AXIS]); // Compute arc radius for mc_arc + repetier_radius = utilities::hypotf(repetier_offset[X_AXIS], repetier_offset[Y_AXIS]); // Compute arc radius for mc_arc } float repetier_f = static_cast(target.f); @@ -175,12 +175,12 @@ void repetier::arc_1_0_5(float* position, float* target, float* offset, float ra if (repetier_is_close(position[X_AXIS], target[X_AXIS]) && repetier_is_close(position[Y_AXIS], target[Y_AXIS])) { // Preserve direction for circles - angular_travel = isclockwise ? -2.0f * (float)M_PI : 2.0f * (float)M_PI; + angular_travel = isclockwise ? -2.0f * PI_FLOAT : 2.0f * PI_FLOAT; } else { // CCW angle between position and target from circle center. Only one atan2() trig computation required. - angular_travel = (float)utilities::atan2((double)(r_axis0 * rt_axis1) - (double)(r_axis1 * rt_axis0), (double)(r_axis0 * rt_axis0) + (double)(r_axis1 * rt_axis1)); + angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1); // No need to draw an arc if there is no angular travel if (!angular_travel) return; @@ -190,12 +190,12 @@ void repetier::arc_1_0_5(float* position, float* target, float* offset, float ra { if (isclockwise) { - angular_travel -= 2.0f * (float)M_PI; + angular_travel -= 2.0f * PI_FLOAT; } } else if (!isclockwise) { - angular_travel += 2.0f * (float)M_PI; + angular_travel += 2.0f * PI_FLOAT; } } @@ -204,7 +204,7 @@ void repetier::arc_1_0_5(float* position, float* target, float* offset, float ra if (linear_travel) { // If we have any Z motion, add this to the total mm of travel. - millimeters_of_travel = (float)utilities::hypot(millimeters_of_travel, linear_travel); + millimeters_of_travel = utilities::hypotf(millimeters_of_travel, linear_travel); } if (millimeters_of_travel < 0.001f) { @@ -287,8 +287,8 @@ void repetier::arc_1_0_5(float* position, float* target, float* offset, float ra else { // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = (float)utilities::cos(i * theta_per_segment); - sin_Ti = (float)utilities::sin(i * theta_per_segment); + cos_Ti = (float)utilities::cos(i * (double)theta_per_segment); + sin_Ti = (float)utilities::sin(i * (double)theta_per_segment); r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti; r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti; count = 0; @@ -347,21 +347,21 @@ void repetier::arc_1_0_4(float* position, float* target, float* offset, float ra long etarget = Printer::destinationSteps[E_AXIS]; */ // CCW angle between position and target from circle center. Only one atan2() trig computation required. - float angular_travel = (float)utilities::atan2((double)(r_axis0 * rt_axis1) - (double)(r_axis1 * rt_axis0), (double)(r_axis0 * rt_axis0) + (double)(r_axis1 * rt_axis1)); + float angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1); if ((!isclockwise && angular_travel <= 0.00001) || (isclockwise && angular_travel < -0.000001)) { - angular_travel += 2.0f * (float)M_PI; + angular_travel += 2.0f * PI_FLOAT; } if (isclockwise) { - angular_travel -= 2.0f * (float)M_PI; + angular_travel -= 2.0f * PI_FLOAT; } - float millimeters_of_travel = (float)utilities::fabs(angular_travel) * radius; //hypot(angular_travel*radius, fabs(linear_travel)); + float millimeters_of_travel = (float)utilities::fabs(angular_travel) * radius; if (millimeters_of_travel < 0.001f) { return; // treat as succes because there is nothing to do; } //uint16_t segments = (radius>=BIG_ARC_RADIUS ? floor(millimeters_of_travel/MM_PER_ARC_SEGMENT_BIG) : floor(millimeters_of_travel/MM_PER_ARC_SEGMENT)); // Increase segment size if printing faster then computation speed allows - uint16_t segments = (uint16_t)(feedrate > 60.0f ? utilities::floor(millimeters_of_travel / utilities::min(static_cast(args_.mm_per_arc_segment), feedrate * 0.01666f * static_cast(args_.mm_per_arc_segment))) : utilities::floor(millimeters_of_travel / static_cast(args_.mm_per_arc_segment))); + uint16_t segments = (uint16_t)(feedrate > 60.0f ? utilities::floorf(millimeters_of_travel / utilities::minf(static_cast(args_.mm_per_arc_segment), feedrate * 0.01666f * static_cast(args_.mm_per_arc_segment))) : utilities::floorf(millimeters_of_travel / static_cast(args_.mm_per_arc_segment))); if (segments == 0) segments = 1; /* @@ -426,8 +426,8 @@ void repetier::arc_1_0_4(float* position, float* target, float* offset, float ra else { // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = (float)utilities::cos(i * theta_per_segment); - sin_Ti = (float)utilities::sin(i * theta_per_segment); + cos_Ti = (float)utilities::cos(i * (double)theta_per_segment); + sin_Ti = (float)utilities::sin(i * (double)theta_per_segment); r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti; r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti; count = 0; @@ -444,14 +444,6 @@ void repetier::arc_1_0_4(float* position, float* target, float* offset, float ra moveToReal(target[X_AXIS], target[Y_AXIS], position[Z_AXIS], target[E_AXIS]); } -float repetier::min(float x, float y) -{ - if (x < y) - { - return x; - } - return y; -} //void repetier::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target) void repetier::moveToReal(float x, float y, float z, float e) diff --git a/ArcWelderInverseProcessor/repetier.h b/ArcWelderInverseProcessor/repetier.h index 6b4bcb3..0c381c5 100644 --- a/ArcWelderInverseProcessor/repetier.h +++ b/ArcWelderInverseProcessor/repetier.h @@ -33,7 +33,6 @@ private: // Note that trailing underscore are sometimes dropped to keep the ported function as close as possible to the original float feedrate; // Repetier Function Defs - float min(float x, float y); void moveToReal(float x, float y, float z, float e); }; diff --git a/ArcWelderInverseProcessor/smoothieware.cpp b/ArcWelderInverseProcessor/smoothieware.cpp index bf17934..a747489 100644 --- a/ArcWelderInverseProcessor/smoothieware.cpp +++ b/ArcWelderInverseProcessor/smoothieware.cpp @@ -105,23 +105,23 @@ bool smoothieware::append_arc_2021_06_19(SmoothiewareGcode* gcode, const float t //check for condition where atan2 formula will fail due to everything canceling out exactly if ((this->machine_position[this->plane_axis_0] == target[this->plane_axis_0]) && (this->machine_position[this->plane_axis_1] == target[this->plane_axis_1])) { if (is_clockwise) { // set angular_travel to -2pi for a clockwise full circle - angular_travel = (-2 * (float)PI); + angular_travel = (-2 * PI_FLOAT); } else { // set angular_travel to 2pi for a counterclockwise full circle - angular_travel = (2 * (float)PI); + angular_travel = (2 * PI_FLOAT); } } else { // Patch from GRBL Firmware - Christoph Baumann 04072015 // CCW angle between position and target from circle center. Only one atan2() trig computation required. // Only run if not a full circle or angular travel will incorrectly result in 0.0f - angular_travel = atan2f(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1); + angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1); if (plane_axis_2 == Y_AXIS) { is_clockwise = !is_clockwise; } //Math for XZ plane is reverse of other 2 planes if (is_clockwise) { // adjust angular_travel to be in the range of -2pi to 0 for clockwise arcs - if (angular_travel > 0) { angular_travel -= (2 * (float)PI); } + if (angular_travel > 0) { angular_travel -= (2 * PI_FLOAT); } } else { // adjust angular_travel to be in the range of 0 to 2pi for counterclockwise arcs - if (angular_travel < 0) { angular_travel += (2 * (float)PI); } + if (angular_travel < 0) { angular_travel += (2 * PI_FLOAT); } } } @@ -135,7 +135,7 @@ bool smoothieware::append_arc_2021_06_19(SmoothiewareGcode* gcode, const float t // Find the distance for this gcode - float millimeters_of_travel = (float)utilities::hypot(angular_travel * radius, utilities::fabsf(linear_travel)); + float millimeters_of_travel = (float)utilities::hypot((double)angular_travel * radius, utilities::fabsf(linear_travel)); // We don't care about non-XYZ moves ( for example the extruder produces some of those ) if (millimeters_of_travel < 0.000001F) { @@ -144,8 +144,8 @@ bool smoothieware::append_arc_2021_06_19(SmoothiewareGcode* gcode, const float t // limit segments by maximum arc error float arc_segment = (float)args_.mm_per_arc_segment; - if ((args_.mm_max_arc_error > 0) && (2 * radius > args_.mm_max_arc_error)) { - float min_err_segment = 2 * (float)utilities::sqrtf((args_.mm_max_arc_error * (2 * radius - args_.mm_max_arc_error))); + if ((args_.mm_max_arc_error > 0) && (2.0 * (double)radius > args_.mm_max_arc_error)) { + float min_err_segment = 2 * (float)utilities::sqrt((args_.mm_max_arc_error * (2.0 * (double)radius - args_.mm_max_arc_error))); if (args_.mm_per_arc_segment < min_err_segment) { arc_segment = min_err_segment; } @@ -158,7 +158,7 @@ bool smoothieware::append_arc_2021_06_19(SmoothiewareGcode* gcode, const float t // Figure out how many segments for this gcode // TODO for deltas we need to make sure we are at least as many segments as requested, also if mm_per_line_segment is set we need to use the - uint16_t segments = (uint16_t)floorf(millimeters_of_travel / arc_segment); + uint16_t segments = (uint16_t)utilities::floorf(millimeters_of_travel / arc_segment); bool moved = false; if (segments > 1) { @@ -223,8 +223,8 @@ bool smoothieware::append_arc_2021_06_19(SmoothiewareGcode* gcode, const float t else { // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - cos_Ti = (float)utilities::cosf(i * theta_per_segment); - sin_Ti = (float)utilities::sinf(i * theta_per_segment); + cos_Ti = (float)utilities::cos(i * (double)theta_per_segment); + sin_Ti = (float)utilities::sin(i * (double)theta_per_segment); r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti; r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti; count = 0; diff --git a/ArcWelderInverseProcessor/smoothieware.h b/ArcWelderInverseProcessor/smoothieware.h index 61461c6..77d54bd 100644 --- a/ArcWelderInverseProcessor/smoothieware.h +++ b/ArcWelderInverseProcessor/smoothieware.h @@ -57,7 +57,6 @@ private: static const int plane_axis_1 = AxisEnum::Y_AXIS; static const int plane_axis_2 = AxisEnum::Z_AXIS; static const int plane_axis_3 = AxisEnum::E_AXIS; - const double PI = M_PI; SmoothiewareGcode gcode_; SmoothiewareKernel *THEKERNEL; float feed_rate; diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 23588fd..31ca28f 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -34,7 +34,7 @@ int main(int argc, char* argv[]) int run_tests(int argc, char* argv[]) { - _CrtMemState state; + _CrtMemState state1, state2, state3; // This line will take a snapshot // of the memory allocated at this point. _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); @@ -46,7 +46,7 @@ int run_tests(int argc, char* argv[]) //std::string filename = argv[1]; unsigned int num_runs = 1; - _CrtMemCheckpoint(&state); + _CrtMemCheckpoint(&state1); auto start = std::chrono::high_resolution_clock::now(); for (unsigned int index = 0; index < num_runs; index++) @@ -94,7 +94,11 @@ int run_tests(int argc, char* argv[]) } auto end = std::chrono::high_resolution_clock::now(); - _CrtMemDumpAllObjectsSince(&state); + _CrtMemCheckpoint(&state2); + if (_CrtMemDifference(&state3, &state1, &state2)) { + _CrtMemDumpStatistics(&state3); + } + //_CrtMemDumpAllObjectsSince(&state); std::chrono::duration diff = end - start; std::cout << "Tests completed in " << diff.count() << " seconds"; //std::cout << "Has Memory Leak = " << has_leak << ".\r\n"; diff --git a/GcodeProcessorLib/gcode_position.cpp b/GcodeProcessorLib/gcode_position.cpp index 444a879..daf66e4 100644 --- a/GcodeProcessorLib/gcode_position.cpp +++ b/GcodeProcessorLib/gcode_position.cpp @@ -616,7 +616,7 @@ void gcode_position::update(parsed_command& command, const long file_line_number { double r; r = snapshot_x_max_; // good stand in for radius - const double dist = sqrt(p_current_pos->x*p_current_pos->x + p_current_pos->y*p_current_pos->y); + const double dist = utilities::sqrt(p_current_pos->x*p_current_pos->x + p_current_pos->y*p_current_pos->y); is_in_bounds = utilities::less_than_or_equal(dist, r); } diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index c1ac9fc..c53ddf8 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -38,11 +38,11 @@ namespace utilities { bool utilities::is_zero(double x, double tolerance) { - return fabs(x) < tolerance; + return utilities::abs(x) < tolerance; } bool utilities::is_zero(double x) { - return fabs(x) < ZERO_TOLERANCE; + return utilities::abs(x) < ZERO_TOLERANCE; } int utilities::round_up_to_int(double x, double tolerance) @@ -57,13 +57,13 @@ int utilities::round_up_to_int(double x) bool utilities::is_equal(double x, double y, double tolerance) { - double abs_difference = fabs(x - y); + double abs_difference = utilities::abs(x - y); return abs_difference < tolerance; } bool utilities::is_equal(double x, double y) { - double abs_difference = fabs(x - y); + double abs_difference = utilities::abs(x - y); return abs_difference < ZERO_TOLERANCE; } @@ -114,7 +114,7 @@ double utilities::get_cartesian_distance(double x1, double y1, double x2, double double xdif = x1 - x2; double ydif = y1 - y2; double dist_squared = xdif * xdif + ydif * ydif; - return sqrt(dist_squared); + return utilities::sqrt(dist_squared); } double utilities::get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2) @@ -124,30 +124,30 @@ double utilities::get_cartesian_distance(double x1, double y1, double z1, double double ydif = y1 - y2; double zdif = z1 - z2; double dist_squared = xdif * xdif + ydif * ydif + zdif * zdif; - return sqrt(dist_squared); + return utilities::sqrt(dist_squared); } double utilities::get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise) { double center_x = x1 - i; double center_y = y1 - j; - double radius = hypot(i, j); + double radius = utilities::hypot(i, j); double z_dist = z2 - z1; double rt_x = x2 - center_x; double rt_y = y2 - center_y; - double angular_travel_total = atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y); - if (angular_travel_total < 0) { angular_travel_total += (double)(2.0 * PI_DOUBLE); } + double angular_travel_total = utilities::atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y); + if (angular_travel_total < 0) { angular_travel_total += 2.0 * PI_DOUBLE; } // Adjust the angular travel if the direction is clockwise - if (is_clockwise) { angular_travel_total -= (float)(2 * PI_DOUBLE); } + if (is_clockwise) { angular_travel_total -= 2.0 * PI_DOUBLE; } // Full circle fix. if (x1 == x2 && y1 == y2 && angular_travel_total == 0) { - angular_travel_total += (float)(2 * PI_DOUBLE); + angular_travel_total += 2.0 * PI_DOUBLE; } // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are // calculating here - return hypot(angular_travel_total * radius, fabs(z_dist)); + return utilities::hypot(angular_travel_total * radius, utilities::abs(z_dist)); } @@ -298,7 +298,7 @@ std::string utilities::get_percent_change_string(int v1, int v2, int precision) int utilities::get_num_digits(int x) { - x = (int)abs(x); + x = utilities::abs(x); return (x < 10 ? 1 : (x < 100 ? 2 : (x < 1000 ? 3 : @@ -314,8 +314,8 @@ int utilities::get_num_digits(int x) int utilities::get_num_digits(double x, int precision) { return get_num_digits( - (int)ceil(x * pow(10, (double)precision) - .4999999999999) - / pow(10, (double)precision) + (int)utilities::ceil(x * utilities::pow(10, precision) - .4999999999999) + / utilities::pow(10, precision) ); } @@ -398,7 +398,20 @@ double utilities::hypot(double x, double y) } if (y == 0.0) return x; y /= x; - return x * sqrt(1.0 + y * y); + return x * utilities::sqrt(1.0 + y * y); +} + +float utilities::hypotf(float x, float y) +{ + if (x < 0.0f) x = -x; + if (y < 0.0f) y = -y; + if (x < y) { + float tmp = x; + x = y; y = tmp; + } + if (y == 0.0f) return x; + y /= x; + return x * utilities::sqrtf(1.0f + y * y); } double utilities::atan2(double y, double x) @@ -406,7 +419,7 @@ double utilities::atan2(double y, double x) return std::atan2(y, x); } -double utilities::atan2f(double y, double x) +float utilities::atan2f(float y, float x) { return std::atan2(y, x); } @@ -416,7 +429,7 @@ double utilities::floor(double x) return std::floor(x); } -double utilities::floorf(double x) +float utilities::floorf(float x) { return std::floor(x); } @@ -426,22 +439,27 @@ double utilities::ceil(double x) return std::ceil(x); } +float utilities::ceilf(float x) +{ + return std::ceil(x); +} + double utilities::cos(double x) { return std::cos(x); } -double utilities::sin(double x) +float utilities::cosf(float x) { - return std::sin(x); + return std::cos(x); } -double utilities::cosf(double x) +double utilities::sin(double x) { - return std::cos(x); + return std::sin(x); } -double utilities::sinf(double x) +float utilities::sinf(float x) { return std::sin(x); } @@ -451,12 +469,22 @@ double utilities::abs(double x) return std::abs(x); } +int utilities::abs(int x) +{ + return std::abs(x); +} + +float utilities::absf(float x) +{ + return std::abs(x); +} + double utilities::fabs(double x) { return std::fabs(x); } -double utilities::fabsf(double x) +float utilities::fabsf(float x) { return std::fabs(x); } @@ -466,7 +494,7 @@ double utilities::sqrt(double x) return std::sqrt(x); } -double utilities::sqrtf(double x) +float utilities::sqrtf(float x) { return std::sqrt(x); } @@ -476,11 +504,81 @@ double utilities::pow(int e, double x) return std::pow(e, x); } -double utilities::min(float x, float y) +double utilities::pow(int e, int x) +{ + return std::pow(e, x); +} + +double utilities::min(double x, double y) +{ + return std::min(x, y); +} + +float utilities::minf(float x, float y) { return std::min(x, y); } +double utilities::max(double x, double y) +{ + return std::max(x, y); +} + +float utilities::maxf(float x, float y) +{ + return std::max(x, y); +} + +double utilities::radians(double x) +{ + return (x * PI_DOUBLE) / 180.0; +} + +float utilities::radiansf(float x) +{ + return (x * PI_FLOAT) / 180.0f; +} + +double utilities::sq(double x) +{ + return x * x; +} + +float utilities::sqf(float x) +{ + return x * x; +} + +bool utilities::within(double value, double min, double max) +{ + return ((value) >= (min) && (value) <= (max)); +} + +bool utilities::withinf(float value, float min, float max) +{ + return ((value) >= (min) && (value) <= (max)); +} + +double utilities::constrain(double value, double arg_min, double arg_max) +{ + return ((value) < (arg_min) ? (arg_min) : ((value) > (arg_max) ? (arg_max) : (value))); +} + +float utilities::constrainf(float value, float arg_min, float arg_max) +{ + return ((value) < (arg_min) ? (arg_min) : ((value) > (arg_max) ? (arg_max) : (value))); +} + +double utilities::reciprocal(double x) +{ + return 1.0 / x; +} + +float utilities::reciprocalf(float x) +{ + return 1.0f / x; +} + void* utilities::memcpy(void* dest, const void* src, size_t n) { return std::memcpy(dest, src, n); diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index 7e2f80b..88e8c40 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -38,6 +38,7 @@ // retract enough while wiping. #define ZERO_TOLERANCE 0.000005 #define PI_DOUBLE 3.14159265358979323846264338327950288 +#define PI_FLOAT 3.14159265358979323846264338327950288f namespace utilities{ extern const std::string WHITESPACE_; @@ -107,38 +108,74 @@ namespace utilities{ bool get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path); double hypot(double x, double y); + + float hypotf(float x, float y); double atan2(double y, double x); - double atan2f(double y, double x); + float atan2f(float y, float x); double floor(double x); - double floorf(double x); + float floorf(float x); double ceil(double x); + + float ceilf(float x); double cos(double x); - double sin(double x); + float cosf(float x); - double cosf(double x); + double sin(double x); - double sinf(double x); + float sinf(float x); double abs(double x); + int abs(int x); + + float absf(float x); + double fabs(double x); - double fabsf(double x); + float fabsf(float x); double sqrt(double x); - double sqrtf(double x); + float sqrtf(float x); double pow(int e, double x); - double min(float x, float y); + double pow(int e, int x); + + double min(double x, double y); + + float minf(float x, float y); + + double max(double x, double y); + + float maxf(float x, float y); + + double radians(double x); + + float radiansf(float x); + + double sq(double x); + + float sqf(float x); + + bool within(double n, double l, double h); + + bool withinf(float n, float l, float h); + + double constrain(double value, double arg_min, double arg_max); + + float constrainf(float value, float arg_min, float arg_max); + + double reciprocal(double x); + + float reciprocalf(float x); void* memcpy(void* dest, const void* src, size_t n); -- cgit v1.2.3 From 0312dededf21b604e228c5b01e8f59d02c13ee95 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 11:40:18 -0600 Subject: Test archive in current directory --- .github/workflows/ccpp.yml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 368c8b8..169fbd6 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -18,30 +18,23 @@ jobs: config: - { name: "Windows Latest MSVC", - artifact_tar: "Windows-MSVC.tar.xz", - artifact_zip: "Windows-MSVC.zip", + tar_artifact: "Windows-MSVC.tar.xz", + zip_artifact: "Windows-MSVC.zip", os: windows-latest, cc: "cl", cxx: "cl", environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat" } - #- { - # name: "Windows Latest MinGW", - # artifact_tar: "Windows-MinGW.tar.xz", - # artifact_zip: "Windows-MinGW.zip", - # os: windows-latest, - # cc: "gcc", cxx: "g++" - # } - { name: "Ubuntu Latest GCC", - artifact_tar: "Linux.tar.xz", - artifact_zip: "Linux.zip", + tar_artifact: "Linux.tar.xz", + zip_artifact: "Linux.zip", os: ubuntu-latest, cc: "gcc", cxx: "g++" } - { name: "macOS Latest GCC", - artifact_tar: "macOS.tar.xz", - artifact_zip: "macOS.zip", + tar_artifact: "macOS.tar.xz", + zip_artifact: "macOS.zip", os: macos-latest, cc: "gcc", cxx: "g++" } @@ -222,21 +215,21 @@ jobs: - name: Pack tar working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv ../${{ matrix.config.artifact_tar }} . + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "./${{ matrix.config.tar_artifact }}" . - name: Pack zip working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.artifact_zip }}" --format=zip . + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "./${{ matrix.config.zip_artifact }}" --format=zip . - name: Upload tar uses: actions/upload-artifact@v1 with: - path: ./${{ matrix.config.artifact_tar }} - name: ${{ matrix.config.artifact_tar }} + path: ./${{ matrix.config.tar_artifact }} + name: ${{ matrix.config.tar_artifact }} - name: Upload zip uses: actions/upload-artifact@v1 with: - path: ./${{ matrix.config.artifact_zip }} - name: ${{ matrix.config.artifact_zip }} + path: ./${{ matrix.config.zip_artifact }} + name: ${{ matrix.config.zip_artifact }} -- cgit v1.2.3 From 0ffe280beb95a1e7564b9157a1aad8f3bef18d89 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 11:42:21 -0600 Subject: Fix action error --- .github/workflows/ccpp.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 169fbd6..645c7e4 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -214,12 +214,12 @@ jobs: - name: Pack tar - working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "./${{ matrix.config.tar_artifact }}" . + working-directory: instdir/${{ matrix.config.tar_artifact }} + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "../${{ matrix.config.tar_artifact }}" . - name: Pack zip - working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "./${{ matrix.config.zip_artifact }}" --format=zip . + working-directory: instdir/${{ matrix.config.tar_artifact }} + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.zip_artifact }}" --format=zip . - name: Upload tar -- cgit v1.2.3 From 450cddad024a6ac776e282f554eac25ee8df60e0 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 11:44:28 -0600 Subject: Test action change --- .github/workflows/ccpp.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 645c7e4..fda6218 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -214,12 +214,12 @@ jobs: - name: Pack tar - working-directory: instdir/${{ matrix.config.tar_artifact }} - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "../${{ matrix.config.tar_artifact }}" . + working-directory: instdir + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "${{ matrix.config.tar_artifact }}" . - name: Pack zip - working-directory: instdir/${{ matrix.config.tar_artifact }} - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.zip_artifact }}" --format=zip . + working-directory: instdir + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "${{ matrix.config.zip_artifact }}" --format=zip . - name: Upload tar -- cgit v1.2.3 From bce40db1d586426d4a45ae4dd2e0b4b2ec9411a5 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 11:48:22 -0600 Subject: Reorder pack actions --- .github/workflows/ccpp.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index fda6218..1087cf1 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -213,14 +213,13 @@ jobs: run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --install build --prefix instdir --strip - - name: Pack tar - working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "${{ matrix.config.tar_artifact }}" . - - name: Pack zip working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "${{ matrix.config.zip_artifact }}" --format=zip . + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.zip_artifact }}" --format=zip . + - name: Pack tar + working-directory: instdir + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "../${{ matrix.config.tar_artifact }}" . - name: Upload tar uses: actions/upload-artifact@v1 -- cgit v1.2.3 From da99bff87a14814bd9684d519a886643de2bc7fc Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 11:58:26 -0600 Subject: Another attempt to fix zip and tar packing --- .github/workflows/ccpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 1087cf1..96ca171 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -215,11 +215,11 @@ jobs: - name: Pack zip working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.zip_artifact }}" --format=zip . + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "${{ matrix.config.zip_artifact }}" "../" --format=zip . - name: Pack tar working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "../${{ matrix.config.tar_artifact }}" . + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "${{ matrix.config.tar_artifact }}" "../" . - name: Upload tar uses: actions/upload-artifact@v1 -- cgit v1.2.3 From 32708379fe8fe91e4e21b6928d6384162abcd091 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 12:12:28 -0600 Subject: Give up trying to fix zip format (exclude directory with file name) and revert. --- .github/workflows/ccpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 96ca171..1087cf1 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -215,11 +215,11 @@ jobs: - name: Pack zip working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "${{ matrix.config.zip_artifact }}" "../" --format=zip . + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.zip_artifact }}" --format=zip . - name: Pack tar working-directory: instdir - run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "${{ matrix.config.tar_artifact }}" "../" . + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "../${{ matrix.config.tar_artifact }}" . - name: Upload tar uses: actions/upload-artifact@v1 -- cgit v1.2.3 From 4c193828056771381f7bfc6f04133bd1a45447ac Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sat, 13 Nov 2021 13:37:28 -0600 Subject: Encode more points when moving to/from extruding/retracting/travel. --- ArcWelder/arc_welder.cpp | 8 +++++--- ArcWelder/segmented_arc.cpp | 8 +++++--- ArcWelderTest/ArcWelderTest.cpp | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index fe129b4..ab2f6ef 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -582,11 +582,13 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess ( !waiting_for_arc_ || extruder_current.is_extruding || + extruder_current.is_retracting || // Test for travel conversion - (allow_travel_arcs_ && p_cur_pos->is_travel()) || - //(previous_extruder.is_extruding && extruder_current.is_extruding) || // Test to see if + (allow_travel_arcs_ && p_cur_pos->is_travel()) + //|| (previous_extruder.is_extruding && extruder_current.is_extruding) // Test to see if + // we can get more arcs. + // || (previous_extruder.is_retracting && extruder_current.is_retracting) // Test to see if // we can get more arcs. - (previous_extruder.is_retracting && extruder_current.is_retracting) ) && p_cur_pos->is_extruder_relative == is_previous_extruder_relative && (!waiting_for_arc_ || p_pre_pos->f == p_cur_pos->f) && // might need to skip the waiting for arc check... diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 527ad3a..f6cfff1 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -155,13 +155,15 @@ bool segmented_arc::try_add_point(printer_point p) return false; } - // Need to separate travel moves from moves with extrusion - if (points_.count() > 1) + // If we have more than 2 points, we need to make sure the current and previous moves are all of the same type. + if (points_.count() > 2) { + // TODO: Do we need this? // We already have at least an initial point and a second point. Make cure the current point and the previous are either both // travel moves, or both extrusion if (!( - (p1.e_relative != 0 && p.e_relative != 0) // Extrusions + (p1.e_relative > 0 && p.e_relative > 0) // Extrusions + || (p1.e_relative < 0 && p.e_relative < 0) // Retractions || (p1.e_relative == 0 && p.e_relative == 0) // Travel ) ) diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 31ca28f..2aaa8e4 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -306,7 +306,7 @@ static void TestAntiStutter(std::string filePath) // BENCHY_L1_DIFFICULT // SPIRAL_TEST // SPIRAL_VASE_TEST_FUNNEL - std::string source_path = TravelWipeTest; + std::string source_path = BENCHY_DIFFICULT; std::string target_path = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode"; arc_welder_args args(source_path, target_path, p_logger); args.callback = on_progress; @@ -315,6 +315,7 @@ static void TestAntiStutter(std::string filePath) args.allow_3d_arcs = true; args.max_radius_mm = 9999; args.resolution_mm = 0.05; + args.extrusion_rate_variance_percent = 1000; arc_welder arc_welder_obj(args); arc_welder_results results = arc_welder_obj.process(); -- cgit v1.2.3 From 9b0afec63643275ecdc9005bfbbba20e359a7938 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sun, 21 Nov 2021 12:31:00 -0600 Subject: Make extrusion-rate-variance-percent optional (0 to disable). Add HTML box drawing for pretty statistics. Separate extrusion and retraction statistics for analyzation purposes, but keep combined statistics. --- ArcWelder/arc_welder.cpp | 320 +++++++++++---------- ArcWelder/arc_welder.h | 289 +++++++++++++------ ArcWelder/segmented_shape.cpp | 1 - ArcWelder/unwritten_command.h | 11 +- ArcWelderConsole/ArcWelderConsole.cpp | 43 +-- .../ArcWelderInverseProcessor.cpp | 18 +- ArcWelderTest/ArcWelderTest.cpp | 26 +- ArcWelderTest/ArcWelderTest.h | 1 + GcodeProcessorLib/logger.cpp | 26 +- GcodeProcessorLib/logger.h | 14 +- GcodeProcessorLib/utilities.cpp | 88 +++++- GcodeProcessorLib/utilities.h | 31 +- PyArcWelder/py_arc_welder.cpp | 6 +- PyArcWelder/py_arc_welder_extension.cpp | 8 +- PyArcWelder/py_logger.cpp | 16 +- 15 files changed, 566 insertions(+), 332 deletions(-) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index ab2f6ef..883f66b 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -36,89 +36,79 @@ #include #include -arc_welder::arc_welder( - std::string source_path, - std::string target_path, - logger* log, - double resolution_mm, - double path_tolerance_percent, - double max_radius, - int min_arc_segments, - double mm_per_arc_segment, - bool g90_g91_influences_extruder, - bool allow_3d_arcs, - bool allow_travel_arcs, - bool allow_dynamic_precision, - unsigned char default_xyz_precision, - unsigned char default_e_precision, - double extrusion_rate_variance_percent, - int max_gcode_length, - int buffer_size, - double notification_period_seconds, - progress_callback callback) : current_arc_( - DEFAULT_MIN_SEGMENTS, - buffer_size, - resolution_mm, - path_tolerance_percent, - max_radius, - min_arc_segments, - mm_per_arc_segment, - allow_3d_arcs, - default_xyz_precision, - default_e_precision, - max_gcode_length - ), - segment_statistics_( - segment_statistic_lengths, - segment_statistic_lengths_count, - log - ), - travel_statistics_( - segment_statistic_lengths, - segment_statistic_lengths_count, - log - ) + + + +arc_welder::arc_welder(arc_welder_args args) : current_arc_( + DEFAULT_MIN_SEGMENTS, + args.buffer_size, + args.resolution_mm, + args.path_tolerance_percent, + args.max_radius_mm, + args.min_arc_segments, + args.mm_per_arc_segment, + args.allow_3d_arcs, + args.default_xyz_precision, + args.default_e_precision, + args.max_gcode_length + ), + segment_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + args.log + ), + segment_retraction_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + args.log + ), + travel_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + args.log + ) { - p_logger_ = log; - debug_logging_enabled_ = false; - info_logging_enabled_ = false; - error_logging_enabled_ = false; - verbose_logging_enabled_ = false; - - logger_type_ = 0; - resolution_mm_ = resolution_mm; - progress_callback_ = callback; - verbose_output_ = false; - source_path_ = source_path; - target_path_ = target_path; - gcode_position_args_ = get_args_(g90_g91_influences_extruder, buffer_size); - allow_3d_arcs_ = allow_3d_arcs; - allow_travel_arcs_ = allow_travel_arcs; - allow_dynamic_precision_ = allow_dynamic_precision; - extrusion_rate_variance_percent_ = extrusion_rate_variance_percent; - lines_processed_ = 0; - gcodes_processed_ = 0; - file_size_ = 0; - notification_period_seconds_ = notification_period_seconds; - last_gcode_line_written_ = 0; - points_compressed_ = 0; - arcs_created_ = 0; - arcs_aborted_by_flow_rate_ = 0; - waiting_for_arc_ = false; - previous_feedrate_ = -1; - gcode_position_args_.set_num_extruders(8); - previous_extrusion_rate_ = 0; - for (int index = 0; index < 8; index++) - { - gcode_position_args_.retraction_lengths[0] = .0001; - gcode_position_args_.z_lift_heights[0] = 0.001; - gcode_position_args_.x_firmware_offsets[0] = 0.0; - gcode_position_args_.y_firmware_offsets[0] = 0.0; - } + p_logger_ = args.log; + debug_logging_enabled_ = false; + info_logging_enabled_ = false; + error_logging_enabled_ = false; + verbose_logging_enabled_ = false; + + logger_type_ = 0; + resolution_mm_ = args.resolution_mm; + progress_callback_ = args.callback; + verbose_output_ = false; + source_path_ = args.source_path; + target_path_ = args.target_path; + gcode_position_args_ = get_args_(args.g90_g91_influences_extruder, args.buffer_size); + allow_3d_arcs_ = args.allow_3d_arcs; + allow_travel_arcs_ = args.allow_travel_arcs; + allow_dynamic_precision_ = args.allow_dynamic_precision; + extrusion_rate_variance_percent_ = args.extrusion_rate_variance_percent; + lines_processed_ = 0; + gcodes_processed_ = 0; + file_size_ = 0; + notification_period_seconds_ = args.notification_period_seconds; + last_gcode_line_written_ = 0; + points_compressed_ = 0; + arcs_created_ = 0; + arcs_aborted_by_flow_rate_ = 0; + waiting_for_arc_ = false; + previous_feedrate_ = -1; + gcode_position_args_.set_num_extruders(8); + previous_extrusion_rate_ = 0; + box_encoding_ = args.box_encoding; + for (int index = 0; index < 8; index++) + { + gcode_position_args_.retraction_lengths[0] = .0001; + gcode_position_args_.z_lift_heights[0] = 0.001; + gcode_position_args_.x_firmware_offsets[0] = 0.0; + gcode_position_args_.y_firmware_offsets[0] = 0.0; + } - // We don't care about the printer settings, except for g91 influences extruder. + // We don't care about the printer settings, except for g91 influences extruder. - p_source_position_ = new gcode_position(gcode_position_args_); + p_source_position_ = new gcode_position(gcode_position_args_); } gcode_position_args arc_welder::get_args_(bool g90_g91_influences_extruder, int buffer_size) @@ -170,7 +160,7 @@ void arc_welder::set_logger_type(int logger_type) void arc_welder::reset() { - p_logger_->log(logger_type_, DEBUG, "Resetting all tracking variables."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Resetting all tracking variables."); lines_processed_ = 0; gcodes_processed_ = 0; last_gcode_line_written_ = 0; @@ -204,11 +194,11 @@ double arc_welder::get_time_elapsed(double start_clock, double end_clock) arc_welder_results arc_welder::process() { arc_welder_results results; - p_logger_->log(logger_type_, DEBUG, "Configuring logging settings."); - verbose_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, VERBOSE); - debug_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, DEBUG); - info_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, INFO); - error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, ERROR); + p_logger_->log(logger_type_, log_levels::DEBUG, "Configuring logging settings."); + verbose_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::VERBOSE); + debug_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::DEBUG); + info_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::INFO); + error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::ERROR); std::stringstream stream; // reset tracking variables @@ -216,16 +206,16 @@ arc_welder_results arc_welder::process() // local variable to hold the progress update return. If it's false, we will exit. bool continue_processing = true; - p_logger_->log(logger_type_, DEBUG, "Configuring progress updates."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Configuring progress updates."); int read_lines_before_clock_check = 1000; double next_update_time = get_next_update_time(); const clock_t start_clock = clock(); - p_logger_->log(logger_type_, DEBUG, "Getting source file size."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Getting source file size."); file_size_ = get_file_size(source_path_); stream.clear(); stream.str(""); stream << "Source file size: " << file_size_; - p_logger_->log(logger_type_, DEBUG, stream.str()); + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); // Determine if we need to overwrite the source file bool overwrite_source_file = false; @@ -244,13 +234,13 @@ arc_welder_results arc_welder::process() stream.clear(); stream.str(""); stream << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; - p_logger_->log(logger_type_, DEBUG, stream.str()); + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); target_path_ = temp_file_path; } // Create the source file read stream and target write stream std::ifstream gcodeFile; - p_logger_->log(logger_type_, DEBUG, "Opening the source file for reading."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Opening the source file for reading."); gcodeFile.open(source_path_.c_str(), std::ifstream::in); if (!gcodeFile.is_open()) { @@ -259,9 +249,9 @@ arc_welder_results arc_welder::process() p_logger_->log_exception(logger_type_, results.message); return results; } - p_logger_->log(logger_type_, DEBUG, "Source file opened successfully."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Source file opened successfully."); - p_logger_->log(logger_type_, DEBUG, "Opening the target file for writing."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Opening the target file for writing."); output_file_.open(target_path_.c_str(), std::ios_base::binary | std::ios_base::out); if (!output_file_.is_open()) @@ -273,14 +263,14 @@ arc_welder_results arc_welder::process() return results; } - p_logger_->log(logger_type_, DEBUG, "Target file opened successfully."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Target file opened successfully."); std::string line; int lines_with_no_commands = 0; parsed_command cmd; // Communicate every second - p_logger_->log(logger_type_, DEBUG, "Sending initial progress update."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Sending initial progress update."); continue_processing = on_progress_(get_progress_(static_cast(gcodeFile.tellg()), static_cast(start_clock))); - p_logger_->log(logger_type_, DEBUG, "Processing source file."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Processing source file."); bool arc_Welder_comment_added = false; while (std::getline(gcodeFile, line) && continue_processing) @@ -311,7 +301,7 @@ arc_welder_results arc_welder::process() stream.clear(); stream.str(""); stream << "Parsing: " << line; - p_logger_->log(logger_type_, VERBOSE, stream.str()); + p_logger_->log(logger_type_, log_levels::VERBOSE, stream.str()); } parser_.try_parse_gcode(line.c_str(), cmd, true); bool has_gcode = false; @@ -337,7 +327,7 @@ arc_welder_results arc_welder::process() { if (verbose_logging_enabled_) { - p_logger_->log(logger_type_, VERBOSE, "Sending progress update."); + p_logger_->log(logger_type_, log_levels::VERBOSE, "Sending progress update."); } continue_processing = on_progress_(get_progress_(static_cast(gcodeFile.tellg()), static_cast(start_clock))); next_update_time = get_next_update_time(); @@ -347,22 +337,22 @@ arc_welder_results arc_welder::process() if (current_arc_.is_shape() && waiting_for_arc_) { - p_logger_->log(logger_type_, DEBUG, "Processing the final line."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Processing the final line."); process_gcode(cmd, true, false); } - p_logger_->log(logger_type_, DEBUG, "Writing all unwritten gcodes to the target file."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Writing all unwritten gcodes to the target file."); write_unwritten_gcodes_to_file(); - p_logger_->log(logger_type_, DEBUG, "Fetching the final progress struct."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Fetching the final progress struct."); arc_welder_progress final_progress = get_progress_(static_cast(file_size_), static_cast(start_clock)); if (debug_logging_enabled_) { - p_logger_->log(logger_type_, DEBUG, "Sending final progress update message."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Sending final progress update message."); } on_progress_(final_progress); - p_logger_->log(logger_type_, DEBUG, "Closing source and target files."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Closing source and target files."); output_file_.close(); gcodeFile.close(); @@ -371,19 +361,19 @@ arc_welder_results arc_welder::process() stream.clear(); stream.str(""); stream << "Deleting the original source file at '" << source_path_ << "'."; - p_logger_->log(logger_type_, DEBUG, stream.str()); + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); stream.clear(); stream.str(""); std::remove(source_path_.c_str()); stream << "Renaming temporary file at '" << target_path_ << "' to '" << source_path_ << "'."; - p_logger_->log(0, DEBUG, stream.str()); + p_logger_->log(0, log_levels::DEBUG, stream.str()); std::rename(target_path_.c_str(), source_path_.c_str()); } results.success = continue_processing; results.cancelled = !continue_processing; results.progress = final_progress; - p_logger_->log(logger_type_, DEBUG, "Returning processing results."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Returning processing results."); return results; } @@ -396,7 +386,7 @@ bool arc_welder::on_progress_(const arc_welder_progress& progress) } else if (info_logging_enabled_) { - p_logger_->log(logger_type_, INFO, progress.str()); + p_logger_->log(logger_type_, log_levels::INFO, progress.str()); } return true; @@ -430,19 +420,17 @@ arc_welder_progress arc_welder::get_progress_(long source_file_position, double progress.num_firmware_compensations = current_arc_.get_num_firmware_compensations(); progress.num_gcode_length_exceptions = current_arc_.get_num_gcode_length_exceptions(); progress.segment_statistics = segment_statistics_; + progress.segment_retraction_statistics = segment_retraction_statistics_; progress.travel_statistics = travel_statistics_; + progress.box_encoding = box_encoding_; return progress; } int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess) { - /* use to catch gcode for debugging since I can't set conditions equal to strings - if (cmd.gcode == "G1 X118.762 Y104.054 E0.0163") - { - std::cout << "Found it!"; - } - */ + + // Update the position for the source gcode file p_source_position_->update(cmd, lines_processed_, gcodes_processed_, -1); position* p_cur_pos = p_source_position_->get_current_position_ptr(); @@ -462,7 +450,10 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess bool arc_added = false; bool clear_shapes = false; double movement_length_mm = 0; - bool has_e_changed = extruder_current.e_relative != 0; + bool is_extrusion = extruder_current.e_relative > 0; + bool is_retraction = extruder_current.e_relative < 0; + bool is_travel = !(is_extrusion || is_retraction) && (is_g0_g1 || is_g2_g3); + // Update the source file statistics if (p_cur_pos->has_xy_position_changed) { @@ -512,11 +503,15 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { if (!is_reprocess) { - if (has_e_changed) + if (is_extrusion) { segment_statistics_.update(movement_length_mm, true); } - else if (allow_travel_arcs_) + else if (is_retraction) + { + segment_retraction_statistics_.update(movement_length_mm, true); + } + else if (allow_travel_arcs_ && is_travel) { travel_statistics_.update(movement_length_mm, true); } @@ -529,20 +524,24 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess double mm_extruded_per_mm_travel = 0; double extrusion_rate_change_percent = 0; bool aborted_by_flow_rate = false; - // TODO: MAKE SURE THIS WORKS FOR TRANSITIONS FROM TRAVEL TO NON TRAVEL MOVES - if (movement_length_mm > 0 && has_e_changed) - { - mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; - if (previous_extrusion_rate_ > 0) - { - extrusion_rate_change_percent = utilities::abs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); - } - } - if (previous_extrusion_rate_ != 0 && utilities::greater_than(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) + if (extrusion_rate_variance_percent_ != 0) { - arcs_aborted_by_flow_rate_++; - aborted_by_flow_rate = true; + // TODO: MAKE SURE THIS WORKS FOR TRANSITIONS FROM TRAVEL TO NON TRAVEL MOVES + if (movement_length_mm > 0 && (is_extrusion || is_retraction)) + { + mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; + if (previous_extrusion_rate_ > 0) + { + extrusion_rate_change_percent = utilities::abs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); + } + } + if (previous_extrusion_rate_ != 0 && utilities::greater_than(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) + { + arcs_aborted_by_flow_rate_++; + aborted_by_flow_rate = true; + } } + // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position // TODO: Handle relative XYZ axis. This is possible, but maybe not so important. @@ -567,7 +566,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess bool z_axis_ok = allow_3d_arcs_ || utilities::is_equal(p_cur_pos->z, p_pre_pos->z); - + if ( !is_end && cmd.is_known_command && !cmd.is_empty && ( is_g0_g1 && z_axis_ok && @@ -603,7 +602,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { if (debug_logging_enabled_) { - p_logger_->log(logger_type_, DEBUG, "Starting new arc from Gcode:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Starting new arc from Gcode:" + cmd.gcode); } write_unwritten_gcodes_to_file(); // add the previous point as the starting point for the current arc @@ -635,7 +634,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { if (num_points + 1 == current_arc_.get_num_segments()) { - p_logger_->log(logger_type_, DEBUG, "Adding point to arc from Gcode:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Adding point to arc from Gcode:" + cmd.gcode); } } @@ -647,25 +646,25 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess if (debug_logging_enabled_) { if (is_end) { - p_logger_->log(logger_type_, DEBUG, "Procesing final shape, if one exists."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Procesing final shape, if one exists."); } else if (!cmd.is_empty) { if (!cmd.is_known_command) { - p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); } else if (cmd.command != "G0" && cmd.command != "G1") { - p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Command '" + cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); } else if (!allow_3d_arcs_ && !utilities::is_equal(p_cur_pos->z, p_pre_pos->z)) { - p_logger_->log(logger_type_, DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); } else if (p_cur_pos->is_relative) { - p_logger_->log(logger_type_, DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); } else if ( waiting_for_arc_ && !( @@ -696,37 +695,37 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") + ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False") ); - p_logger_->log(logger_type_, VERBOSE, message); + p_logger_->log(logger_type_, log_levels::VERBOSE, message); } else { - p_logger_->log(logger_type_, DEBUG, message); + p_logger_->log(logger_type_, log_levels::DEBUG, message); } } else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative) { - p_logger_->log(logger_type_, DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); } else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f) { - p_logger_->log(logger_type_, DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); } else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag) { - p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); } else if (aborted_by_flow_rate) { std::stringstream stream; stream << std::fixed << std::setprecision(5); stream << "Arc Canceled - The extrusion rate variance of " << extrusion_rate_variance_percent_ << "% exceeded by " << extrusion_rate_change_percent - extrusion_rate_variance_percent_ << "% on line " << lines_processed_ << ". Extruded " << extruder_current.e_relative << "mm over " << movement_length_mm << "mm of travel (" << mm_extruded_per_mm_travel << "mm/mm). Previous rate: " << previous_extrusion_rate_ << "mm/mm."; - p_logger_->log(logger_type_, DEBUG, stream.str()); + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); } else { // Todo: Add all the relevant values - p_logger_->log(logger_type_, DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); } } } @@ -742,7 +741,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { if (current_arc_.get_num_segments() != 0) { - p_logger_->log(logger_type_, DEBUG, "Not enough segments, resetting. Gcode:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Not enough segments, resetting. Gcode:" + cmd.gcode); } } @@ -773,7 +772,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { if (debug_logging_enabled_) { - p_logger_->log(logger_type_, DEBUG, "Final arc created, exiting."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Final arc created, exiting."); } return 0; } @@ -783,7 +782,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { if (debug_logging_enabled_) { - p_logger_->log(logger_type_, DEBUG, "The current arc is not a valid arc, resetting."); + p_logger_->log(logger_type_, log_levels::DEBUG, "The current arc is not a valid arc, resetting."); } current_arc_.clear(); waiting_for_arc_ = false; @@ -791,7 +790,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess } else if (debug_logging_enabled_) { - p_logger_->log(logger_type_, DEBUG, "Could not add point to arc from gcode:" + cmd.gcode); + p_logger_->log(logger_type_, log_levels::DEBUG, "Could not add point to arc from gcode:" + cmd.gcode); } } @@ -800,8 +799,7 @@ int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess { // This might not work.... //position* cur_pos = p_source_position_->get_current_position_ptr(); - - unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, !has_e_changed && (is_g0_g1 || is_g2_g3), movement_length_mm)); + unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, is_extrusion, is_retraction, is_travel, movement_length_mm)); } else if (!waiting_for_arc_) @@ -844,19 +842,26 @@ void arc_welder::write_arc_gcodes(double current_feedrate) message += buffer; message += " segments: "; message += gcode; - p_logger_->log(logger_type_, DEBUG, message); + p_logger_->log(logger_type_, log_levels::DEBUG, message); } // Write everything that hasn't yet been written write_unwritten_gcodes_to_file(); // Update the current extrusion statistics for the current arc gcode - if (current_arc_.get_shape_e_relative() != 0) + double shape_e_relative = current_arc_.get_shape_e_relative(); + bool is_retraction = shape_e_relative < 0; + bool is_extrusion = shape_e_relative > 0; + if (is_extrusion) { segment_statistics_.update(current_arc_.get_shape_length(), false); } - else if (allow_travel_arcs_) { + else if (is_retraction) + { + segment_retraction_statistics_.update(current_arc_.get_shape_length(), false); + } + else if (allow_travel_arcs_ ) { travel_statistics_.update(current_arc_.get_shape_length(), false); } // now write the current arc to the file @@ -909,11 +914,16 @@ int arc_welder::write_unwritten_gcodes_to_file() unwritten_command p = unwritten_commands_.pop_front(); if ((p.is_g0_g1 || p.is_g2_g3) && p.length > 0) { - if (!p.is_travel) + + if (p.is_extrusion) { segment_statistics_.update(p.length, false); } - else if (allow_travel_arcs_) { + else if (p.is_retraction) + { + segment_retraction_statistics_.update(p.length, false); + } + else if (p.is_travel && allow_travel_arcs_) { travel_statistics_.update(p.length, false); } } @@ -941,7 +951,7 @@ std::string arc_welder::get_arc_gcode(const std::string comment) void arc_welder::add_arcwelder_comment_to_target() { - p_logger_->log(logger_type_, DEBUG, "Adding ArcWelder comment to the target file."); + p_logger_->log(logger_type_, log_levels::DEBUG, "Adding ArcWelder comment to the target file."); std::stringstream stream; stream << std::fixed; stream << "; Postprocessed by [ArcWelder](https://github.com/FormerLurker/ArcWelderLib)\n"; diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index 7bcff0c..c7d7465 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -61,7 +61,8 @@ struct segment_statistic { }; struct source_target_segment_statistics { - source_target_segment_statistics(const double segment_tracking_lengths[], const int num_lengths, logger* p_logger = NULL) { + source_target_segment_statistics(const double segment_tracking_lengths[], const int num_lengths, logger* p_logger = NULL) + { total_length_source = 0; total_length_target = 0; total_count_source = 0; @@ -73,6 +74,7 @@ struct source_target_segment_statistics { for (int index = 0; index < num_lengths; index++) { double current_max = segment_tracking_lengths[index]; + segment_statistic_lengths.push_back(segment_tracking_lengths[index]); source_segments.push_back(segment_statistic(current_min, segment_tracking_lengths[index])); target_segments.push_back(segment_statistic(current_min, segment_tracking_lengths[index])); current_min = current_max; @@ -83,7 +85,8 @@ struct source_target_segment_statistics { p_logger_ = p_logger; logger_type_ = 0; } - + + std::vector segment_statistic_lengths; std::vector source_segments; std::vector target_segments; double total_length_source; @@ -93,7 +96,7 @@ struct source_target_segment_statistics { int total_count_source; int total_count_target; int num_segment_tracking_lengths; - + double get_total_count_reduction_percent() const { return utilities::get_percent_change(total_count_source, total_count_target); } @@ -127,10 +130,49 @@ struct source_target_segment_statistics { } } + static source_target_segment_statistics add(source_target_segment_statistics stats1, const source_target_segment_statistics stats2) + { + + double * lengths = &stats1.segment_statistic_lengths[0]; + std::copy(stats1.segment_statistic_lengths.begin(), stats1.segment_statistic_lengths.end(), lengths); + source_target_segment_statistics combined_stats(lengths, segment_statistic_lengths_count, stats1.p_logger_); + if (stats1.num_segment_tracking_lengths != stats2.num_segment_tracking_lengths) + { + // Todo: throw a reasonable exception + throw std::exception(); + } + + // Copy the segment statistics + for (int index = 0; index <= stats1.num_segment_tracking_lengths; index++) + { + // Verify the stats are the same + if ( + stats1.source_segments[index].min_mm != stats2.source_segments[index].min_mm + || stats1.source_segments[index].max_mm != stats2.source_segments[index].max_mm + ) + { + // Todo: throw a reasonable exception + throw std::exception(); + } + combined_stats.source_segments[index].count = stats1.source_segments[index].count + stats2.source_segments[index].count; + combined_stats.target_segments[index].count = stats1.target_segments[index].count + stats2.target_segments[index].count; + } + + combined_stats.total_length_source = stats1.total_length_source + stats2.total_length_source; + combined_stats.total_length_target = stats1.total_length_target + stats2.total_length_target; + combined_stats.total_count_source = stats1.total_count_source + stats2.total_count_source; + combined_stats.total_count_target = stats1.total_count_target + stats2.total_count_target; + + return combined_stats; + } std::string str() const { + return str("", utilities::box_drawing::BoxEncodingEnum::ASCII); + } + + std::string str(std::string title, utilities::box_drawing::BoxEncodingEnum box_encoding) const { //if (p_logger_ != NULL) p_logger_->log(logger_type_, VERBOSE, "Building Segment Statistics."); - + std::stringstream output_stream; std::stringstream format_stream; const int min_column_size = 8; @@ -207,21 +249,51 @@ struct source_target_segment_statistics { } // Get the table width int table_width = mm_col_size + min_max_label_col_size + mm_col_size + source_col_size + target_col_size + percent_col_size; - // Add a separator for the statistics - //output_stream << std::setw(table_width) << std::setfill('-') << "-" << "\n" << std::setfill(' ') ; - // Output the column headers - // Center the min and max column. - output_stream << utilities::center("Min", mm_col_size); + int table_left_padding = 0; + int table_right_padding = 0; + if (table_width < (int)title.length()) + { + table_left_padding = ((int)title.length() - table_width) / 2; + table_right_padding = ((int)title.length() - table_width - table_left_padding); + table_width = (int)title.length(); + + } + utilities::box_drawing box(box_encoding, table_width); + // Draw the top border + box.top(output_stream); + + if (title != "") + { + // Draw the title + box.row(output_stream, utilities::center(title, table_width)); + // Draw the title separator + box.middle(output_stream); + } + + // Output the centered column headers + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); + // add the left padding for the table + output_stream << std::string(table_left_padding, ' '); + output_stream << std::setfill(' ') << utilities::center("Min", mm_col_size); output_stream << std::setw(min_max_label_col_size) << ""; output_stream << utilities::center("Max", mm_col_size); // right align the source, target and change columns output_stream << std::setw(source_col_size) << std::right << "Source"; output_stream << std::setw(target_col_size) << std::right << "Target"; output_stream << std::setw(percent_col_size) << std::right << "Change"; - output_stream << "\n"; - output_stream << std::setw(table_width) << std::setfill('-') << "" << std::setfill(' ') << "\n"; + // Add the right padding for the table + output_stream << std::string(table_right_padding, ' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; output_stream << std::fixed << std::setprecision(max_precision); + // Add the separator + box.middle(output_stream); for (int index = 0; index < source_segments.size(); index++) { + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); + // add the left padding for the table + output_stream << std::string(table_left_padding, ' '); //extract the necessary variables from the source and target segments double min_mm = source_segments[index].min_mm; double max_mm = source_segments[index].max_mm; @@ -284,24 +356,34 @@ struct source_target_segment_statistics { output_stream << std::setw(target_col_size) << target_count_string; // Add the percent change string output_stream << std::setw(percent_col_size) << percent_change_string; - // End the line - output_stream << "\n"; + // Add the right padding for the table + output_stream << std::string(table_right_padding, ' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; } - // Add the total rows separator - output_stream << std::setw(table_width) << std::setfill('-') << "" << std::setfill(' ') << "\n"; + + // Add the total rows; + // Draw the totals separator + box.middle(output_stream); if (utilities::is_equal(total_length_source, total_length_target, 0.001)) { + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); std::string total_distance_string; format_stream.str(std::string()); format_stream << std::fixed << std::setprecision(max_precision) << total_length_source << "mm"; total_distance_string = format_stream.str(); output_stream << std::setw(totals_row_label_size) << std::right << "Total distance:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_distance_string << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_distance_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; } else { - // We need to output two different distances (this probably should never happen) + // We need to output two different distances + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Format the total source distance string std::string total_source_distance_string; format_stream.str(std::string()); @@ -309,8 +391,12 @@ struct source_target_segment_statistics { total_source_distance_string = format_stream.str(); // Add the total source distance row output_stream << std::setw(totals_row_label_size) << std::right << "Total distance source:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_source_distance_string << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_source_distance_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Format the total target distance string std::string total_target_distance_string; format_stream.str(std::string()); @@ -318,32 +404,55 @@ struct source_target_segment_statistics { total_target_distance_string = format_stream.str(); // Add the total target distance row output_stream << std::setw(totals_row_label_size) << std::right << "Total distance target:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_target_distance_string << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_target_distance_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; } // Add the total count rows + + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Add the source count output_stream << std::setprecision(0) << std::setw(totals_row_label_size) << std::right << "Total count source:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_source << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_source << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Add the target count output_stream << std::setw(totals_row_label_size) << std::right << "Total count target:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_target << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_target << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Add the total percent change row std::string total_percent_change_string = utilities::get_percent_change_string(total_count_source, total_count_target, 1); output_stream << std::setw(totals_row_label_size) << std::right << "Total percent change:"; output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_percent_change_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + + // Add the final separator + box.bottom(output_stream); + std::string output_string = output_stream.str(); + box.make_replacements(output_string); return output_string; } private: + logger* p_logger_; int logger_type_; }; // Struct to hold the progress, statistics, and return values struct arc_welder_progress { - arc_welder_progress() : segment_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL), travel_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL) { + arc_welder_progress() : segment_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL), segment_retraction_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL), travel_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL) { percent_complete = 0.0; seconds_elapsed = 0.0; seconds_remaining = 0.0; @@ -359,6 +468,8 @@ struct arc_welder_progress { target_file_size = 0; compression_ratio = 0; compression_percent = 0; + combine_extrusion_and_retraction = true; + box_encoding = utilities::box_drawing::BoxEncodingEnum::ASCII; } double percent_complete; double seconds_elapsed; @@ -375,7 +486,11 @@ struct arc_welder_progress { long source_file_position; long source_file_size; long target_file_size; + bool combine_extrusion_and_retraction; + utilities::box_drawing::BoxEncodingEnum box_encoding; + source_target_segment_statistics segment_statistics; + source_target_segment_statistics segment_retraction_statistics; source_target_segment_statistics travel_statistics; std::string simple_progress_str() const { @@ -395,6 +510,7 @@ struct arc_welder_progress { } std::string str() const { + std::stringstream stream; stream << std::fixed << std::setprecision(2); @@ -407,20 +523,36 @@ struct arc_welder_progress { stream << ", num_firmware_compensations: " << num_firmware_compensations; stream << ", num_gcode_length_exceptions: " << num_gcode_length_exceptions; stream << ", compression_ratio: " << compression_ratio; - stream << ", size_reduction: " << compression_percent << "% "; + stream << ", size_reduction: " << compression_percent << "% " ; return stream.str(); } std::string detail_str() const { - std::stringstream stream; + std::stringstream wstream; + wstream << "\n"; + if (travel_statistics.total_count_source > 0) { - stream << "Target File Travel Statistics:" << "\n" << travel_statistics.str() << "\n"; + wstream << travel_statistics.str("Target File Travel Statistics", box_encoding) << "\n"; } - - stream << "\n" << "Target File Extrusion Statistics:" << "\n" << segment_statistics.str() << "\n"; - - return stream.str(); + + if (combine_extrusion_and_retraction) + { + source_target_segment_statistics combined_stats = source_target_segment_statistics::add(segment_statistics, segment_retraction_statistics); + wstream << combined_stats.str("Target File Extrusion/Retraction Statistics", box_encoding) << "\n"; + } + else + { + + if (segment_retraction_statistics.total_count_source > 0) + { + wstream << segment_retraction_statistics.str("Target File Retraction Statistics", box_encoding) << "\n"; + } + + wstream << segment_statistics.str("Target File Extrusion Statistics", box_encoding) << "\n"; + } + return wstream.str(); } + }; // define the progress callback type typedef bool(*progress_callback)(arc_welder_progress, logger* p_logger, int logger_type); @@ -437,30 +569,14 @@ typedef bool(*progress_callback)(arc_welder_progress, logger* p_logger, int logg struct arc_welder_args { - arc_welder_args() : - source_path(""), - target_path(""), - log(NULL), - resolution_mm(DEFAULT_RESOLUTION_MM), - path_tolerance_percent(ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT), - max_radius_mm(DEFAULT_MAX_RADIUS_MM), - min_arc_segments(DEFAULT_MIN_ARC_SEGMENTS), - mm_per_arc_segment(DEFAULT_MM_PER_ARC_SEGMENT), - g90_g91_influences_extruder(DEFAULT_G90_G91_INFLUENCES_EXTRUDER), - allow_3d_arcs(DEFAULT_ALLOW_3D_ARCS), - allow_travel_arcs(DEFAULT_ALLOW_TRAVEL_ARCS), - allow_dynamic_precision(DEFAULT_ALLOW_DYNAMIC_PRECISION), - default_xyz_precision(DEFAULT_XYZ_PRECISION), - default_e_precision(DEFAULT_E_PRECISION), - extrusion_rate_variance_percent(DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT), - max_gcode_length(DEFAULT_MAX_GCODE_LENGTH), - buffer_size(DEFAULT_GCODE_BUFFER_SIZE), - notification_period_seconds(DEFAULT_NOTIFICATION_PERIOD_SECONDS), - callback(NULL){}; + arc_welder_args() { + set_defaults(); + }; - arc_welder_args(std::string source, std::string target, logger* ptr_log) : arc_welder_args() + arc_welder_args(std::string source, std::string target, logger* ptr_log) { + set_defaults(); source_path = source; target_path = target; log = ptr_log; @@ -483,6 +599,7 @@ struct arc_welder_args int buffer_size; int max_gcode_length; double notification_period_seconds; + utilities::box_drawing::BoxEncodingEnum box_encoding; progress_callback callback; @@ -529,6 +646,31 @@ struct arc_welder_args return stream.str(); }; +private: + void set_defaults() + { + source_path = "", + target_path = "", + log = NULL, + resolution_mm = DEFAULT_RESOLUTION_MM, + path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT, + max_radius_mm = DEFAULT_MAX_RADIUS_MM, + min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS, + mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT, + g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTRUDER, + allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS, + allow_travel_arcs = DEFAULT_ALLOW_TRAVEL_ARCS, + allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION, + default_xyz_precision = DEFAULT_XYZ_PRECISION, + default_e_precision = DEFAULT_E_PRECISION, + extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, + max_gcode_length = DEFAULT_MAX_GCODE_LENGTH, + buffer_size = DEFAULT_GCODE_BUFFER_SIZE, + notification_period_seconds = DEFAULT_NOTIFICATION_PERIOD_SECONDS, + callback = NULL; + box_encoding = utilities::box_drawing::BoxEncodingEnum::ASCII; + } + }; struct arc_welder_results { @@ -547,48 +689,8 @@ struct arc_welder_results { class arc_welder { public: - arc_welder( - std::string source_path, - std::string target_path, - logger* log, - double resolution_mm, - double path_tolerance_percent, - double max_radius, - int min_arc_segments, - double mm_per_arc_segment, - bool g90_g91_influences_extruder, - bool allow_3d_arcs, - bool allow_travel_arcs, - bool allow_dynamic_precision, - unsigned char default_xyz_precision, - unsigned char default_e_precision, - double extrusion_rate_variance_percent, - int max_gcode_length, - int buffer_size, - double notification_period_seconds, - progress_callback callback); - - arc_welder(arc_welder_args args) : arc_welder( - args.source_path, - args.target_path, - args.log, - args.resolution_mm, - args.path_tolerance_percent, - args.max_radius_mm, - args.min_arc_segments, - args.mm_per_arc_segment, - args.g90_g91_influences_extruder, - args.allow_3d_arcs, - args.allow_travel_arcs, - args.allow_dynamic_precision, - args.default_xyz_precision, - args.default_e_precision, - args.extrusion_rate_variance_percent, - args.max_gcode_length, - args.buffer_size, - args.notification_period_seconds, - args.callback - ){}; + + arc_welder(arc_welder_args args); void set_logger_type(int logger_type); @@ -627,6 +729,7 @@ private: int arcs_aborted_by_flow_rate_; double notification_period_seconds_; source_target_segment_statistics segment_statistics_; + source_target_segment_statistics segment_retraction_statistics_; source_target_segment_statistics travel_statistics_; long get_file_size(const std::string& file_path); double get_time_elapsed(double start_clock, double end_clock); @@ -649,5 +752,5 @@ private: bool info_logging_enabled_; bool verbose_logging_enabled_; bool error_logging_enabled_; - + utilities::box_drawing::BoxEncodingEnum box_encoding_; }; diff --git a/ArcWelder/segmented_shape.cpp b/ArcWelder/segmented_shape.cpp index dd422a4..d50e5b5 100644 --- a/ArcWelder/segmented_shape.cpp +++ b/ArcWelder/segmented_shape.cpp @@ -521,7 +521,6 @@ bool arc::try_create_arc( { circle test_circle = (circle)target_arc; - if (!circle::try_create_circle(points, max_radius_mm, resolution_mm, xyz_tolerance, allow_3d_arcs, test_circle)) { return false; diff --git a/ArcWelder/unwritten_command.h b/ArcWelder/unwritten_command.h index 5acf999..bfca84f 100644 --- a/ArcWelder/unwritten_command.h +++ b/ArcWelder/unwritten_command.h @@ -32,9 +32,14 @@ struct unwritten_command length = 0; is_g0_g1 = false; is_g2_g3 = false; + is_travel = false; + is_extrusion = false; + is_retraction = false; + gcode = ""; + comment = ""; } - unwritten_command(parsed_command &cmd, bool is_relative, bool is_travel, double command_length) - : is_extruder_relative(is_relative), is_travel(is_travel), is_g0_g1(cmd.command == "G0" || cmd.command == "G1"), is_g2_g3(cmd.command == "G2" || cmd.command == "G3"), gcode(cmd.gcode), comment(cmd.comment), length(command_length) + unwritten_command(parsed_command &cmd, bool is_relative, bool is_extrusion, bool is_retraction, bool is_travel, double command_length) + : is_extruder_relative(is_relative), is_extrusion(is_extrusion), is_retraction(is_retraction), is_travel(is_travel), is_g0_g1(cmd.command == "G0" || cmd.command == "G1"), is_g2_g3(cmd.command == "G2" || cmd.command == "G3"), gcode(cmd.gcode), comment(cmd.comment), length(command_length) { } @@ -42,6 +47,8 @@ struct unwritten_command bool is_g2_g3; bool is_extruder_relative; bool is_travel; + bool is_extrusion; + bool is_retraction; double length; std::string gcode; std::string comment; diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp index e64d9d4..ad63b37 100644 --- a/ArcWelderConsole/ArcWelderConsole.cpp +++ b/ArcWelderConsole/ArcWelderConsole.cpp @@ -81,8 +81,8 @@ int main(int argc, char* argv[]) // -t --path-tolerance-percent arg_description_stream.clear(); arg_description_stream.str(""); - arg_description_stream << "This is the maximum allowable difference between the arc path and the original toolpath. Since most slicers use interpolation when generating arc moves, this value can be relatively high without impacting print quality."; - arg_description_stream << " Expressed as a decimal percent, where 0.05 = 5.0%. Default Value: " << ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; + arg_description_stream << "This is the maximum allowable difference between the arc path and the original toolpath."; + arg_description_stream << " Expressed as a decimal percent, where 0.05 = 5.0%. The lower this value is, the more arcs will be aborted, but values over 0.25 (25%) are not recommended, as they could negatively impact print quality. Default Value: " << ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT << " (" << ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT * 100 << "%)"; TCLAP::ValueArg path_tolerance_percent_arg("t", "path-tolerance-percent", arg_description_stream.str(), false, DEFAULT_RESOLUTION_MM, "float"); // -m --max-radius-mm @@ -142,7 +142,7 @@ int main(int argc, char* argv[]) // -v --extrusion-rate-variance arg_description_stream.clear(); arg_description_stream.str(""); - arg_description_stream << "(experimental) - The allowed variance in extrusion rate by percent. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; + arg_description_stream << "(experimental) - The allowed variance in extrusion rate by percent, where 0.05 = 5.0%. A value of 0 will disable this feature. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT << " (" << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT * 100 << "%)"; TCLAP::ValueArg extrusion_rate_variance_percent_arg("v", "extrusion-rate-variance-percent", arg_description_stream.str(), false, DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, "double"); // -c --max-gcode-length @@ -236,7 +236,7 @@ int main(int argc, char* argv[]) has_error = true; } - if (args.path_tolerance_percent <= 0) + if (args.path_tolerance_percent < 0) { std::cerr << "error: The provided path tolerance percentage of " << args.path_tolerance_percent << " is negative, which is not allowed." << std::endl; has_error = true; @@ -262,15 +262,15 @@ int main(int argc, char* argv[]) args.mm_per_arc_segment = 0; } - if (args.path_tolerance_percent > 0.05) + if (args.path_tolerance_percent > 0.25) { // warning - std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is greater than 0.05 (5%), which is not recommended." << std::endl; + std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is greater than 0.25 (25%), which is not recommended." << std::endl; } - else if (args.path_tolerance_percent < 0.0001 && args.path_tolerance_percent > 0) + else if (args.path_tolerance_percent < 0.001 && args.path_tolerance_percent > 0) { // warning - std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is less than greater than 0.001 (0.1%), which is not recommended." << std::endl; + std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is less than 0.001 (0.1%), which is not recommended, and will result in very few arcs being generated." << std::endl; } if (xyz_precision < 3) @@ -308,7 +308,7 @@ int main(int argc, char* argv[]) if (args.extrusion_rate_variance_percent < 0) { // warning - std::cout << "warning: The provided extrusion_rate_variance_percent " << args.extrusion_rate_variance_percent << " is less than 0. Setting to the default." << std::endl; + std::cout << "warning: The provided extrusion_rate_variance_percent " << args.extrusion_rate_variance_percent << " is less than 0. Applying the default setting of " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT*100 << "%." << std::endl; args.extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; } @@ -351,7 +351,7 @@ int main(int argc, char* argv[]) std::vector log_names; log_names.push_back(ARC_WELDER_LOGGER_NAME); std::vector log_levels; - log_levels.push_back(log_levels::DEBUG); + log_levels.push_back((int)log_levels::DEBUG); logger* p_logger = new logger(log_names, log_levels); p_logger->set_log_level_by_value(log_level_value); args.log = p_logger; @@ -360,12 +360,12 @@ int main(int argc, char* argv[]) if (progress_type == PROGRESS_TYPE_NONE) { - p_logger->log(0, INFO, "Suppressing progress messages."); + p_logger->log(0, log_levels::INFO, "Suppressing progress messages."); args.callback = on_progress_suppress; } else if (progress_type == PROGRESS_TYPE_FULL) { - p_logger->log(0, INFO, "Displaying full progress messages."); + p_logger->log(0, log_levels::INFO, "Displaying full progress messages."); args.callback = on_progress_full; } else { @@ -374,11 +374,14 @@ int main(int argc, char* argv[]) // Log the arguments std::stringstream log_messages; log_messages << "Processing GCode."; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); log_messages.clear(); log_messages.str(""); log_messages << args.str(); - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); + + // Set the box encoding + args.box_encoding = args.box_encoding = utilities::box_drawing::ASCII; p_arc_welder = new arc_welder(args); @@ -395,15 +398,17 @@ int main(int argc, char* argv[]) } else { - log_messages << "Target File Travel Statistics:" << std::endl << results.progress.travel_statistics.str(); + log_messages << "\n" << results.progress.travel_statistics.str("Target File Travel Statistics", utilities::box_drawing::ASCII); } - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); } log_messages.clear(); log_messages.str(""); - log_messages << "Target File Extrusion Statistics:" << std::endl << results.progress.segment_statistics.str(); + // Extrusion Statistics + source_target_segment_statistics combined_stats = source_target_segment_statistics::add(results.progress.segment_statistics, results.progress.segment_retraction_statistics); + log_messages << "\n" << combined_stats.str("Target File Extrusion Statistics", utilities::box_drawing::ASCII); p_logger->log(0, INFO, log_messages.str() ); @@ -411,14 +416,14 @@ int main(int argc, char* argv[]) log_messages.clear(); log_messages.str(""); log_messages << "Arc Welder process completed successfully."; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); } else { log_messages.clear(); log_messages.str(""); log_messages << "File processing failed."; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); } delete p_arc_welder; diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp index a3147eb..d43aec7 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp @@ -436,7 +436,7 @@ int run_arc_straightener(int argc, char* argv[]) std::vector log_names; log_names.push_back("arc_welder.gcode_conversion"); std::vector log_levels; - log_levels.push_back(log_levels::DEBUG); + log_levels.push_back((int)log_levels::DEBUG); logger* p_logger = new logger(log_names, log_levels); p_logger->set_log_level_by_value(log_level_value); @@ -449,7 +449,7 @@ int run_arc_straightener(int argc, char* argv[]) if (!utilities::get_temp_file_path_for_file(args.source_path, temp_file_path)) { log_messages << "The source and target path are the same, but a temporary file path could not be created. Is the path empty?"; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); log_messages.clear(); log_messages.str(""); } @@ -457,7 +457,7 @@ int run_arc_straightener(int argc, char* argv[]) // create a uuid with a tmp extension for the temporary file log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); log_messages.clear(); log_messages.str(""); } @@ -483,11 +483,11 @@ int run_arc_straightener(int argc, char* argv[]) arc_interpolation interpolator(args); log_messages << interpolator.get_firmware_argument_description(); - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); - p_logger->log(0, INFO, "Running interpolation..."); + p_logger->log(0, log_levels::INFO, "Running interpolation..."); interpolator.process(); - p_logger->log(0, INFO, "Interpolation Complete."); + p_logger->log(0, log_levels::INFO, "Interpolation Complete."); log_messages.clear(); log_messages.str(""); @@ -498,19 +498,19 @@ int run_arc_straightener(int argc, char* argv[]) log_messages.clear(); log_messages.str(""); log_messages << "Deleting the original source file at '" << args.source_path << "'."; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); log_messages.clear(); log_messages.str(""); std::remove(args.source_path.c_str()); log_messages << "Renaming temporary file at '" << args.target_path << "' to '" << args.source_path << "'."; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); std::rename(args.target_path.c_str(), args.source_path.c_str()); } log_messages.clear(); log_messages.str(""); log_messages << "Process completed successfully."; - p_logger->log(0, INFO, log_messages.str()); + p_logger->log(0, log_levels::INFO, log_messages.str()); return 0; diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 2aaa8e4..bd173d5 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -25,7 +25,6 @@ #include "logger.h" #include - int main(int argc, char* argv[]) { run_tests(argc, argv); @@ -273,15 +272,15 @@ static void TestAntiStutter(std::string filePath) std::vector logger_names; logger_names.push_back("arc_welder.gcode_conversion"); std::vector logger_levels; - logger_levels.push_back(log_levels::NOSET); - logger_levels.push_back(log_levels::VERBOSE); - logger_levels.push_back(log_levels::DEBUG); - logger_levels.push_back(log_levels::INFO); - logger_levels.push_back(log_levels::WARNING); - logger_levels.push_back(log_levels::ERROR); - logger_levels.push_back(log_levels::CRITICAL); + logger_levels.push_back((int)log_levels::NOSET); + logger_levels.push_back((int)log_levels::VERBOSE); + logger_levels.push_back((int)log_levels::DEBUG); + logger_levels.push_back((int)log_levels::INFO); + logger_levels.push_back((int)log_levels::WARNING); + //logger_levels.push_back((int)(log_levels::ERROR)); + logger_levels.push_back((int)log_levels::CRITICAL); logger* p_logger = new logger(logger_names, logger_levels); - p_logger->set_log_level(INFO); + p_logger->set_log_level(log_levels::INFO); //FIRMWARE_COMPENSATION_TEST_1 //BENCHY_MIN_RADIUS_TEST @@ -306,9 +305,10 @@ static void TestAntiStutter(std::string filePath) // BENCHY_L1_DIFFICULT // SPIRAL_TEST // SPIRAL_VASE_TEST_FUNNEL - std::string source_path = BENCHY_DIFFICULT; + std::string source_path = SPIRAL_VASE_TEST_SINGLE_LAYER_CYLINDER; std::string target_path = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode"; arc_welder_args args(source_path, target_path, p_logger); + args.box_encoding = args.box_encoding = utilities::box_drawing::HTML; args.callback = on_progress; // override any arguments here; args.allow_travel_arcs = true; @@ -319,14 +319,14 @@ static void TestAntiStutter(std::string filePath) arc_welder arc_welder_obj(args); arc_welder_results results = arc_welder_obj.process(); - p_logger->log(0, INFO, results.progress.detail_str()); - p_logger->log(0, INFO, "Processing Complete."); + p_logger->log(0, log_levels::INFO, results.progress.detail_str()); + p_logger->log(0, log_levels::INFO, "Processing Complete."); delete p_logger; } bool on_progress(arc_welder_progress progress, logger * p_logger, int logger_type) { - p_logger->log(logger_type, INFO, progress.str()); + p_logger->log(logger_type, log_levels::INFO, progress.str()); return true; } diff --git a/ArcWelderTest/ArcWelderTest.h b/ArcWelderTest/ArcWelderTest.h index e854c4b..4c8f217 100644 --- a/ArcWelderTest/ArcWelderTest.h +++ b/ArcWelderTest/ArcWelderTest.h @@ -112,6 +112,7 @@ static std::string DIFFICULT_ARCS_ISSUE_34 = "C:\\Users\\Brad\\Documents\\3DPrin static std::string TORTURE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\performance\\torture_test.gcode"; static std::string CURA_PLUGIN_ISSUE_18 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\CuraPlugin\\18\\Unwelded.gcode"; static std::string METALTEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\MetalAltered.gcode"; +static std::string TEMP_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\temp.gcode"; diff --git a/GcodeProcessorLib/logger.cpp b/GcodeProcessorLib/logger.cpp index 3bde18e..6aa591a 100644 --- a/GcodeProcessorLib/logger.cpp +++ b/GcodeProcessorLib/logger.cpp @@ -36,7 +36,7 @@ logger::logger(std::vector names, std::vector levels) { logger_names_[index] = names[index]; logger_levels_[index] = levels[index]; } - set_log_level_by_value(NOSET); + set_log_level_by_value((int)log_levels::NOSET); } @@ -58,9 +58,9 @@ void logger::set_log_level_by_value(const int level_value) } } -void logger::set_log_level(const int logger_type, const int log_level) +void logger::set_log_level(const int logger_type, log_levels log_level) { - logger_levels_[logger_type] = log_level; + logger_levels_[logger_type] = (int)log_level; } std::string logger::get_log_level_name(std::string logger_name) { @@ -76,17 +76,17 @@ std::string logger::get_log_level_name(std::string logger_name) return log_level_name; } -void logger::set_log_level(const int log_level) +void logger::set_log_level(log_levels log_level) { for (int type_index = 0; type_index < num_loggers_; type_index++) { - logger_levels_[type_index] = log_level; + logger_levels_[type_index] = (int)log_level; } } -int logger::get_log_level_value(const int log_level) +int logger::get_log_level_value(log_levels log_level) { - return log_level_values[log_level]; + return log_level_values[(int)log_level]; } int logger::get_log_level_for_value(int log_level_value) { @@ -97,12 +97,12 @@ int logger::get_log_level_for_value(int log_level_value) } return 0; } -bool logger::is_log_level_enabled(const int logger_type, const int log_level) +bool logger::is_log_level_enabled(const int logger_type, log_levels log_level) { - return logger_levels_[logger_type] <= log_level; + return logger_levels_[logger_type] <= (int)log_level; } -void logger::create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output) +void logger::create_log_message(const int logger_type, log_levels log_level, const std::string& message, std::string& output) { // example message // 2020-04-20 21:36:59,414 - arc_welder.__init__ - INFO - MESSAGE_GOES_HERE @@ -116,7 +116,7 @@ void logger::create_log_message(const int logger_type, const int log_level, cons // add a spacer output.append(" - "); // add the log level name - output.append(log_level_names[log_level]); + output.append(log_level_names[(int)log_level]); // add a spacer output.append(" - "); // add the message @@ -128,12 +128,12 @@ void logger::log_exception(const int logger_type, const std::string& message) log(logger_type, log_levels::ERROR, message, true); } -void logger::log(const int logger_type, const int log_level, const std::string& message) +void logger::log(const int logger_type, log_levels log_level, const std::string& message) { log(logger_type, log_level, message, false); } -void logger::log(const int logger_type, const int log_level, const std::string& message, bool is_exception) +void logger::log(const int logger_type, log_levels log_level, const std::string& message, bool is_exception) { // Make sure the loggers have been initialized if (!loggers_created_) diff --git a/GcodeProcessorLib/logger.h b/GcodeProcessorLib/logger.h index f345097..8cbd5e3 100644 --- a/GcodeProcessorLib/logger.h +++ b/GcodeProcessorLib/logger.h @@ -46,18 +46,18 @@ public: void set_log_level_by_value(const int logger_type, const int log_level_value); void set_log_level_by_value(const int log_level_value); - void set_log_level(const int logger_type, const int log_level); - void set_log_level(const int log_level); + void set_log_level(const int logger_type, log_levels log_level); + void set_log_level(log_levels log_level); std::string get_log_level_name(std::string logger_name); - virtual void log(const int logger_type, const int log_level, const std::string& message); - virtual void log(const int logger_type, const int log_level, const std::string& message, bool is_exception); + virtual void log(const int logger_type, log_levels log_level, const std::string& message); + virtual void log(const int logger_type, log_levels log_level, const std::string& message, bool is_exception); virtual void log_exception(const int logger_type, const std::string& message); - static int get_log_level_value(const int log_level); + static int get_log_level_value(log_levels log_level); static int get_log_level_for_value(int log_level_value); - virtual bool is_log_level_enabled(const int logger_type, const int log_level); + virtual bool is_log_level_enabled(const int logger_type, log_levels log_level); protected: - virtual void create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output); + virtual void create_log_message(const int logger_type, log_levels log_level, const std::string& message, std::string& output); bool loggers_created_; private: diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp index c53ddf8..74fef46 100644 --- a/GcodeProcessorLib/utilities.cpp +++ b/GcodeProcessorLib/utilities.cpp @@ -23,6 +23,89 @@ #include "utilities.h" namespace utilities { + // Box Drawing Consts + // String Consts + // Note: these ascii replacement characters must NOT appear in the text unless they will be replaced with box characters. + const char box_drawing::table_elements_replacement[8] = {char(128),char(129),char(130),char(131),char(132),char(133),char(134),char(135) }; + //enum BoxElementEnum { HORIZONTAL = 0, VERTICAL = 1, UPPER_LEFT = 2, UPPER_RIGHT = 3, MIDDLE_LEFT = 4, MIDDLE_RIGHT = 5, LOWER_LEFT = 6, LOWER_RIGHT = 7 }; + const std::string box_drawing::table_elements_ascii[8] = {"-","|","+" ,"+" ,"+" ,"+" ,"+" ,"+" }; + const std::string box_drawing::table_elements_utf8[8] = { "\u2500","\u2502","\u250C" ,"\u2510" ,"\u251C" ,"\u2524" ,"\u2514" ,"\u2518" }; + const std::string box_drawing::table_elements_html[8] = { "─","│", "┌","┐","├","┤","└","┘" }; + + box_drawing::box_drawing() + { + set_box_type(BoxEncodingEnum::ASCII); + + width_ = 100; + } + box_drawing::box_drawing(BoxEncodingEnum encoding, int width) + { + set_box_type(encoding); + width_ = width; + } + + void box_drawing::set_box_type(BoxEncodingEnum encoding) + { + box_encoding_ = encoding; + for (int index = 0; index < 8; index++) + { + switch (box_encoding_) + { + case BoxEncodingEnum::ASCII: + table_elements_[index] = table_elements_ascii[index]; + break; + case BoxEncodingEnum::UTF8: + table_elements_[index] = table_elements_utf8[index]; + break; + case BoxEncodingEnum::HTML: + table_elements_[index] = table_elements_html[index]; + break; + default: + table_elements_[index] = table_elements_ascii[index]; + } + + } + } + + void box_drawing::top(std::stringstream& stream) + { + stream << get_box_replacement_element(BoxElementEnum::UPPER_LEFT) << std::setw(width_) << std::setfill(get_box_replacement_element(BoxElementEnum::HORIZONTAL)) << "" << get_box_replacement_element(BoxElementEnum::UPPER_RIGHT) << "\n" << std::setfill(' '); + } + + void box_drawing::row(std::stringstream& stream, std::string line) + { + stream << get_box_replacement_element(BoxElementEnum::VERTICAL) << line << get_box_replacement_element(BoxElementEnum::VERTICAL) << "\n" << std::setfill(' '); + } + + void box_drawing::middle(std::stringstream& stream) + { + stream << get_box_replacement_element(BoxElementEnum::MIDDLE_LEFT) << std::setw(width_) << std::setfill(get_box_replacement_element(BoxElementEnum::HORIZONTAL)) << "" << get_box_replacement_element(BoxElementEnum::MIDDLE_RIGHT) << "\n" << std::setfill(' '); + } + void box_drawing::bottom(std::stringstream& stream) + { + stream << get_box_replacement_element(BoxElementEnum::LOWER_LEFT) << std::setw(width_) << std::setfill(get_box_replacement_element(BoxElementEnum::HORIZONTAL)) << "" << get_box_replacement_element(BoxElementEnum::LOWER_RIGHT) << "\n" << std::setfill(' '); + } + + char box_drawing::get_box_replacement_element(BoxElementEnum element) + { + return table_elements_replacement[(int)element]; + } + + void box_drawing::make_replacements(std::string &box) + { + for (int index = 0; index < 8; index++) + { + char c = table_elements_replacement[index]; + std::string search; + search += c; + box = utilities::replace(box, search, table_elements_[index]); + } + + } + + + + const std::string WHITESPACE_ = " \n\r\t\f\v"; const char GUID_RANGE[] = "0123456789abcdef"; const bool GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 }; @@ -504,11 +587,6 @@ double utilities::pow(int e, double x) return std::pow(e, x); } -double utilities::pow(int e, int x) -{ - return std::pow(e, x); -} - double utilities::min(double x, double y) { return std::min(x, y); diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h index 88e8c40..1b7923f 100644 --- a/GcodeProcessorLib/utilities.h +++ b/GcodeProcessorLib/utilities.h @@ -147,7 +147,6 @@ namespace utilities{ double pow(int e, double x); - double pow(int e, int x); double min(double x, double y); @@ -187,7 +186,35 @@ namespace utilities{ unsigned char rand_range(unsigned char min, unsigned char max); int rand_range(int min, int max); - + + + class box_drawing { + + public: + enum BoxElementEnum { HORIZONTAL = 0, VERTICAL = 1, UPPER_LEFT = 2, UPPER_RIGHT = 3, MIDDLE_LEFT = 4, MIDDLE_RIGHT = 5, LOWER_LEFT = 6, LOWER_RIGHT = 7 }; + enum BoxEncodingEnum {ASCII=0, UTF8=1, HTML=2}; + box_drawing(); + box_drawing(BoxEncodingEnum encoding, int width); + static const char table_elements_replacement[8]; + static const std::string table_elements_ascii[8]; + static const std::string table_elements_utf8[8]; + static const std::string table_elements_html[8]; + char get_box_replacement_element(BoxElementEnum element); + void top(std::stringstream& stream); + void row(std::stringstream& stream, std::string line); + void middle(std::stringstream& stream); + void bottom(std::stringstream& stream); + void set_box_type(BoxEncodingEnum encoding); + void make_replacements(std::string &box); + + private: + std::string table_elements_[8]; + BoxEncodingEnum box_encoding_; + std::string output_; + std::stringstream output_stream_; + int width_; + + }; } #endif \ No newline at end of file diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp index fcb4b71..635ad9d 100644 --- a/PyArcWelder/py_arc_welder.cpp +++ b/PyArcWelder/py_arc_welder.cpp @@ -28,13 +28,15 @@ PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, if (pyGuid == NULL) return NULL; // Extrusion Statistics - std::string segment_statistics = progress.segment_statistics.str(); + source_target_segment_statistics combined_stats = source_target_segment_statistics::add(progress.segment_statistics, progress.segment_retraction_statistics); + + std::string segment_statistics = combined_stats.str("", utilities::box_drawing::HTML); PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_statistics); if (pyMessage == NULL) return NULL; double total_count_reduction_percent = progress.segment_statistics.get_total_count_reduction_percent(); // Travel Statistics - std::string segment_travel_statistics = progress.travel_statistics.str(); + std::string segment_travel_statistics = progress.travel_statistics.str("", utilities::box_drawing::HTML); PyObject* pyTravelMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_travel_statistics); if (pyMessage == NULL) return NULL; diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp index 1e440f9..c08e3d7 100644 --- a/PyArcWelder/py_arc_welder_extension.cpp +++ b/PyArcWelder/py_arc_welder_extension.cpp @@ -190,19 +190,21 @@ extern "C" { return NULL; } - p_py_logger->set_log_level(args.log_level); + p_py_logger->set_log_level((log_levels)args.log_level); std::string message = "py_gcode_arc_converter.ConvertFile - Beginning Arc Conversion."; - p_py_logger->log(GCODE_CONVERSION, INFO, message); + p_py_logger->log(GCODE_CONVERSION, log_levels::INFO, message); args.py_progress_callback = py_progress_callback; args.log = p_py_logger; + // Set the encoding to html for the progress output + args.box_encoding = utilities::box_drawing::HTML; py_arc_welder arc_welder_obj(args); arc_welder_results results; results = arc_welder_obj.process(); message = "py_gcode_arc_converter.ConvertFile - Arc Conversion Complete."; - p_py_logger->log(GCODE_CONVERSION, INFO, message); + p_py_logger->log(GCODE_CONVERSION, log_levels::INFO, message); Py_XDECREF(py_progress_callback); // return the arguments PyObject* p_progress = py_arc_welder::build_py_progress(results.progress, args.guid); diff --git a/PyArcWelder/py_logger.cpp b/PyArcWelder/py_logger.cpp index 0ae6548..3269c42 100644 --- a/PyArcWelder/py_logger.cpp +++ b/PyArcWelder/py_logger.cpp @@ -119,7 +119,7 @@ void py_logger::set_internal_log_levels(bool check_real_time) void py_logger::log_exception(const int logger_type, const std::string& message) { - log(logger_type, ERROR, message, true); + log(logger_type, log_levels::ERROR, message, true); } void py_logger::log(const int logger_type, const int log_level, const std::string& message) @@ -176,24 +176,24 @@ void py_logger::log(const int logger_type, const int log_level, const std::strin } else { - switch (log_level) + switch ((log_levels)log_level) { - case INFO: + case log_levels::INFO: pyFunctionName = py_info_function_name; break; - case WARNING: + case log_levels::WARNING: pyFunctionName = py_warn_function_name; break; - case ERROR: + case log_levels::ERROR: pyFunctionName = py_error_function_name; break; - case DEBUG: + case log_levels::DEBUG: pyFunctionName = py_debug_function_name; break; - case VERBOSE: + case log_levels::VERBOSE: pyFunctionName = py_verbose_function_name; break; - case CRITICAL: + case log_levels::CRITICAL: pyFunctionName = py_critical_function_name; break; default: -- cgit v1.2.3 From 3bd3d54894cd49fb564396fd043c4b0c80f4b116 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sun, 21 Nov 2021 12:51:53 -0600 Subject: For python progress updates, make detailed statistics optional (they take a while to create, and are unused in the current Octoprint plugin until the final message). --- PyArcWelder/py_arc_welder.cpp | 23 +++++++++++++++-------- PyArcWelder/py_arc_welder.h | 2 +- PyArcWelder/py_arc_welder_extension.cpp | 3 ++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp index 635ad9d..2cdfde5 100644 --- a/PyArcWelder/py_arc_welder.cpp +++ b/PyArcWelder/py_arc_welder.cpp @@ -22,23 +22,30 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "py_arc_welder.h" -PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, std::string guid) +PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, std::string guid, bool include_detailed_statistics) { PyObject* pyGuid = gcode_arc_converter::PyUnicode_SafeFromString(guid); if (pyGuid == NULL) return NULL; - // Extrusion Statistics - source_target_segment_statistics combined_stats = source_target_segment_statistics::add(progress.segment_statistics, progress.segment_retraction_statistics); - std::string segment_statistics = combined_stats.str("", utilities::box_drawing::HTML); + std::string segment_statistics = ""; + std::string segment_travel_statistics = ""; + + if (include_detailed_statistics) + { + // Extrusion Statistics + source_target_segment_statistics combined_stats = source_target_segment_statistics::add(progress.segment_statistics, progress.segment_retraction_statistics); + segment_statistics = combined_stats.str("", utilities::box_drawing::HTML); + // Travel Statistics + segment_travel_statistics = progress.travel_statistics.str("", utilities::box_drawing::HTML); + } PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_statistics); if (pyMessage == NULL) return NULL; double total_count_reduction_percent = progress.segment_statistics.get_total_count_reduction_percent(); - // Travel Statistics - std::string segment_travel_statistics = progress.travel_statistics.str("", utilities::box_drawing::HTML); + PyObject* pyTravelMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_travel_statistics); - if (pyMessage == NULL) + if (pyTravelMessage == NULL) return NULL; double total_travel_count_reduction_percent = progress.travel_statistics.get_total_count_reduction_percent(); PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i,s:f,s:f,s:f,s:i,s:i,s:f}", @@ -110,7 +117,7 @@ PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, bool py_arc_welder::on_progress_(const arc_welder_progress& progress) { - PyObject* py_dict = py_arc_welder::build_py_progress(progress, guid_); + PyObject* py_dict = py_arc_welder::build_py_progress(progress, guid_, false); if (py_dict == NULL) { return false; diff --git a/PyArcWelder/py_arc_welder.h b/PyArcWelder/py_arc_welder.h index 686a9cd..a8f0819 100644 --- a/PyArcWelder/py_arc_welder.h +++ b/PyArcWelder/py_arc_welder.h @@ -61,7 +61,7 @@ public: virtual ~py_arc_welder() { } - static PyObject* build_py_progress(const arc_welder_progress& progress, std::string guid); + static PyObject* build_py_progress(const arc_welder_progress& progress, std::string guid, bool include_detailed_statistics); protected: std::string guid_; virtual bool on_progress_(const arc_welder_progress& progress); diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp index c08e3d7..7609b61 100644 --- a/PyArcWelder/py_arc_welder_extension.cpp +++ b/PyArcWelder/py_arc_welder_extension.cpp @@ -207,7 +207,8 @@ extern "C" p_py_logger->log(GCODE_CONVERSION, log_levels::INFO, message); Py_XDECREF(py_progress_callback); // return the arguments - PyObject* p_progress = py_arc_welder::build_py_progress(results.progress, args.guid); + PyObject* p_progress = py_arc_welder::build_py_progress(results.progress, args.guid, true); + if (p_progress == NULL) p_progress = Py_None; -- cgit v1.2.3