From ae7b425561749844da1cc5e23135625b22367be5 Mon Sep 17 00:00:00 2001 From: FormerLurker Date: Sun, 17 Jan 2021 14:15:33 -0600 Subject: Debug install --- ArcWelder/CMakeLists.txt | 2 +- ArcWelder/arc_welder.cpp | 2 +- ArcWelderConsole/CMakeLists.txt | 2 +- .../ArcWelderInverseProcessor.cpp | 87 ++++++- ArcWelderInverseProcessor/CMakeLists.txt | 2 +- ArcWelderInverseProcessor/inverse_processor.cpp | 267 +++++++++++++++++---- ArcWelderInverseProcessor/inverse_processor.h | 17 +- ArcWelderLib.sln | 4 +- ArcWelderTest/ArcWelderTest.cpp | 2 +- ArcWelderTest/ArcWelderTest.h | 4 +- CMakeLists.txt | 2 +- GcodeProcessorLib/CMakeLists.txt | 2 +- PyArcWelder/CMakeLists.txt | 2 +- TCLAP/CMakeLists.txt | 2 +- 14 files changed, 329 insertions(+), 68 deletions(-) diff --git a/ArcWelder/CMakeLists.txt b/ArcWelder/CMakeLists.txt index 5a1569b..688a466 100644 --- a/ArcWelder/CMakeLists.txt +++ b/ArcWelder/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION "3.15") +cmake_minimum_required (VERSION "3.13") project(ArcWelder C CXX) diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index d4b4e67..b720f05 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -213,7 +213,7 @@ arc_welder_results results; bool continue_processing = true; p_logger_->log(logger_type_, DEBUG, "Configuring progress updates."); - int read_lines_before_clock_check = 1000; + int read_lines_before_clock_check = 500; double next_update_time = get_next_update_time(); const clock_t start_clock = clock(); p_logger_->log(logger_type_, DEBUG, "Getting source file size."); diff --git a/ArcWelderConsole/CMakeLists.txt b/ArcWelderConsole/CMakeLists.txt index 0859c5b..f8f8990 100644 --- a/ArcWelderConsole/CMakeLists.txt +++ b/ArcWelderConsole/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION "3.15") +cmake_minimum_required (VERSION "3.13") project(ArcWelderConsole C CXX) diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp index ea333b3..54c8630 100644 --- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp +++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp @@ -60,11 +60,17 @@ int main(int argc, char* argv[]) double mm_per_arc_segment; double min_mm_per_arc_segment; int min_arc_segments; + int n_arc_corrections; double arc_segments_per_sec; std::string log_level_string; std::string log_level_string_default = "INFO"; int log_level_value; + + std::string interpolation_function_string; + std::string interpolation_function_string_default = "INT_SEGMENTS"; + int interpolation_function_value; + // Extract arguments try { // Define the command line object @@ -102,6 +108,12 @@ int main(int argc, char* argv[]) 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"); + // -c --n_arc_correction + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "The number of segments to interpolate using the small angle approximation before correcting with real sin/cos values. Default Value: " << DEFAULT_N_ARC_CORRECTIONS; + TCLAP::ValueArg n_arc_corrections_arg("C", "n-arc-corrections", arg_description_stream.str(), false, DEFAULT_N_ARC_CORRECTIONS, "int"); + // -s --arc-segments-per-second arg_description_stream.clear(); arg_description_stream.str(""); @@ -124,6 +136,17 @@ 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); + // -i --interpolation-function + std::vector interpolation_functions_vector; + interpolation_functions_vector.push_back("INT_SEGMENTS"); + interpolation_functions_vector.push_back("FLOAT_SEGMENTS"); + TCLAP::ValuesConstraint interpolation_functions_constraint(interpolation_functions_vector); + arg_description_stream.clear(); + arg_description_stream.str(""); + arg_description_stream << "Sets the interpolation function to be used. Options are INT_SEGMENTS and FLOAT_SEGMENTS. Default Value: " << interpolation_function_string_default; + TCLAP::ValueArg interpolation_function_arg("i", "interpolation-type", arg_description_stream.str(), false, interpolation_function_string_default, &interpolation_functions_constraint); + + // Add all arguments cmd.add(source_arg); cmd.add(target_arg); @@ -132,9 +155,10 @@ int main(int argc, char* argv[]) 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_corrections_arg); cmd.add(arc_segments_per_sec_arg); - cmd.add(log_level_arg); + cmd.add(interpolation_function_arg); // Parse the argv array. cmd.parse(argc, argv); @@ -145,11 +169,14 @@ int main(int argc, char* argv[]) 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(); + n_arc_corrections = n_arc_corrections_arg.getValue(); arc_segments_per_sec = arc_segments_per_sec_arg.getValue(); + interpolation_function_string = interpolation_function_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.n_arc_correction = n_arc_corrections; cs.arc_segments_per_sec = arc_segments_per_sec; if (target_file_path.size() == 0) @@ -158,9 +185,25 @@ int main(int argc, char* argv[]) } g90_g91_influences_extruder = g90_arg.getValue(); + interpolation_function_string = interpolation_function_arg.getValue(); + interpolation_function_value = -1; + for (unsigned int interpolation_function_index = 0; interpolation_function_index < interpolation_function_names_size; interpolation_function_index++) + { + if (interpolation_function_string == interpolation_function_names[interpolation_function_index]) + { + interpolation_function_value = interpolation_function_index; + break; + } + } + if (interpolation_function_value == -1) + { + // TODO: Does this work? + throw new TCLAP::ArgException("Unknown interpolation function"); + } + cs.interpolation_function = (InterpolationFunction)interpolation_function_value; + log_level_string = log_level_arg.getValue(); log_level_value = -1; - for (unsigned int log_name_index = 0; log_name_index < log_level_names_size; log_name_index++) { if (log_level_string == log_level_names[log_name_index]) @@ -175,6 +218,29 @@ int main(int argc, char* argv[]) throw new TCLAP::ArgException("Unknown log level"); } + // Do some parameter sanity checking. + if (cs.n_arc_correction < 0) + { + cs.n_arc_correction = 0; + } + if (cs.mm_per_arc_segment <= 0) + { + cs.mm_per_arc_segment = 1; + } + if (cs.min_mm_per_arc_segment < cs.mm_per_arc_segment) + { + cs.min_mm_per_arc_segment = 0; + } + if (cs.min_arc_segments < 0) + { + cs.min_arc_segments = 0; + } + if (cs.arc_segments_per_sec < 0) + { + cs.arc_segments_per_sec = 0; + } + + } // catch argument exceptions catch (TCLAP::ArgException& e) @@ -224,8 +290,23 @@ int main(int argc, char* argv[]) { log_messages << "\tTarget File File : " << target_file_path << "\n"; } - log_messages << "\tLog Level : " << log_level_string << "\n"; + + // Log the parameters used + log_messages << "\tMax MM Per Arc Segment : " << cs.mm_per_arc_segment << "\n"; + log_messages << "\tMin MM Per Arc Segment : " << cs.min_mm_per_arc_segment << "\n"; + log_messages << "\tMin Arc Segments : " << cs.min_arc_segments << "\n"; + log_messages << "\tArc Segments per Second : " << cs.arc_segments_per_sec << "\n"; + log_messages << "\tN Arc Corrections : " << cs.n_arc_correction << "\n"; + log_messages << "\tInterpolation Function : " << interpolation_function_string << "\n"; + + 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.n_arc_correction = n_arc_corrections; + cs.arc_segments_per_sec = arc_segments_per_sec; + + p_logger->log(0, INFO, log_messages.str()); if (overwrite_source_file) diff --git a/ArcWelderInverseProcessor/CMakeLists.txt b/ArcWelderInverseProcessor/CMakeLists.txt index 5636096..3962858 100644 --- a/ArcWelderInverseProcessor/CMakeLists.txt +++ b/ArcWelderInverseProcessor/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION "3.15") +cmake_minimum_required (VERSION "3.13") project(ArcWelderInverseProcessor C CXX) diff --git a/ArcWelderInverseProcessor/inverse_processor.cpp b/ArcWelderInverseProcessor/inverse_processor.cpp index 4b287d7..70adb8d 100644 --- a/ArcWelderInverseProcessor/inverse_processor.cpp +++ b/ArcWelderInverseProcessor/inverse_processor.cpp @@ -200,7 +200,15 @@ 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; - mc_arc(position, target, offset, static_cast(p_cur_pos->f), radius, isclockwise, 0); + switch (cs.interpolation_function) + { + case InterpolationFunction::INT_SEGMENTS: + mc_arc_int_segments(position, target, offset, static_cast(p_cur_pos->f), radius, isclockwise, 0); + break; + case InterpolationFunction::FLOAT_SEGMENTS: + mc_arc_float_segments(position, target, offset, static_cast(p_cur_pos->f), radius, isclockwise, 0); + } + } else { @@ -237,59 +245,53 @@ void inverse_processor::process() // 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) +void inverse_processor::mc_arc_int_segments(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 extruder_travel_total = target[E_AXIS] - position[E_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 - bool correction_enabled = cs.n_arc_correction > 1; 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 * 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 = 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 = (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) + // 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) { - 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; + 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; } - - 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 -= 2 * M_PI; } + if (isclockwise) { angular_travel_total -= 2.0f * 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 @@ -301,18 +303,13 @@ void inverse_processor::mc_arc(float* position, float* target, float* offset, fl // 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, fabs(travel_z)); - if (millimeters_of_travel_arc < 0.001) { return; } + const float millimeters_of_travel_arc = hypot(angular_travel_total * radius, fabs(travel_z)); + if (millimeters_of_travel_arc < 0.001f) { return; } // Calculate the total travel per segment - // Calculate the number of arc segments + // Calculate the number of arc segments as a float. This is important so that the extrusion + // and z travel is consistant throughout the arc. Otherwise we will see artifacts and gaps 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); @@ -334,44 +331,201 @@ void inverse_processor::mc_arc(float* position, float* target, float* offset, fl 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. + //std::cout << "Generating arc with " << segments << " segments. Extruding " << target[E_AXIS] - position[E_AXIS] << "mm.\n"; + // There must be more than 1 segment, else this is just a linear move + float interpolatedExtrusion = 0; + float interpolatedTravel = 0; + float totalExtrusion = target[E_AXIS] - position[E_AXIS]; if (segments > 1) { - // Initialize the extruder axis - - float cos_T; - float sin_T; - - if (correction_enabled > 1) { - float sq_theta_per_segment = theta_per_segment * theta_per_segment; - // Small angle approximation + + // Calculate theta per segments and linear (z) travel per segment + 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; + cos_T = 1 - 0.5f * sq_theta_per_segment; + // Calculate the number of interpolations we will do, but use the ceil + // function so that we can start our index with I=1. This is important + // so that the arc correction starts out with segment 1, and not 0, without + // doing extra calculations + + for (uint16_t i = 1; i < segments; i++) { // Increment (segments-1) + 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 { + 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; + } + interpolatedExtrusion += segment_extruder_travel; + interpolatedTravel += hypot(position[X_AXIS] - (center_axis_x + r_axis_x), position[Y_AXIS] - (center_axis_y + r_axis_y)); + // Update arc_target location + 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; + // 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(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS], feed_rate, extruder); } - else { - cos_T = cos(theta_per_segment); - sin_T = sin(theta_per_segment); + } + // 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); + if (segments > 1) + { + float fil_per_mm_interpolated = interpolatedTravel == 0 ? 0 : interpolatedExtrusion / interpolatedTravel; + float mm_travel_final = hypot(target[X_AXIS] - position[X_AXIS], target[Y_AXIS] - position[Y_AXIS]); + float extrusion_final = target[E_AXIS] - position[E_AXIS]; + float fil_per_mm_final = mm_travel_final == 0 ? 0 : extrusion_final / mm_travel_final; + float fil_per_mm_difference = fabs(fil_per_mm_interpolated - fil_per_mm_final); + //if (mm_travel_final > 0.001 && totalExtrusion - (interpolatedExtrusion + extrusion_final) > 0.00001f && max_extrusion_rate_difference < fil_per_mm_difference) + if (mm_travel_final > 0.001 && (totalExtrusion - interpolatedExtrusion) > 0.00001f && max_extrusion_rate_difference < fil_per_mm_difference) + { + max_extrusion_rate_difference = fil_per_mm_difference; + std::cout << "New Maximum extrusion rate difference detected:" << max_extrusion_rate_difference << "\n"; } + } + + plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder); + +} + + +// 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_segments(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 * M_PI; } - float r_axisi; - uint16_t i; + 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 * 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 / cs.arc_segments_per_sec); + if (mm_per_arc_segment_sec < mm_per_arc_segment) + mm_per_arc_segment = mm_per_arc_segment_sec; + } - for (i = 1; i < segments; i++) { // Increment (segments-1) - if (correction_enabled && --n_arc_correction == 0) { + // 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 * 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 * 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 total travel per segment + // Calculate the number of arc segments as a float. This is important so that the extrusion + // and z travel is consistant throughout the arc. Otherwise we will see artifacts and gaps + float segments = 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. + */ + float interpolatedExtrusion = 0; + float interpolatedTravel = 0; + float totalExtrusion = target[E_AXIS] - position[E_AXIS]; + // There must be more than 1 segment, else this is just a linear move + if (segments > 1) + { + // Calculate theta per segments and linear (z) travel per segment + 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; + sin_T = sin(theta_per_segment), + cos_T = cos(theta_per_segment); + // Calculate the number of interpolations we will do, but use the ceil + // function so that we can start our index with I=1. This is important + // so that the arc correction starts out with segment 1, and not 0, without + // doing extra calculations + uint16_t num_interpolations = static_cast(ceil(segments)); + for (uint16_t i = 1; i < num_interpolations; i++) { // Increment (segments-1) + /*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 = 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 { - r_axisi = r_axis_x * sin_T + r_axis_y * cos_T; + else { */ + 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; - } - + //} + interpolatedExtrusion += segment_extruder_travel; + interpolatedTravel += hypot(position[X_AXIS] - (center_axis_x + r_axis_x), position[Y_AXIS] - (center_axis_y + r_axis_y)); // Update arc_target location position[X_AXIS] = center_axis_x + r_axis_x; position[Y_AXIS] = center_axis_y + r_axis_y; @@ -386,7 +540,22 @@ void inverse_processor::mc_arc(float* position, float* target, float* offset, fl // 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); + if (segments > 1) + { + float fil_per_mm_interpolated = interpolatedTravel == 0 ? 0 : interpolatedExtrusion / interpolatedTravel; + float mm_travel_final = hypot(target[X_AXIS] - position[X_AXIS], target[Y_AXIS] - position[Y_AXIS]); + float extrusion_final = target[E_AXIS] - position[E_AXIS]; + float fil_per_mm_final = mm_travel_final == 0 ? 0 : extrusion_final / mm_travel_final; + float fil_per_mm_difference = fabs(fil_per_mm_interpolated - fil_per_mm_final); + if (segments > 1 && mm_travel_final > 0.001 && totalExtrusion - (interpolatedExtrusion + extrusion_final) > 0.00001f && max_extrusion_rate_difference < fil_per_mm_difference) + { + max_extrusion_rate_difference = fil_per_mm_difference; + std::cout << "New Maximum extrusion rate difference detected:" << max_extrusion_rate_difference << "\n"; + } + } + plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder); + } void inverse_processor::clamp_to_software_endstops(float* target) diff --git a/ArcWelderInverseProcessor/inverse_processor.h b/ArcWelderInverseProcessor/inverse_processor.h index 9fe99a0..f880898 100644 --- a/ArcWelderInverseProcessor/inverse_processor.h +++ b/ArcWelderInverseProcessor/inverse_processor.h @@ -35,17 +35,22 @@ typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef signed char int8_t; -#define M_PI 3.14159265358979323846 // pi +#define M_PI 3.14159265358979323846f // pi enum AxisEnum { X_AXIS = 0, Y_AXIS= 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 }; +enum InterpolationFunction {INT_SEGMENTS = 0, FLOAT_SEGMENTS = 1}; +static const int interpolation_function_names_size = 2; +static const char* interpolation_function_names[] = { "INT_SEGMENTS", "FLOAT_SEGMENTS"}; + // 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_MIN_ARC_SEGMENTS 24 // 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 1 +#define DEFAULT_N_ARC_CORRECTIONS 25 +#define DEFAULT_INTERPOLATION_FUNCTION InterpolationFunction::INT_SEGMENTS struct ConfigurationStore { ConfigurationStore() { @@ -54,12 +59,14 @@ struct ConfigurationStore { min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC; n_arc_correction = DEFAULT_N_ARC_CORRECTIONS; + interpolation_function = DEFAULT_INTERPOLATION_FUNCTION; } 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; + InterpolationFunction interpolation_function; }; class inverse_processor { @@ -67,7 +74,8 @@ 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); + void mc_arc_float_segments(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); + void mc_arc_int_segments(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder); private: ConfigurationStore cs; @@ -82,6 +90,7 @@ private: float total_e_adjustment; int trig_calc_count = 0; int lines_processed_ = 0; + float max_extrusion_rate_difference = 0; 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=NULL); diff --git a/ArcWelderLib.sln b/ArcWelderLib.sln index e254539..23395a1 100644 --- a/ArcWelderLib.sln +++ b/ArcWelderLib.sln @@ -57,8 +57,8 @@ Global {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Release|x64.Build.0 = Release|x64 {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Release|x86.ActiveCfg = Release|Win32 {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Release|x86.Build.0 = Release|Win32 - {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Remote_Pi|ARM.ActiveCfg = Remote_Pi|ARM - {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Remote_Pi|ARM.Build.0 = Remote_Pi|ARM + {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Remote_Pi|ARM.ActiveCfg = Remote_Pi|Win32 + {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Remote_Pi|ARM.Build.0 = Remote_Pi|Win32 {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Remote_Pi|x64.ActiveCfg = Remote_Pi|x64 {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Remote_Pi|x64.Build.0 = Remote_Pi|x64 {1A4DBAB1-BB42-4DB1-B168-F113784EFCEF}.Remote_Pi|x86.ActiveCfg = Remote_Pi|Win32 diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp index 054ed36..4ce65de 100644 --- a/ArcWelderTest/ArcWelderTest.cpp +++ b/ArcWelderTest/ArcWelderTest.cpp @@ -293,7 +293,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( - SLOW_COUPLER, + SIX_SPEED_TEST, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, diff --git a/ArcWelderTest/ArcWelderTest.h b/ArcWelderTest/ArcWelderTest.h index fd690d1..e250426 100644 --- a/ArcWelderTest/ArcWelderTest.h +++ b/ArcWelderTest/ArcWelderTest.h @@ -73,7 +73,7 @@ static std::string BENCHY_DIFFICULT = "C:\\Users\\Brad\\Documents\\3DPrinter\\An static std::string BENCHY_L1_DIFFICULT = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\BenchyArc_L1_Difficult.gcode"; -static std::string SIX_SPEED_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\6_speed_test.gcode"; +static std::string SIX_SPEED_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\SixSpeedTest_print.gcode"; // Issues static std::string ISSUE_MIMUPREFERIDA = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\MIMUPREFERIDA\\TESTSTUTTER.gcode"; static std::string BARBARIAN = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\PricklyPear\\Barbarian.gcode"; @@ -109,3 +109,5 @@ static std::string UNICODE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiSt + + diff --git a/CMakeLists.txt b/CMakeLists.txt index bc84053..4c4bce6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION "3.15") +cmake_minimum_required (VERSION "3.13") set(CMAKE_VERBOSE_MAKEFILE ON) # You can tweak some common (for all subprojects) stuff here. For example: diff --git a/GcodeProcessorLib/CMakeLists.txt b/GcodeProcessorLib/CMakeLists.txt index 8e1ed3e..09b8044 100644 --- a/GcodeProcessorLib/CMakeLists.txt +++ b/GcodeProcessorLib/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION "3.15") +cmake_minimum_required (VERSION "3.13") project(GcodeProcessorLib C CXX) diff --git a/PyArcWelder/CMakeLists.txt b/PyArcWelder/CMakeLists.txt index c4462a6..f14b908 100644 --- a/PyArcWelder/CMakeLists.txt +++ b/PyArcWelder/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION "3.15") +cmake_minimum_required (VERSION "3.13") project(PyArcWelder C CXX) diff --git a/TCLAP/CMakeLists.txt b/TCLAP/CMakeLists.txt index eefe7d0..6b0b43f 100644 --- a/TCLAP/CMakeLists.txt +++ b/TCLAP/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION "3.15") +cmake_minimum_required (VERSION "3.13") project(TCLAP C CXX) -- cgit v1.2.3