diff options
author | FormerLurker <hochgebe@gmail.com> | 2021-11-21 22:40:04 +0300 |
---|---|---|
committer | FormerLurker <hochgebe@gmail.com> | 2021-11-21 22:40:04 +0300 |
commit | 33946985e505a5d72d47bcfbd573f0b4587c09f8 (patch) | |
tree | febb4f56214b8a1d64ed78966a6bd6750e88e185 /ArcWelder | |
parent | fe18f18b4c005a2c4e4b69a5866d0d065bde82aa (diff) | |
parent | 3bd3d54894cd49fb564396fd043c4b0c80f4b116 (diff) |
Merge devel into master
Diffstat (limited to 'ArcWelder')
-rw-r--r-- | ArcWelder/CMakeLists.txt | 2 | ||||
-rw-r--r-- | ArcWelder/arc_welder.cpp | 1606 | ||||
-rw-r--r-- | ArcWelder/arc_welder.h | 374 | ||||
-rw-r--r-- | ArcWelder/segmented_arc.cpp | 138 | ||||
-rw-r--r-- | ArcWelder/segmented_arc.h | 13 | ||||
-rw-r--r-- | ArcWelder/segmented_shape.cpp | 83 | ||||
-rw-r--r-- | ArcWelder/segmented_shape.h | 24 | ||||
-rw-r--r-- | ArcWelder/unwritten_command.h | 43 |
8 files changed, 1399 insertions, 884 deletions
diff --git a/ArcWelder/CMakeLists.txt b/ArcWelder/CMakeLists.txt index 738983c..f84fd23 100644 --- a/ArcWelder/CMakeLists.txt +++ b/ArcWelder/CMakeLists.txt @@ -31,3 +31,5 @@ set(${PROJECT_NAME}_DEFINITIONS ${GcodeProcessorLib_DEFINITIONS} set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/ ${GcodeProcessorLib_INCLUDE_DIRS} CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE) + +
\ No newline at end of file diff --git a/ArcWelder/arc_welder.cpp b/ArcWelder/arc_welder.cpp index c49f64d..883f66b 100644 --- a/ArcWelder/arc_welder.cpp +++ b/ArcWelder/arc_welder.cpp @@ -34,813 +34,957 @@ #include <fstream> #include <iomanip> #include <sstream> - -arc_welder::arc_welder( - std::string source_path, - std::string target_path, - logger * log, - double resolution_mm, - double path_tolerance_percent, - double max_radius, - int min_arc_segments, - double mm_per_arc_segment, - bool g90_g91_influences_extruder, - bool allow_3d_arcs, - bool allow_dynamic_precision, - unsigned char default_xyz_precision, - unsigned char default_e_precision, - int buffer_size, - progress_callback callback) : current_arc_( - DEFAULT_MIN_SEGMENTS, - buffer_size - 5, - resolution_mm, - path_tolerance_percent, - max_radius, - min_arc_segments, - mm_per_arc_segment, - allow_3d_arcs, - default_xyz_precision, - default_e_precision - ), - segment_statistics_( - segment_statistic_lengths, - segment_statistic_lengths_count, - log - ) +#include <version.h> + + + + +arc_welder::arc_welder(arc_welder_args args) : current_arc_( + DEFAULT_MIN_SEGMENTS, + args.buffer_size, + args.resolution_mm, + args.path_tolerance_percent, + args.max_radius_mm, + args.min_arc_segments, + args.mm_per_arc_segment, + args.allow_3d_arcs, + args.default_xyz_precision, + args.default_e_precision, + args.max_gcode_length + ), + segment_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + args.log + ), + segment_retraction_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + args.log + ), + travel_statistics_( + segment_statistic_lengths, + segment_statistic_lengths_count, + args.log + ) { - p_logger_ = log; - debug_logging_enabled_ = false; - info_logging_enabled_ = false; - error_logging_enabled_ = false; - verbose_logging_enabled_ = false; - - logger_type_ = 0; - resolution_mm_ = resolution_mm; - progress_callback_ = callback; - verbose_output_ = false; - source_path_ = source_path; - target_path_ = target_path; - gcode_position_args_ = get_args_(g90_g91_influences_extruder, buffer_size); - allow_3d_arcs_ = allow_3d_arcs; - allow_dynamic_precision_ = allow_dynamic_precision; - notification_period_seconds = 1; - lines_processed_ = 0; - gcodes_processed_ = 0; - file_size_ = 0; - last_gcode_line_written_ = 0; - points_compressed_ = 0; - arcs_created_ = 0; - waiting_for_arc_ = false; - previous_feedrate_ = -1; - gcode_position_args_.set_num_extruders(8); - for (int index = 0; index < 8; index++) - { - gcode_position_args_.retraction_lengths[0] = .0001; - gcode_position_args_.z_lift_heights[0] = 0.001; - gcode_position_args_.x_firmware_offsets[0] = 0.0; - gcode_position_args_.y_firmware_offsets[0] = 0.0; - } - - // We don't care about the printer settings, except for g91 influences extruder. - - p_source_position_ = new gcode_position(gcode_position_args_); + p_logger_ = args.log; + debug_logging_enabled_ = false; + info_logging_enabled_ = false; + error_logging_enabled_ = false; + verbose_logging_enabled_ = false; + + logger_type_ = 0; + resolution_mm_ = args.resolution_mm; + progress_callback_ = args.callback; + verbose_output_ = false; + source_path_ = args.source_path; + target_path_ = args.target_path; + gcode_position_args_ = get_args_(args.g90_g91_influences_extruder, args.buffer_size); + allow_3d_arcs_ = args.allow_3d_arcs; + allow_travel_arcs_ = args.allow_travel_arcs; + allow_dynamic_precision_ = args.allow_dynamic_precision; + extrusion_rate_variance_percent_ = args.extrusion_rate_variance_percent; + lines_processed_ = 0; + gcodes_processed_ = 0; + file_size_ = 0; + notification_period_seconds_ = args.notification_period_seconds; + last_gcode_line_written_ = 0; + points_compressed_ = 0; + arcs_created_ = 0; + arcs_aborted_by_flow_rate_ = 0; + waiting_for_arc_ = false; + previous_feedrate_ = -1; + gcode_position_args_.set_num_extruders(8); + previous_extrusion_rate_ = 0; + box_encoding_ = args.box_encoding; + for (int index = 0; index < 8; index++) + { + gcode_position_args_.retraction_lengths[0] = .0001; + gcode_position_args_.z_lift_heights[0] = 0.001; + gcode_position_args_.x_firmware_offsets[0] = 0.0; + gcode_position_args_.y_firmware_offsets[0] = 0.0; + } + + // We don't care about the printer settings, except for g91 influences extruder. + + p_source_position_ = new gcode_position(gcode_position_args_); } gcode_position_args arc_welder::get_args_(bool g90_g91_influences_extruder, int buffer_size) { - gcode_position_args args; - // Configure gcode_position_args - args.g90_influences_extruder = g90_g91_influences_extruder; - 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<std::string>(); - args.is_bound_ = false; - args.is_circular_bed = false; - args.x_min = -9999; - args.x_max = 9999; - args.y_min = -9999; - args.y_max = 9999; - args.z_min = -9999; - args.z_max = 9999; - return args; + gcode_position_args args; + // Configure gcode_position_args + args.g90_influences_extruder = g90_g91_influences_extruder; + if (buffer_size < 2) + { + buffer_size = 2; + } + args.position_buffer_size = buffer_size; + args.autodetect_position = true; + args.home_x = 0; + args.home_x_none = true; + args.home_y = 0; + args.home_y_none = true; + args.home_z = 0; + args.home_z_none = true; + args.shared_extruder = true; + args.zero_based_extruder = true; + + + args.default_extruder = 0; + args.xyz_axis_default_mode = "absolute"; + args.e_axis_default_mode = "absolute"; + args.units_default = "millimeters"; + args.location_detection_commands = std::vector<std::string>(); + args.is_bound_ = false; + args.is_circular_bed = false; + args.x_min = -9999; + args.x_max = 9999; + args.y_min = -9999; + args.y_max = 9999; + args.z_min = -9999; + args.z_max = 9999; + return args; } arc_welder::~arc_welder() { - delete p_source_position_; + delete p_source_position_; } void arc_welder::set_logger_type(int logger_type) { - logger_type_ = logger_type; + logger_type_ = logger_type; } void arc_welder::reset() { - p_logger_->log(logger_type_, DEBUG, "Resetting all tracking variables."); - lines_processed_ = 0; - gcodes_processed_ = 0; - last_gcode_line_written_ = 0; - file_size_ = 0; - points_compressed_ = 0; - arcs_created_ = 0; - waiting_for_arc_ = false; + p_logger_->log(logger_type_, log_levels::DEBUG, "Resetting all tracking variables."); + lines_processed_ = 0; + gcodes_processed_ = 0; + last_gcode_line_written_ = 0; + file_size_ = 0; + points_compressed_ = 0; + arcs_created_ = 0; + waiting_for_arc_ = false; } long arc_welder::get_file_size(const std::string& file_path) { - // Todo: Fix this function. This is a pretty weak implementation :( - std::ifstream file(file_path.c_str(), std::ios::in | std::ios::binary); - const long l = (long)file.tellg(); - file.seekg(0, std::ios::end); - const long m = (long)file.tellg(); - file.close(); - return (m - l); + // Todo: Fix this function. This is a pretty weak implementation :( + std::ifstream file(file_path.c_str(), std::ios::in | std::ios::binary); + const long l = (long)file.tellg(); + file.seekg(0, std::ios::end); + const long m = (long)file.tellg(); + file.close(); + return (m - l); } double arc_welder::get_next_update_time() const { - return clock() + (notification_period_seconds * CLOCKS_PER_SEC); + return clock() + (notification_period_seconds_ * CLOCKS_PER_SEC); } double arc_welder::get_time_elapsed(double start_clock, double end_clock) { - return static_cast<double>(end_clock - start_clock) / CLOCKS_PER_SEC; + return static_cast<double>(end_clock - start_clock) / CLOCKS_PER_SEC; } arc_welder_results arc_welder::process() { -arc_welder_results results; - p_logger_->log(logger_type_, DEBUG, "Configuring logging settings."); - verbose_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, VERBOSE); - debug_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, DEBUG); - info_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, INFO); - error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, ERROR); - - std::stringstream stream; - stream << std::fixed << std::setprecision(2); - stream << "arc_welder::process - Parameters received: source_file_path: '" << - source_path_ << "', target_file_path:'" << target_path_ << "', resolution_mm:" << - resolution_mm_ << "mm (+-" << current_arc_.get_resolution_mm() << "mm), path_tolerance_percent: " << current_arc_.get_path_tolerance_percent() - << ", max_radius_mm:" << current_arc_.get_max_radius() - << ", min_arc_segments:" << std::setprecision(0) <<current_arc_.get_min_arc_segments() - << ", mm_per_arc_segment:" << std::setprecision(0) << current_arc_.get_mm_per_arc_segment() - << ", g90_91_influences_extruder: " << (p_source_position_->get_g90_91_influences_extruder() ? "True" : "False") - << ", allow_3d_arcs: " << (allow_3d_arcs_ ? "True" : "False") - << ", allow_dynamic_precision: " << (allow_dynamic_precision_ ? "True" : "False") - << ", default_xyz_precision: " << std::setprecision(0) << static_cast<double>(current_arc_.get_xyz_precision()) - << ", default_e_precision: " << std::setprecision(0) << static_cast<double>(current_arc_.get_e_precision()); - p_logger_->log(logger_type_, INFO, stream.str()); - - - // reset tracking variables - reset(); - // local variable to hold the progress update return. If it's false, we will exit. - bool continue_processing = true; - - p_logger_->log(logger_type_, DEBUG, "Configuring progress updates."); - int read_lines_before_clock_check = 1000; - double next_update_time = get_next_update_time(); - const clock_t start_clock = clock(); - p_logger_->log(logger_type_, DEBUG, "Getting source file size."); - file_size_ = get_file_size(source_path_); - stream.clear(); - stream.str(""); - stream << "Source file size: " << file_size_; - p_logger_->log(logger_type_, DEBUG, stream.str()); - // Create the source file read stream and target write stream - std::ifstream gcodeFile; - p_logger_->log(logger_type_, DEBUG, "Opening the source file for reading."); - gcodeFile.open(source_path_.c_str(), std::ifstream::in); - if (!gcodeFile.is_open()) - { - results.success = false; - results.message = "Unable to open the source file."; - p_logger_->log_exception(logger_type_, results.message); - return results; - } - p_logger_->log(logger_type_, DEBUG, "Source file opened successfully."); - - p_logger_->log(logger_type_, DEBUG, "Opening the target file for writing."); - - output_file_.open(target_path_.c_str(), std::ifstream::out); - if (!output_file_.is_open()) - { - results.success = false; - results.message = "Unable to open the target file."; - p_logger_->log_exception(logger_type_, results.message); - gcodeFile.close(); - return results; - } - - p_logger_->log(logger_type_, DEBUG, "Target file opened successfully."); - std::string line; - int lines_with_no_commands = 0; - parsed_command cmd; - // Communicate every second - p_logger_->log(logger_type_, DEBUG, "Sending initial progress update."); - continue_processing = on_progress_(get_progress_(static_cast<long>(gcodeFile.tellg()), static_cast<double>(start_clock))); - p_logger_->log(logger_type_, DEBUG, "Processing source file."); - - while (std::getline(gcodeFile, line) && continue_processing) - { - lines_processed_++; - // Check the first line of gcode and see if it = ;FLAVOR:UltiGCode + arc_welder_results results; + p_logger_->log(logger_type_, log_levels::DEBUG, "Configuring logging settings."); + verbose_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::VERBOSE); + debug_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::DEBUG); + info_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::INFO); + error_logging_enabled_ = p_logger_->is_log_level_enabled(logger_type_, log_levels::ERROR); + + std::stringstream stream; + // reset tracking variables + reset(); + // local variable to hold the progress update return. If it's false, we will exit. + bool continue_processing = true; + + p_logger_->log(logger_type_, log_levels::DEBUG, "Configuring progress updates."); + int read_lines_before_clock_check = 1000; + double next_update_time = get_next_update_time(); + const clock_t start_clock = clock(); + p_logger_->log(logger_type_, log_levels::DEBUG, "Getting source file size."); + file_size_ = get_file_size(source_path_); + stream.clear(); + stream.str(""); + stream << "Source file size: " << file_size_; + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); + + // Determine if we need to overwrite the source file + bool overwrite_source_file = false; + std::string temp_file_path; + if (source_path_ == target_path_) + { + overwrite_source_file = true; + if (!utilities::get_temp_file_path_for_file(source_path_, temp_file_path)) + { + results.success = false; + results.message = "The source and target path are the same, but a temporary file path could not be created. Are the paths empty?"; + p_logger_->log_exception(logger_type_, results.message); + return results; + } + + stream.clear(); + stream.str(""); + stream << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path; + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); + target_path_ = temp_file_path; + } + + // Create the source file read stream and target write stream + std::ifstream gcodeFile; + p_logger_->log(logger_type_, log_levels::DEBUG, "Opening the source file for reading."); + gcodeFile.open(source_path_.c_str(), std::ifstream::in); + if (!gcodeFile.is_open()) + { + results.success = false; + results.message = "Unable to open the source file."; + p_logger_->log_exception(logger_type_, results.message); + return results; + } + p_logger_->log(logger_type_, log_levels::DEBUG, "Source file opened successfully."); + + p_logger_->log(logger_type_, log_levels::DEBUG, "Opening the target file for writing."); + + output_file_.open(target_path_.c_str(), std::ios_base::binary | std::ios_base::out); + if (!output_file_.is_open()) + { + results.success = false; + results.message = "Unable to open the target file."; + p_logger_->log_exception(logger_type_, results.message); + gcodeFile.close(); + return results; + } + + p_logger_->log(logger_type_, log_levels::DEBUG, "Target file opened successfully."); + std::string line; + int lines_with_no_commands = 0; + parsed_command cmd; + // Communicate every second + p_logger_->log(logger_type_, log_levels::DEBUG, "Sending initial progress update."); + continue_processing = on_progress_(get_progress_(static_cast<long>(gcodeFile.tellg()), static_cast<double>(start_clock))); + p_logger_->log(logger_type_, log_levels::DEBUG, "Processing source file."); + + bool arc_Welder_comment_added = false; + while (std::getline(gcodeFile, line) && continue_processing) + { + lines_processed_++; + // Check the first line of gcode and see if it = ;FLAVOR:UltiGCode // This comment MUST be preserved as the first line for ultimakers, else things won't work - if (lines_processed_ == 1) - { - bool isUltiGCode = line == ";FLAVOR:UltiGCode"; - if (isUltiGCode) - { - write_gcode_to_file(line); - } - add_arcwelder_comment_to_target(); - if (isUltiGCode) - { - lines_with_no_commands++; - continue; - } - } - cmd.clear(); - if (verbose_logging_enabled_) - { - stream.clear(); - stream.str(""); - stream << "Parsing: " << line; - p_logger_->log(logger_type_, VERBOSE, stream.str()); - } - parser_.try_parse_gcode(line.c_str(), cmd, true); - bool has_gcode = false; - if (cmd.gcode.length() > 0) - { - has_gcode = true; - gcodes_processed_++; - } - else - { - lines_with_no_commands++; - } - - // Always process the command through the printer, even if no command is found - // This is important so that comments can be analyzed - //std::cout << "stabilization::process_file - updating position..."; - process_gcode(cmd, false, false); - - // Only continue to process if we've found a command and either a progress_callback_ is supplied, or debug loggin is enabled. - if (has_gcode) - { - if ((lines_processed_ % read_lines_before_clock_check) == 0 && next_update_time < clock()) - { - if (verbose_logging_enabled_) - { - p_logger_->log(logger_type_, VERBOSE, "Sending progress update."); - } - continue_processing = on_progress_(get_progress_(static_cast<long>(gcodeFile.tellg()), static_cast<double>(start_clock))); - next_update_time = get_next_update_time(); - } - } - } - - if (current_arc_.is_shape() && waiting_for_arc_) - { - p_logger_->log(logger_type_, DEBUG, "Processing the final line."); - process_gcode(cmd, true, false); - } - p_logger_->log(logger_type_, DEBUG, "Writing all unwritten gcodes to the target file."); - write_unwritten_gcodes_to_file(); - p_logger_->log(logger_type_, DEBUG, "Fetching the final progress struct."); - - arc_welder_progress final_progress = get_progress_(static_cast<long>(file_size_), static_cast<double>(start_clock)); - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Sending final progress update message."); - } - on_progress_(arc_welder_progress(final_progress)); - - p_logger_->log(logger_type_, DEBUG, "Processing complete, closing source and target file."); - output_file_.close(); - gcodeFile.close(); - const clock_t end_clock = clock(); - - results.success = continue_processing; - results.cancelled = !continue_processing; - results.progress = final_progress; - p_logger_->log(logger_type_, DEBUG, "Returning processing results."); - - return results; + if (lines_processed_ == 1) + { + bool isUltiGCode = line == ";FLAVOR:UltiGCode"; + bool isPrusaSlicer = line.rfind("; generated by PrusaSlicer", 0) == 0; + if (isUltiGCode || isPrusaSlicer) + { + write_gcode_to_file(line); + } + add_arcwelder_comment_to_target(); + if (isUltiGCode || isPrusaSlicer) + { + lines_with_no_commands++; + continue; + } + } + + + cmd.clear(); + if (verbose_logging_enabled_) + { + stream.clear(); + stream.str(""); + stream << "Parsing: " << line; + p_logger_->log(logger_type_, log_levels::VERBOSE, stream.str()); + } + parser_.try_parse_gcode(line.c_str(), cmd, true); + bool has_gcode = false; + if (cmd.gcode.length() > 0) + { + has_gcode = true; + gcodes_processed_++; + } + else + { + lines_with_no_commands++; + } + + // Always process the command through the printer, even if no command is found + // This is important so that comments can be analyzed + //std::cout << "stabilization::process_file - updating position..."; + process_gcode(cmd, false, false); + + // Only continue to process if we've found a command and either a progress_callback_ is supplied, or debug loggin is enabled. + if (has_gcode) + { + if ((lines_processed_ % read_lines_before_clock_check) == 0 && next_update_time < clock()) + { + if (verbose_logging_enabled_) + { + p_logger_->log(logger_type_, log_levels::VERBOSE, "Sending progress update."); + } + continue_processing = on_progress_(get_progress_(static_cast<long>(gcodeFile.tellg()), static_cast<double>(start_clock))); + next_update_time = get_next_update_time(); + } + } + } + + if (current_arc_.is_shape() && waiting_for_arc_) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Processing the final line."); + process_gcode(cmd, true, false); + } + p_logger_->log(logger_type_, log_levels::DEBUG, "Writing all unwritten gcodes to the target file."); + write_unwritten_gcodes_to_file(); + + p_logger_->log(logger_type_, log_levels::DEBUG, "Fetching the final progress struct."); + + arc_welder_progress final_progress = get_progress_(static_cast<long>(file_size_), static_cast<double>(start_clock)); + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Sending final progress update message."); + } + on_progress_(final_progress); + + p_logger_->log(logger_type_, log_levels::DEBUG, "Closing source and target files."); + output_file_.close(); + gcodeFile.close(); + + if (overwrite_source_file) + { + stream.clear(); + stream.str(""); + stream << "Deleting the original source file at '" << source_path_ << "'."; + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); + stream.clear(); + stream.str(""); + std::remove(source_path_.c_str()); + stream << "Renaming temporary file at '" << target_path_ << "' to '" << source_path_ << "'."; + p_logger_->log(0, log_levels::DEBUG, stream.str()); + std::rename(target_path_.c_str(), source_path_.c_str()); + } + + results.success = continue_processing; + results.cancelled = !continue_processing; + results.progress = final_progress; + p_logger_->log(logger_type_, log_levels::DEBUG, "Returning processing results."); + + return results; } bool arc_welder::on_progress_(const arc_welder_progress& progress) { - if (progress_callback_ != NULL) - { - return progress_callback_(progress, p_logger_, logger_type_); - } - else if (info_logging_enabled_) - { - p_logger_->log(logger_type_, INFO, progress.str()); - } - - return true; + if (progress_callback_ != NULL) + { + return progress_callback_(progress, p_logger_, logger_type_); + } + else if (info_logging_enabled_) + { + p_logger_->log(logger_type_, log_levels::INFO, progress.str()); + } + + return true; } arc_welder_progress arc_welder::get_progress_(long source_file_position, double start_clock) { - arc_welder_progress progress; - progress.gcodes_processed = gcodes_processed_; - progress.lines_processed = lines_processed_; - progress.points_compressed = points_compressed_; - progress.arcs_created = arcs_created_; - progress.source_file_position = source_file_position; - progress.target_file_size = static_cast<long>(output_file_.tellp()); - progress.source_file_size = file_size_; - long bytesRemaining = file_size_ - static_cast<long>(source_file_position); - progress.percent_complete = static_cast<double>(source_file_position) / static_cast<double>(file_size_) * 100.0; - progress.seconds_elapsed = get_time_elapsed(start_clock, clock()); - double bytesPerSecond = static_cast<double>(source_file_position) / progress.seconds_elapsed; - progress.seconds_remaining = bytesRemaining / bytesPerSecond; - - if (source_file_position > 0) { - progress.compression_ratio = (static_cast<float>(source_file_position) / static_cast<float>(progress.target_file_size)); - progress.compression_percent = (1.0 - (static_cast<float>(progress.target_file_size) / static_cast<float>(source_file_position))) * 100.0f; - } - progress.num_firmware_compensations = current_arc_.get_num_firmware_compensations(); - progress.segment_statistics = segment_statistics_; - return progress; - + arc_welder_progress progress; + progress.gcodes_processed = gcodes_processed_; + progress.lines_processed = lines_processed_; + progress.points_compressed = points_compressed_; + progress.arcs_created = arcs_created_; + progress.arcs_aborted_by_flow_rate = arcs_aborted_by_flow_rate_; + progress.source_file_position = source_file_position; + progress.target_file_size = static_cast<long>(output_file_.tellp()); + progress.source_file_size = file_size_; + long bytesRemaining = file_size_ - static_cast<long>(source_file_position); + progress.percent_complete = static_cast<double>(source_file_position) / static_cast<double>(file_size_) * 100.0; + progress.seconds_elapsed = get_time_elapsed(start_clock, clock()); + double bytesPerSecond = static_cast<double>(source_file_position) / progress.seconds_elapsed; + progress.seconds_remaining = bytesRemaining / bytesPerSecond; + + if (source_file_position > 0) { + progress.compression_ratio = (static_cast<float>(source_file_position) / static_cast<float>(progress.target_file_size)); + progress.compression_percent = (1.0 - (static_cast<float>(progress.target_file_size) / static_cast<float>(source_file_position))) * 100.0f; + } + else { + progress.compression_ratio = 0; + progress.compression_percent = 0; + } + progress.num_firmware_compensations = current_arc_.get_num_firmware_compensations(); + progress.num_gcode_length_exceptions = current_arc_.get_num_gcode_length_exceptions(); + progress.segment_statistics = segment_statistics_; + progress.segment_retraction_statistics = segment_retraction_statistics_; + progress.travel_statistics = travel_statistics_; + progress.box_encoding = box_encoding_; + return progress; + } int arc_welder::process_gcode(parsed_command cmd, bool is_end, bool is_reprocess) { - /* use to catch gcode for debugging since I can't set conditions equal to strings - if (cmd.gcode == "G1 X118.762 Y104.054 E0.0163") - { - std::cout << "Found it!"; - } - */ - // Update the position for the source gcode file - p_source_position_->update(cmd, lines_processed_, gcodes_processed_, -1); - position* p_cur_pos = p_source_position_->get_current_position_ptr(); - position* p_pre_pos = p_source_position_->get_previous_position_ptr(); - bool is_previous_extruder_relative = p_pre_pos->is_extruder_relative; - extruder extruder_current = p_cur_pos->get_current_extruder(); - extruder previous_extruder = p_pre_pos->get_current_extruder(); - //std::cout << lines_processed_ << " - " << cmd.gcode << ", CurrentEAbsolute: " << cur_extruder.e <<", ExtrusionLength: " << cur_extruder.extrusion_length << ", Retraction Length: " << cur_extruder.retraction_length << ", IsExtruding: " << cur_extruder.is_extruding << ", IsRetracting: " << cur_extruder.is_retracting << ".\n"; - - int lines_written = 0; - // see if this point is an extrusion - - bool arc_added = false; - bool clear_shapes = false; - double movement_length_mm = 0; - bool has_e_changed = extruder_current.is_extruding || extruder_current.is_retracting; - // Update the source file statistics - if (p_cur_pos->has_xy_position_changed && (has_e_changed)) - { - if (allow_3d_arcs_) { - movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z); - } - else { - movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_cur_pos->x, p_cur_pos->y); - } - - if (movement_length_mm > 0) - { - if (!is_reprocess) - segment_statistics_.update(movement_length_mm, true); - } - } - - // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position - // TODO: Handle relative XYZ axis. This is possible, but maybe not so important. - bool is_g1_g2 = cmd.command == "G0" || cmd.command == "G1"; - if (allow_dynamic_precision_ && is_g1_g2) - { - for (std::vector<parsed_command_parameter>::iterator it = cmd.parameters.begin(); it != cmd.parameters.end(); ++it) - { - switch ((*it).name[0]) - { - case 'X': - case 'Y': - case 'Z': - current_arc_.update_xyz_precision((*it).double_precision); - break; - case 'E': - current_arc_.update_e_precision((*it).double_precision); - break; - } - } - } - - bool z_axis_ok = allow_3d_arcs_ || - utilities::is_equal(p_cur_pos->z, p_pre_pos->z); - - if ( - !is_end && cmd.is_known_command && !cmd.is_empty && ( - is_g1_g2 && z_axis_ok && - utilities::is_equal(p_cur_pos->x_offset, p_pre_pos->x_offset) && - utilities::is_equal(p_cur_pos->y_offset, p_pre_pos->y_offset) && - utilities::is_equal(p_cur_pos->z_offset, p_pre_pos->z_offset) && - utilities::is_equal(p_cur_pos->x_firmware_offset, p_pre_pos->x_firmware_offset) && - utilities::is_equal(p_cur_pos->y_firmware_offset, p_pre_pos->y_firmware_offset) && - utilities::is_equal(p_cur_pos->z_firmware_offset, p_pre_pos->z_firmware_offset) && - !p_cur_pos->is_relative && - ( - !waiting_for_arc_ || - extruder_current.is_extruding || - //(previous_extruder.is_extruding && extruder_current.is_extruding) || // Test to see if - // we can get more arcs. - (previous_extruder.is_retracting && extruder_current.is_retracting) - ) && - p_cur_pos->is_extruder_relative == is_previous_extruder_relative && - (!waiting_for_arc_ || p_pre_pos->f == p_cur_pos->f) && // might need to skip the waiting for arc check... - (!waiting_for_arc_ || p_pre_pos->feature_type_tag == p_cur_pos->feature_type_tag) - ) - ) { - - 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_) - { - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Starting new arc from Gcode:" + cmd.gcode); - } - write_unwritten_gcodes_to_file(); - // add the previous point as the starting point for the current arc - printer_point previous_p(p_pre_pos->get_gcode_x(), p_pre_pos->get_gcode_y(), p_pre_pos->get_gcode_z(), previous_extruder.e_relative, 0); - // Don't add any extrusion, or you will over extrude! - //std::cout << "Trying to add first point (" << p.x << "," << p.y << "," << p.z << ")..."; - - current_arc_.try_add_point(previous_p); - } - - double e_relative = extruder_current.e_relative; - int num_points = current_arc_.get_num_segments(); - arc_added = current_arc_.try_add_point(p); - if (arc_added) - { - if (!waiting_for_arc_) - { - waiting_for_arc_ = true; - previous_feedrate_ = p_pre_pos->f; - } - else - { - if (debug_logging_enabled_) - { - if (num_points+1 == current_arc_.get_num_segments()) - { - p_logger_->log(logger_type_, DEBUG, "Adding point to arc from Gcode:" + cmd.gcode); - } - - } - } - } - } - else if (debug_logging_enabled_ ){ - if (is_end) - { - p_logger_->log(logger_type_, DEBUG, "Procesing final shape, if one exists."); - } - else if (!cmd.is_empty) - { - if (!cmd.is_known_command) - { - p_logger_->log(logger_type_, DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); - } - else if (cmd.command != "G0" && cmd.command != "G1") - { - p_logger_->log(logger_type_, DEBUG, "Command '"+ cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); - } - else if (!allow_3d_arcs_ && !utilities::is_equal(p_cur_pos->z, p_pre_pos->z)) - { - p_logger_->log(logger_type_, DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); - } - else if (p_cur_pos->is_relative) - { - p_logger_->log(logger_type_, DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); - } - else if ( - waiting_for_arc_ && !( - (previous_extruder.is_extruding && extruder_current.is_extruding) || - (previous_extruder.is_retracting && extruder_current.is_retracting) - ) - ) - { - std::string message = "Extruding or retracting state changed, cannot add point to current arc: " + cmd.gcode; - if (verbose_logging_enabled_) - { - - message.append( - " - Verbose Info\n\tCurrent Position Info - Absolute E:" + utilities::to_string(extruder_current.e) + - ", Offset E:" + utilities::to_string(extruder_current.get_offset_e()) + - ", Mode:" + (p_cur_pos->is_extruder_relative_null ? "NULL" : p_cur_pos->is_extruder_relative ? "relative" : "absolute") + - ", Retraction: " + utilities::to_string(extruder_current.retraction_length) + - ", Extrusion: " + utilities::to_string(extruder_current.extrusion_length) + - ", Retracting: " + (extruder_current.is_retracting ? "True" : "False") + - ", Extruding: " + (extruder_current.is_extruding ? "True" : "False") - ); - message.append( - "\n\tPrevious Position Info - Absolute E:" + utilities::to_string(previous_extruder.e) + - ", Offset E:" + utilities::to_string(previous_extruder.get_offset_e()) + - ", Mode:" + (p_pre_pos->is_extruder_relative_null ? "NULL" : p_pre_pos->is_extruder_relative ? "relative" : "absolute") + - ", Retraction: " + utilities::to_string(previous_extruder.retraction_length) + - ", Extrusion: " + utilities::to_string(previous_extruder.extrusion_length) + - ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") + - ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False") - ); - p_logger_->log(logger_type_, VERBOSE, message); - } - else - { - p_logger_->log(logger_type_, DEBUG, message); - } - - } - else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative) - { - p_logger_->log(logger_type_, DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); - } - else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f) - { - p_logger_->log(logger_type_, DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); - } - else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag) - { - p_logger_->log(logger_type_, DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); - } - else - { - // Todo: Add all the relevant values - p_logger_->log(logger_type_, DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); - } - } - } - - if (!arc_added && !(cmd.is_empty && cmd.comment.length() == 0)) - { - if (current_arc_.get_num_segments() < current_arc_.get_min_segments()) { - if (debug_logging_enabled_ && !cmd.is_empty) - { - if (current_arc_.get_num_segments() != 0) - { - p_logger_->log(logger_type_, DEBUG, "Not enough segments, resetting. Gcode:" + cmd.gcode); - } - - } - waiting_for_arc_ = false; - current_arc_.clear(); - } - else if (waiting_for_arc_) - { - - if (current_arc_.is_shape()) - { - // update our statistics - points_compressed_ += current_arc_.get_num_segments()-1; - arcs_created_++; // increment the number of generated arcs - write_arc_gcodes(p_pre_pos->is_extruder_relative, p_pre_pos->f); - // Now clear the arc and flag the processor as not waiting for an arc - waiting_for_arc_ = false; - current_arc_.clear(); - p_cur_pos = NULL; - p_pre_pos = NULL; - - // Reprocess this line - if (!is_end) - { - return process_gcode(cmd, false, true); - } - else - { - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Final arc created, exiting."); - } - return 0; - } - - } - else - { - if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "The current arc is not a valid arc, resetting."); - } - current_arc_.clear(); - waiting_for_arc_ = false; - } - } - else if (debug_logging_enabled_) - { - p_logger_->log(logger_type_, DEBUG, "Could not add point to arc from gcode:" + cmd.gcode); - } - - } - - if (waiting_for_arc_ || !arc_added) - { - // This might not work.... - //position* cur_pos = p_source_position_->get_current_position_ptr(); - - unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, movement_length_mm)); - - } - else if (!waiting_for_arc_) - { - write_unwritten_gcodes_to_file(); - current_arc_.clear(); - } - return lines_written; + + + // Update the position for the source gcode file + p_source_position_->update(cmd, lines_processed_, gcodes_processed_, -1); + position* p_cur_pos = p_source_position_->get_current_position_ptr(); + position* p_pre_pos = p_source_position_->get_previous_position_ptr(); + bool is_previous_extruder_relative = p_pre_pos->is_extruder_relative; + extruder extruder_current = p_cur_pos->get_current_extruder(); + extruder previous_extruder = p_pre_pos->get_current_extruder(); + + // Determine if this is a G0, G1, G2 or G3 + bool is_g0_g1 = cmd.command == "G0" || cmd.command == "G1"; + bool is_g2_g3 = cmd.command == "G2" || cmd.command == "G3"; + //std::cout << lines_processed_ << " - " << cmd.gcode << ", CurrentEAbsolute: " << cur_extruder.e <<", ExtrusionLength: " << cur_extruder.extrusion_length << ", Retraction Length: " << cur_extruder.retraction_length << ", IsExtruding: " << cur_extruder.is_extruding << ", IsRetracting: " << cur_extruder.is_retracting << ".\n"; + + int lines_written = 0; + // see if this point is an extrusion + + bool arc_added = false; + bool clear_shapes = false; + double movement_length_mm = 0; + bool is_extrusion = extruder_current.e_relative > 0; + bool is_retraction = extruder_current.e_relative < 0; + bool is_travel = !(is_extrusion || is_retraction) && (is_g0_g1 || is_g2_g3); + + // Update the source file statistics + if (p_cur_pos->has_xy_position_changed) + { + // If this is a g2/g3 command, we need to do a bit more to get the length of the arc. + // The movement_length_mm variable will contain the chord length, which we will need + if (is_g2_g3) + { + // Determine the radius of the arc, which is necessary to calculate the arc length from the chord length. + double i = 0; + double j = 0; + double r = 0; + // Iterate through the parameters and fill in I, J and R; + for (std::vector<parsed_command_parameter>::iterator it = cmd.parameters.begin(); it != cmd.parameters.end(); ++it) + { + switch ((*it).name[0]) + { + case 'I': + i = (*it).double_precision; + break; + case 'J': + j = (*it).double_precision; + break; + // Note that the R form isn't fully implemented! + case 'R': + r = (*it).double_precision; + break; + } + } + + // Calculate R + if (r == 0) + { + r = utilities::sqrt(i * i + j * j); + } + // Now we know the radius and the chord length; + movement_length_mm = utilities::get_arc_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z, i, j, r, p_cur_pos->command.command == "G2"); + + } + else if (allow_3d_arcs_) { + movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_pre_pos->z, p_cur_pos->x, p_cur_pos->y, p_cur_pos->z); + } + else { + movement_length_mm = utilities::get_cartesian_distance(p_pre_pos->x, p_pre_pos->y, p_cur_pos->x, p_cur_pos->y); + } + + if (movement_length_mm > 0) + { + if (!is_reprocess) + { + if (is_extrusion) + { + segment_statistics_.update(movement_length_mm, true); + } + else if (is_retraction) + { + segment_retraction_statistics_.update(movement_length_mm, true); + } + else if (allow_travel_arcs_ && is_travel) + { + travel_statistics_.update(movement_length_mm, true); + } + + } + } + } + + // 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 (extrusion_rate_variance_percent_ != 0) + { + // TODO: MAKE SURE THIS WORKS FOR TRANSITIONS FROM TRAVEL TO NON TRAVEL MOVES + if (movement_length_mm > 0 && (is_extrusion || is_retraction)) + { + mm_extruded_per_mm_travel = extruder_current.e_relative / movement_length_mm; + if (previous_extrusion_rate_ > 0) + { + extrusion_rate_change_percent = utilities::abs(utilities::get_percent_change(previous_extrusion_rate_, mm_extruded_per_mm_travel)); + } + } + if (previous_extrusion_rate_ != 0 && utilities::greater_than(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) + { + arcs_aborted_by_flow_rate_++; + aborted_by_flow_rate = true; + } + } + + + // We need to make sure the printer is using absolute xyz, is extruding, and the extruder axis mode is the same as that of the previous position + // TODO: Handle relative XYZ axis. This is possible, but maybe not so important. + + if (allow_dynamic_precision_ && is_g0_g1) + { + for (std::vector<parsed_command_parameter>::iterator it = cmd.parameters.begin(); it != cmd.parameters.end(); ++it) + { + switch ((*it).name[0]) + { + case 'X': + case 'Y': + case 'Z': + current_arc_.update_xyz_precision((*it).double_precision); + break; + case 'E': + current_arc_.update_e_precision((*it).double_precision); + break; + } + } + } + + bool z_axis_ok = allow_3d_arcs_ || + utilities::is_equal(p_cur_pos->z, p_pre_pos->z); + + if ( + !is_end && cmd.is_known_command && !cmd.is_empty && ( + is_g0_g1 && z_axis_ok && + utilities::is_equal(p_cur_pos->x_offset, p_pre_pos->x_offset) && + utilities::is_equal(p_cur_pos->y_offset, p_pre_pos->y_offset) && + utilities::is_equal(p_cur_pos->z_offset, p_pre_pos->z_offset) && + utilities::is_equal(p_cur_pos->x_firmware_offset, p_pre_pos->x_firmware_offset) && + utilities::is_equal(p_cur_pos->y_firmware_offset, p_pre_pos->y_firmware_offset) && + utilities::is_equal(p_cur_pos->z_firmware_offset, p_pre_pos->z_firmware_offset) && + (previous_extrusion_rate_ == 0 || utilities::less_than_or_equal(extrusion_rate_change_percent, extrusion_rate_variance_percent_)) && + !p_cur_pos->is_relative && + ( + !waiting_for_arc_ || + extruder_current.is_extruding || + extruder_current.is_retracting || + // Test for travel conversion + (allow_travel_arcs_ && p_cur_pos->is_travel()) + //|| (previous_extruder.is_extruding && extruder_current.is_extruding) // Test to see if + // we can get more arcs. + // || (previous_extruder.is_retracting && extruder_current.is_retracting) // Test to see if + // we can get more arcs. + ) && + p_cur_pos->is_extruder_relative == is_previous_extruder_relative && + (!waiting_for_arc_ || p_pre_pos->f == p_cur_pos->f) && // might need to skip the waiting for arc check... + (!waiting_for_arc_ || p_pre_pos->feature_type_tag == p_cur_pos->feature_type_tag) + ) + ) { + + // Record the extrusion rate + previous_extrusion_rate_ = mm_extruded_per_mm_travel; + printer_point p(p_cur_pos->get_gcode_x(), p_cur_pos->get_gcode_y(), p_cur_pos->get_gcode_z(), extruder_current.get_offset_e(), extruder_current.e_relative, p_cur_pos->f, movement_length_mm, p_pre_pos->is_extruder_relative); + if (!waiting_for_arc_) + { + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Starting new arc from Gcode:" + cmd.gcode); + } + write_unwritten_gcodes_to_file(); + // add the previous point as the starting point for the current arc + printer_point previous_p(p_pre_pos->get_gcode_x(), p_pre_pos->get_gcode_y(), p_pre_pos->get_gcode_z(), previous_extruder.get_offset_e(), previous_extruder.e_relative, p_pre_pos->f, 0, p_pre_pos->is_extruder_relative); + // Don't add any extrusion, or you will over extrude! + //std::cout << "Trying to add first point (" << p.x << "," << p.y << "," << p.z << ")..."; + + current_arc_.try_add_point(previous_p); + } + + double e_relative = extruder_current.e_relative; + int num_points = current_arc_.get_num_segments(); + arc_added = current_arc_.try_add_point(p); + if (arc_added) + { + // Make sure our position list is large enough to handle all the segments + if (current_arc_.get_num_segments() + 2 > p_source_position_->get_max_positions()) + { + p_source_position_->grow_max_positions(p_source_position_->get_max_positions() * 2); + } + if (!waiting_for_arc_) + { + waiting_for_arc_ = true; + previous_feedrate_ = p_pre_pos->f; + } + else + { + if (debug_logging_enabled_) + { + if (num_points + 1 == current_arc_.get_num_segments()) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Adding point to arc from Gcode:" + cmd.gcode); + } + + } + } + } + } + else { + + if (debug_logging_enabled_) { + if (is_end) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Procesing final shape, if one exists."); + } + else if (!cmd.is_empty) + { + if (!cmd.is_known_command) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Command '" + cmd.command + "' is Unknown. Gcode:" + cmd.gcode); + } + else if (cmd.command != "G0" && cmd.command != "G1") + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Command '" + cmd.command + "' is not G0/G1, skipping. Gcode:" + cmd.gcode); + } + else if (!allow_3d_arcs_ && !utilities::is_equal(p_cur_pos->z, p_pre_pos->z)) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Z axis position changed, cannot convert:" + cmd.gcode); + } + else if (p_cur_pos->is_relative) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "XYZ Axis is in relative mode, cannot convert:" + cmd.gcode); + } + else if ( + waiting_for_arc_ && !( + (previous_extruder.is_extruding && extruder_current.is_extruding) || + (previous_extruder.is_retracting && extruder_current.is_retracting) + ) + ) + { + std::string message = "Extruding or retracting state changed, cannot add point to current arc: " + cmd.gcode; + if (verbose_logging_enabled_) + { + + message.append( + " - Verbose Info\n\tCurrent Position Info - Absolute E:" + utilities::to_string(extruder_current.e) + + ", Offset E:" + utilities::to_string(extruder_current.get_offset_e()) + + ", Mode:" + (p_cur_pos->is_extruder_relative_null ? "NULL" : p_cur_pos->is_extruder_relative ? "relative" : "absolute") + + ", Retraction: " + utilities::to_string(extruder_current.retraction_length) + + ", Extrusion: " + utilities::to_string(extruder_current.extrusion_length) + + ", Retracting: " + (extruder_current.is_retracting ? "True" : "False") + + ", Extruding: " + (extruder_current.is_extruding ? "True" : "False") + ); + message.append( + "\n\tPrevious Position Info - Absolute E:" + utilities::to_string(previous_extruder.e) + + ", Offset E:" + utilities::to_string(previous_extruder.get_offset_e()) + + ", Mode:" + (p_pre_pos->is_extruder_relative_null ? "NULL" : p_pre_pos->is_extruder_relative ? "relative" : "absolute") + + ", Retraction: " + utilities::to_string(previous_extruder.retraction_length) + + ", Extrusion: " + utilities::to_string(previous_extruder.extrusion_length) + + ", Retracting: " + (previous_extruder.is_retracting ? "True" : "False") + + ", Extruding: " + (previous_extruder.is_extruding ? "True" : "False") + ); + p_logger_->log(logger_type_, log_levels::VERBOSE, message); + } + else + { + p_logger_->log(logger_type_, log_levels::DEBUG, message); + } + + } + else if (p_cur_pos->is_extruder_relative != p_pre_pos->is_extruder_relative) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Extruder axis mode changed, cannot add point to current arc: " + cmd.gcode); + } + else if (waiting_for_arc_ && p_pre_pos->f != p_cur_pos->f) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Feedrate changed, cannot add point to current arc: " + cmd.gcode); + } + else if (waiting_for_arc_ && p_pre_pos->feature_type_tag != p_cur_pos->feature_type_tag) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Feature type changed, cannot add point to current arc: " + cmd.gcode); + } + else if (aborted_by_flow_rate) + { + std::stringstream stream; + stream << std::fixed << std::setprecision(5); + stream << "Arc Canceled - The extrusion rate variance of " << extrusion_rate_variance_percent_ << "% exceeded by " << extrusion_rate_change_percent - extrusion_rate_variance_percent_ << "% on line " << lines_processed_ << ". Extruded " << extruder_current.e_relative << "mm over " << movement_length_mm << "mm of travel (" << mm_extruded_per_mm_travel << "mm/mm). Previous rate: " << previous_extrusion_rate_ << "mm/mm."; + p_logger_->log(logger_type_, log_levels::DEBUG, stream.str()); + } + else + { + // Todo: Add all the relevant values + p_logger_->log(logger_type_, log_levels::DEBUG, "There was an unknown issue preventing the current point from being added to the arc: " + cmd.gcode); + } + } + } + + // Reset the previous extrusion rate + previous_extrusion_rate_ = 0; + } + + if (!arc_added && !(cmd.is_empty && cmd.comment.length() == 0)) + { + if (current_arc_.get_num_segments() < current_arc_.get_min_segments()) { + if (debug_logging_enabled_ && !cmd.is_empty) + { + if (current_arc_.get_num_segments() != 0) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Not enough segments, resetting. Gcode:" + cmd.gcode); + } + + } + waiting_for_arc_ = false; + current_arc_.clear(); + } + else if (waiting_for_arc_) + { + + if (current_arc_.is_shape()) + { + // update our statistics + points_compressed_ += current_arc_.get_num_segments() - 1; + arcs_created_++; // increment the number of generated arcs + write_arc_gcodes(p_pre_pos->f); + // Now clear the arc and flag the processor as not waiting for an arc + waiting_for_arc_ = false; + current_arc_.clear(); + p_cur_pos = NULL; + p_pre_pos = NULL; + + // Reprocess this line + if (!is_end) + { + return process_gcode(cmd, false, true); + } + else + { + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Final arc created, exiting."); + } + return 0; + } + + } + else + { + if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "The current arc is not a valid arc, resetting."); + } + current_arc_.clear(); + waiting_for_arc_ = false; + } + } + else if (debug_logging_enabled_) + { + p_logger_->log(logger_type_, log_levels::DEBUG, "Could not add point to arc from gcode:" + cmd.gcode); + } + + } + + if (waiting_for_arc_ || !arc_added) + { + // This might not work.... + //position* cur_pos = p_source_position_->get_current_position_ptr(); + unwritten_commands_.push_back(unwritten_command(cmd, is_previous_extruder_relative, is_extrusion, is_retraction, is_travel, movement_length_mm)); + + } + else if (!waiting_for_arc_) + { + write_unwritten_gcodes_to_file(); + current_arc_.clear(); + } + return lines_written; } -void arc_welder::write_arc_gcodes(bool is_extruder_relative, double current_feedrate) +void arc_welder::write_arc_gcodes(double current_feedrate) { - std::string comment = get_comment_for_arc(); - // remove the same number of unwritten gcodes as there are arc segments, minus 1 for the start point - // Which isn't a movement - // note, skip the first point, it is the starting point - int num_segments = current_arc_.get_num_segments() - 1; - for (int index = 0; index < num_segments; index++) - { - while (!unwritten_commands_.pop_back().is_g1_g2); - } - - // Undo the current command, since it isn't included in the arc - p_source_position_->undo_update(); - - // Set the current feedrate if it is different, else set to 0 to indicate that no feedrate should be included - if (previous_feedrate_ > 0 && previous_feedrate_ == current_feedrate) { - current_feedrate = 0; - } - - // Craete the arc gcode - std::string gcode; - if (is_extruder_relative) { - gcode = get_arc_gcode_relative(current_feedrate, comment); - } - - else { - gcode = get_arc_gcode_absolute(p_source_position_->get_current_position_ptr()->get_current_extruder().get_offset_e(), current_feedrate, comment); - } - - - if (debug_logging_enabled_) - { - char buffer[20]; - std::string message = "Arc created with "; - sprintf(buffer, "%d", current_arc_.get_num_segments()); - message += buffer; - message += " segments: "; - message += gcode; - p_logger_->log(logger_type_, DEBUG, message); - } - - // Write everything that hasn't yet been written - write_unwritten_gcodes_to_file(); - - // Update the current extrusion statistics for the current arc gcode - segment_statistics_.update(current_arc_.get_shape_length() , false); - // now write the current arc to the file - write_gcode_to_file(gcode); - - + std::string comment = get_comment_for_arc(); + // remove the same number of unwritten gcodes as there are arc segments, minus 1 for the start point + // Which isn't a movement + // note, skip the first point, it is the starting point + int num_segments = current_arc_.get_num_segments() - 1; + for (int index = 0; index < num_segments; index++) + { + while (!unwritten_commands_.pop_back().is_g0_g1); + } + + // Undo the current command, since it isn't included in the arc + p_source_position_->undo_update(); + + // Set the current feedrate if it is different, else set to 0 to indicate that no feedrate should be included + if (previous_feedrate_ > 0 && previous_feedrate_ == current_feedrate) { + current_feedrate = 0; + } + + // Craete the arc gcode + std::string gcode = get_arc_gcode(comment); + + if (debug_logging_enabled_) + { + char buffer[20]; + std::string message = "Arc created with "; + sprintf(buffer, "%d", current_arc_.get_num_segments()); + message += buffer; + message += " segments: "; + message += gcode; + p_logger_->log(logger_type_, log_levels::DEBUG, message); + } + + // Write everything that hasn't yet been written + write_unwritten_gcodes_to_file(); + + // Update the current extrusion statistics for the current arc gcode + double shape_e_relative = current_arc_.get_shape_e_relative(); + bool is_retraction = shape_e_relative < 0; + bool is_extrusion = shape_e_relative > 0; + if (is_extrusion) + { + segment_statistics_.update(current_arc_.get_shape_length(), false); + + } + else if (is_retraction) + { + segment_retraction_statistics_.update(current_arc_.get_shape_length(), false); + } + else if (allow_travel_arcs_ ) { + travel_statistics_.update(current_arc_.get_shape_length(), false); + } + // now write the current arc to the file + write_gcode_to_file(gcode); } std::string arc_welder::get_comment_for_arc() { - // build a comment string from the commands making up the arc - // We need to start with the first command entered. - int comment_index = unwritten_commands_.count() - (current_arc_.get_num_segments() - 1); - std::string comment; - for (; comment_index < unwritten_commands_.count(); comment_index++) - { - std::string old_comment = unwritten_commands_[comment_index].comment; - if (old_comment != comment && old_comment.length() > 0) - { - if (comment.length() > 0) - { - comment += " - "; - } - comment += old_comment; - } - } - return comment; + // build a comment string from the commands making up the arc + // We need to start with the first command entered. + int comment_index = unwritten_commands_.count() - (current_arc_.get_num_segments() - 1); + std::string comment; + for (; comment_index < unwritten_commands_.count(); comment_index++) + { + std::string old_comment = unwritten_commands_[comment_index].comment; + if (old_comment != comment && old_comment.length() > 0) + { + if (comment.length() > 0) + { + comment += " - "; + } + comment += old_comment; + } + } + return comment; } std::string arc_welder::create_g92_e(double absolute_e) { - std::stringstream stream; - stream << std::fixed << std::setprecision(5); - stream << "G92 E" << absolute_e; - return stream.str(); + std::stringstream stream; + stream << std::fixed << std::setprecision(5); + stream << "G92 E" << absolute_e; + return stream.str(); } int arc_welder::write_gcode_to_file(std::string gcode) { - output_file_ << gcode << "\n"; - return 1; + output_file_ << gcode << "\n"; + return 1; } int arc_welder::write_unwritten_gcodes_to_file() { - int size = unwritten_commands_.count(); - std::string lines_to_write; - - for (int index = 0; index < size; index++) - { - // The the current unwritten position and remove it from the list - unwritten_command p = unwritten_commands_.pop_front(); - if (p.extrusion_length > 0) - { - segment_statistics_.update(p.extrusion_length, false); - } - lines_to_write.append(p.to_string()).append("\n"); - } - - output_file_ << lines_to_write; - return size; -} - -std::string arc_welder::get_arc_gcode_relative(double f, const std::string comment) -{ - // Write gcode to file - std::string gcode; - - gcode = current_arc_.get_shape_gcode_relative(f); - - if (comment.length() > 0) - { - gcode += ";" + comment; - } - return gcode; - + int size = unwritten_commands_.count(); + std::string lines_to_write; + + for (int index = 0; index < size; index++) + { + // The the current unwritten position and remove it from the list + unwritten_command p = unwritten_commands_.pop_front(); + if ((p.is_g0_g1 || p.is_g2_g3) && p.length > 0) + { + + if (p.is_extrusion) + { + segment_statistics_.update(p.length, false); + } + else if (p.is_retraction) + { + segment_retraction_statistics_.update(p.length, false); + } + else if (p.is_travel && allow_travel_arcs_) { + travel_statistics_.update(p.length, false); + } + } + lines_to_write.append(p.to_string()).append("\n"); + } + + output_file_ << lines_to_write; + return size; } -std::string arc_welder::get_arc_gcode_absolute(double e, double f, const std::string comment) +std::string arc_welder::get_arc_gcode(const std::string comment) { - // Write gcode to file - std::string gcode; + // Write gcode to file + std::string gcode; - gcode = current_arc_.get_shape_gcode_absolute(e, f); + gcode = current_arc_.get_shape_gcode(); - if (comment.length() > 0) - { - gcode += ";" + comment; - } - return gcode; + if (comment.length() > 0) + { + gcode += ";" + comment; + } + return gcode; } void arc_welder::add_arcwelder_comment_to_target() { - p_logger_->log(logger_type_, DEBUG, "Adding ArcWelder comment to the target file."); - std::stringstream stream; - stream << std::fixed; - stream << "; Postprocessed by [ArcWelder](https://github.com/FormerLurker/ArcWelderLib)\n"; - stream << "; Copyright(C) 2020 - Brad Hochgesang\n"; - stream << "; 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"; - if (gcode_position_args_.g90_influences_extruder) - { - stream << "; g90_influences_extruder=True\n"; - } - if (current_arc_.get_mm_per_arc_segment() > 0 && current_arc_.get_min_arc_segments() > 0) - { - stream << "; firmware_compensation=True\n"; - stream << "; mm_per_arc_segment="<< std::setprecision(2) << current_arc_.get_mm_per_arc_segment() << "mm\n"; - stream << "; min_arc_segments=" << std::setprecision(0) << current_arc_.get_min_arc_segments() << "\n"; - } - if (allow_3d_arcs_) - { - stream << "; allow_3d_arcs=True\n"; - - } - if (allow_dynamic_precision_) - { - stream << "; allow_dynamic_precision=True\n"; - } - stream << "; default_xyz_precision=" << std::setprecision(0) << static_cast<int>(current_arc_.get_xyz_precision()) << "\n"; - stream << "; default_e_precision=" << std::setprecision(0) << static_cast<int>(current_arc_.get_e_precision()) << "\n\n"; - - - output_file_ << stream.str(); + p_logger_->log(logger_type_, log_levels::DEBUG, "Adding ArcWelder comment to the target file."); + std::stringstream stream; + stream << std::fixed; + stream << "; Postprocessed by [ArcWelder](https://github.com/FormerLurker/ArcWelderLib)\n"; + stream << "; Copyright(C) 2020 - Brad Hochgesang\n"; + stream << "; Version: " << GIT_TAGGED_VERSION << ", Branch: " << GIT_BRANCH << ", BuildDate: " << BUILD_DATE << "\n"; + stream << "; resolution=" << std::setprecision(2) << resolution_mm_ << "mm\n"; + stream << "; path_tolerance=" << std::setprecision(1) << (current_arc_.get_path_tolerance_percent() * 100.0) << "%\n"; + stream << "; max_radius=" << std::setprecision(2) << (current_arc_.get_max_radius()) << "mm\n"; + if (gcode_position_args_.g90_influences_extruder) + { + stream << "; g90_influences_extruder=True\n"; + } + if (current_arc_.get_mm_per_arc_segment() > 0 && current_arc_.get_min_arc_segments() > 0) + { + stream << "; firmware_compensation=True\n"; + stream << "; mm_per_arc_segment=" << std::setprecision(2) << current_arc_.get_mm_per_arc_segment() << "mm\n"; + stream << "; min_arc_segments=" << std::setprecision(0) << current_arc_.get_min_arc_segments() << "\n"; + } + if (allow_3d_arcs_) + { + stream << "; allow_3d_arcs=True\n"; + + } + if (allow_dynamic_precision_) + { + stream << "; allow_dynamic_precision=True\n"; + } + stream << "; default_xyz_precision=" << std::setprecision(0) << static_cast<int>(current_arc_.get_xyz_precision()) << "\n"; + stream << "; default_e_precision=" << std::setprecision(0) << static_cast<int>(current_arc_.get_e_precision()) << "\n"; + stream << "; extrusion_rate_variance_percent=" << std::setprecision(1) << (extrusion_rate_variance_percent_ * 100.0) << "%\n\n"; + + + output_file_ << stream.str(); } diff --git a/ArcWelder/arc_welder.h b/ArcWelder/arc_welder.h index 6d00ec4..c7d7465 100644 --- a/ArcWelder/arc_welder.h +++ b/ArcWelder/arc_welder.h @@ -44,9 +44,6 @@ #define _CRT_SECURE_NO_WARNINGS #endif - -#define DEFAULT_G90_G91_INFLUENCES_EXTREUDER false - static const int segment_statistic_lengths_count = 12; const double segment_statistic_lengths[] = { 0.002f, 0.005f, 0.01f, 0.05f, 0.1f, 0.5f, 1.0f, 5.0f, 10.0f, 20.0f, 50.0f, 100.0f }; @@ -64,7 +61,8 @@ struct segment_statistic { }; struct source_target_segment_statistics { - source_target_segment_statistics(const double segment_tracking_lengths[], const int num_lengths, logger* p_logger = NULL) { + source_target_segment_statistics(const double segment_tracking_lengths[], const int num_lengths, logger* p_logger = NULL) + { total_length_source = 0; total_length_target = 0; total_count_source = 0; @@ -76,6 +74,7 @@ struct source_target_segment_statistics { for (int index = 0; index < num_lengths; index++) { double current_max = segment_tracking_lengths[index]; + segment_statistic_lengths.push_back(segment_tracking_lengths[index]); source_segments.push_back(segment_statistic(current_min, segment_tracking_lengths[index])); target_segments.push_back(segment_statistic(current_min, segment_tracking_lengths[index])); current_min = current_max; @@ -86,7 +85,8 @@ struct source_target_segment_statistics { p_logger_ = p_logger; logger_type_ = 0; } - + + std::vector<double> segment_statistic_lengths; std::vector<segment_statistic> source_segments; std::vector<segment_statistic> target_segments; double total_length_source; @@ -96,7 +96,7 @@ struct source_target_segment_statistics { int total_count_source; int total_count_target; int num_segment_tracking_lengths; - + double get_total_count_reduction_percent() const { return utilities::get_percent_change(total_count_source, total_count_target); } @@ -130,10 +130,49 @@ struct source_target_segment_statistics { } } + static source_target_segment_statistics add(source_target_segment_statistics stats1, const source_target_segment_statistics stats2) + { + + double * lengths = &stats1.segment_statistic_lengths[0]; + std::copy(stats1.segment_statistic_lengths.begin(), stats1.segment_statistic_lengths.end(), lengths); + source_target_segment_statistics combined_stats(lengths, segment_statistic_lengths_count, stats1.p_logger_); + if (stats1.num_segment_tracking_lengths != stats2.num_segment_tracking_lengths) + { + // Todo: throw a reasonable exception + throw std::exception(); + } + + // Copy the segment statistics + for (int index = 0; index <= stats1.num_segment_tracking_lengths; index++) + { + // Verify the stats are the same + if ( + stats1.source_segments[index].min_mm != stats2.source_segments[index].min_mm + || stats1.source_segments[index].max_mm != stats2.source_segments[index].max_mm + ) + { + // Todo: throw a reasonable exception + throw std::exception(); + } + combined_stats.source_segments[index].count = stats1.source_segments[index].count + stats2.source_segments[index].count; + combined_stats.target_segments[index].count = stats1.target_segments[index].count + stats2.target_segments[index].count; + } + + combined_stats.total_length_source = stats1.total_length_source + stats2.total_length_source; + combined_stats.total_length_target = stats1.total_length_target + stats2.total_length_target; + combined_stats.total_count_source = stats1.total_count_source + stats2.total_count_source; + combined_stats.total_count_target = stats1.total_count_target + stats2.total_count_target; + + return combined_stats; + } std::string str() const { + return str("", utilities::box_drawing::BoxEncodingEnum::ASCII); + } + + std::string str(std::string title, utilities::box_drawing::BoxEncodingEnum box_encoding) const { //if (p_logger_ != NULL) p_logger_->log(logger_type_, VERBOSE, "Building Segment Statistics."); - + std::stringstream output_stream; std::stringstream format_stream; const int min_column_size = 8; @@ -210,21 +249,51 @@ struct source_target_segment_statistics { } // Get the table width int table_width = mm_col_size + min_max_label_col_size + mm_col_size + source_col_size + target_col_size + percent_col_size; - // Add a separator for the statistics - //output_stream << std::setw(table_width) << std::setfill('-') << "-" << "\n" << std::setfill(' ') ; - // Output the column headers - // Center the min and max column. - output_stream << utilities::center("Min", mm_col_size); + int table_left_padding = 0; + int table_right_padding = 0; + if (table_width < (int)title.length()) + { + table_left_padding = ((int)title.length() - table_width) / 2; + table_right_padding = ((int)title.length() - table_width - table_left_padding); + table_width = (int)title.length(); + + } + utilities::box_drawing box(box_encoding, table_width); + // Draw the top border + box.top(output_stream); + + if (title != "") + { + // Draw the title + box.row(output_stream, utilities::center(title, table_width)); + // Draw the title separator + box.middle(output_stream); + } + + // Output the centered column headers + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); + // add the left padding for the table + output_stream << std::string(table_left_padding, ' '); + output_stream << std::setfill(' ') << utilities::center("Min", mm_col_size); output_stream << std::setw(min_max_label_col_size) << ""; output_stream << utilities::center("Max", mm_col_size); // right align the source, target and change columns output_stream << std::setw(source_col_size) << std::right << "Source"; output_stream << std::setw(target_col_size) << std::right << "Target"; output_stream << std::setw(percent_col_size) << std::right << "Change"; - output_stream << "\n"; - output_stream << std::setw(table_width) << std::setfill('-') << "" << std::setfill(' ') << "\n"; + // Add the right padding for the table + output_stream << std::string(table_right_padding, ' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; output_stream << std::fixed << std::setprecision(max_precision); + // Add the separator + box.middle(output_stream); for (int index = 0; index < source_segments.size(); index++) { + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); + // add the left padding for the table + output_stream << std::string(table_left_padding, ' '); //extract the necessary variables from the source and target segments double min_mm = source_segments[index].min_mm; double max_mm = source_segments[index].max_mm; @@ -287,24 +356,34 @@ struct source_target_segment_statistics { output_stream << std::setw(target_col_size) << target_count_string; // Add the percent change string output_stream << std::setw(percent_col_size) << percent_change_string; - // End the line - output_stream << "\n"; + // Add the right padding for the table + output_stream << std::string(table_right_padding, ' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; } - // Add the total rows separator - output_stream << std::setw(table_width) << std::setfill('-') << "" << std::setfill(' ') << "\n"; + + // Add the total rows; + // Draw the totals separator + box.middle(output_stream); if (utilities::is_equal(total_length_source, total_length_target, 0.001)) { + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); std::string total_distance_string; format_stream.str(std::string()); format_stream << std::fixed << std::setprecision(max_precision) << total_length_source << "mm"; total_distance_string = format_stream.str(); output_stream << std::setw(totals_row_label_size) << std::right << "Total distance:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_distance_string << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_distance_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; } else { - // We need to output two different distances (this probably should never happen) + // We need to output two different distances + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Format the total source distance string std::string total_source_distance_string; format_stream.str(std::string()); @@ -312,8 +391,12 @@ struct source_target_segment_statistics { total_source_distance_string = format_stream.str(); // Add the total source distance row output_stream << std::setw(totals_row_label_size) << std::right << "Total distance source:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_source_distance_string << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_source_distance_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Format the total target distance string std::string total_target_distance_string; format_stream.str(std::string()); @@ -321,32 +404,55 @@ struct source_target_segment_statistics { total_target_distance_string = format_stream.str(); // Add the total target distance row output_stream << std::setw(totals_row_label_size) << std::right << "Total distance target:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_target_distance_string << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_target_distance_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; } // Add the total count rows + + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Add the source count output_stream << std::setprecision(0) << std::setw(totals_row_label_size) << std::right << "Total count source:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_source << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_source << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Add the target count output_stream << std::setw(totals_row_label_size) << std::right << "Total count target:"; - output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_target << "\n" << std::setfill(' '); + output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_count_target << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + + // start the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL); // Add the total percent change row std::string total_percent_change_string = utilities::get_percent_change_string(total_count_source, total_count_target, 1); output_stream << std::setw(totals_row_label_size) << std::right << "Total percent change:"; output_stream << std::setw(table_width - totals_row_label_size) << std::setfill('.') << std::right << total_percent_change_string << std::setfill(' '); + // end the row + output_stream << box.get_box_replacement_element(utilities::box_drawing::BoxElementEnum::VERTICAL) << "\n"; + + // Add the final separator + box.bottom(output_stream); + std::string output_string = output_stream.str(); + box.make_replacements(output_string); return output_string; } private: + logger* p_logger_; int logger_type_; }; // Struct to hold the progress, statistics, and return values struct arc_welder_progress { - arc_welder_progress() : segment_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL) { + arc_welder_progress() : segment_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL), segment_retraction_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL), travel_statistics(segment_statistic_lengths, segment_statistic_lengths_count, NULL) { percent_complete = 0.0; seconds_elapsed = 0.0; seconds_remaining = 0.0; @@ -354,12 +460,16 @@ struct arc_welder_progress { lines_processed = 0; points_compressed = 0; arcs_created = 0; + arcs_aborted_by_flow_rate = 0; num_firmware_compensations = 0; + num_gcode_length_exceptions = 0; source_file_size = 0; source_file_position = 0; target_file_size = 0; compression_ratio = 0; compression_percent = 0; + combine_extrusion_and_retraction = true; + box_encoding = utilities::box_drawing::BoxEncodingEnum::ASCII; } double percent_complete; double seconds_elapsed; @@ -368,15 +478,39 @@ struct arc_welder_progress { int lines_processed; int points_compressed; int arcs_created; + int arcs_aborted_by_flow_rate; int num_firmware_compensations; + int num_gcode_length_exceptions; double compression_ratio; double compression_percent; long source_file_position; long source_file_size; long target_file_size; + bool combine_extrusion_and_retraction; + utilities::box_drawing::BoxEncodingEnum box_encoding; + source_target_segment_statistics segment_statistics; + source_target_segment_statistics segment_retraction_statistics; + source_target_segment_statistics travel_statistics; + + std::string simple_progress_str() const { + std::stringstream stream; + if (percent_complete == 0) { + stream << " 00.0% complete - Estimating remaining time."; + } + else if (percent_complete == 100) + { + stream << "100.0% complete - " << seconds_elapsed << " seconds total."; + } + else { + stream << " " << std::fixed << std::setprecision(1) << std::setfill('0') << std::setw(4) << percent_complete << "% complete - Estimated " << std::setprecision(0) << std::setw(-1) << seconds_remaining << " of " << seconds_elapsed + seconds_remaining << " seconds remaing."; + } + + return stream.str(); + } std::string str() const { + std::stringstream stream; stream << std::fixed << std::setprecision(2); @@ -385,20 +519,159 @@ 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 << ", num_gcode_length_exceptions: " << num_gcode_length_exceptions; stream << ", compression_ratio: " << compression_ratio; - stream << ", size_reduction: " << compression_percent << "% "; + stream << ", size_reduction: " << compression_percent << "% " ; return stream.str(); } std::string detail_str() const { - std::stringstream stream; - stream << "\n" << "Extrusion/Retraction Counts" << "\n" << segment_statistics.str() << "\n"; - return stream.str(); + std::stringstream wstream; + wstream << "\n"; + + if (travel_statistics.total_count_source > 0) + { + wstream << travel_statistics.str("Target File Travel Statistics", box_encoding) << "\n"; + } + + if (combine_extrusion_and_retraction) + { + source_target_segment_statistics combined_stats = source_target_segment_statistics::add(segment_statistics, segment_retraction_statistics); + wstream << combined_stats.str("Target File Extrusion/Retraction Statistics", box_encoding) << "\n"; + } + else + { + + if (segment_retraction_statistics.total_count_source > 0) + { + wstream << segment_retraction_statistics.str("Target File Retraction Statistics", box_encoding) << "\n"; + } + + wstream << segment_statistics.str("Target File Extrusion Statistics", box_encoding) << "\n"; + } + return wstream.str(); } + }; - // define the progress callback type typedef bool(*progress_callback)(arc_welder_progress, logger* p_logger, int logger_type); +// LOGGER_NAME +#define ARC_WELDER_LOGGER_NAME "arc_welder.gcode_conversion" +// Default argument values +#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false +#define DEFAULT_GCODE_BUFFER_SIZE 10 +#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false +#define DEFAULT_ALLOW_DYNAMIC_PRECISION false +#define DEFAULT_ALLOW_TRAVEL_ARCS true +#define DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT 0.05 +#define DEFAULT_NOTIFICATION_PERIOD_SECONDS 0.5 + +struct arc_welder_args +{ + arc_welder_args() { + set_defaults(); + }; + + + arc_welder_args(std::string source, std::string target, logger* ptr_log) + { + set_defaults(); + source_path = source; + target_path = target; + log = ptr_log; + } + std::string source_path; + std::string target_path; + logger* log; + double resolution_mm; + double path_tolerance_percent; + double max_radius_mm; + int min_arc_segments; + double mm_per_arc_segment; + bool g90_g91_influences_extruder; + bool allow_3d_arcs; + bool allow_travel_arcs; + bool allow_dynamic_precision; + unsigned char default_xyz_precision; + unsigned char default_e_precision; + double extrusion_rate_variance_percent; + int buffer_size; + int max_gcode_length; + double notification_period_seconds; + utilities::box_drawing::BoxEncodingEnum box_encoding; + + progress_callback callback; + + std::string str() const { + std::string log_level_name = "NO_LOGGING"; + if (log != NULL) + { + log_level_name = log->get_log_level_name(ARC_WELDER_LOGGER_NAME); + } + std::stringstream stream; + stream << "Arc Welder Arguments\n"; + stream << std::fixed << std::setprecision(2); + stream << "\tSource File Path : " << source_path << "\n"; + if (source_path == target_path) + { + stream << "\tTarget File Path (overwrite) : " << target_path << "\n"; + } + else + { + stream << "\tTarget File Path : " << target_path << "\n"; + } + stream << "\tResolution : " << resolution_mm << "mm (+-" << std::setprecision(5) << resolution_mm / 2.0 << "mm)\n"; + stream << "\tPath Tolerance : " << std::setprecision(3) << path_tolerance_percent * 100.0 << "%\n"; + stream << "\tMaximum Arc Radius : " << std::setprecision(0) << max_radius_mm << "mm\n"; + stream << "\tMin Arc Segments : " << std::setprecision(0) << min_arc_segments << "\n"; + stream << "\tMM Per Arc Segment : " << std::setprecision(3) << mm_per_arc_segment << "\n"; + stream << "\tAllow 3D Arcs : " << (allow_3d_arcs ? "True" : "False") << "\n"; + stream << "\tAllow Travel Arcs : " << (allow_travel_arcs ? "True" : "False") << "\n"; + stream << "\tAllow Dynamic Precision : " << (allow_dynamic_precision ? "True" : "False") << "\n"; + stream << "\tDefault XYZ Precision : " << std::setprecision(0) << static_cast<int>(default_xyz_precision) << "\n"; + stream << "\tDefault E Precision : " << std::setprecision(0) << static_cast<int>(default_e_precision) << "\n"; + stream << "\tExtrusion Rate Variance % : " << std::setprecision(3) << extrusion_rate_variance_percent * 100.0 << "%\n"; + stream << "\tG90/G91 Influences Extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n"; + if (max_gcode_length == 0) + { + stream << "\tMax Gcode Length : Unlimited\n"; + } + else { + stream << "\tMax Gcode Length : " << std::setprecision(0) << max_gcode_length << " characters\n"; + } + stream << "\tLog Level : " << log_level_name << "\n"; + stream << "\tHide Progress Updates : " << (callback == NULL ? "True" : "False") << "\n"; + stream << "\tProgress Notification Period : " << std::setprecision(2) << notification_period_seconds << " seconds"; + return stream.str(); + }; + +private: + void set_defaults() + { + source_path = "", + target_path = "", + log = NULL, + resolution_mm = DEFAULT_RESOLUTION_MM, + path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT, + max_radius_mm = DEFAULT_MAX_RADIUS_MM, + min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS, + mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT, + g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTRUDER, + allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS, + allow_travel_arcs = DEFAULT_ALLOW_TRAVEL_ARCS, + allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION, + default_xyz_precision = DEFAULT_XYZ_PRECISION, + default_e_precision = DEFAULT_E_PRECISION, + extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, + max_gcode_length = DEFAULT_MAX_GCODE_LENGTH, + buffer_size = DEFAULT_GCODE_BUFFER_SIZE, + notification_period_seconds = DEFAULT_NOTIFICATION_PERIOD_SECONDS, + callback = NULL; + box_encoding = utilities::box_drawing::BoxEncodingEnum::ASCII; + } + +}; struct arc_welder_results { arc_welder_results() : progress() @@ -412,45 +685,31 @@ struct arc_welder_results { std::string message; arc_welder_progress progress; }; -#define DEFAULT_GCODE_BUFFER_SIZE 1000 -#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false -#define DEFAULT_ALLOW_DYNAMIC_PRECISION false + class arc_welder { public: - arc_welder( - std::string source_path, - std::string target_path, - logger* log, - double resolution_mm, - double path_tolerance_percent, - double max_radius, - int min_arc_segments, - double mm_per_arc_segment, - bool g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTRUDER, - bool allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS, - bool allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION, - unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION, - unsigned char default_e_precision = DEFAULT_E_PRECISION, - int buffer_size = DEFAULT_GCODE_BUFFER_SIZE, - progress_callback callback = NULL); + + arc_welder(arc_welder_args args); + + void set_logger_type(int logger_type); virtual ~arc_welder(); arc_welder_results process(); - double notification_period_seconds; + protected: virtual bool on_progress_(const arc_welder_progress& progress); private: + arc_welder_progress get_progress_(long source_file_position, double start_clock); void add_arcwelder_comment_to_target(); void reset(); static gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size); progress_callback progress_callback_; int process_gcode(parsed_command cmd, bool is_end, bool is_reprocess); - void write_arc_gcodes(bool is_extruder_relative, double current_feedrate); + void write_arc_gcodes(double current_feedrate); int write_gcode_to_file(std::string gcode); - std::string get_arc_gcode_relative(double f, const std::string comment); - std::string get_arc_gcode_absolute(double e, double f, const std::string comment); + std::string get_arc_gcode(const std::string comment); std::string get_comment_for_arc(); int write_unwritten_gcodes_to_file(); std::string create_g92_e(double absolute_e); @@ -460,13 +719,18 @@ private: gcode_position_args gcode_position_args_; bool allow_dynamic_precision_; bool allow_3d_arcs_; + bool allow_travel_arcs_; long file_size_; int lines_processed_; int gcodes_processed_; int last_gcode_line_written_; int points_compressed_; int arcs_created_; + int arcs_aborted_by_flow_rate_; + double notification_period_seconds_; source_target_segment_statistics segment_statistics_; + source_target_segment_statistics segment_retraction_statistics_; + source_target_segment_statistics travel_statistics_; long get_file_size(const std::string& file_path); double get_time_elapsed(double start_clock, double end_clock); double get_next_update_time() const; @@ -478,6 +742,8 @@ private: // We don't care about the printer settings, except for g91 influences extruder. gcode_position* p_source_position_; double previous_feedrate_; + double previous_extrusion_rate_; + double extrusion_rate_variance_percent_; gcode_parser parser_; bool verbose_output_; int logger_type_; @@ -486,5 +752,5 @@ private: bool info_logging_enabled_; bool verbose_logging_enabled_; bool error_logging_enabled_; - + utilities::box_drawing::BoxEncodingEnum box_encoding_; }; diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp index 4da8f8e..f6cfff1 100644 --- a/ArcWelder/segmented_arc.cpp +++ b/ArcWelder/segmented_arc.cpp @@ -27,8 +27,6 @@ #include "utilities.h" #include "segmented_shape.h" #include <iostream> -//#include <iomanip> -//#include <sstream> #include <stdio.h> #include <cmath> @@ -38,7 +36,10 @@ segmented_arc::segmented_arc() : segmented_shape(DEFAULT_MIN_SEGMENTS, DEFAULT_M min_arc_segments_ = DEFAULT_MIN_ARC_SEGMENTS, mm_per_arc_segment_ = DEFAULT_MM_PER_ARC_SEGMENT; allow_3d_arcs_ = DEFAULT_ALLOW_3D_ARCS; + max_gcode_length_ = DEFAULT_MAX_GCODE_LENGTH; + num_gcode_length_exceptions_ = 0; num_firmware_compensations_ = 0; + } segmented_arc::segmented_arc( @@ -51,7 +52,8 @@ segmented_arc::segmented_arc( double mm_per_arc_segment, bool allow_3d_arcs, unsigned char default_xyz_precision, - unsigned char default_e_precision + unsigned char default_e_precision, + int max_gcode_length ) : segmented_shape(min_segments, max_segments, resolution_mm, path_tolerance_percent, default_xyz_precision, default_e_precision) { max_radius_mm_ = max_radius_mm; @@ -69,7 +71,15 @@ segmented_arc::segmented_arc( min_arc_segments_ = 0; } allow_3d_arcs_ = allow_3d_arcs; + max_gcode_length_ = max_gcode_length; + if (max_gcode_length_ < 1) + { + max_gcode_length_ = 0; + } num_firmware_compensations_ = 0; + num_gcode_length_exceptions_ = 0; + + } segmented_arc::~segmented_arc() @@ -108,7 +118,10 @@ int segmented_arc::get_num_firmware_compensations() const { return num_firmware_compensations_; } - +int segmented_arc::get_num_gcode_length_exceptions() const +{ + return num_gcode_length_exceptions_; +} double segmented_arc::get_mm_per_arc_segment() const { return mm_per_arc_segment_; @@ -127,11 +140,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]; @@ -141,6 +155,23 @@ bool segmented_arc::try_add_point(printer_point p) return false; } + // If we have more than 2 points, we need to make sure the current and previous moves are all of the same type. + if (points_.count() > 2) + { + // TODO: Do we need this? + // We already have at least an initial point and a second point. Make cure the current point and the previous are either both + // travel moves, or both extrusion + if (!( + (p1.e_relative > 0 && p.e_relative > 0) // Extrusions + || (p1.e_relative < 0 && p.e_relative < 0) // Retractions + || (p1.e_relative == 0 && p.e_relative == 0) // Travel + ) + ) + { + return false; + } + } + if (utilities::is_zero(p.distance)) { // there must be some distance between the points @@ -193,9 +224,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_; @@ -204,15 +232,22 @@ bool segmented_arc::try_add_point_internal_(printer_point p) if (arc::try_create_arc(points_, current_arc_, original_shape_length_, max_radius_mm_, resolution_mm_, path_tolerance_percent_, min_arc_segments_, mm_per_arc_segment_, get_xyz_tolerance(), allow_3d_arcs_)) { bool abort_arc = false; + if (max_gcode_length_ > 0 && get_shape_gcode_length() > max_gcode_length_) + { + abort_arc = true; + num_gcode_length_exceptions_++; + } if (min_arc_segments_ > 0 && mm_per_arc_segment_ > 0) { // Apply firmware compensation // See how many arcs will be interpolated double circumference = 2.0 * PI_DOUBLE * current_arc_.radius; - int num_segments = (int)std::floor(circumference / min_arc_segments_); + // TODO: Should this be ceil? + int num_segments = (int)utilities::floor(circumference / min_arc_segments_); if (num_segments < min_arc_segments_) { //num_segments = (int)std::ceil(circumference/approximate_length) * (int)std::ceil(approximate_length / mm_per_arc_segment); - num_segments = (int)std::floor(circumference / original_shape_length_); + // TODO: Should this be ceil? + num_segments = (int)utilities::floor(circumference / original_shape_length_); if (num_segments < min_arc_segments_) { abort_arc = true; num_firmware_compensations_++; @@ -239,7 +274,7 @@ bool segmented_arc::try_add_point_internal_(printer_point p) // or because both I and J == 0 current_arc_ = original_arc; } - else if (!abort_arc) + else { if (!is_shape()) { @@ -254,22 +289,12 @@ bool segmented_arc::try_add_point_internal_(printer_point p) return false; } -std::string segmented_arc::get_shape_gcode_absolute(double e, double f) -{ - bool has_e = e_relative_ != 0; - return get_shape_gcode_(has_e, e, f); -} - -std::string segmented_arc::get_shape_gcode_relative(double f) -{ - bool has_e = e_relative_ != 0; - return get_shape_gcode_(has_e, e_relative_, f); -} - -std::string segmented_arc::get_shape_gcode_(bool has_e, double e, double f) const +std::string segmented_arc::get_shape_gcode() const { std::string gcode; - // Calculate gcode size + double e = current_arc_.end_point.is_extruder_relative ? e_relative_ : current_arc_.end_point.e_offset; + double f = current_arc_.start_point.f == current_arc_.end_point.f ? 0 : current_arc_.end_point.f; + bool has_e = e_relative_ != 0; bool has_f = utilities::greater_than_or_equal(f, 1); bool has_z = allow_3d_arcs_ && !utilities::is_equal( current_arc_.start_point.z, current_arc_.end_point.z, get_xyz_tolerance() @@ -285,6 +310,9 @@ std::string segmented_arc::get_shape_gcode_(bool has_e, double e, double f) cons gcode += "G3"; } + // TODO: Limit Gcode Precision based on max_gcode_length + + // Add X, Y, I and J gcode += " X"; gcode += utilities::dtos(current_arc_.end_point.x, get_xyz_precision()); @@ -327,6 +355,62 @@ std::string segmented_arc::get_shape_gcode_(bool has_e, double e, double f) cons } +int segmented_arc::get_shape_gcode_length() +{ + double e = current_arc_.end_point.is_extruder_relative ? e_relative_ : current_arc_.end_point.e_offset; + double f = current_arc_.start_point.f == current_arc_.end_point.f ? 0 : current_arc_.end_point.f; + bool has_e = e_relative_ != 0; + bool has_f = utilities::greater_than_or_equal(f, 1); + bool has_z = allow_3d_arcs_ && !utilities::is_equal( + current_arc_.start_point.z, current_arc_.end_point.z, get_xyz_tolerance() + ); + + int xyz_precision = get_xyz_precision(); + int e_precision = get_e_precision(); + + double i = current_arc_.get_i(); + double j = current_arc_.get_j(); + + + int num_spaces = 4 + (has_z ? 1 : 0) + (has_e ? 1 : 0) + (has_f ? 1 : 0); + int num_decimal_points = 4 + (has_z ? 1 : 0) + (has_e ? 1 : 0); // note f has no decimal point + int num_decimals = xyz_precision * (4 + (has_z ? 1 : 0)) + e_precision * (has_e ? 1 : 0); // Note f is an int + int num_digits = ( + utilities::get_num_digits(current_arc_.end_point.x, xyz_precision) + + utilities::get_num_digits(current_arc_.end_point.y, xyz_precision) + + (has_z ? utilities::get_num_digits(current_arc_.end_point.z, xyz_precision) : 0) + + (has_e ? utilities::get_num_digits(e, e_precision) : 0) + + utilities::get_num_digits(i, xyz_precision) + + utilities::get_num_digits(j, xyz_precision) + + (has_f ? utilities::get_num_digits(f,0) : 0) + ); + int num_minus_signs = ( + (current_arc_.end_point.x < 0 ? 1 : 0) + + (current_arc_.end_point.y < 0 ? 1 : 0) + + (i < 0 ? 1 : 0) + + (j < 0 ? 1 : 0) + + (has_e && e < 0 ? 1 : 0) + + (has_z && current_arc_.end_point.z < 0 ? 1 : 0) + ); + + int num_parameters = 4 + (has_e ? 1 : 0) + (has_z ? 1: 0) + (has_f ? 1: 0); + // Return the length of the gcode. + int gcode_length = 2 + num_spaces + num_decimal_points + num_digits + num_minus_signs + num_decimals + num_parameters; + + // Keep this around in case we have any future issues with the gcode length calculation + #ifdef Debug + std::string gcode = get_shape_gcode(); + if (gcode.length() != gcode_length) + { + return 9999999; + } + #endif + return gcode_length; + + + +} + /* * This is an older implementation using ostringstream. It is substantially slower. * Keep this around in case there are problems with the custom dtos function diff --git a/ArcWelder/segmented_arc.h b/ArcWelder/segmented_arc.h index c67c071..2c7b240 100644 --- a/ArcWelder/segmented_arc.h +++ b/ArcWelder/segmented_arc.h @@ -42,14 +42,14 @@ public: double mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT, bool allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS, unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION, - unsigned char default_e_precision = DEFAULT_E_PRECISION + unsigned char default_e_precision = DEFAULT_E_PRECISION, + int max_gcode_length = DEFAULT_MAX_GCODE_LENGTH ); virtual ~segmented_arc(); virtual bool try_add_point(printer_point p); virtual double get_shape_length(); - std::string get_shape_gcode_absolute(double e, double f); - std::string get_shape_gcode_relative(double f); - + std::string get_shape_gcode() const; + int get_shape_gcode_length(); virtual bool is_shape() const; printer_point pop_front(double e_relative); printer_point pop_back(double e_relative); @@ -57,15 +57,16 @@ public: int get_min_arc_segments() const; double get_mm_per_arc_segment() const; int get_num_firmware_compensations() const; - + int get_num_gcode_length_exceptions() const; private: bool try_add_point_internal_(printer_point p); - std::string get_shape_gcode_(bool has_e, double e, double f) const; arc current_arc_; double max_radius_mm_; int min_arc_segments_; double mm_per_arc_segment_; int num_firmware_compensations_; bool allow_3d_arcs_; + int max_gcode_length_; + int num_gcode_length_exceptions_; }; diff --git a/ArcWelder/segmented_shape.cpp b/ArcWelder/segmented_shape.cpp index 42aadcb..d50e5b5 100644 --- a/ArcWelder/segmented_shape.cpp +++ b/ArcWelder/segmented_shape.cpp @@ -83,7 +83,12 @@ point point::get_midpoint(point p1, point p2) bool point::is_near_collinear(const point& p1, const point& p2, const point& p3, double tolerance) { - return fabs((p1.y - p2.y) * (p1.x - p3.x) - (p1.y - p3.y) * (p1.x - p2.x)) <= 1e-9; + return utilities::abs((p1.y - p2.y) * (p1.x - p3.x) - (p1.y - p3.y) * (p1.x - p2.x)) <= 1e-9; +} + +double point::cartesian_distance(const point& p1, const point& p2) +{ + return utilities::get_cartesian_distance(p1.x, p1.y, p2.x, p2.y); } #pragma endregion Point Functions @@ -119,7 +124,7 @@ bool segment::get_closest_perpendicular_point(const point& p1, const point& p2, #pragma region Vector Functions double vector::get_magnitude() { - return sqrt(x * x + y * y + z * z); + return utilities::sqrt(x * x + y * y + z * z); } double vector::cross_product_magnitude(vector v1, vector v2) @@ -139,24 +144,21 @@ double vector::cross_product_magnitude(vector v1, vector v2) // Users of this code must verify correctness for their application. // dot product (3D) which allows vector operations in arguments #define dot(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) -#define dotxy(u,v) ((u).x * (v).x + (u).y * (v).y) -#define norm(v) sqrt(dot(v,v)) // norm = length of vector -#define d(u,v) norm(u-v) // distance = norm of difference +//#define dotxy(u,v) ((u).x * (v).x + (u).y * (v).y) +//#define norm(v) utilities::sqrt(dot(v,v)) // norm = length of vector +//#define d(u,v) norm(u-v) // distance = norm of difference #pragma endregion Distance Calculation Source #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<printer_point>& 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<printer_point>& 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,11 +254,12 @@ bool circle::try_create_circle(const array_list<printer_point>& points, const do } } return found_circle; + } double circle::get_polar_radians(const point& p1) const { - double polar_radians = atan2(p1.y - center.y, p1.x - center.x); + double polar_radians = utilities::atan2(p1.y - center.y, p1.x - center.x); if (polar_radians < 0) polar_radians = (2.0 * PI_DOUBLE) + polar_radians; return polar_radians; @@ -269,7 +290,7 @@ bool circle::get_deviation_sum_squared(const array_list<printer_point>& points, return false; } } - double deviation = std::fabs(distance_from_center - radius); + double deviation = utilities::abs(distance_from_center - radius); total_deviation += deviation * deviation; if (deviation > resolution_mm) { @@ -284,7 +305,7 @@ bool circle::get_deviation_sum_squared(const array_list<printer_point>& points, if (segment::get_closest_perpendicular_point(points[index], points[index + 1], center, point_to_test)) { double distance = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, center.x, center.y); - double deviation = std::fabs(distance - radius); + double deviation = utilities::abs(distance - radius); total_deviation += deviation * deviation; if (deviation > resolution_mm) { @@ -325,19 +346,20 @@ bool circle::is_over_deviation(const array_list<printer_point>& points, const do return true; } } - if (std::fabs(distance_from_center - radius) > resolution_mm) + if (utilities::abs(distance_from_center - radius) > resolution_mm) { return true; } } // 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); - if (std::fabs(distance - radius) > resolution_mm) + if (utilities::abs(distance - radius) > resolution_mm) { - return true; + return true; } } } @@ -358,9 +380,9 @@ double arc::get_j() const bool arc::try_create_arc( const circle& c, - const point& start_point, - const point& mid_point, - const point& end_point, + const printer_point& start_point, + const printer_point& mid_point, + const printer_point& end_point, arc& target_arc, double approximate_length, double resolution, @@ -425,8 +447,8 @@ bool arc::try_create_arc( } } // Calculate the percent difference of the original path - double difference = (arc_length - approximate_length) / approximate_length; - if (!utilities::is_zero(difference, path_tolerance_percent)) + double path_difference_percent = utilities::get_percent_change(arc_length, approximate_length); + if (!utilities::is_zero(path_difference_percent, path_tolerance_percent)) { // So it's possible our vector calculation above got the direction wrong. // This can happen if there is a crazy arrangement of points @@ -435,7 +457,7 @@ bool arc::try_create_arc( // see if an arc moving in the opposite direction had the correct length. // Find the rest of the angle across the circle - double test_radians = std::fabs(angle_radians - 2 * PI_DOUBLE); + double test_radians = utilities::abs(angle_radians - 2 * PI_DOUBLE); // Calculate the length of that arc double test_arc_length = c.radius * test_radians; if (allow_3d_arcs) @@ -446,8 +468,8 @@ bool arc::try_create_arc( test_arc_length = utilities::hypot(test_arc_length, end_point.z - start_point.z); } } - difference = (test_arc_length - approximate_length) / approximate_length; - if (!utilities::is_zero(difference, path_tolerance_percent)) + path_difference_percent = utilities::get_percent_change(test_arc_length,approximate_length); + if (!utilities::is_zero(path_difference_percent, path_tolerance_percent)) { return false; } @@ -499,7 +521,6 @@ bool arc::try_create_arc( { circle test_circle = (circle)target_arc; - if (!circle::try_create_circle(points, max_radius_mm, resolution_mm, xyz_tolerance, allow_3d_arcs, test_circle)) { return false; @@ -645,7 +666,7 @@ bool arc::ray_intersects_segment(const point rayOrigin, const point rayDirection vector v3 = vector(-rayDirection.y, rayDirection.x, 0); double dot = dot(v2, v3); - if (std::fabs(dot) < 0.000001) + if (utilities::abs(dot) < 0.000001) return false; double t1 = vector::cross_product_magnitude(v2, v1) / dot; @@ -705,7 +726,7 @@ void segmented_shape::set_xyz_precision(unsigned char precision) void segmented_shape::set_xyz_tolerance_from_precision() { - xyz_tolerance_ = std::pow(10.0, -1.0 * static_cast<double>(xyz_precision_)); + xyz_tolerance_ = utilities::pow(10, -1.0 * static_cast<double>(xyz_precision_)); } void segmented_shape::reset_precision() diff --git a/ArcWelder/segmented_shape.h b/ArcWelder/segmented_shape.h index 8b014bc..d27a568 100644 --- a/ArcWelder/segmented_shape.h +++ b/ArcWelder/segmented_shape.h @@ -25,7 +25,6 @@ #pragma once #include <string> #include <limits> -#define PI_DOUBLE 3.14159265358979323846264338327950288 #include <list> #include "utilities.h" @@ -36,7 +35,7 @@ #define DEFAULT_XYZ_TOLERANCE 0.001 #define DEFAULT_E_PRECISION 5 #define ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT 0.05 // one percent - +#define DEFAULT_MAX_GCODE_LENGTH 0 // the maximum gcode length ( < 1 = unlimited) struct point { public: @@ -47,14 +46,19 @@ 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 { public: - printer_point() :point(0, 0, 0), e_relative(0), distance(0) {} - printer_point(double x, double y, double z, double e_relative, double distance) :point(x,y,z), e_relative(e_relative), distance(distance) {} + printer_point() :point(0, 0, 0), e_relative(0), distance(0), is_extruder_relative(false), e_offset(0), f(0) {} + printer_point(double x, double y, double z, double e_offset, double e_relative, double f, double distance, bool is_extruder_relative) + : point(x,y,z), e_offset(e_offset), e_relative(e_relative), f(f), distance(distance), is_extruder_relative(is_extruder_relative) {} + bool is_extruder_relative; + double e_offset; double e_relative; + double f; double distance; }; @@ -99,7 +103,7 @@ struct vector : point }; -#define DEFAULT_MAX_RADIUS_MM 1000000.0 // 1km +#define DEFAULT_MAX_RADIUS_MM 9999.0 // 9.999m struct circle { circle() { center.x = 0; @@ -163,8 +167,8 @@ struct arc : circle double polar_start_theta; double polar_end_theta; double max_deviation; - point start_point; - point end_point; + printer_point start_point; + printer_point end_point; DirectionEnum direction; double get_i() const; double get_j() const; @@ -184,9 +188,9 @@ struct arc : circle private: static bool try_create_arc( const circle& c, - const point& start_point, - const point& mid_point, - const point& end_point, + const printer_point& start_point, + const printer_point& mid_point, + const printer_point& end_point, arc& target_arc, double approximate_length, double resolution = DEFAULT_RESOLUTION_MM, diff --git a/ArcWelder/unwritten_command.h b/ArcWelder/unwritten_command.h index 8f85a50..bfca84f 100644 --- a/ArcWelder/unwritten_command.h +++ b/ArcWelder/unwritten_command.h @@ -29,34 +29,27 @@ struct unwritten_command { unwritten_command() { is_extruder_relative = false; - e_relative = 0; - offset_e = 0; - extrusion_length = 0; - is_g1_g2 = false; + length = 0; + is_g0_g1 = false; + is_g2_g3 = false; + is_travel = false; + is_extrusion = false; + is_retraction = false; + gcode = ""; + comment = ""; } - unwritten_command(parsed_command &cmd, bool is_relative, double command_length) { - is_extruder_relative = is_relative; - is_g1_g2 = cmd.command == "G0" || cmd.command == "G1"; - gcode = cmd.gcode; - comment = cmd.comment; - extrusion_length = command_length; + unwritten_command(parsed_command &cmd, bool is_relative, bool is_extrusion, bool is_retraction, bool is_travel, double command_length) + : is_extruder_relative(is_relative), is_extrusion(is_extrusion), is_retraction(is_retraction), is_travel(is_travel), is_g0_g1(cmd.command == "G0" || cmd.command == "G1"), is_g2_g3(cmd.command == "G2" || cmd.command == "G3"), gcode(cmd.gcode), comment(cmd.comment), length(command_length) + { + } - /* - unwritten_command(position* p, double command_length) { - - e_relative = p->get_current_extruder().e_relative; - offset_e = p->get_current_extruder().get_offset_e(); - is_extruder_relative = p->is_extruder_relative; - is_g1_g2 = p->command.command == "G0" || p->command.command == "G1"; - gcode = p->command.gcode; - comment = p->command.comment; - extrusion_length = command_length; - } */ - bool is_g1_g2; + bool is_g0_g1; + bool is_g2_g3; bool is_extruder_relative; - double e_relative; - double offset_e; - double extrusion_length; + bool is_travel; + bool is_extrusion; + bool is_retraction; + double length; std::string gcode; std::string comment; |