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