Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/FormerLurker/ArcWelderLib.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFormerLurker <hochgebe@gmail.com>2021-11-21 22:40:04 +0300
committerFormerLurker <hochgebe@gmail.com>2021-11-21 22:40:04 +0300
commit33946985e505a5d72d47bcfbd573f0b4587c09f8 (patch)
treefebb4f56214b8a1d64ed78966a6bd6750e88e185
parentfe18f18b4c005a2c4e4b69a5866d0d065bde82aa (diff)
parent3bd3d54894cd49fb564396fd043c4b0c80f4b116 (diff)
Merge devel into master
-rw-r--r--.github/workflows/ccpp.yml36
-rw-r--r--ArcWelder/CMakeLists.txt2
-rw-r--r--ArcWelder/arc_welder.cpp1606
-rw-r--r--ArcWelder/arc_welder.h374
-rw-r--r--ArcWelder/segmented_arc.cpp138
-rw-r--r--ArcWelder/segmented_arc.h13
-rw-r--r--ArcWelder/segmented_shape.cpp83
-rw-r--r--ArcWelder/segmented_shape.h24
-rw-r--r--ArcWelder/unwritten_command.h43
-rw-r--r--ArcWelderConsole/ArcWelderConsole.cpp302
-rw-r--r--ArcWelderConsole/ArcWelderConsole.h6
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp431
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.h5
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj25
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters46
-rw-r--r--ArcWelderInverseProcessor/CMakeLists.txt13
-rw-r--r--ArcWelderInverseProcessor/Makefile6
-rw-r--r--ArcWelderInverseProcessor/arc_interpolation.cpp246
-rw-r--r--ArcWelderInverseProcessor/arc_interpolation.h56
-rw-r--r--ArcWelderInverseProcessor/arc_interpolation_structs.h1
-rw-r--r--ArcWelderInverseProcessor/firmware.cpp153
-rw-r--r--ArcWelderInverseProcessor/firmware.h372
-rw-r--r--ArcWelderInverseProcessor/firmware_types.h12
-rw-r--r--ArcWelderInverseProcessor/inverse_processor.cpp445
-rw-r--r--ArcWelderInverseProcessor/inverse_processor.h94
-rw-r--r--ArcWelderInverseProcessor/marlin_1.cpp325
-rw-r--r--ArcWelderInverseProcessor/marlin_1.h95
-rw-r--r--ArcWelderInverseProcessor/marlin_2.cpp606
-rw-r--r--ArcWelderInverseProcessor/marlin_2.h93
-rw-r--r--ArcWelderInverseProcessor/prusa.cpp466
-rw-r--r--ArcWelderInverseProcessor/prusa.h83
-rw-r--r--ArcWelderInverseProcessor/repetier.cpp468
-rw-r--r--ArcWelderInverseProcessor/repetier.h38
-rw-r--r--ArcWelderInverseProcessor/smoothieware.cpp274
-rw-r--r--ArcWelderInverseProcessor/smoothieware.h64
-rw-r--r--ArcWelderInverseProcessor/sourcelist.cmake18
-rw-r--r--ArcWelderTest/ArcWelderTest.cpp76
-rw-r--r--ArcWelderTest/ArcWelderTest.h9
-rw-r--r--GcodeProcessorLib/GcodeProcessorLib.vcxproj2
-rw-r--r--GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters12
-rw-r--r--GcodeProcessorLib/array_list.cpp22
-rw-r--r--GcodeProcessorLib/array_list.h22
-rw-r--r--GcodeProcessorLib/circular_buffer.cpp22
-rw-r--r--GcodeProcessorLib/circular_buffer.h108
-rw-r--r--GcodeProcessorLib/fpconv.cpp4
-rw-r--r--GcodeProcessorLib/fpconv.h2
-rw-r--r--GcodeProcessorLib/gcode_position.cpp109
-rw-r--r--GcodeProcessorLib/gcode_position.h8
-rw-r--r--GcodeProcessorLib/logger.cpp40
-rw-r--r--GcodeProcessorLib/logger.h19
-rw-r--r--GcodeProcessorLib/position.cpp4
-rw-r--r--GcodeProcessorLib/position.h1
-rw-r--r--GcodeProcessorLib/sourcelist.cmake2
-rw-r--r--GcodeProcessorLib/utilities.cpp459
-rw-r--r--GcodeProcessorLib/utilities.h249
-rw-r--r--Makefile17
-rw-r--r--PyArcWelder/py_arc_welder.cpp516
-rw-r--r--PyArcWelder/py_arc_welder.h60
-rw-r--r--PyArcWelder/py_arc_welder_extension.cpp248
-rw-r--r--PyArcWelder/py_arc_welder_extension.h64
-rw-r--r--PyArcWelder/py_logger.cpp20
-rw-r--r--readme.md13
62 files changed, 6719 insertions, 2451 deletions
diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml
index 368c8b8..1087cf1 100644
--- a/.github/workflows/ccpp.yml
+++ b/.github/workflows/ccpp.yml
@@ -18,30 +18,23 @@ jobs:
config:
- {
name: "Windows Latest MSVC",
- artifact_tar: "Windows-MSVC.tar.xz",
- artifact_zip: "Windows-MSVC.zip",
+ tar_artifact: "Windows-MSVC.tar.xz",
+ zip_artifact: "Windows-MSVC.zip",
os: windows-latest,
cc: "cl", cxx: "cl",
environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat"
}
- #- {
- # name: "Windows Latest MinGW",
- # artifact_tar: "Windows-MinGW.tar.xz",
- # artifact_zip: "Windows-MinGW.zip",
- # os: windows-latest,
- # cc: "gcc", cxx: "g++"
- # }
- {
name: "Ubuntu Latest GCC",
- artifact_tar: "Linux.tar.xz",
- artifact_zip: "Linux.zip",
+ tar_artifact: "Linux.tar.xz",
+ zip_artifact: "Linux.zip",
os: ubuntu-latest,
cc: "gcc", cxx: "g++"
}
- {
name: "macOS Latest GCC",
- artifact_tar: "macOS.tar.xz",
- artifact_zip: "macOS.zip",
+ tar_artifact: "macOS.tar.xz",
+ zip_artifact: "macOS.zip",
os: macos-latest,
cc: "gcc", cxx: "g++"
}
@@ -220,23 +213,22 @@ jobs:
run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --install build --prefix instdir --strip
- - name: Pack tar
- working-directory: instdir
- run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv ../${{ matrix.config.artifact_tar }} .
-
- name: Pack zip
working-directory: instdir
- run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.artifact_zip }}" --format=zip .
+ run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar "cvf" "../${{ matrix.config.zip_artifact }}" --format=zip .
+ - name: Pack tar
+ working-directory: instdir
+ run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv "../${{ matrix.config.tar_artifact }}" .
- name: Upload tar
uses: actions/upload-artifact@v1
with:
- path: ./${{ matrix.config.artifact_tar }}
- name: ${{ matrix.config.artifact_tar }}
+ path: ./${{ matrix.config.tar_artifact }}
+ name: ${{ matrix.config.tar_artifact }}
- name: Upload zip
uses: actions/upload-artifact@v1
with:
- path: ./${{ matrix.config.artifact_zip }}
- name: ${{ matrix.config.artifact_zip }}
+ path: ./${{ matrix.config.zip_artifact }}
+ name: ${{ matrix.config.zip_artifact }}
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;
diff --git a/ArcWelderConsole/ArcWelderConsole.cpp b/ArcWelderConsole/ArcWelderConsole.cpp
index 0ebe847..ad63b37 100644
--- a/ArcWelderConsole/ArcWelderConsole.cpp
+++ b/ArcWelderConsole/ArcWelderConsole.cpp
@@ -34,25 +34,19 @@
#include <tclap/CmdLine.h>
#define DEFAULT_ARG_DOUBLE_PRECISION 4
+#define PROGRESS_TYPE_NONE "NONE"
+#define PROGRESS_TYPE_SIMPLE "SIMPLE"
+#define PROGRESS_TYPE_FULL "FULL"
int main(int argc, char* argv[])
{
- std::string source_file_path;
- std::string target_file_path;
- double resolution_mm;
- double max_radius_mm;
- int min_arc_segments;
- double mm_per_arc_segment;
- double path_tolerance_percent;
- bool g90_g91_influences_extruder;
- bool hide_progress;
- bool overwrite_source_file = false;
- bool allow_3d_arcs = false;
- bool allow_dynamic_precision = DEFAULT_ALLOW_DYNAMIC_PRECISION;
- unsigned char default_xyz_precision = DEFAULT_XYZ_PRECISION;
- unsigned char default_e_precision = DEFAULT_E_PRECISION;
+
+ arc_welder_args args;
std::string log_level_string;
std::string log_level_string_default = "INFO";
+ std::string progress_type;
+ std::string progress_type_default_string = PROGRESS_TYPE_SIMPLE;
int log_level_value;
+ bool hide_progress = false;
// Add info about the application
std::string info = "Arc Welder: Anti-Stutter - Reduces the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3).";
@@ -87,8 +81,8 @@ int main(int argc, char* argv[])
// -t --path-tolerance-percent
arg_description_stream.clear();
arg_description_stream.str("");
- arg_description_stream << "This is the maximum allowable difference between the arc path and the original toolpath. Since most slicers use interpolation when generating arc moves, this value can be relatively high without impacting print quality.";
- arg_description_stream << " Expressed as a decimal percent, where 0.05 = 5.0%. Default Value: " << ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT;
+ arg_description_stream << "This is the maximum allowable difference between the arc path and the original toolpath.";
+ arg_description_stream << " Expressed as a decimal percent, where 0.05 = 5.0%. The lower this value is, the more arcs will be aborted, but values over 0.25 (25%) are not recommended, as they could negatively impact print quality. Default Value: " << ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT << " (" << ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT * 100 << "%)";
TCLAP::ValueArg<double> path_tolerance_percent_arg("t", "path-tolerance-percent", arg_description_stream.str(), false, DEFAULT_RESOLUTION_MM, "float");
// -m --max-radius-mm
@@ -97,7 +91,6 @@ int main(int argc, char* argv[])
arg_description_stream << "The maximum radius of any arc in mm. Default Value: " << DEFAULT_MAX_RADIUS_MM;
TCLAP::ValueArg<double> max_radius_arg("m", "max-radius-mm", arg_description_stream.str(), false, DEFAULT_MAX_RADIUS_MM, "float");
-
// -s --mm-per-arc-segment
arg_description_stream.clear();
arg_description_stream.str("");
@@ -122,6 +115,12 @@ int main(int argc, char* argv[])
arg_description_stream << "(experimental) - If supplied, 3D arcs will be allowed (supports spiral vase mode). Not all firmware supports this. Default Value: " << DEFAULT_ALLOW_3D_ARCS;
TCLAP::SwitchArg allow_3d_arcs_arg("z", "allow-3d-arcs", arg_description_stream.str(), DEFAULT_ALLOW_3D_ARCS);
+ // -y --allow-travel-arcs
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "(experimental) - If supplied, travel arcs will be allowed. Default Value: " << DEFAULT_ALLOW_TRAVEL_ARCS;
+ TCLAP::SwitchArg allow_travel_arcs_arg("y", "allow-travel-arcs", arg_description_stream.str(), DEFAULT_ALLOW_TRAVEL_ARCS);
+
// -d --allow-dynamic-precision
arg_description_stream.clear();
arg_description_stream.str("");
@@ -132,16 +131,37 @@ int main(int argc, char* argv[])
arg_description_stream.clear();
arg_description_stream.str("");
arg_description_stream << "The default precision of X, Y, Z, I and J output gcode parameters. The precision may be larger than this value if allow-dynamic-precision is set to true. Default Value: " << DEFAULT_XYZ_PRECISION;
- TCLAP::ValueArg<unsigned char> default_xyz_precision_arg("x", "default-xyz-precision", arg_description_stream.str(), false, DEFAULT_XYZ_PRECISION, "unsigned char");
+ TCLAP::ValueArg<unsigned int> default_xyz_precision_arg("x", "default-xyz-precision", arg_description_stream.str(), false, DEFAULT_XYZ_PRECISION, "unsigned int");
// -e --default-e-precision
arg_description_stream.clear();
arg_description_stream.str("");
arg_description_stream << "The default precision of E output gcode parameters. The precision may be larger than this value if allow-dynamic-precision is set to true. Default Value: " << DEFAULT_E_PRECISION;
- TCLAP::ValueArg<unsigned char> default_e_precision_arg("e", "default-e-precision", arg_description_stream.str(), false, DEFAULT_E_PRECISION, "unsigned char");
+ TCLAP::ValueArg<unsigned int> default_e_precision_arg("e", "default-e-precision", arg_description_stream.str(), false, DEFAULT_E_PRECISION, "unsigned int");
- // -g --hide-progress
- TCLAP::SwitchArg hide_progress_arg("p", "hide-progress", "If supplied, prevents progress updates from being displayed.", false);
+ // -v --extrusion-rate-variance
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "(experimental) - The allowed variance in extrusion rate by percent, where 0.05 = 5.0%. A value of 0 will disable this feature. Default Value: " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT << " (" << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT * 100 << "%)";
+ TCLAP::ValueArg<double> extrusion_rate_variance_percent_arg("v", "extrusion-rate-variance-percent", arg_description_stream.str(), false, DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT, "double");
+
+ // -c --max-gcode-length
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "The maximum length allowed for a generated G2/G3 command, not including any comments. 0 = no limit. Default Value: " << DEFAULT_MAX_GCODE_LENGTH;
+ TCLAP::ValueArg<int> max_gcode_length_arg("c", "max-gcode-length", arg_description_stream.str(), false, DEFAULT_MAX_GCODE_LENGTH, "int");
+
+ // -p --progress-type
+ std::vector<std::string> progress_type_vector;
+ std::string progress_type_default_string = PROGRESS_TYPE_SIMPLE;
+ progress_type_vector.push_back(PROGRESS_TYPE_NONE);
+ progress_type_vector.push_back(PROGRESS_TYPE_SIMPLE);
+ progress_type_vector.push_back(PROGRESS_TYPE_FULL);
+ TCLAP::ValuesConstraint<std::string> progress_type_constraint(progress_type_vector);
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "Sets the progress type display. Default Value " << progress_type_default_string;
+ TCLAP::ValueArg<std::string> progress_type_arg("p", "progress-type", arg_description_stream.str(), false, progress_type_default_string, &progress_type_constraint);
// -l --log-level
std::vector<std::string> log_levels_vector;
@@ -159,6 +179,7 @@ int main(int argc, char* argv[])
arg_description_stream << "Sets console log level. Default Value: " << log_level_string_default;
TCLAP::ValueArg<std::string> log_level_arg("l", "log-level", arg_description_stream.str(), false, log_level_string_default, &log_levels_constraint);
+
// Add all arguments
cmd.add(source_arg);
cmd.add(target_arg);
@@ -168,111 +189,134 @@ int main(int argc, char* argv[])
cmd.add(min_arc_segments_arg);
cmd.add(mm_per_arc_segment_arg);
cmd.add(allow_3d_arcs_arg);
+ cmd.add(allow_travel_arcs_arg);
cmd.add(allow_dynamic_precision_arg);
cmd.add(default_xyz_precision_arg);
cmd.add(default_e_precision_arg);
+ cmd.add(extrusion_rate_variance_percent_arg);
+ cmd.add(max_gcode_length_arg);
cmd.add(g90_arg);
- cmd.add(hide_progress_arg);
+ cmd.add(progress_type_arg);
cmd.add(log_level_arg);
// Parse the argv array.
cmd.parse(argc, argv);
// Get the value parsed by each arg.
- source_file_path = source_arg.getValue();
- target_file_path = target_arg.getValue();
+ args.source_path = source_arg.getValue();
+ args.target_path = target_arg.getValue();
- if (target_file_path.size() == 0)
+ if (args.target_path.size() == 0)
{
- target_file_path = source_file_path;
+ args.target_path = args.source_path;
}
- resolution_mm = resolution_arg.getValue();
- max_radius_mm = max_radius_arg.getValue();
- min_arc_segments = min_arc_segments_arg.getValue();
- mm_per_arc_segment = mm_per_arc_segment_arg.getValue();
- path_tolerance_percent = path_tolerance_percent_arg.getValue();
- allow_3d_arcs = allow_3d_arcs_arg.getValue();
- g90_g91_influences_extruder = g90_arg.getValue();
- allow_dynamic_precision = allow_dynamic_precision_arg.getValue();
- default_xyz_precision = default_xyz_precision_arg.getValue();
- default_e_precision = default_e_precision_arg.getValue();
-
- hide_progress = hide_progress_arg.getValue();
+ args.resolution_mm = resolution_arg.getValue();
+ args.max_radius_mm = max_radius_arg.getValue();
+ args.min_arc_segments = min_arc_segments_arg.getValue();
+ args.mm_per_arc_segment = mm_per_arc_segment_arg.getValue();
+ args.path_tolerance_percent = path_tolerance_percent_arg.getValue();
+ args.allow_3d_arcs = allow_3d_arcs_arg.getValue();
+ args.allow_travel_arcs = allow_travel_arcs_arg.getValue();
+ args.g90_g91_influences_extruder = g90_arg.getValue();
+ args.allow_dynamic_precision = allow_dynamic_precision_arg.getValue();
+ unsigned int xyz_precision = default_xyz_precision_arg.getValue();
+ unsigned int e_precision = default_e_precision_arg.getValue();
+ args.extrusion_rate_variance_percent = extrusion_rate_variance_percent_arg.getValue();
+ args.max_gcode_length = max_gcode_length_arg.getValue();
+ progress_type = progress_type_arg.getValue();
log_level_string = log_level_arg.getValue();
log_level_value = -1;
// Check the entered values
bool has_error = false;
- if (resolution_mm <= 0)
+ if (args.resolution_mm <= 0)
{
- std::cerr << "error: The provided resolution of " << resolution_mm << " is negative, which is not allowed." <<std::endl;
+ std::cerr << "error: The provided resolution of " << args.resolution_mm << " is negative, which is not allowed." <<std::endl;
has_error = true;
}
- if (path_tolerance_percent <= 0)
+ if (args.path_tolerance_percent < 0)
{
- std::cerr << "error: The provided path tolerance percentage of " << path_tolerance_percent << " is negative, which is not allowed." << std::endl;
+ std::cerr << "error: The provided path tolerance percentage of " << args.path_tolerance_percent << " is negative, which is not allowed." << std::endl;
has_error = true;
}
- if (max_radius_mm > 1000000)
+ if (args.max_radius_mm > 1000000)
+ {
+ // warning
+ std::cout << "warning: The provided path max radius of " << args.max_radius_mm << "mm is greater than 1000000 (1km), which is not recommended." << std::endl;
+ }
+
+ if (args.min_arc_segments < 0)
{
// warning
- std::cout << "warning: The provided path max radius of " << max_radius_mm << "mm is greater than 1000000 (1km), which is not recommended." << std::endl;
+ std::cout << "warning: The provided min_arc_segments " << args.min_arc_segments << " is less than zero. Setting to 0." << std::endl;
+ args.min_arc_segments = 0;
}
- if (min_arc_segments < 0)
+ if (args.mm_per_arc_segment < 0)
{
// warning
- std::cout << "warning: The provided min_arc_segments " << min_arc_segments << " is less than zero. Setting to 0." << std::endl;
- min_arc_segments = 0;
+ std::cout << "warning: The provided mm_per_arc_segment " << args.mm_per_arc_segment << "mm is less than zero. Setting to 0." << std::endl;
+ args.mm_per_arc_segment = 0;
}
- if (mm_per_arc_segment < 0)
+ if (args.path_tolerance_percent > 0.25)
+ {
+ // warning
+ std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is greater than 0.25 (25%), which is not recommended." << std::endl;
+ }
+ else if (args.path_tolerance_percent < 0.001 && args.path_tolerance_percent > 0)
{
// warning
- std::cout << "warning: The provided mm_per_arc_segment " << mm_per_arc_segment << "mm is less than zero. Setting to 0." << std::endl;
- mm_per_arc_segment = 0;
+ std::cout << "warning: The provided path tolerance percent of " << args.path_tolerance_percent << " is less than 0.001 (0.1%), which is not recommended, and will result in very few arcs being generated." << std::endl;
}
- if (path_tolerance_percent > 0.05)
+ if (xyz_precision < 3)
{
// warning
- std::cout << "warning: The provided path tolerance percent of " << path_tolerance_percent << " is greater than 0.05 (5%), which is not recommended." << std::endl;
+ std::cout << "warning: The provided default_xyz_precision " << xyz_precision << "mm is less than 3, with will cause issues printing arcs. A value of 3 will be used instead." << std::endl;
+ xyz_precision = 3;
}
- else if (path_tolerance_percent < 0.0001 && path_tolerance_percent > 0)
+
+ if (e_precision < DEFAULT_E_PRECISION)
{
// warning
- std::cout << "warning: The provided path tolerance percent of " << path_tolerance_percent << " is less than greater than 0.001 (0.1%), which is not recommended." << std::endl;
+ std::cout << "warning: The provided default_e_precision " << e_precision << "mm is less than 3, with will cause extrusion issues. A value of 3 will be used instead." << std::endl;
+ e_precision = 3;
}
- if (default_xyz_precision < 3)
+ if (xyz_precision > 6)
{
// warning
- std::cout << "warning: The provided default_xyz_precision " << default_xyz_precision << "mm is less than 3, with will cause issues printing arcs. A value of 3 will be used instead." << std::endl;
- default_xyz_precision = 3;
+ std::cout << "warning: The provided default_xyz_precision " << xyz_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so a value of 6 will be used instead." << std::endl;
+ xyz_precision = 6;
}
- if (default_e_precision < DEFAULT_E_PRECISION)
+ if (e_precision > 6)
{
// warning
- std::cout << "warning: The provided default_e_precision " << default_e_precision << "mm is less than 3, with will cause extrusion issues. A value of 3 will be used instead." << std::endl;
- default_e_precision = 3;
+ std::cout << "warning: The provided default_e_precision " << e_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so value of 6 will be used instead." << std::endl;
+ e_precision = 6;
}
- if (default_xyz_precision > 6)
+ // Fill in the adjusted precisions
+ args.default_e_precision = (unsigned char)e_precision;
+ args.default_xyz_precision = (unsigned char)xyz_precision;
+
+ if (args.extrusion_rate_variance_percent < 0)
{
// warning
- std::cout << "warning: The provided default_xyz_precision " << default_xyz_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so a value of 6 will be used instead." << std::endl;
- default_xyz_precision = 6;
+ std::cout << "warning: The provided extrusion_rate_variance_percent " << args.extrusion_rate_variance_percent << " is less than 0. Applying the default setting of " << DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT*100 << "%." << std::endl;
+ args.extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT;
}
- if (default_e_precision > 6)
+ if (args.max_gcode_length < 0)
{
// warning
- std::cout << "warning: The provided default_e_precision " << default_e_precision << "mm is greater than 6, which may cause gcode checksum errors while printing depending on your firmeware, so value of 6 will be used instead." << std::endl;
- default_e_precision = 6;
+ std::cout << "warning: The provided max_gcode_length " << args.max_gcode_length << " is less than 0. Setting to the default (no limit)." << std::endl;
+ args.max_gcode_length = DEFAULT_MAX_GCODE_LENGTH;
}
if (has_error)
@@ -305,118 +349,102 @@ int main(int argc, char* argv[])
// Ensure the log level name is valid
std::vector<std::string> log_names;
- log_names.push_back("arc_welder.gcode_conversion");
+ log_names.push_back(ARC_WELDER_LOGGER_NAME);
std::vector<int> log_levels;
- log_levels.push_back(log_levels::DEBUG);
+ log_levels.push_back((int)log_levels::DEBUG);
logger* p_logger = new logger(log_names, log_levels);
p_logger->set_log_level_by_value(log_level_value);
-
- std::stringstream log_messages;
- std::string temp_file_path = "";
- log_messages << std::fixed << std::setprecision(DEFAULT_ARG_DOUBLE_PRECISION);
- if (source_file_path == target_file_path)
+ args.log = p_logger;
+
+ arc_welder* p_arc_welder = NULL;
+
+ if (progress_type == PROGRESS_TYPE_NONE)
{
- overwrite_source_file = true;
- if (!utilities::get_temp_file_path_for_file(source_file_path, temp_file_path))
- {
- log_messages << "The source and target path are the same, but a temporary file path could not be created. Is the path empty?";
- p_logger->log(0, INFO, log_messages.str());
- log_messages.clear();
- log_messages.str("");
- }
-
- // create a uuid with a tmp extension for the temporary file
- log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path;
- p_logger->log(0, INFO, log_messages.str());
- log_messages.clear();
- log_messages.str("");
- target_file_path = temp_file_path;
+ p_logger->log(0, log_levels::INFO, "Suppressing progress messages.");
+ args.callback = on_progress_suppress;
}
- log_messages << "Processing Gcode\n";
- log_messages << "\tSource File Path : " << source_file_path << "\n";
- if (overwrite_source_file)
+ else if (progress_type == PROGRESS_TYPE_FULL)
{
- log_messages << "\tTarget File Path (overwrite) : " << target_file_path << "\n";
- log_messages << "\tTemporary File Path : " << temp_file_path << "\n";
+ p_logger->log(0, log_levels::INFO, "Displaying full progress messages.");
+ args.callback = on_progress_full;
}
- else
- {
- log_messages << "\tTarget File File : " << target_file_path << "\n";
+ else {
+ args.callback = on_progress_simple;
}
-
- log_messages << "\tResolution : " << resolution_mm << "mm (+-" << std::setprecision(5) << resolution_mm/2.0 << "mm)\n";
- log_messages << "\tPath Tolerance : " << std::setprecision(3) << path_tolerance_percent*100.0 << "%\n";
- log_messages << "\tMaximum Arc Radius : " << std::setprecision(0) << max_radius_mm << "mm\n";
- log_messages << "\tMin Arc Segments : " << std::setprecision(0) << min_arc_segments << "\n";
- log_messages << "\tMM Per Arc Segment : " << std::setprecision(3) << mm_per_arc_segment << "\n";
- log_messages << "\tAllow 3D Arcs : " << (allow_3d_arcs ? "True" : "False") << "\n";
- log_messages << "\tAllow Dynamic Precision : " << (allow_dynamic_precision ? "True" : "False") << "\n";
- log_messages << "\tDefault XYZ Precision : " << std::setprecision(0) << static_cast<int>(default_xyz_precision) << "\n";
- log_messages << "\tDefault E Precision : " << std::setprecision(0) << static_cast<int>(default_e_precision) << "\n";
- log_messages << "\tG90/G91 Influences Extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n";
- log_messages << "\tLog Level : " << log_level_string << "\n";
- log_messages << "\tHide Progress Updates : " << (hide_progress ? "True" : "False");
- p_logger->log(0, INFO, log_messages.str());
- arc_welder* p_arc_welder = NULL;
+ // Log the arguments
+ std::stringstream log_messages;
+ log_messages << "Processing GCode.";
+ p_logger->log(0, log_levels::INFO, log_messages.str());
+ log_messages.clear();
+ log_messages.str("");
+ log_messages << args.str();
+ p_logger->log(0, log_levels::INFO, log_messages.str());
- if (overwrite_source_file)
- {
- target_file_path = temp_file_path;
- }
- if (!hide_progress)
- p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, DEFAULT_GCODE_BUFFER_SIZE, on_progress);
- else
- p_arc_welder = new arc_welder(source_file_path, target_file_path, p_logger, resolution_mm, path_tolerance_percent, max_radius_mm, min_arc_segments, mm_per_arc_segment, g90_g91_influences_extruder, allow_3d_arcs, allow_dynamic_precision, default_xyz_precision, default_e_precision, DEFAULT_GCODE_BUFFER_SIZE, suppress_progress);
+ // Set the box encoding
+ args.box_encoding = args.box_encoding = utilities::box_drawing::ASCII;
+ p_arc_welder = new arc_welder(args);
+
arc_welder_results results = p_arc_welder->process();
if (results.success)
{
- log_messages.clear();
- log_messages.str("");
- log_messages << "Target file at '" << target_file_path << "' created.";
-
- if (overwrite_source_file)
+ if (args.allow_travel_arcs)
{
log_messages.clear();
log_messages.str("");
- log_messages << "Deleting the original source file at '" << source_file_path << "'.";
- p_logger->log(0, INFO, log_messages.str());
- log_messages.clear();
- log_messages.str("");
- std::remove(source_file_path.c_str());
- log_messages << "Renaming temporary file at '" << target_file_path << "' to '" << source_file_path <<"'.";
- p_logger->log(0, INFO, log_messages.str());
- std::rename(target_file_path.c_str(), source_file_path.c_str());
+ if (results.progress.travel_statistics.total_count_source == results.progress.travel_statistics.total_count_target)
+ {
+ log_messages << "Target File Travel Statistics: No travel arcs converted." ;
+ }
+ else
+ {
+ log_messages << "\n" << results.progress.travel_statistics.str("Target File Travel Statistics", utilities::box_drawing::ASCII);
+ }
+
+ p_logger->log(0, log_levels::INFO, log_messages.str());
}
+
log_messages.clear();
log_messages.str("");
- log_messages << std::endl << results.progress.segment_statistics.str();
+ // Extrusion Statistics
+ source_target_segment_statistics combined_stats = source_target_segment_statistics::add(results.progress.segment_statistics, results.progress.segment_retraction_statistics);
+ log_messages << "\n" << combined_stats.str("Target File Extrusion Statistics", utilities::box_drawing::ASCII);
p_logger->log(0, INFO, log_messages.str() );
+
+
log_messages.clear();
log_messages.str("");
log_messages << "Arc Welder process completed successfully.";
- p_logger->log(0, INFO, log_messages.str());
+ p_logger->log(0, log_levels::INFO, log_messages.str());
}
else
{
log_messages.clear();
log_messages.str("");
log_messages << "File processing failed.";
- p_logger->log(0, INFO, log_messages.str());
+ p_logger->log(0, log_levels::INFO, log_messages.str());
}
delete p_arc_welder;
return 0;
}
-bool on_progress(arc_welder_progress progress, logger* p_logger, int logger_type)
+bool on_progress_full(arc_welder_progress progress, logger* p_logger, int logger_type)
{
std::cout << "Progress: "<< progress.str() << std::endl;
std::cout.flush();
return true;
}
-bool suppress_progress(arc_welder_progress progress, logger* p_logger, int logger_type)
+
+bool on_progress_simple(arc_welder_progress progress, logger* p_logger, int logger_type)
+{
+ std::cout << "Progress: " << progress.simple_progress_str() << std::endl;
+ std::cout.flush();
+ return true;
+}
+
+bool on_progress_suppress(arc_welder_progress progress, logger* p_logger, int logger_type)
{
return true;
}
diff --git a/ArcWelderConsole/ArcWelderConsole.h b/ArcWelderConsole/ArcWelderConsole.h
index 64425c9..1e4762f 100644
--- a/ArcWelderConsole/ArcWelderConsole.h
+++ b/ArcWelderConsole/ArcWelderConsole.h
@@ -25,6 +25,8 @@
#pragma once
#include "arc_welder.h"
#include "version.h"
-static bool on_progress(arc_welder_progress progress, logger* p_logger, int logger_type);
-static bool suppress_progress(arc_welder_progress progress, logger* p_logger, int logger_type);
+static bool on_progress_full(arc_welder_progress progress, logger* p_logger, int logger_type);
+static bool on_progress_simple(arc_welder_progress progress, logger* p_logger, int logger_type);
+static bool on_progress_suppress(arc_welder_progress progress, logger* p_logger, int logger_type);
+
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
index ea333b3..d43aec7 100644
--- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
@@ -26,22 +26,31 @@
#define _CRT_SECURE_NO_DEPRECATE
#endif
-#include "inverse_processor.h"
#include "ArcWelderInverseProcessor.h"
-#include <cstring>
-#include <iostream>
-#include <sstream>
-#include <iomanip>
-#include "gcode_position.h"
+#include "arc_interpolation.h"
+#include "marlin_1.h"
+#include "marlin_2.h"
+#include "repetier.h"
+#include "prusa.h"
+#include "smoothieware.h"
#include "logger.h"
#include "version.h"
#include "utilities.h"
#include <tclap/CmdLine.h>
#define DEFAULT_ARG_DOUBLE_PRECISION 4
-
int main(int argc, char* argv[])
{
- std::string info = "Arc Straightener - Converts G2/G3 commands to G1/G2 commands..";
+ try {
+ run_arc_straightener(argc, argv);
+ }
+ catch (TCLAP::ArgException *e) {
+ std::cout << (*e).what() << " - " << (*e).typeDescription() << "\n";
+ return -1;
+ }
+}
+int run_arc_straightener(int argc, char* argv[])
+{
+ std::string info = "Arc Straightener - Converts G2/G3 commands to G1/G2 commands..";
info.append("\nVersion: ").append(GIT_TAGGED_VERSION);
info.append(", Branch: ").append(GIT_BRANCH);
@@ -51,20 +60,20 @@ int main(int argc, char* argv[])
std::stringstream arg_description_stream;
arg_description_stream << std::fixed << std::setprecision(5);
- std::string source_file_path;
- std::string target_file_path;
+ arc_interpolation_args args;
bool overwrite_source_file = false;
- bool g90_g91_influences_extruder;
-
- ConfigurationStore cs;
- double mm_per_arc_segment;
- double min_mm_per_arc_segment;
- int min_arc_segments;
- double arc_segments_per_sec;
-
+
std::string log_level_string;
std::string log_level_string_default = "INFO";
int log_level_value;
+
+ // Create an instance of all supported firmware types using the default args
+ marlin_1 marlin_1_firmware(args.firmware_args);
+ marlin_2 marlin_2_firmware(args.firmware_args);
+ repetier repetier_firmware(args.firmware_args);
+ prusa prusa_firmware(args.firmware_args);
+ smoothieware smoothieware_firmware(args.firmware_args);
+
// Extract arguments
try {
// Define the command line object
@@ -77,18 +86,55 @@ int main(int argc, char* argv[])
// <TARGET>
TCLAP::UnlabeledValueArg<std::string> target_arg("target", "The target gcode file containing the converted code. If this is not supplied, the source path will be used and the source file will be overwritten.", false, "", "path to target gcode file");
-
+
+ // -f --firmware-type
+ std::vector<std::string> firmware_types_vector;
+ for (int i = 0; i < NUM_FIRMWARE_TYPES; i++)
+ {
+ firmware_types_vector.push_back(firmware_type_names[i]);
+ }
+ TCLAP::ValuesConstraint<std::string> firmware_type_constraint(firmware_types_vector);
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "Sets the firmware to emulate. Default Value: " << firmware_type_names[DEFAULT_FIRMWARE_TYPE];
+ TCLAP::ValueArg<std::string> firmware_type_arg("f", "firmware-type", arg_description_stream.str(), false, firmware_type_names[DEFAULT_FIRMWARE_TYPE], &firmware_type_constraint);
+
+ // -v --firmware-version
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "Sets the firmware version to use. The available versions depend on the firmware type selected. " << DEFAULT_FIRMWARE_VERSION_NAME << " will select the most recent version available.\n";
+ arg_description_stream << "\tMARLIN 1 versions: " << utilities::join(marlin_1_firmware.get_version_names(), ", ") << "\n";
+ arg_description_stream << "\tMARLIN 2 versions: " << utilities::join(marlin_2_firmware.get_version_names(), ", ") << "\n";
+ arg_description_stream << "\tREPETIER versions: " << utilities::join(repetier_firmware.get_version_names(), ", ") << "\n";
+ arg_description_stream << "\tPRUSA versions: " << utilities::join(prusa_firmware.get_version_names(), ", ") << "\n";
+ arg_description_stream << "\tSMOOTHIEWARE versions: " << utilities::join(smoothieware_firmware.get_version_names(), ", ") << "\n";
+ arg_description_stream << "\tDefault Value: " << DEFAULT_FIRMWARE_VERSION_NAME;
+ TCLAP::ValueArg<std::string> firmware_version_arg("v", "firmware-version", arg_description_stream.str(), false, DEFAULT_FIRMWARE_VERSION_NAME, "string");
+
// -g --g90-influences-extruder
+ std::string g90_g91_influences_extruder_default_value = "DEFAULT";
+ std::vector<std::string> g90_g91_influences_extruder_vector;
+ g90_g91_influences_extruder_vector.push_back("TRUE");
+ g90_g91_influences_extruder_vector.push_back("FALSE");
+ g90_g91_influences_extruder_vector.push_back(g90_g91_influences_extruder_default_value);
+ TCLAP::ValuesConstraint<std::string> g90_g91_influences_extruder_constraint(g90_g91_influences_extruder_vector);
arg_description_stream.clear();
arg_description_stream.str("");
- arg_description_stream << "If supplied, G90/G91 influences the extruder axis. Default Value: " << DEFAULT_G90_G91_INFLUENCES_EXTRUDER;
- TCLAP::SwitchArg g90_arg("g", "g90-influences-extruder", arg_description_stream.str(), false);
+ arg_description_stream << "Sets the firmware's G90/G91 influences extruder axis behavior. By default this is determined by the firmware's behavior. Default Value: " << g90_g91_influences_extruder_default_value;
+ TCLAP::ValueArg<std::string> g90_arg("g", "g90-influences-extruder", arg_description_stream.str(), false, g90_g91_influences_extruder_default_value, &g90_g91_influences_extruder_constraint);
// -m --mm-per-arc-segment
arg_description_stream.clear();
arg_description_stream.str("");
arg_description_stream << "The default segment length. Default Value: " << DEFAULT_MM_PER_ARC_SEGMENT;
TCLAP::ValueArg<double> mm_per_arc_segment_arg("m", "mm-per-arc-segment", arg_description_stream.str(), false, DEFAULT_MM_PER_ARC_SEGMENT, "float");
+
+ // max_arc_segment_mm_arg
+ // -d --mm-per-arc-segment
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "The maximum length of an arc segment. Default Value: " << DEFAULT_MM_PER_ARC_SEGMENT;
+ TCLAP::ValueArg<double> max_arc_segment_mm_arg("d", "max-arc-segment-mm", arg_description_stream.str(), false, DEFAULT_MM_PER_ARC_SEGMENT, "float");
// -n --min-mm-per-arc-segment
arg_description_stream.clear();
@@ -96,18 +142,50 @@ int main(int argc, char* argv[])
arg_description_stream << "The minimum mm per arc segment. Used to prevent unnecessarily small segments from being generated. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_MM_PER_ARC_SEGMENT;
TCLAP::ValueArg<double> min_mm_per_arc_segment_arg("n", "min-mm-per-arc-segment", arg_description_stream.str(), false, DEFAULT_MIN_MM_PER_ARC_SEGMENT, "float");
- // -s --min-arc-segments
+ // min_arc_segment_mm
+ // -b --min-arc-segment-mm
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "The minimum mm per arc segment. Used to prevent unnecessarily small segments from being generated. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_MM_PER_ARC_SEGMENT;
+ TCLAP::ValueArg<double> min_arc_segment_mm_arg("b", "min-arc-segment-mm", arg_description_stream.str(), false, DEFAULT_MIN_MM_PER_ARC_SEGMENT, "float");
+
+ // -r --min-arc-segments
arg_description_stream.clear();
arg_description_stream.str("");
arg_description_stream << "The minimum number of segments within a circle of the same radius as the arc. Can be used to increase detail on small arcs. The smallest segment generated will be no larger than min_mm_per_arc_segment. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_ARC_SEGMENTS;
TCLAP::ValueArg<int> min_arc_segments_arg("r", "min-arc-segments", arg_description_stream.str(), false, DEFAULT_MIN_ARC_SEGMENTS, "int");
+ // min_circle_segments_arg
+ // -a --min-circle-segments-arg
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "The minimum number of segments within a circle of the same radius as the arc. Can be used to increase detail on small arcs. The smallest segment generated will be no larger than min_mm_per_arc_segment. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_MIN_ARC_SEGMENTS;
+ TCLAP::ValueArg<int> min_circle_segments_arg("a", "min-circle-segments", arg_description_stream.str(), false, DEFAULT_MIN_ARC_SEGMENTS, "int");
+
+ // -c --n-arc-correction
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "The number of segments that will be interpolated using a small angle approximation before true sin/cos corrections are applied. A value less than or equal to 1 will disable this feature. Default Value: " << DEFAULT_N_ARC_CORRECTIONS;
+ TCLAP::ValueArg<int> n_arc_correction_arg("c", "n-arc-correction", arg_description_stream.str(), false, DEFAULT_N_ARC_CORRECTIONS, "int");
+
// -s --arc-segments-per-second
arg_description_stream.clear();
arg_description_stream.str("");
arg_description_stream << "The number of segments per second. This will produce a constant number of arcs, clamped between mm-per-arc-segment and min-mm-per-arc-segment. Can be used to prevent stuttering when printing very quickly. A value less than or equal to 0 will disable this feature. Default Value: " << DEFAULT_ARC_SEGMENTS_PER_SEC;
TCLAP::ValueArg<double> arc_segments_per_sec_arg("s", "arc-segments-per-second", arg_description_stream.str(), false, DEFAULT_MIN_MM_PER_ARC_SEGMENT, "float");
+ // -e --mm-max-arc-error
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "This currently is only used in Smoothieware. The maximum error for line segments that divide arcs. Set to 0 to disable. Default Value: " << DEFAULT_MM_MAX_ARC_ERROR;
+ TCLAP::ValueArg<double> mm_max_arc_error_arg("e", "mm-max-arc-error", arg_description_stream.str(), false, DEFAULT_MM_MAX_ARC_ERROR, "float");
+
+ // -p --print-firmware-defaults
+ arg_description_stream.clear();
+ arg_description_stream.str("");
+ arg_description_stream << "Prints all available settings and defaults for the provided firmware type and version. All other parameters will be ignored.";
+ TCLAP::SwitchArg print_firmware_defaults_arg("p", "print-firmware-defaults", arg_description_stream.str());
+
// -l --log-level
std::vector<std::string> log_levels_vector;
log_levels_vector.push_back("NOSET");
@@ -121,43 +199,213 @@ int main(int argc, char* argv[])
TCLAP::ValuesConstraint<std::string> log_levels_constraint(log_levels_vector);
arg_description_stream.clear();
arg_description_stream.str("");
- arg_description_stream << "Sets console log level. Default Value: " << log_level_string_default;
+ arg_description_stream << "Sets console log level. Possible values: Default Value: " << log_level_string_default;
TCLAP::ValueArg<std::string> log_level_arg("l", "log-level", arg_description_stream.str(), false, log_level_string_default, &log_levels_constraint);
// Add all arguments
cmd.add(source_arg);
cmd.add(target_arg);
+ cmd.add(firmware_type_arg);
+ cmd.add(firmware_version_arg);
cmd.add(g90_arg);
-
cmd.add(mm_per_arc_segment_arg);
cmd.add(min_mm_per_arc_segment_arg);
cmd.add(min_arc_segments_arg);
+ cmd.add(n_arc_correction_arg);
cmd.add(arc_segments_per_sec_arg);
-
cmd.add(log_level_arg);
+ cmd.add(min_circle_segments_arg);
+ cmd.add(min_arc_segment_mm_arg);
+ cmd.add(max_arc_segment_mm_arg);
+ cmd.add(print_firmware_defaults_arg);
// Parse the argv array.
cmd.parse(argc, argv);
+ // Ok! Now let's see what firmware and version were selected, then we can start adding parameters
+ // First, Set the firmware type
+ std::string firmware_type_string = firmware_type_arg.getValue();
+ for (int i = 0; i < NUM_FIRMWARE_TYPES; i++)
+ {
+ if (firmware_type_names[i] == firmware_type_string)
+ {
+ args.firmware_args.firmware_type = static_cast<firmware_types>(i);
+ }
+ }
+ // Now set the version
+ // Set the firmware version, and check to make sure that the version supplied is supported.
+ std::string firmware_version_string = firmware_version_arg.getValue();
+ switch (args.firmware_args.firmware_type)
+ {
+ case firmware_types::MARLIN_1:
+ if (!marlin_1_firmware.is_valid_version(firmware_version_string))
+ {
+ throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type.");
+ }
+ break;
+ case firmware_types::MARLIN_2:
+ if (!marlin_2_firmware.is_valid_version(firmware_version_string))
+ {
+ throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type.");
+ }
+ break;
+ case firmware_types::REPETIER:
+ if (!repetier_firmware.is_valid_version(firmware_version_string))
+ {
+ throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type.");
+ }
+ break;
+ case firmware_types::PRUSA:
+ if (!prusa_firmware.is_valid_version(firmware_version_string))
+ {
+ throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type.");
+ }
+ break;
+ case firmware_types::SMOOTHIEWARE:
+ if (!smoothieware_firmware.is_valid_version(firmware_version_string))
+ {
+ throw new TCLAP::ArgException("Unknown Version Exception", firmware_version_arg.getName(), "'" + firmware_version_string + "' is not a valid version for " + firmware_type_string + " firmware type.");
+ }
+ break;
+ }
+ args.firmware_args.version = firmware_version_string;
+
+ // now that we have the firmware type and version, we can extract the default arguments and override any settings that are supplied
+ switch (args.firmware_args.firmware_type)
+ {
+ case firmware_types::MARLIN_1:
+ marlin_1_firmware.set_arguments(args.firmware_args);
+ args.firmware_args = marlin_1_firmware.get_default_arguments_for_current_version();
+ break;
+ case firmware_types::MARLIN_2:
+ marlin_2_firmware.set_arguments(args.firmware_args);
+ args.firmware_args = marlin_2_firmware.get_default_arguments_for_current_version();
+ break;
+ case firmware_types::REPETIER:
+ repetier_firmware.set_arguments(args.firmware_args);
+ args.firmware_args = repetier_firmware.get_default_arguments_for_current_version();
+ break;
+ case firmware_types::PRUSA:
+ prusa_firmware.set_arguments(args.firmware_args);
+ args.firmware_args = prusa_firmware.get_default_arguments_for_current_version();
+ break;
+ case firmware_types::SMOOTHIEWARE:
+ smoothieware_firmware.set_arguments(args.firmware_args);
+ args.firmware_args = smoothieware_firmware.get_default_arguments_for_current_version();
+ break;
+ }
+
+ // see if the user want's to see the default settings
+ if (print_firmware_defaults_arg.getValue())
+ {
+ std::cout << "Showing arguments and defaults for " << firmware_type_string << " ("<< firmware_version_string << ")\n";
+ std::cout << "Available argument for firmware: " << get_available_arguments_string(args.firmware_args.get_available_arguments()) << "\n";
+ std::cout << "Default " << args.firmware_args.get_argument_description();
+ return 0;
+ }
// Get the value parsed by each arg.
- source_file_path = source_arg.getValue();
- target_file_path = target_arg.getValue();
- mm_per_arc_segment = mm_per_arc_segment_arg.getValue();
- min_mm_per_arc_segment = min_mm_per_arc_segment_arg.getValue();
- min_arc_segments = min_arc_segments_arg.getValue();
- arc_segments_per_sec = arc_segments_per_sec_arg.getValue();
-
- cs.mm_per_arc_segment = (float)mm_per_arc_segment;
- cs.min_mm_per_arc_segment = (float)min_mm_per_arc_segment;
- cs.min_arc_segments = min_arc_segments;
- cs.arc_segments_per_sec = arc_segments_per_sec;
-
- if (target_file_path.size() == 0)
+ args.source_path = source_arg.getValue();
+ args.target_path = target_arg.getValue();
+ if (args.target_path.size() == 0)
{
- target_file_path = source_file_path;
+ args.target_path = args.source_path;
}
- g90_g91_influences_extruder = g90_arg.getValue();
+ // If the arguments are set, apply them. If not, don't.
+ if (mm_per_arc_segment_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("mm_per_arc_segment"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", mm_per_arc_segment_arg.getName(), "The argument does not apply to the " + firmware_type_string +" " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.mm_per_arc_segment = mm_per_arc_segment_arg.getValue();
+ }
+ if (min_mm_per_arc_segment_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("min_mm_per_arc_segment"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", min_mm_per_arc_segment_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.min_mm_per_arc_segment = min_mm_per_arc_segment_arg.getValue();
+ }
+ if (min_arc_segments_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("min_arc_segments"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", min_arc_segments_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.min_arc_segments = min_arc_segments_arg.getValue();
+ }
+ if (arc_segments_per_sec_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("arc_segments_per_sec"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", arc_segments_per_sec_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.arc_segments_per_sec = arc_segments_per_sec_arg.getValue();
+ }
+ if (g90_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("g90_g91_influences_extruder"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", g90_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.g90_g91_influences_extruder = g90_arg.getValue() == "TRUE";
+ }
+ if (n_arc_correction_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("n_arc_correction"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", n_arc_correction_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.n_arc_correction = n_arc_correction_arg.getValue();
+ }
+ if (mm_max_arc_error_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("mm_max_arc_error"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", mm_max_arc_error_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.mm_max_arc_error = mm_max_arc_error_arg.getValue();
+ }
+
+ // min_circle_segments
+ if (min_circle_segments_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("min_circle_segments"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", min_circle_segments_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.set_min_circle_segments(min_circle_segments_arg.getValue());
+ }
+ // min_arc_segment_mm
+ if (min_arc_segment_mm_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("min_arc_segment_mm"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", min_arc_segment_mm_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.set_min_arc_segment_mm(min_arc_segment_mm_arg.getValue());
+ }
+ // max_arc_segment_mm
+ if (max_arc_segment_mm_arg.isSet())
+ {
+ // See if this argument is supported
+ if (!args.firmware_args.is_argument_used("max_arc_segment_mm"))
+ {
+ throw new TCLAP::ArgException("Invalid Argument For Firmware", max_arc_segment_mm_arg.getName(), "The argument does not apply to the " + firmware_type_string + " " + firmware_version_arg.getValue() + " firmware. Only the following parameters are supported: " + get_available_arguments_string(args.firmware_args.get_available_arguments()));
+ }
+ args.firmware_args.set_max_arc_segment_mm(max_arc_segment_mm_arg.getValue());
+ }
log_level_string = log_level_arg.getValue();
log_level_value = -1;
@@ -188,91 +436,98 @@ int main(int argc, char* argv[])
std::vector<std::string> log_names;
log_names.push_back("arc_welder.gcode_conversion");
std::vector<int> log_levels;
- log_levels.push_back(log_levels::DEBUG);
+ log_levels.push_back((int)log_levels::DEBUG);
logger* p_logger = new logger(log_names, log_levels);
p_logger->set_log_level_by_value(log_level_value);
std::stringstream log_messages;
std::string temp_file_path = "";
log_messages << std::fixed << std::setprecision(DEFAULT_ARG_DOUBLE_PRECISION);
- if (source_file_path == target_file_path)
+ if (args.source_path == args.target_path)
{
overwrite_source_file = true;
- if (!utilities::get_temp_file_path_for_file(source_file_path, temp_file_path))
+ if (!utilities::get_temp_file_path_for_file(args.source_path, temp_file_path))
{
log_messages << "The source and target path are the same, but a temporary file path could not be created. Is the path empty?";
- p_logger->log(0, INFO, log_messages.str());
+ p_logger->log(0, log_levels::INFO, log_messages.str());
log_messages.clear();
log_messages.str("");
}
// create a uuid with a tmp extension for the temporary file
- log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << target_file_path;
- p_logger->log(0, INFO, log_messages.str());
+ log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path;
+ p_logger->log(0, log_levels::INFO, log_messages.str());
log_messages.clear();
log_messages.str("");
}
- log_messages << "Processing Gcode\n";
- log_messages << "\tSource File Path : " << source_file_path << "\n";
+ log_messages << "Arguments: \n";
+ log_messages << "\tSource File Path : " << args.source_path << "\n";
if (overwrite_source_file)
{
- log_messages << "\tTarget File Path (overwrite) : " << target_file_path << "\n";
+ log_messages << "\tTarget File Path (overwrite) : " << args.target_path << "\n";
log_messages << "\tTemporary File Path : " << temp_file_path << "\n";
}
else
{
- log_messages << "\tTarget File File : " << target_file_path << "\n";
+ log_messages << "\tTarget File File : " << args.target_path << "\n";
}
log_messages << "\tLog Level : " << log_level_string << "\n";
- p_logger->log(0, INFO, log_messages.str());
-
+
+
if (overwrite_source_file)
{
- target_file_path = temp_file_path;
+ args.target_path = temp_file_path;
}
-
- inverse_processor processor(source_file_path, target_file_path, g90_g91_influences_extruder, 50, cs);
- processor.process();
- // Todo: get some results!
- if (true)
- {
- log_messages.clear();
- log_messages.str("");
- log_messages << "Target file at '" << target_file_path << "' created.";
- if (overwrite_source_file)
- {
- log_messages.clear();
- log_messages.str("");
- log_messages << "Deleting the original source file at '" << source_file_path << "'.";
- p_logger->log(0, INFO, log_messages.str());
- log_messages.clear();
- log_messages.str("");
- std::remove(source_file_path.c_str());
- log_messages << "Renaming temporary file at '" << target_file_path << "' to '" << source_file_path << "'.";
- p_logger->log(0, INFO, log_messages.str());
- std::rename(target_file_path.c_str(), source_file_path.c_str());
- }
- /*
- log_messages.clear();
- log_messages.str("");
- log_messages << std::endl << results.progress.segment_statistics.str();
- p_logger->log(0, INFO, log_messages.str());
- */
+ arc_interpolation interpolator(args);
+ log_messages << interpolator.get_firmware_argument_description();
+ p_logger->log(0, log_levels::INFO, log_messages.str());
+
+ p_logger->log(0, log_levels::INFO, "Running interpolation...");
+ interpolator.process();
+ p_logger->log(0, log_levels::INFO, "Interpolation Complete.");
+
+ log_messages.clear();
+ log_messages.str("");
+ log_messages << "Target file at '" << args.target_path << "' created.";
+
+ if (overwrite_source_file)
+ {
log_messages.clear();
log_messages.str("");
- log_messages << "Process completed successfully.";
- p_logger->log(0, INFO, log_messages.str());
- }
- else
- {
+ log_messages << "Deleting the original source file at '" << args.source_path << "'.";
+ p_logger->log(0, log_levels::INFO, log_messages.str());
log_messages.clear();
log_messages.str("");
- log_messages << "File processing failed.";
- p_logger->log(0, INFO, log_messages.str());
+ std::remove(args.source_path.c_str());
+ log_messages << "Renaming temporary file at '" << args.target_path << "' to '" << args.source_path << "'.";
+ p_logger->log(0, log_levels::INFO, log_messages.str());
+ std::rename(args.target_path.c_str(), args.source_path.c_str());
}
+
+ log_messages.clear();
+ log_messages.str("");
+ log_messages << "Process completed successfully.";
+ p_logger->log(0, log_levels::INFO, log_messages.str());
+
return 0;
}
+
+
+std::string get_available_arguments_string(std::vector<std::string> firmware_arguments)
+{
+ std::string available_argument_string = "";
+
+ for (std::vector<std::string>::iterator it = firmware_arguments.begin(); it != firmware_arguments.end(); it++)
+ {
+ if (available_argument_string.size() > 0)
+ {
+ available_argument_string += ", ";
+ }
+ available_argument_string += "--" +utilities::replace(*it, "_", "-");
+ }
+ return available_argument_string;
+} \ No newline at end of file
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h
index 95433f7..3d76259 100644
--- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.h
@@ -24,8 +24,9 @@
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <string>
-#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false
-
+#include <vector>
+int run_arc_straightener(int argc, char* argv[]);
+static std::string get_available_arguments_string(std::vector<std::string> firmware_arguments);
/*
static void TestInverseProcessor(std::string source_path, std::string target_path);
static std::string ANTI_STUTTER_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\5x5_cylinder_2000Fn_0.2mm_PLA_MK2.5MMU2_4m.gcode";
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj
index 37ec7d0..334d94e 100644
--- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj
@@ -105,11 +105,11 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
- <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\</IncludePath>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Remote_Pi|x64'">
<LinkIncremental>true</LinkIncremental>
- <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
@@ -117,7 +117,8 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
- <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(SolutionDir)\ArcWelder\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\</IncludePath>
+ <IncludePath>$(SolutionDir)\GcodeProcessorLib\;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)\TCLAP\</IncludePath>
+ <ExecutablePath>$(VC_ExecutablePath_x64);$(CommonExecutablePath)</ExecutablePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@@ -202,11 +203,25 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="ArcWelderInverseProcessor.h" />
- <ClInclude Include="inverse_processor.h" />
+ <ClInclude Include="arc_interpolation.h" />
+ <ClInclude Include="arc_interpolation_structs.h" />
+ <ClInclude Include="firmware.h" />
+ <ClInclude Include="firmware_types.h" />
+ <ClInclude Include="marlin_1.h" />
+ <ClInclude Include="marlin_2.h" />
+ <ClInclude Include="prusa.h" />
+ <ClInclude Include="repetier.h" />
+ <ClInclude Include="smoothieware.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ArcWelderInverseProcessor.cpp" />
- <ClCompile Include="inverse_processor.cpp" />
+ <ClCompile Include="arc_interpolation.cpp" />
+ <ClCompile Include="firmware.cpp" />
+ <ClCompile Include="marlin_1.cpp" />
+ <ClCompile Include="marlin_2.cpp" />
+ <ClCompile Include="prusa.cpp" />
+ <ClCompile Include="repetier.cpp" />
+ <ClCompile Include="smoothieware.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ArcWelder\ArcWelder.vcxproj">
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters
index fe669d6..476116b 100644
--- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters
@@ -18,7 +18,31 @@
<ClInclude Include="ArcWelderInverseProcessor.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="inverse_processor.h">
+ <ClInclude Include="firmware_types.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="repetier.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="arc_interpolation.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="firmware.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="arc_interpolation_structs.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="prusa.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="marlin_1.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="marlin_2.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="smoothieware.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
@@ -26,7 +50,25 @@
<ClCompile Include="ArcWelderInverseProcessor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="inverse_processor.cpp">
+ <ClCompile Include="repetier.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="arc_interpolation.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="firmware.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prusa.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="marlin_1.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="marlin_2.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="smoothieware.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
diff --git a/ArcWelderInverseProcessor/CMakeLists.txt b/ArcWelderInverseProcessor/CMakeLists.txt
index 8a9de2c..d2111d1 100644
--- a/ArcWelderInverseProcessor/CMakeLists.txt
+++ b/ArcWelderInverseProcessor/CMakeLists.txt
@@ -1,21 +1,19 @@
project(ArcWelderInverseProcessor C CXX)
# add definitions from the GcodeProcessorLib and ArcWelder libraries
-add_definitions(${GcodeProcessorLib_DEFINITIONS} ${ArcWelder_DEFINITIONS})
+add_definitions(${GcodeProcessorLib_DEFINITIONS} )
#add_definitions("-DHAS_GENERATED_VERSION")
# Include the GcodeProcessorLib and ArcWelder's directories
-include_directories(${GcodeProcessorLib_INCLUDE_DIRS} ${ArcWelder_INCLUDE_DIRS} ${TCLAP_INCLUDE_DIRS})
+include_directories(${GcodeProcessorLib_INCLUDE_DIRS} ${TCLAP_INCLUDE_DIRS})
# include sourcelist.cmake, which contains our source list and exposes it as the
# ArcWelderConsoleSources variable
include(sourcelist.cmake)
# Add an executable our ArcWelderConsoleSources variable from our sourcelist file
-add_executable(
- ${PROJECT_NAME}
- ${ArcWelderInverseProcessorSources}
-)
+add_executable(${PROJECT_NAME} ${ArcWelderInverseProcessorSources})
+# change the executable name to ArcWelder or ArcStraightener.exe
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "ArcStraightener")
install(
@@ -23,7 +21,6 @@ install(
DESTINATION bin
)
-
# specify linking to the GcodeProcessorLib and ArcWelder libraries
-target_link_libraries(${PROJECT_NAME} TCLAP GcodeProcessorLib ArcWelder)
+target_link_libraries(${PROJECT_NAME} GcodeProcessorLib TCLAP)
diff --git a/ArcWelderInverseProcessor/Makefile b/ArcWelderInverseProcessor/Makefile
deleted file mode 100644
index a5b9b86..0000000
--- a/ArcWelderInverseProcessor/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-all:
- g++ -c -I../GcodeProcessorLib/ inverse_processor.cpp
- g++ -o ArcWelderInverseProcessor -I../GcodeProcessorLib/ ../GcodeProcessorLib/*.o inverse_processor.o ArcWelderInverseProcessor.cpp
-
-clean:
- rm -f *.o ArcWelderInverseProcessor
diff --git a/ArcWelderInverseProcessor/arc_interpolation.cpp b/ArcWelderInverseProcessor/arc_interpolation.cpp
new file mode 100644
index 0000000..9ef4e9e
--- /dev/null
+++ b/ArcWelderInverseProcessor/arc_interpolation.cpp
@@ -0,0 +1,246 @@
+#include "arc_interpolation.h"
+#include <string>
+#include "gcode_position.h"
+#include "marlin_1.h"
+#include "marlin_2.h"
+#include "repetier.h"
+#include "prusa.h"
+#include "smoothieware.h"
+#include "utilities.h"
+
+
+gcode_position_args arc_interpolation::get_args_(bool g90_g91_influences_extruder, int buffer_size)
+{
+ gcode_position_args args;
+ // Configure gcode_position_args
+ args.g90_influences_extruder = g90_g91_influences_extruder;
+ args.position_buffer_size = buffer_size;
+ args.autodetect_position = true;
+ args.home_x = 0;
+ args.home_x_none = true;
+ args.home_y = 0;
+ args.home_y_none = true;
+ args.home_z = 0;
+ args.home_z_none = true;
+ args.shared_extruder = true;
+ args.zero_based_extruder = true;
+
+
+ args.default_extruder = 0;
+ args.xyz_axis_default_mode = "absolute";
+ args.e_axis_default_mode = "absolute";
+ args.units_default = "millimeters";
+ args.location_detection_commands = std::vector<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_interpolation::arc_interpolation()
+{
+ p_current_firmware_ = NULL;
+}
+
+arc_interpolation::arc_interpolation(arc_interpolation_args args)
+{
+ args_ = args;
+ switch (args.firmware_args.firmware_type)
+ {
+ case firmware_types::MARLIN_1:
+ p_current_firmware_ = new marlin_1(args.firmware_args);
+ break;
+ case firmware_types::MARLIN_2:
+ p_current_firmware_ = new marlin_2(args.firmware_args);
+ break;
+ case firmware_types::REPETIER:
+ p_current_firmware_ = new repetier(args.firmware_args);
+ break;
+ case firmware_types::PRUSA:
+ p_current_firmware_ = new prusa(args.firmware_args);
+ break;
+ case firmware_types::SMOOTHIEWARE:
+ p_current_firmware_ = new smoothieware(args.firmware_args);
+ }
+ // Initialize the source position
+ p_source_position_ = new gcode_position(get_args_(p_current_firmware_->get_g90_g91_influences_extruder(), DEFAULT_GCODE_BUFFER_SIZE));
+}
+
+arc_interpolation::~arc_interpolation()
+{
+ delete p_source_position_;
+ if (p_current_firmware_ != NULL)
+ {
+ delete p_current_firmware_;
+ }
+
+}
+
+void arc_interpolation::process()
+{
+ // Create a stringstream we can use for messaging.
+ std::stringstream stream;
+
+ // Create a current and target position variable for arc processing
+ firmware_state state;
+ firmware_position current;
+ firmware_position target;
+ // I, J, and R parameter values
+ double i=0, j=0, r=0;
+ // bool values for is_clockwise and is_relative
+ bool is_clockwise = false, is_relative = false;
+ // e absolute offset, in case we are outputting absolute e
+ double offset_absolute_e = 0;
+
+ int read_lines_before_clock_check = 5000;
+ //std::cout << "stabilization::process_file - Processing file.\r\n";
+ stream << "Decompressing gcode file.";
+ stream << "Source File: " << args_.source_path << "\n";
+ stream << "Target File: " << args_.target_path << "\n";
+ std::cout << stream.str();
+ const clock_t start_clock = clock();
+
+ // Create the source file read stream and target write stream
+ std::ifstream gcode_file;
+ gcode_file.open(args_.source_path.c_str());
+ output_file_.open(args_.target_path.c_str());
+ std::string line;
+ int lines_with_no_commands = 0;
+ gcode_file.sync_with_stdio(false);
+ output_file_.sync_with_stdio(false);
+ gcode_parser parser;
+ int gcodes_processed = 0;
+ if (gcode_file.is_open())
+ {
+ if (output_file_.is_open())
+ {
+ parsed_command cmd;
+ // Communicate every second
+ while (std::getline(gcode_file, line))
+ {
+ lines_processed_++;
+
+ cmd.clear();
+ parser.try_parse_gcode(line.c_str(), cmd);
+ bool has_gcode = false;
+ if (cmd.gcode.length() > 0)
+ {
+ has_gcode = true;
+ gcodes_processed++;
+ }
+ else
+ {
+ lines_with_no_commands++;
+ }
+
+ p_source_position_->update(cmd, lines_processed_, gcodes_processed, -1);
+
+ if (cmd.command == "G2" || cmd.command == "G3")
+ {
+ // increment the number of arc commands encountered
+ num_arc_commands_++;
+ // Get the current and previous positions
+ position* p_cur_pos = p_source_position_->get_current_position_ptr();
+ position* p_pre_pos = p_source_position_->get_previous_position_ptr();
+ // create the current and target positions
+ current.x = p_pre_pos->get_gcode_x();
+ current.y = p_pre_pos->get_gcode_y();
+ current.z = p_pre_pos->get_gcode_z();
+ current.e = p_pre_pos->get_current_extruder().get_offset_e();
+ current.f = p_pre_pos->f;
+ // set the current firmware position
+ p_current_firmware_->set_current_position(current);
+
+ target.x = p_cur_pos->get_gcode_x();
+ target.y = p_cur_pos->get_gcode_y();
+ target.z = p_cur_pos->get_gcode_z();
+ target.e = p_cur_pos->get_current_extruder().get_offset_e();
+ target.f = p_cur_pos->f;
+
+ state.is_extruder_relative = p_pre_pos->is_extruder_relative;
+ state.is_relative = p_pre_pos->is_relative;
+ // set the current firmware state
+ p_current_firmware_->set_current_state(state);
+
+ // get I, J, and R
+ i = 0;
+ j = 0;
+ r = 0;
+ for (unsigned int index = 0; index < cmd.parameters.size(); index++)
+ {
+ parsed_command_parameter p = cmd.parameters[index];
+ if (p.name == "I")
+ {
+ i = p.double_value;
+ }
+ else if (p.name == "J")
+ {
+ j = p.double_value;
+ }
+ else if (p.name == "R")
+ {
+ r = p.double_value;
+ }
+ }
+
+ // If r is 0, calculate the radius
+ if(r==0)
+ {
+ r = utilities::hypot(i, j);
+ }
+
+ is_clockwise = cmd.command == "G2" ? 1 : 0;
+ is_relative = p_cur_pos->is_extruder_relative;
+ offset_absolute_e = p_pre_pos->get_current_extruder().get_offset_e();
+
+ // run the callback and capture any created gcode commands
+ std::string gcodes = p_current_firmware_->interpolate_arc(target, i, j, r, is_clockwise);
+ if (gcodes.length() > 0)
+ {
+ // there are gcodes to write, write them!
+ output_file_ << gcodes << "\n";
+ }
+ }
+ else
+ {
+ // Nothing to do with the current line, just write it to disk.
+ output_file_ << line << "\n";
+ }
+
+ }
+ output_file_.close();
+ }
+ else
+ {
+ std::cout << "Unable to open the output file for writing.\n";
+ }
+ std::cout << "Closing the input file.\n";
+ gcode_file.close();
+ }
+ else
+ {
+ std::cout << "Unable to open the gcode file for processing.\n";
+ }
+
+ const clock_t end_clock = clock();
+ const double total_seconds = (static_cast<double>(end_clock) - static_cast<double>(start_clock)) / CLOCKS_PER_SEC;
+
+ stream.clear();
+ stream.str("");
+ stream << "Completed file processing\r\n";
+ stream << "\tLines Processed : " << lines_processed_ << "\r\n";
+ stream << "\tArc Commands Processed: " << num_arc_commands_ << "\r\n";
+ stream << "\tArc Segments Generated: " << p_current_firmware_->get_num_arc_segments_generated() << "\r\n";
+ stream << "\tTotal Seconds : " << total_seconds << "\r\n";
+ std::cout << stream.str();
+}
+
+std::string arc_interpolation::get_firmware_argument_description() const
+{
+ return p_current_firmware_->get_argument_description();
+} \ No newline at end of file
diff --git a/ArcWelderInverseProcessor/arc_interpolation.h b/ArcWelderInverseProcessor/arc_interpolation.h
new file mode 100644
index 0000000..bdea61f
--- /dev/null
+++ b/ArcWelderInverseProcessor/arc_interpolation.h
@@ -0,0 +1,56 @@
+#pragma once
+#include "firmware.h"
+#include <cstring>
+#include <fstream>
+#include "gcode_position.h"
+
+#define DEFAULT_GCODE_BUFFER_SIZE 50
+struct arc_interpolation_args
+{
+ arc_interpolation_args()
+ {
+
+ source_path = "";
+ target_path = "";
+ }
+ /// <summary>
+ /// Firmware arguments. Not all options will apply to all firmware types.
+ /// </summary>
+ firmware_arguments firmware_args;
+ /// <summary>
+ /// Required: the path to the source file containing G2/G3 commands.
+ /// </summary>
+ std::string source_path;
+ /// <summary>
+ /// Optional: the path to the target file. If left blank the source file will be overwritten by the target.
+ /// </summary>
+ std::string target_path;
+
+};
+
+class arc_interpolation
+{
+
+ public:
+ arc_interpolation();
+ arc_interpolation(arc_interpolation_args args);
+ virtual ~arc_interpolation();
+ void process();
+ /// <summary>
+ /// Outputs a string description of the firmware arguments.
+ /// </summary>
+ /// <returns></returns>
+ std::string get_firmware_argument_description() const;
+ private:
+ arc_interpolation_args args_;
+ gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size);
+ std::string source_path_;
+ std::string target_path_;
+ gcode_position* p_source_position_;
+ std::ofstream output_file_;
+ int lines_processed_ = 0;
+ firmware* p_current_firmware_;
+ int num_arc_commands_;
+
+};
+
diff --git a/ArcWelderInverseProcessor/arc_interpolation_structs.h b/ArcWelderInverseProcessor/arc_interpolation_structs.h
new file mode 100644
index 0000000..6f70f09
--- /dev/null
+++ b/ArcWelderInverseProcessor/arc_interpolation_structs.h
@@ -0,0 +1 @@
+#pragma once
diff --git a/ArcWelderInverseProcessor/firmware.cpp b/ArcWelderInverseProcessor/firmware.cpp
new file mode 100644
index 0000000..c8237e9
--- /dev/null
+++ b/ArcWelderInverseProcessor/firmware.cpp
@@ -0,0 +1,153 @@
+#include "firmware.h"
+#include "utilities.h"
+
+firmware::firmware() {
+ version_index_ = -1;
+ num_arc_segments_generated_ = 0;
+};
+
+firmware::firmware(firmware_arguments args) : args_(args) {
+ version_index_ = -1;
+ num_arc_segments_generated_ = 0;
+};
+
+std::string firmware::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise)
+{
+ throw "Function not yet implemented";
+}
+
+void firmware::apply_arguments()
+{
+ throw "Function not yet implemented";
+}
+
+void firmware::set_current_position(firmware_position& position)
+{
+ position_ = position;
+}
+
+void firmware::set_current_state(firmware_state& state)
+{
+ state_ = state;
+}
+
+int firmware::get_num_arc_segments_generated()
+{
+ return num_arc_segments_generated_;
+}
+
+std::string firmware::g1_command(firmware_position& target)
+{
+ num_arc_segments_generated_++;
+ std::string gcode = "G1 ";
+ gcode.reserve(96);
+
+ bool has_x = position_.x != target.x;
+ bool has_y = position_.y != target.y;
+ bool has_z = position_.z != target.z;
+ bool has_e = position_.e != target.e;
+ bool has_f = position_.f != target.f;
+ bool is_first_parameter = true;
+ if (has_x)
+ {
+ gcode += is_first_parameter ? "X" : " X";
+ gcode += utilities::dtos(state_.is_relative ? target.x - position_.x : target.x, 3);
+ is_first_parameter = false;
+ }
+
+ if (has_y)
+ {
+ gcode += is_first_parameter ? "Y" : " Y";
+ gcode += utilities::dtos(state_.is_relative ? target.y - position_.y : target.y, 3);
+ is_first_parameter = false;
+ }
+
+ if (has_z)
+ {
+ gcode += is_first_parameter ? "Z" : " Z";
+ gcode += utilities::dtos(state_.is_relative ? target.z - position_.z : target.z, 3);
+ is_first_parameter = false;
+ }
+
+ if (has_e)
+ {
+ gcode += is_first_parameter ? "E" : " E";
+ gcode += utilities::dtos(state_.is_extruder_relative ? target.e - position_.e : target.e, 3);
+ is_first_parameter = false;
+ }
+
+ if (has_f)
+ {
+ gcode += is_first_parameter ? "F" : " F";
+ gcode += utilities::dtos(target.f, 0);
+ }
+
+ return gcode;
+}
+
+bool firmware::is_valid_version(std::string version)
+{
+ if (version == LATEST_FIRMWARE_VERSION_NAME)
+ {
+ return true;
+ }
+ for (std::vector<std::string>::const_iterator i = version_names_.begin(); i != version_names_.end(); ++i) {
+ // process i
+ if (*i == version)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::vector<std::string> firmware::get_version_names()
+{
+ return version_names_;
+}
+
+bool firmware::get_g90_g91_influences_extruder()
+{
+ return args_.g90_g91_influences_extruder;
+}
+
+std::string firmware::get_argument_description() {
+
+ return args_.get_argument_description();
+}
+
+void firmware::set_versions(std::vector<std::string> version_names, std::string latest_release_version_name)
+{
+ args_.latest_release_version = latest_release_version_name;
+ std::string requested_version = args_.version;
+ if (requested_version == LATEST_FIRMWARE_VERSION_NAME || args_.version == latest_release_version_name)
+ {
+ requested_version = latest_release_version_name;
+ }
+ version_index_ = (int)version_names.size() - 1;
+ for (int i = 0; i < version_names.size(); i++)
+ {
+ std::string version = version_names[i];
+ version_names_.push_back(version);
+ if (version == requested_version)
+ {
+ version_index_ = i;
+ }
+ }
+}
+
+firmware_arguments firmware::get_default_arguments_for_current_version() const
+{
+ return firmware_arguments();
+}
+
+void firmware::set_arguments(firmware_arguments args)
+{
+ args_ = args;
+ this->apply_arguments();
+}
+
+firmware_arguments firmware::arguments_changed(firmware_arguments current_args, firmware_arguments new_args)
+{
+ return new_args;
+} \ No newline at end of file
diff --git a/ArcWelderInverseProcessor/firmware.h b/ArcWelderInverseProcessor/firmware.h
new file mode 100644
index 0000000..80d5d07
--- /dev/null
+++ b/ArcWelderInverseProcessor/firmware.h
@@ -0,0 +1,372 @@
+#pragma once
+#include "firmware_types.h"
+#include <vector>
+#include <sstream>
+#include <iomanip>
+#include <algorithm>
+
+#define DEFAULT_FIRMWARE_TYPE firmware_types::MARLIN_2
+#define LATEST_FIRMWARE_VERSION_NAME "LATEST_RELEASE"
+#define DEFAULT_FIRMWARE_VERSION_NAME LATEST_FIRMWARE_VERSION_NAME
+// Arc interpretation settings:
+#define DEFAULT_MM_PER_ARC_SEGMENT 1.0 // REQUIRED - The enforced maximum length of an arc segment
+#define DEFAULT_ARC_SEGMENTS_PER_R 0;
+#define DEFAULT_MIN_MM_PER_ARC_SEGMENT 0 /* OPTIONAL - the enforced minimum length of an interpolated segment. Must be smaller than
+ MM_PER_ARC_SEGMENT. Only has an effect if MIN_ARC_SEGMENTS > 0 or ARC_SEGMENTS_PER_SEC > 0 */
+ // If both MIN_ARC_SEGMENTS and ARC_SEGMENTS_PER_SEC is defined, the minimum calculated segment length is used.
+#define DEFAULT_MIN_ARC_SEGMENTS 0 // OPTIONAL - The enforced minimum segments in a full circle of the same radius.
+#define DEFAULT_ARC_SEGMENTS_PER_SEC 0 // OPTIONAL - Use feedrate to choose segment length.
+// approximation will not be used for the first segment. Subsequent segments will be corrected following DEFAULT_N_ARC_CORRECTION.
+#define DEFAULT_N_ARC_CORRECTIONS 24
+// This setting is for the gcode position processor to help interpret G90/G91 behavior
+#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false
+// This currently is only used in Smoothieware. The maximum error for line segments that divide arcs. Set to 0 to disable.
+#define DEFAULT_MM_MAX_ARC_ERROR 0.01
+
+struct firmware_state {
+ firmware_state() {
+ is_relative = false;
+ is_extruder_relative = false;
+ }
+ bool is_relative;
+ bool is_extruder_relative;
+};
+
+struct firmware_position {
+ firmware_position() {
+ x = 0;
+ y = 0;
+ z = 0;
+ e = 0;
+ f = 0;
+ }
+ double x;
+ double y;
+ double z;
+ double e;
+ double f;
+};
+
+struct firmware_arguments {
+public:
+
+ firmware_arguments() {
+ mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT;
+ arc_segments_per_r = DEFAULT_ARC_SEGMENTS_PER_R;
+ min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT;
+ min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS;
+ arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC;
+ n_arc_correction = DEFAULT_N_ARC_CORRECTIONS;
+ g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTRUDER;
+ mm_max_arc_error = DEFAULT_MM_MAX_ARC_ERROR;
+ version = DEFAULT_FIRMWARE_VERSION_NAME;
+ firmware_type = (firmware_types)DEFAULT_FIRMWARE_TYPE;
+ latest_release_version = LATEST_FIRMWARE_VERSION_NAME;
+
+ // add a list of all possible arguments, including aliases
+ all_arguments_.clear();
+ all_arguments_.push_back("mm_per_arc_segment");
+ all_arguments_.push_back("arc_segments_per_r");
+ all_arguments_.push_back("min_mm_per_arc_segment");
+ all_arguments_.push_back("min_arc_segments");
+ all_arguments_.push_back("arc_segments_per_sec");
+ all_arguments_.push_back("n_arc_correction");
+ all_arguments_.push_back("g90_g91_influences_extruder");
+ all_arguments_.push_back("mm_max_arc_error");
+ all_arguments_.push_back("min_circle_segments");
+ all_arguments_.push_back("min_arc_segment_mm");
+ all_arguments_.push_back("max_arc_segment_mm");
+ };
+
+ /// <summary>
+ /// The maximum mm per arc segment.
+ /// </summary>
+ double mm_per_arc_segment;
+ /// <summary>
+ /// The maximum segment length
+ /// </summary>
+ double arc_segments_per_r;
+ /// <summary>
+ /// The minimum mm per arc segment. If less than or equal to 0, this is disabled
+ /// </summary>
+ double min_mm_per_arc_segment;
+ /// <summary>
+ /// The number of arc segments that will be drawn per second based on the given feedrate.
+ /// If less than or equal to zero, this is disabled.
+ /// </summary>
+ double arc_segments_per_sec;
+ /// <summary>
+ /// This currently is only used in Smoothieware. The maximum error for line segments that divide arcs. Set to 0 to disable.
+ /// </summary>
+ double mm_max_arc_error;
+ /// <summary>
+ /// The minimum number of arc segments in a full circle of the arc's radius.
+ /// If less than or equal to zero, this is disabled
+ /// </summary>
+ int min_arc_segments;
+ /// <summary>
+ /// // Number of interpolated segments before true sin and cos corrections will be applied.
+ /// If less than or equal to zero, true sin and cos will always be used.
+ /// </summary>
+ int n_arc_correction;
+ /// <summary>
+ /// This value will set the behavior of G90/G91.
+ /// </summary>
+ bool g90_g91_influences_extruder;
+
+ /// <summary>
+ /// The type of firmware to use when interpolating.
+ /// </summary>
+ firmware_types firmware_type;
+ /// <summary>
+ /// The firmware version to use. Defaults to LATEST
+ /// </summary>
+ std::string version;
+ /// <summary>
+ /// True if the current version is the latest release. For informational purposes only
+ /// </summary>
+ std::string latest_release_version;
+ /// Aliases for variour parameters
+ int get_min_circle_segments() const
+ {
+ return min_arc_segments;
+ }
+ void set_min_circle_segments(int segments)
+ {
+ min_arc_segments = segments;
+ }
+
+ double get_min_arc_segment_mm() const
+ {
+ return min_mm_per_arc_segment;
+ }
+
+ void set_min_arc_segment_mm(double mm)
+ {
+ min_mm_per_arc_segment = mm;
+ }
+
+ double get_max_arc_segment_mm() const
+ {
+ return mm_per_arc_segment;
+ }
+
+ void set_max_arc_segment_mm(double mm)
+ {
+ mm_per_arc_segment = mm;
+ }
+
+ void set_used_arguments(std::vector<std::string> arguments)
+ {
+ used_arguments_ = arguments;
+ }
+
+ std::vector<std::string> get_unused_arguments()
+ {
+ std::vector<std::string> unused_arguments;
+ for (std::vector<std::string>::iterator it = all_arguments_.begin(); it != all_arguments_.end(); it++)
+ {
+ if (!is_argument_used(*it))
+ {
+ unused_arguments.push_back(*it);
+ }
+ }
+ return unused_arguments;
+ }
+
+ std::string get_unused_arguments_string()
+ {
+ std::string unusaed_argument_string = "";
+ std::vector<std::string> unused_argumnts = get_unused_arguments();
+ for (std::vector<std::string>::iterator it = unused_argumnts.begin(); it != unused_argumnts.end(); it++)
+ {
+ if (unusaed_argument_string.size() > 0)
+ {
+ unusaed_argument_string += ", ";
+ }
+ unusaed_argument_string += *it;
+ }
+ return unusaed_argument_string;
+ }
+
+ std::vector<std::string> get_available_arguments()
+ {
+ return used_arguments_;
+ }
+
+ std::string get_argument_description() {
+ std::stringstream stream;
+ stream << "Firmware Arguments:\n";
+ stream << "\tFirmware Type : " << firmware_type_names[firmware_type] << "\n";
+ stream << "\tFirmware Version : " << (version == LATEST_FIRMWARE_VERSION_NAME || version == latest_release_version ? latest_release_version + " (" + LATEST_FIRMWARE_VERSION_NAME + ")" : version) <<"\n";
+ stream << std::fixed << std::setprecision(0);
+ // Bool values
+ if (is_argument_used("g90_g91_influences_extruder"))
+ {
+ stream << "\tg90_g91_influences_extruder : " << (g90_g91_influences_extruder ? "True" : "False") << "\n";
+ }
+
+ // Int values
+ if (is_argument_used("min_arc_segments"))
+ {
+ stream << "\tmin_arc_segments : " << min_arc_segments << "\n";
+ }
+ if (is_argument_used("min_circle_segments"))
+ {
+ stream << "\tmin_circle_segments : " << get_min_circle_segments() << "\n";
+ }
+ if (is_argument_used("n_arc_correction"))
+ {
+ stream << "\tn_arc_correction : " << n_arc_correction << "\n";
+ }
+
+ stream << std::fixed << std::setprecision(2);
+ // Double values
+ //
+ if (is_argument_used("mm_per_arc_segment"))
+ {
+ stream << "\tmm_per_arc_segment : " << mm_per_arc_segment << "\n";
+ }
+ //
+ if (is_argument_used("arc_segments_per_r"))
+ {
+ stream << "\tarc_segments_per_r : " << arc_segments_per_r << "\n";
+ }
+ //
+ if (is_argument_used("min_mm_per_arc_segment"))
+ {
+ stream << "\tmin_mm_per_arc_segment : " << min_mm_per_arc_segment << "\n";
+ }
+ //
+ if (is_argument_used("arc_segments_per_sec"))
+ {
+ stream << "\tarc_segments_per_sec : " << arc_segments_per_sec << "\n";
+ }
+ //
+ if (is_argument_used("mm_max_arc_error"))
+ {
+ stream << "\tmm_max_arc_error : " << mm_max_arc_error << "\n";
+ }
+ //
+ if (is_argument_used("min_arc_segment_mm"))
+ {
+ stream << "\tmin_arc_segment_mm : " << get_min_arc_segment_mm() << "\n";
+ }
+ //
+ if (is_argument_used("max_arc_segment_mm"))
+ {
+ stream << "\tmax_arc_segment_mm : " << get_max_arc_segment_mm() << "\n";
+ }
+
+ std::string unused_argument_string = get_unused_arguments_string();
+ if (unused_argument_string.size() > 0)
+ {
+ stream << "The following parameters do not apply to this firmware version: " << unused_argument_string << "\n";
+ }
+ return stream.str();
+
+ }
+ bool is_argument_used(std::string argument_name)
+ {
+ return (std::find(used_arguments_.begin(), used_arguments_.end(), argument_name) != used_arguments_.end());
+ }
+ private:
+ std::vector<std::string> all_arguments_;
+ std::vector<std::string> used_arguments_;
+
+};
+
+class firmware
+{
+public:
+
+ firmware();
+
+ firmware(firmware_arguments args);
+
+ /// <summary>
+ /// Generate G1 gcode strings separated by line breaks representing the supplied G2/G3 command.
+ /// </summary>
+ /// <param name="current">The current printer position</param>
+ /// <param name="target">The target printer position</param>
+ /// <param name="i">Specifies the X offset for the arc's center.</param>
+ /// <param name="j">Specifies the Y offset for the arc's center.</param>
+ /// <param name="r">Specifies the radius of the arc. If r is greater than 0, this will override the i and j parameters.</param>
+ /// <param name="is_clockwise">If true, this is a G2 command. If false, this is a G3 command.</param>
+ /// <param name="is_relative">If this is true, the extruder is currently in relative mode. Else it is in absolute mode.</param>
+ /// <param name="offest_absolute_e">This is the absolute offset for absolute E coordinates if the extruder is not in relative mode.</param>
+ /// <returns></returns>
+ virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise);
+
+ /// <summary>
+ /// Sets the current position. Should be called before interpolate_arc.
+ /// </summary>
+ /// <param name="position">The position to set</param>
+ void set_current_position(firmware_position& position);
+
+ /// <summary>
+ /// Sets firmware offsets and the xyze axis mode.
+ /// </summary>
+ /// <param name="state">The state to set</param>
+ void set_current_state(firmware_state& state);
+ /// <summary>
+ /// Create a G1 command from the current position and offsets.
+ /// </summary>
+ /// <param name="target">The position of the printer after the G1 command is completed.</param>
+ /// <returns>The G1 command</returns>
+ virtual std::string g1_command(firmware_position& target);
+
+ /// <summary>
+ /// Checks a string to see if it is a valid version.
+ /// </summary>
+ /// <param name="version">The version to check.</param>
+ /// <returns>True if the supplied version is valid</returns>
+ bool is_valid_version(std::string version);
+
+ /// <summary>
+ /// Returns all valid versions for this firmware.
+ /// </summary>
+ /// <returns>Vector of strings, one for each supported version</returns>
+ std::vector<std::string> get_version_names();
+
+ /// <summary>
+ /// Returns the current g90_g91_influences_extruder value for the firmware.
+ /// </summary>
+ /// <returns></returns>
+ bool get_g90_g91_influences_extruder();
+
+ /// <summary>
+ /// Returns the number of arc segments that were generated from g2/g3 commands.
+ /// </summary>
+ /// <returns></returns>
+ int get_num_arc_segments_generated();
+
+ /// <summary>
+ /// Outputs a string description of the firmware arguments.
+ /// </summary>
+ /// <returns></returns>
+ std::string get_argument_description();
+
+ /// <summary>
+ /// Sets all available versions names and the version index based on args_.version
+ /// </summary>
+ /// <returns></returns>
+ void set_versions(std::vector<std::string> version_names, std::string latest_release_version_name);
+
+ virtual firmware_arguments get_default_arguments_for_current_version()const;
+
+
+ void set_arguments(firmware_arguments args);
+
+ virtual void apply_arguments();
+
+protected:
+ firmware_position position_;
+ firmware_state state_;
+ firmware_arguments args_;
+ std::vector<std::string> version_names_;
+ int version_index_;
+ int num_arc_segments_generated_;
+
+ virtual firmware_arguments arguments_changed(firmware_arguments current_args, firmware_arguments new_args);
+};
diff --git a/ArcWelderInverseProcessor/firmware_types.h b/ArcWelderInverseProcessor/firmware_types.h
new file mode 100644
index 0000000..775a731
--- /dev/null
+++ b/ArcWelderInverseProcessor/firmware_types.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <string>
+
+enum firmware_types { MARLIN_1=0, MARLIN_2=1, REPETIER=2, PRUSA=3, SMOOTHIEWARE=4};
+#define NUM_FIRMWARE_TYPES 5
+static const std::string firmware_type_names[NUM_FIRMWARE_TYPES] = {
+ "MARLIN_1", "MARLIN_2", "REPETIER", "PRUSA", "SMOOTHIEWARE"
+};
+
+
+
+
diff --git a/ArcWelderInverseProcessor/inverse_processor.cpp b/ArcWelderInverseProcessor/inverse_processor.cpp
deleted file mode 100644
index c2f0b4d..0000000
--- a/ArcWelderInverseProcessor/inverse_processor.cpp
+++ /dev/null
@@ -1,445 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Arc Welder: Inverse Processor Console Application
-//
-// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
-// This reduces file size and the number of gcodes per second.
-//
-// Built using the 'Arc Welder: Anti Stutter' library
-//
-// Copyright(C) 2020 - Brad Hochgesang
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// This program is free software : you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
-// GNU Affero General Public License for more details.
-//
-//
-// You can contact the author at the following email address:
-// FormerLurker@pm.me
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// This file includes portions from Marlin's motion_control.c file since it is intended to test some firmware modifications.
-// This file was included in the AntiStutter project for convenience, and will not be included within the final version.
-/*
- motion_control.c - high level interface for issuing motion commands
- Part of Grbl
-
- Copyright (c) 2009-2011 Simen Svale Skogsrud
- Copyright (c) 2011 Sungeun K. Jeon
- Copyright (c) 2020 Brad Hochgesang
-
- Grbl is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Grbl is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Grbl. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "inverse_processor.h"
-#include <cmath>
-
-//#include "Marlin.h"
-//#include "stepper.h"
-//#include "planner.h"
-
-inverse_processor::inverse_processor(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs)
-{
- source_path_ = source_path;
- target_path_ = target_path;
- p_source_position_ = new gcode_position(get_args_(g90_g91_influences_extruder, buffer_size));
- cs_ = cs;
- // ** Gloabal Variable Definition **
- // 20200417 - FormerLurker - Declare two globals and pre-calculate some values that will reduce the
- // amount of trig funcitons we need to call while printing. For the price of having two globals we
- // save one trig calc per G2/G3 for both MIN_ARC_SEGMENTS and MIN_MM_PER_ARC_SEGMENT. This is a good trade IMHO.
-
-
-#ifdef MIN_ARC_SEGMENTS
-// Determines the radius at which the transition from using MM_PER_ARC_SEGMENT to MIN_ARC_SEGMENTS
- arc_max_radius_threshold = MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS));
-#endif
-/*
-#if defined(MIN_ARC_SEGMENTS) && defined(MIN_MM_PER_ARC_SEGMENT)
- // Determines the radius at which the transition from using MIN_ARC_SEGMENTS to MIN_MM_PER_ARC_SEGMENT.
- arc_min_radius_threshold = MIN_MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS));
-#endif
-*/
-}
-
-gcode_position_args inverse_processor::get_args_(bool g90_g91_influences_extruder, int buffer_size)
-{
- gcode_position_args args;
- // Configure gcode_position_args
- args.g90_influences_extruder = g90_g91_influences_extruder;
- args.position_buffer_size = buffer_size;
- args.autodetect_position = true;
- args.home_x = 0;
- args.home_x_none = true;
- args.home_y = 0;
- args.home_y_none = true;
- args.home_z = 0;
- args.home_z_none = true;
- args.shared_extruder = true;
- args.zero_based_extruder = true;
-
-
- args.default_extruder = 0;
- args.xyz_axis_default_mode = "absolute";
- args.e_axis_default_mode = "absolute";
- args.units_default = "millimeters";
- args.location_detection_commands = std::vector<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;
-}
-
-inverse_processor::~inverse_processor()
-{
- delete p_source_position_;
-}
-
-void inverse_processor::process()
-{
- // Create a stringstream we can use for messaging.
- std::stringstream stream;
-
- int read_lines_before_clock_check = 5000;
- //std::cout << "stabilization::process_file - Processing file.\r\n";
- stream << "Decompressing gcode file.";
- stream << "Source File: " << source_path_ << "\n";
- stream << "Target File: " << target_path_ << "\n";
- std::cout << stream.str();
- const clock_t start_clock = clock();
-
- // Create the source file read stream and target write stream
- std::ifstream gcode_file;
- gcode_file.open(source_path_.c_str());
- output_file_.open(target_path_.c_str());
- std::string line;
- int lines_with_no_commands = 0;
- gcode_file.sync_with_stdio(false);
- output_file_.sync_with_stdio(false);
- gcode_parser parser;
- int gcodes_processed = 0;
- if (gcode_file.is_open())
- {
- if (output_file_.is_open())
- {
- //stream.clear();
- //stream.str("");
- //stream << "Opened file for reading. File Size: " << file_size_ << "\n";
- //std::cout << stream.str();
- parsed_command cmd;
- // Communicate every second
- while (std::getline(gcode_file, line))
- {
- lines_processed_++;
-
- cmd.clear();
- parser.try_parse_gcode(line.c_str(), cmd);
- bool has_gcode = false;
- if (cmd.gcode.length() > 0)
- {
- has_gcode = true;
- gcodes_processed++;
- }
- else
- {
- lines_with_no_commands++;
- }
-
- p_source_position_->update(cmd, lines_processed_, gcodes_processed, -1);
-
- if (cmd.command == "G2" || cmd.command == "G3")
- {
- position* p_cur_pos = p_source_position_->get_current_position_ptr();
- position* p_pre_pos = p_source_position_->get_previous_position_ptr();
- float position[4];
- position[X_AXIS] = static_cast<float>(p_pre_pos->get_gcode_x());
- position[Y_AXIS] = static_cast<float>(p_pre_pos->get_gcode_y());
- position[Z_AXIS] = static_cast<float>(p_pre_pos->get_gcode_z());
- position[E_AXIS] = static_cast<float>(p_pre_pos->get_current_extruder().get_offset_e());
- float target[4];
- target[X_AXIS] = static_cast<float>(p_cur_pos->get_gcode_x());
- target[Y_AXIS] = static_cast<float>(p_cur_pos->get_gcode_y());
- target[Z_AXIS] = static_cast<float>(p_cur_pos->get_gcode_z());
- target[E_AXIS] = static_cast<float>(p_cur_pos->get_current_extruder().get_offset_e());
- float offset[2];
- offset[0] = 0.0;
- offset[1] = 0.0;
- for (unsigned int index = 0; index < cmd.parameters.size(); index++)
- {
- parsed_command_parameter p = cmd.parameters[index];
- if (p.name == "I")
- {
- offset[0] = static_cast<float>(p.double_value);
- }
- else if (p.name == "J")
- {
- offset[1] = static_cast<float>(p.double_value);
- }
- }
- float radius = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc
- uint8_t isclockwise = cmd.command == "G2" ? 1 : 0;
- output_relative_ = p_cur_pos->is_extruder_relative;
- mc_arc(position, target, offset, static_cast<float>(p_cur_pos->f), radius, isclockwise, 0);
- }
- else
- {
- output_file_ << line << "\n";
- }
-
- }
- output_file_.close();
- }
- else
- {
- std::cout << "Unable to open the output file for writing.\n";
- }
- std::cout << "Closing the input file.\n";
- gcode_file.close();
- }
- else
- {
- std::cout << "Unable to open the gcode file for processing.\n";
- }
-
- const clock_t end_clock = clock();
- const double total_seconds = (static_cast<double>(end_clock) - static_cast<double>(start_clock)) / CLOCKS_PER_SEC;
-
- stream.clear();
- stream.str("");
- stream << "Completed file processing\r\n";
- stream << "\tLines Processed : " << lines_processed_ << "\r\n";
- stream << "\tTotal Seconds : " << total_seconds << "\r\n";
- stream << "\tExtra Trig Count : " << trig_calc_count << "\r\n";
- stream << "\tTotal E Adjustment : " << total_e_adjustment << "\r\n";
- std::cout << stream.str();
-}
-
-// The arc is approximated by generating a huge number of tiny, linear segments. The length of each
-// segment is configured in settings.mm_per_arc_segment.
-void inverse_processor::mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder)
-{
- // Extract the position to reduce indexing at the cost of a few bytes of mem
- float p_x = position[X_AXIS];
- float p_y = position[Y_AXIS];
- float p_z = position[Z_AXIS];
- float p_e = position[E_AXIS];
-
- float t_x = target[X_AXIS];
- float t_y = target[Y_AXIS];
- float t_z = target[Z_AXIS];
- float t_e = target[E_AXIS];
-
- float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location
- float r_axis_y = -offset[Y_AXIS];
- float center_axis_x = p_x - r_axis_x;
- float center_axis_y = p_y - r_axis_y;
- float travel_z = t_z - p_z;
- float extruder_travel_total = t_e - p_e;
-
- float rt_x = t_x - center_axis_x;
- float rt_y = t_y - center_axis_y;
- // 20200419 - Add a variable that will be used to hold the arc segment length
- float mm_per_arc_segment = cs_.mm_per_arc_segment;
-
- // CCW angle between position and target from circle center. Only one atan2() trig computation required.
- float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y);
- if (angular_travel_total < 0) { angular_travel_total += (float)(2 * M_PI); }
-
- bool check_mm_per_arc_segment_max = false;
- if (cs_.min_arc_segments > 0)
- {
- // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
- // Do this before converting the angular travel for clockwise rotation
- mm_per_arc_segment = (float)(radius * ((2.0f * M_PI) / cs_.min_arc_segments));
- check_mm_per_arc_segment_max = true;
- }
-
- if (cs_.arc_segments_per_sec > 0)
- {
- // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
- float mm_per_arc_segment_sec = (float)((feed_rate / 60.0f) * (1.0f / cs_.arc_segments_per_sec));
- if (mm_per_arc_segment_sec < mm_per_arc_segment)
- mm_per_arc_segment = mm_per_arc_segment_sec;
- check_mm_per_arc_segment_max = true;
- }
-
- if (cs_.min_mm_per_arc_segment > 0)
- {
- check_mm_per_arc_segment_max = true;
- // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined
- // This prevents a very high number of segments from being generated for curves of a short radius
- if (mm_per_arc_segment < cs_.min_mm_per_arc_segment) mm_per_arc_segment = cs_.min_mm_per_arc_segment;
- }
-
- if (check_mm_per_arc_segment_max && mm_per_arc_segment > cs_.mm_per_arc_segment) mm_per_arc_segment = cs_.mm_per_arc_segment;
-
-
-
- // Adjust the angular travel if the direction is clockwise
- if (isclockwise) { angular_travel_total -= (float)(2 * M_PI); }
-
- //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
- //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
- if (p_x == t_x && p_y == t_y && angular_travel_total == 0)
- {
- angular_travel_total += (float)(2 * M_PI);
- }
- //end fix G03
-
- // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are
- // calculating here
- float millimeters_of_travel_arc = hypot(angular_travel_total * radius, std::fabs(travel_z));
- if (millimeters_of_travel_arc < 0.001) { return; }
- // Calculate the total travel per segment
- // Calculate the number of arc segments
- uint16_t segments = static_cast<uint16_t>(ceil(millimeters_of_travel_arc / mm_per_arc_segment));
-
-
- // Calculate theta per segments and linear (z) travel per segment
- float theta_per_segment = angular_travel_total / segments;
- float linear_per_segment = travel_z / (segments);
- // Calculate the extrusion amount per segment
- float segment_extruder_travel = extruder_travel_total / (segments);
- /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
- and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
- r_T = [cos(phi) -sin(phi);
- sin(phi) cos(phi] * r ;
-
- For arc generation, the center of the circle is the axis of rotation and the radius vector is
- defined from the circle center to the initial position. Each line segment is formed by successive
- vector rotations. This requires only two cos() and sin() computations to form the rotation
- matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
- all double numbers are single precision on the Arduino. (True double precision will not have
- round off issues for CNC applications.) Single precision error can accumulate to be greater than
- tool precision in some cases. Therefore, arc path correction is implemented.
-
- The small angle approximation was removed because of excessive errors for small circles (perhaps unique to
- 3d printing applications, causing significant path deviation and extrusion issues).
- Now there will be no corrections applied, but an accurate initial sin and cos will be calculated.
- This seems to work with a very high degree of accuracy and results in much simpler code.
-
- Finding a faster way to approximate sin, knowing that there can be substantial deviations from the true
- arc when using the previous approximation, would be beneficial.
- */
-
- // Don't bother calculating cot_T or sin_T if there is only 1 segment.
- if (segments > 1)
- {
- // Initialize the extruder axis
- float cos_T, sin_T, sin_Ti, cos_Ti;
- //float cos_T = cos(theta_per_segment);
- //float sin_T = sin(theta_per_segment);
- float sq_theta_per_segment = theta_per_segment * theta_per_segment;
- sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6;
- cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
-
- //cos_T = 1 - 0.5 * theta_per_segment * theta_per_segment; // Small angle approximation
- //sin_T = theta_per_segment;
- float r_axisi;
- uint16_t i;
- int8_t count = 0;
- for (i = 1; i < segments; i++) { // Increment (segments-1)
-
- if (count < cs_.n_arc_correction) {
- // Apply vector rotation matrix
- r_axisi = r_axis_x * sin_T + r_axis_y * cos_T;
- r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T;
- r_axis_y = r_axisi;
- count++;
- }
- else {
- // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
- // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
- cos_Ti = cos(i * theta_per_segment);
- sin_Ti = sin(i * theta_per_segment);
- r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti;
- r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti;
- count = 0;
-
- }
-
-
-
- // Update arc_target location
- p_x = center_axis_x + r_axis_x;
- p_y = center_axis_y + r_axis_y;
- p_z += linear_per_segment;
- p_e += segment_extruder_travel;
- // We can't clamp to the target because we are interpolating! We would need to update a position, clamp to it
- // after updating from calculated values.
- //clamp_to_software_endstops(position);
- plan_buffer_line(p_x, p_y, travel_z > 0, p_z, p_e, feed_rate, extruder);
- }
- }
- // Ensure last segment arrives at target location.
- // Here we could clamp, but why bother. We would need to update our current position, clamp to it
- //clamp_to_software_endstops(target);
- plan_buffer_line(t_x, t_y, travel_z> 0, t_z, t_e, feed_rate, extruder);
- position[X_AXIS] = t_x;
- position[Y_AXIS] = t_y;
- position[Z_AXIS] = t_z;
- position[E_AXIS] = t_e;
-}
-
-void inverse_processor::clamp_to_software_endstops(float* target)
-{
- // Do nothing, just added to keep mc_arc identical to the firmware version
- return;
-}
-
-void inverse_processor::plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
-{
- std::stringstream stream;
- stream << std::fixed;
-
- position * previous_pos = p_source_position_->get_previous_position_ptr();
- position* current_pos = p_source_position_->get_current_position_ptr();
-
- stream << "G1 X" << std::setprecision(3) << x << " Y" << y;
- if (has_z)
- {
- stream << " Z" << z;
- }
-
- double output_e = e;
- if (previous_pos->is_extruder_relative)
- {
- output_e = e - previous_pos->get_current_extruder().get_offset_e();
- }
-
- stream << std::setprecision(5) << " E" << output_e;
-
-
- if (feed_rate != previous_pos->f)
- {
- stream << std::setprecision(0) << " F" << feed_rate;
- }
-
- if (!current_pos->command.comment.empty())
- {
- stream << ";" << current_pos->command.comment;
- }
- stream << "\n";
- output_file_ << stream.str();
-}
diff --git a/ArcWelderInverseProcessor/inverse_processor.h b/ArcWelderInverseProcessor/inverse_processor.h
deleted file mode 100644
index 42dd8bc..0000000
--- a/ArcWelderInverseProcessor/inverse_processor.h
+++ /dev/null
@@ -1,94 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Arc Welder: Inverse Processor Console Application
-//
-// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
-// This reduces file size and the number of gcodes per second.
-//
-// Built using the 'Arc Welder: Anti Stutter' library
-//
-// Copyright(C) 2020 - Brad Hochgesang
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// This program is free software : you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
-// GNU Affero General Public License for more details.
-//
-//
-// You can contact the author at the following email address:
-// FormerLurker@pm.me
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma once
-
-#include <string>
-#include "gcode_position.h"
-#include <iostream>
-#include <string>
-#include <sstream>
-#include <iomanip>
-#include <fstream>
-
-typedef unsigned char uint8_t;
-typedef unsigned short uint16_t;
-typedef signed char int8_t;
-#define M_PI 3.14159265358979323846 // pi
-enum AxisEnum { X_AXIS = 0, Y_AXIS= 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 };
-// Arc interpretation settings:
-#define DEFAULT_MM_PER_ARC_SEGMENT 1.0 // REQUIRED - The enforced maximum length of an arc segment
-#define DEFAULT_MIN_MM_PER_ARC_SEGMENT 0 /* OPTIONAL - the enforced minimum length of an interpolated segment. Must be smaller than
- MM_PER_ARC_SEGMENT. Only has an effect if MIN_ARC_SEGMENTS > 0 or ARC_SEGMENTS_PER_SEC > 0 */
- // If both MIN_ARC_SEGMENTS and ARC_SEGMENTS_PER_SEC is defined, the minimum calculated segment length is used.
-#define DEFAULT_MIN_ARC_SEGMENTS 0 // OPTIONAL - The enforced minimum segments in a full circle of the same radius.
-#define DEFAULT_ARC_SEGMENTS_PER_SEC 0 // OPTIONAL - Use feedrate to choose segment length.
-// approximation will not be used for the first segment. Subsequent segments will be corrected following DEFAULT_N_ARC_CORRECTION.
-#define DEFAULT_N_ARC_CORRECTIONS 24
-
-struct ConfigurationStore {
- ConfigurationStore() {
- mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT;
- min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT;
- min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS;
- arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC;
- n_arc_correction = DEFAULT_N_ARC_CORRECTIONS;
- }
- float mm_per_arc_segment; // This value is ALWAYS used.
- float min_mm_per_arc_segment; // if less than or equal to 0, this is disabled
- int min_arc_segments; // If less than or equal to zero, this is disabled
- double arc_segments_per_sec; // If less than or equal to zero, this is disabled
- int n_arc_correction;
-
-};
-class inverse_processor {
-public:
- inverse_processor(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs = ConfigurationStore());
- virtual ~inverse_processor();
- void process();
- void mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder);
-
-private:
- ConfigurationStore cs_;
- gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size);
- std::string source_path_;
- std::string target_path_;
- gcode_position* p_source_position_;
- std::ofstream output_file_;
- bool output_relative_;
- float arc_max_radius_threshold;
- //float arc_min_radius_threshold;
- float total_e_adjustment;
- int trig_calc_count = 0;
- int lines_processed_ = 0;
- void clamp_to_software_endstops(float* target);
-
- void plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target=NULL);
-
-};
-
-
-
-
-
diff --git a/ArcWelderInverseProcessor/marlin_1.cpp b/ArcWelderInverseProcessor/marlin_1.cpp
new file mode 100644
index 0000000..0cca4e9
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_1.cpp
@@ -0,0 +1,325 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 1 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#include "marlin_1.h"
+#include "utilities.h"
+marlin_1::marlin_1(firmware_arguments args) : firmware(args) {
+ feedrate_mm_s = 0;
+ current_position = new float[MARLIN_XYZE];
+ apply_arguments();
+};
+
+marlin_1::~marlin_1()
+{
+ delete current_position;
+}
+
+void marlin_1::apply_arguments()
+{
+ static const std::vector<std::string> marlin_1_firmware_version_names{
+ "1.1.9.1"
+ };
+ set_versions(marlin_1_firmware_version_names, "1.1.9.1");
+ marlin_1_version_ = (marlin_1::marlin_1_firmware_versions)version_index_;
+ std::vector<std::string> used_arguments;
+ /* Add case statement if we ever add any additional firmware versions
+ switch (marlin_1_version_)
+ {
+ default:*/
+ plan_arc_ = &marlin_1::plan_arc_1_1_9_1;
+ used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" };
+ //break;
+ //}
+
+ args_.set_used_arguments(used_arguments);
+}
+
+firmware_arguments marlin_1::get_default_arguments_for_current_version() const
+{
+ // Start off with the current args so they are set up correctly for this firmware type and version
+ firmware_arguments default_args = args_;
+
+ // firmware defaults
+ default_args.g90_g91_influences_extruder = false;
+ // Add the switch in here in case we want to add more versions.
+ //switch (marlin_1_version_)
+ //{
+ //default:
+ // Active Settings
+ default_args.mm_per_arc_segment = 1.0f;
+ default_args.min_arc_segments = 24;
+ default_args.n_arc_correction = 25;
+ // Inactive Settings
+ default_args.arc_segments_per_r = 0;
+ default_args.min_mm_per_arc_segment = 0;
+ default_args.arc_segments_per_sec = 0;
+ // Settings that do not apply
+ default_args.mm_max_arc_error = 0;
+ //break;
+ //}
+
+ return default_args;
+}
+
+std::string marlin_1::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise)
+{
+ // Clear the current list of gcodes
+ gcodes_.clear();
+
+ // Setup the current position
+ current_position[X_AXIS] = static_cast<float>(position_.x);
+ current_position[Y_AXIS] = static_cast<float>(position_.y);
+ current_position[Z_AXIS] = static_cast<float>(position_.z);
+ current_position[E_AXIS] = static_cast<float>(position_.e);
+ float marlin_target[MARLIN_XYZE];
+ marlin_target[X_AXIS] = static_cast<float>(target.x);
+ marlin_target[Y_AXIS] = static_cast<float>(target.y);
+ marlin_target[Z_AXIS] = static_cast<float>(target.z);
+ marlin_target[E_AXIS] = static_cast<float>(target.e);
+ float marlin_offset[2];
+ marlin_offset[0] = static_cast<float>(i);
+ marlin_offset[1] = static_cast<float>(j);
+ // TODO: handle R form!!
+
+ // Set the feedrate
+ feedrate_mm_s = static_cast<float>(target.f);
+ uint8_t marlin_isclockwise = is_clockwise ? 1 : 0;
+
+ (this->*plan_arc_)(marlin_target, marlin_offset, marlin_isclockwise);
+
+ return gcodes_;
+}
+
+/// <summary>
+/// This function was adapted from the 1.1.9.1 release of Marlin firmware, which can be found at the following link:
+/// https://github.com/MarlinFirmware/Marlin/blob/1314b31d97bba8cd74c6625c47176d4692f57790/Marlin/Marlin_main.cpp
+/// Copyright Notice found on that page:
+///
+///
+/// Marlin 3D Printer Firmware
+/// Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+///
+/// Based on Sprinter and grbl.
+/// Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+///
+/// This program is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, either version 3 of the License, or
+/// (at your option) any later version.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/// GNU General Public License for more details.
+///
+/// You should have received a copy of the GNU General Public License
+/// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/// </summary>
+/// <param name="cart">The target position</param>
+/// <param name="offset">The I and J offset</param>
+/// <param name="clockwise">Is the motion clockwise or counterclockwise</param>
+void marlin_1::plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination position
+ const float(&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise // Clockwise?
+)
+{
+ // cnc workspace planes variables -- Note: This is NOT implemented, but is added for completeness in case it is in the future.
+ int active_extruder = 0;
+ AxisEnum p_axis, q_axis, l_axis;
+ p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS;
+
+
+ // Radius vector from center to current location
+ float r_P = -offset[0], r_Q = -offset[1];
+
+ const float radius = utilities::hypotf(r_P, r_Q),
+ center_P = current_position[p_axis] - r_P,
+ center_Q = current_position[q_axis] - r_Q,
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ linear_travel = cart[l_axis] - current_position[l_axis],
+ extruder_travel = cart[E_CART] - current_position[E_CART];
+
+ // CCW angle of rotation between position and target from the circle center. Only one atan2() trig computation required.
+ float angular_travel = (float)utilities::atan2((double)r_P * rt_Y - (double)r_Q * rt_X, (double)r_P * rt_X + (double)r_Q * rt_Y);
+ if (angular_travel < 0) angular_travel += utilities::radiansf(360.0f);
+ if (clockwise) angular_travel -= utilities::radiansf(360.0f);
+
+ // Make a circle if the angular rotation is 0 and the target is current position
+ if (angular_travel == 0 && current_position[p_axis] == cart[p_axis] && current_position[q_axis] == cart[q_axis])
+ angular_travel = utilities::radiansf(360.0f);
+
+ const float flat_mm = radius * angular_travel,
+ mm_of_travel = linear_travel ? utilities::hypotf(flat_mm, linear_travel) : utilities::absf(flat_mm);
+ if (mm_of_travel < 0.001f) return;
+
+ uint16_t segments = (uint16_t)utilities::floorf(mm_of_travel / (float)(args_.mm_per_arc_segment));
+ NOLESS(segments, 1);
+
+ /**
+ * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ * and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ * r_T = [cos(phi) -sin(phi);
+ * sin(phi) cos(phi)] * r ;
+ *
+ * For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ * defined from the circle center to the initial position. Each line segment is formed by successive
+ * vector rotations. This requires only two cos() and sin() computations to form the rotation
+ * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ * all double numbers are single precision on the Arduino. (True double precision will not have
+ * round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ * tool precision in some cases. Therefore, arc path correction is implemented.
+ *
+ * Small angle approximation may be used to reduce computation overhead further. This approximation
+ * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words,
+ * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ * issue for CNC machines with the single precision Arduino calculations.
+ *
+ * This approximation also allows plan_arc to immediately insert a line segment into the planner
+ * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead.
+ * This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float raw[MARLIN_XYZE];
+ const float theta_per_segment = angular_travel / segments,
+ linear_per_segment = linear_travel / segments,
+ extruder_per_segment = extruder_travel / segments,
+ sin_T = theta_per_segment,
+ cos_T = 1 - 0.5f * utilities::sqf(theta_per_segment); // Small angle approximation
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+
+ // Initialize the extruder axis
+ raw[E_CART] = current_position[E_CART];
+
+ const float fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ int8_t arc_recalc_count = 0;
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+
+
+ for (uint16_t i = 1; i < segments; i++) // Iterate (segments-1) times
+ {
+
+ if (args_.n_arc_correction > 1 && --arc_recalc_count)
+ {
+ // Apply vector rotation matrix to previous r_P / 1
+ const float r_new_Y = r_P * sin_T + r_Q * cos_T;
+ r_P = r_P * cos_T - r_Q * sin_T;
+ r_Q = r_new_Y;
+ }
+ else
+ {
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ // To reduce stuttering, the sin and cos could be computed at different times.
+ // For now, compute both at the same time.
+ const float cos_Ti = (float)utilities::cos(i * (double)theta_per_segment), sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ r_Q = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + r_P;
+ raw[q_axis] = center_Q + r_Q;
+ raw[l_axis] += linear_per_segment;
+ raw[E_CART] += extruder_per_segment;
+
+ clamp_to_software_endstops(raw);
+
+
+ if (!buffer_line_kinematic(raw, feedrate_mm_s, active_extruder))
+ break;
+ }
+
+ buffer_line_kinematic(cart, feedrate_mm_s, active_extruder);
+
+ COPY(current_position, cart);
+}
+
+void marlin_1::NOLESS(uint16_t &x, uint16_t y)
+{
+ if (x < y)
+ x = y;
+}
+
+float marlin_1::MMS_SCALED(float x)
+{
+ // No scaling
+ return x;
+}
+
+void marlin_1::COPY(float target[MARLIN_XYZE], const float(&source)[MARLIN_XYZE])
+{
+ // This is a slow copy, but speed isn't much of an issue here.
+ for (int i = 0; i < MARLIN_XYZE; i++)
+ {
+ target[i] = source[i];
+ }
+}
+
+
+void marlin_1::clamp_to_software_endstops(const float(&raw)[MARLIN_XYZE])
+{
+ // Do nothing, just added to keep mc_arc identical to the firmware version
+ return;
+}
+
+//void marlin::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+bool marlin_1::buffer_line_kinematic(const float(&cart)[MARLIN_XYZE], double fr_mm_s, int active_extruder)
+{
+
+ // create the target position
+ firmware_position target;
+ target.x = cart[AxisEnum::X_AXIS];
+ target.y = cart[AxisEnum::Y_AXIS];
+ target.z = cart[AxisEnum::Z_AXIS];
+ target.e = cart[AxisEnum::E_AXIS];
+ target.f = fr_mm_s;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ // update the current position
+ set_current_position(target);
+ return true;
+}
diff --git a/ArcWelderInverseProcessor/marlin_1.h b/ArcWelderInverseProcessor/marlin_1.h
new file mode 100644
index 0000000..0fc11c4
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_1.h
@@ -0,0 +1,95 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 1 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+#include "firmware.h"
+
+#define NUM_MARLIN_1_FIRMWARE_VERSIONS 1
+
+
+#define MARLIN_XYZE 5
+
+class marlin_1 :
+ public firmware
+{
+public:
+ enum class marlin_1_firmware_versions { V1_1_9_1 = 0 };
+ /// <summary>
+ /// Types and enums taken from https://github.com/MarlinFirmware/Marlin/blob/1314b31d97bba8cd74c6625c47176d4692f57790/Marlin/enum.h
+ /// Note that HANGPRINTER has been disabled to reduce impementation complexity
+ /// </summary>
+ enum AxisEnum : unsigned char {
+ X_AXIS = 0,
+ A_AXIS = 0,
+ Y_AXIS = 1,
+ B_AXIS = 1,
+ Z_AXIS = 2,
+ C_AXIS = 2,
+ E_CART = 3,
+//#if ENABLED(HANGPRINTER) // Hangprinter order: A_AXIS, B_AXIS, C_AXIS, D_AXIS, E_AXIS
+// D_AXIS = 3,
+// E_AXIS = 4,
+//#else
+ E_AXIS = 3,
+//#endif
+ X_HEAD, Y_HEAD, Z_HEAD,
+ ALL_AXES = 0xFE,
+ NO_AXIS = 0xFF
+ };
+
+ marlin_1(firmware_arguments args);
+ virtual ~marlin_1();
+ virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override;
+ virtual firmware_arguments get_default_arguments_for_current_version() const override;
+ virtual void apply_arguments() override;
+private:
+ marlin_1_firmware_versions marlin_1_version_;
+ std::string gcodes_;
+ float* current_position;
+ float feedrate_mm_s;
+
+ /// <summary>
+ /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name.
+ /// </summary>
+ typedef void(marlin_1::* plan_arc_func)(const float(&cart)[MARLIN_XYZE], // Destination position
+ const float(&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise // Clockwise?
+ );
+
+ void plan_arc_1_1_9_1(const float(&cart)[MARLIN_XYZE], // Destination position
+ const float(&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise // Clockwise?
+ );
+
+ plan_arc_func plan_arc_;
+ // Marlin Function Defs
+ void NOLESS(uint16_t& x, uint16_t y);
+ float MMS_SCALED(float x);
+ void COPY(float target[MARLIN_XYZE], const float (&source)[MARLIN_XYZE]);
+ bool buffer_line_kinematic(const float (&cart)[MARLIN_XYZE], double fr_mm_s, int active_extruder);
+ void clamp_to_software_endstops(const float (&raw)[MARLIN_XYZE]);
+};
+
diff --git a/ArcWelderInverseProcessor/marlin_2.cpp b/ArcWelderInverseProcessor/marlin_2.cpp
new file mode 100644
index 0000000..afd17a6
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_2.cpp
@@ -0,0 +1,606 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 2 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "marlin_2.h"
+#include "utilities.h"
+marlin_2::marlin_2(firmware_arguments args) : firmware(args) {
+ feedrate_mm_s = 0;
+ current_position = new float[MARLIN_2_XYZE];
+ apply_arguments();
+};
+
+marlin_2::~marlin_2()
+{
+ delete current_position;
+}
+
+void marlin_2::apply_arguments()
+{
+ static const std::vector<std::string> marlin_2_firmware_version_names{
+ "2.0.9.1", "2.0.9.2"
+ };
+ set_versions(marlin_2_firmware_version_names, "2.0.9.1");
+ marlin_2_version_ = (marlin_2::marlin_2_firmware_versions)version_index_;
+ std::vector<std::string> used_arguments;
+ switch (marlin_2_version_)
+ {
+ case marlin_2::marlin_2_firmware_versions::V2_0_9_2:
+ used_arguments = {"min_arc_segment_mm", "max_arc_segment_mm", "min_circle_segments", "arc_segments_per_sec", "n_arc_correction", "g90_g91_influences_extruder" };
+ plan_arc_ = &marlin_2::plan_arc_2_0_9_2;
+ break;
+ default:
+ used_arguments = { "mm_per_arc_segment", "arc_segments_per_r", "min_arc_segments", "arc_segments_per_sec", "n_arc_correction", "g90_g91_influences_extruder" };
+ plan_arc_ = &marlin_2::plan_arc_2_0_9_1;
+ break;
+ }
+ args_.set_used_arguments(used_arguments);
+}
+
+firmware_arguments marlin_2::get_default_arguments_for_current_version() const
+{
+ // Start off with the current args so they are set up correctly for this firmware type and version
+ firmware_arguments default_args = args_;
+
+ // firmware defaults
+ default_args.g90_g91_influences_extruder = true;
+
+ switch (marlin_2_version_)
+ {
+ case marlin_2::marlin_2_firmware_versions::V2_0_9_2:
+ // Active Settings
+ default_args.set_min_arc_segment_mm(0.1f);
+ default_args.set_max_arc_segment_mm(1.0f);
+ default_args.set_min_circle_segments(72);
+ default_args.n_arc_correction = 25;
+ // Inactive Settings
+ default_args.arc_segments_per_r = 0;
+ default_args.arc_segments_per_sec = 0;
+ // Settings that do not apply
+ default_args.mm_max_arc_error = 0;
+ break;
+ default:
+ // Active Settings
+ default_args.mm_per_arc_segment = 1.0f;
+ default_args.min_arc_segments = 24;
+ default_args.n_arc_correction = 25;
+ // Inactive Settings
+ default_args.arc_segments_per_r = 0;
+ default_args.min_mm_per_arc_segment = 0;
+ default_args.arc_segments_per_sec = 0;
+ // Settings that do not apply
+ default_args.mm_max_arc_error = 0;
+ break;
+ }
+ return default_args;
+}
+
+std::string marlin_2::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise)
+{
+ // Clear the current list of gcodes
+ gcodes_.clear();
+
+ // Setup the current position
+ current_position[X_AXIS] = static_cast<float>(position_.x);
+ current_position[Y_AXIS] = static_cast<float>(position_.y);
+ current_position[Z_AXIS] = static_cast<float>(position_.z);
+ current_position[E_AXIS] = static_cast<float>(position_.e);
+ float marlin_target[MARLIN_2_XYZE];
+ marlin_target[X_AXIS] = static_cast<float>(target.x);
+ marlin_target[Y_AXIS] = static_cast<float>(target.y);
+ marlin_target[Z_AXIS] = static_cast<float>(target.z);
+ marlin_target[E_AXIS] = static_cast<float>(target.e);
+ float marlin_offset[2];
+ marlin_offset[0] = static_cast<float>(i);
+ marlin_offset[1] = static_cast<float>(j);
+ // TODO: handle R form!!
+
+ // Set the feedrate
+ feedrate_mm_s = static_cast<float>(target.f);
+ uint8_t marlin_isclockwise = is_clockwise ? 1 : 0;
+
+ (this->*plan_arc_)(marlin_target, marlin_offset, marlin_isclockwise, 0);
+
+ return gcodes_;
+}
+
+/// <summary>
+/// This function was adapted from the 2.0.9.1 release of Marlin firmware, which can be found at the following link:
+/// https://github.com/MarlinFirmware/Marlin/blob/b878127ea04cc72334eb35ce0dca39ccf7d73a68/Marlin/src/gcode/motion/G2_G3.cpp
+/// Copyright Notice found on that page:
+///
+///
+/// Marlin 3D Printer Firmware
+/// Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+///
+/// Based on Sprinter and grbl.
+/// Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+///
+/// This program is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, either version 3 of the License, or
+/// (at your option) any later version.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/// GNU General Public License for more details.
+///
+/// You should have received a copy of the GNU General Public License
+/// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/// </summary>
+/// <param name="cart">The target position</param>
+/// <param name="offset">The I and J offset</param>
+/// <param name="clockwise">Is the motion clockwise or counterclockwise</param>
+void marlin_2::plan_arc_2_0_9_1(
+ const float(&cart)[MARLIN_2_XYZE], // Destination position
+ const float(&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise, // Clockwise?
+ const uint8_t circles // Take the scenic route
+)
+{
+ uint8_t p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS;
+
+ // Radius vector from center to current location
+ float rvec[2];
+ rvec[0] = - offset[X_AXIS];
+ rvec[1] = - offset[Y_AXIS];
+
+ const float radius = utilities::hypotf(rvec[0], rvec[1]),
+ center_P = current_position[p_axis] - rvec[0],
+ center_Q = current_position[q_axis] - rvec[1],
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ start_L = current_position[l_axis];
+
+ uint16_t min_segments = args_.min_arc_segments > 0 ? args_.min_arc_segments : 1;
+
+ // Angle of rotation between position and target from the circle center.
+ float angular_travel;
+
+ // Do a full circle if starting and ending positions are "identical"
+ if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) {
+ // Preserve direction for circles
+ angular_travel = clockwise ? -utilities::radiansf(360.0f) : utilities::radiansf(360.0f);
+ }
+ else {
+ // Calculate the angle
+ angular_travel = utilities::atan2f(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y);
+
+ // Angular travel too small to detect? Just return.
+ if (!angular_travel) return;
+
+ // Make sure angular travel over 180 degrees goes the other way around.
+ switch (((angular_travel < 0) << 1) | (int)clockwise) {
+ case 1: angular_travel -= utilities::radiansf(360.0f); break; // Positive but CW? Reverse direction.
+ case 2: angular_travel += utilities::radiansf(360.0f); break; // Negative but CCW? Reverse direction.
+ }
+
+ if (args_.min_arc_segments > 1)
+ {
+ min_segments = (uint16_t)utilities::ceilf(min_segments * utilities::absf(angular_travel) / utilities::radiansf(360.0f));
+ NOLESS(min_segments, 1U);
+ }
+ }
+
+
+ float linear_travel = cart[Z_AXIS] - start_L;
+ float extruder_travel = cart[E_AXIS] - current_position[E_AXIS];
+
+ const float flat_mm = radius * angular_travel,
+ mm_of_travel = linear_travel ? utilities::hypotf(flat_mm, linear_travel) : utilities::absf(flat_mm);
+ if (mm_of_travel < 0.001f) return;
+
+ const float scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ // Start with a nominal segment length
+ float seg_length = (float)args_.mm_per_arc_segment;
+ if (args_.arc_segments_per_r > 0)
+ {
+ seg_length = utilities::constrainf((float)args_.mm_per_arc_segment * radius, (float)args_.mm_per_arc_segment, (float)args_.arc_segments_per_r);
+ }
+ else if (args_.arc_segments_per_sec > 0)
+ {
+ seg_length = utilities::maxf(scaled_fr_mm_s * utilities::reciprocalf((float)args_.arc_segments_per_sec), (float)args_.mm_per_arc_segment);
+ }
+
+ // Divide total travel by nominal segment length
+ uint16_t segments = (uint16_t)utilities::floorf(mm_of_travel / seg_length);
+ //uint16_t segments = utilities::floorf(mm_of_travel / seg_length);
+ NOLESS(segments, min_segments); // At least some segments
+ seg_length = mm_of_travel / segments;
+
+ /**
+ * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ * and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ * r_T = [cos(phi) -sin(phi);
+ * sin(phi) cos(phi)] * r ;
+ *
+ * For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ * defined from the circle center to the initial position. Each line segment is formed by successive
+ * vector rotations. This requires only two cos() and sin() computations to form the rotation
+ * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ * all double numbers are single precision on the Arduino. (True double precision will not have
+ * round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ * tool precision in some cases. Therefore, arc path correction is implemented.
+ *
+ * Small angle approximation may be used to reduce computation overhead further. This approximation
+ * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words,
+ * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ * issue for CNC machines with the single precision Arduino calculations.
+ *
+ * This approximation also allows plan_arc to immediately insert a line segment into the planner
+ * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead.
+ * This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float raw[MARLIN_2_XYZE];
+ const float theta_per_segment = angular_travel / segments,
+ sq_theta_per_segment = utilities::sqf(theta_per_segment),
+ sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
+ cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
+
+
+ const float linear_per_segment = linear_travel / segments;
+ const float extruder_per_segment = extruder_travel / segments;
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+
+ // Initialize the extruder axis
+ raw[E_AXIS] = current_position[E_AXIS];
+
+ int8_t arc_recalc_count = args_.n_arc_correction;
+
+ for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
+
+ if (args_.n_arc_correction > 1 && --arc_recalc_count)
+ {
+ // Apply vector rotation matrix to previous rvec[0] / 1
+ const float r_new_Y = rvec[0] * sin_T + rvec[1] * cos_T;
+ rvec[0] = rvec[0] * cos_T - rvec[1] * sin_T;
+ rvec[1] = r_new_Y;
+ }
+ else
+ {
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ // To reduce stuttering, the sin and cos could be computed at different times.
+ // For now, compute both at the same time.
+ const float cos_Ti = (float)utilities::cos((i * (double)theta_per_segment));
+ const float sin_Ti = (float)utilities::sin((i * (double)theta_per_segment));
+ rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + rvec[0];
+ raw[q_axis] = center_Q + rvec[1];
+ raw[l_axis] = start_L, raw[l_axis] + linear_per_segment;
+
+ raw[E_AXIS] += extruder_per_segment;
+
+ apply_motion_limits(raw);
+
+ if (!buffer_line(raw, scaled_fr_mm_s, 0))
+ {
+ break;
+ }
+ }
+
+ // Ensure last segment arrives at target location.
+ COPY(raw, cart);
+ raw[l_axis] = start_L;
+
+ apply_motion_limits(raw);
+
+
+ buffer_line(raw, scaled_fr_mm_s, 0);
+
+ raw[l_axis] = start_L;
+ COPY(current_position, raw);
+}
+
+
+
+/// <summary>
+/// This function was adapted from the 2.0.9.1 release of Marlin firmware, which can be found at the following link:
+/// https://github.com/MarlinFirmware/Marlin/blob/b878127ea04cc72334eb35ce0dca39ccf7d73a68/Marlin/src/gcode/motion/G2_G3.cpp
+/// Copyright Notice found on that page:
+///
+///
+/// Marlin 3D Printer Firmware
+/// Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+///
+/// Based on Sprinter and grbl.
+/// Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+///
+/// This program is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, either version 3 of the License, or
+/// (at your option) any later version.
+///
+/// This program is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/// GNU General Public License for more details.
+///
+/// You should have received a copy of the GNU General Public License
+/// along with this program. If not, see <http://www.gnu.org/licenses/>.
+/// </summary>
+/// <param name="cart">The target position</param>
+/// <param name="offset">The I and J offset</param>
+/// <param name="clockwise">Is the motion clockwise or counterclockwise</param>
+void marlin_2::plan_arc_2_0_9_2(
+ const float(&cart)[MARLIN_2_XYZE], // Destination position
+ const float(&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise, // Clockwise?
+ const uint8_t circles // Take the scenic route
+)
+{
+ int min_circle_segments = args_.get_min_circle_segments() > 0 ? args_.get_min_circle_segments() : 1;
+ uint8_t p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS;
+
+ // Radius vector from center to current location
+ float rvec[2];
+ rvec[0] = -offset[X_AXIS];
+ rvec[1] = -offset[Y_AXIS];
+
+ const float radius = utilities::hypotf(rvec[0], rvec[1]),
+ center_P = current_position[p_axis] - rvec[0],
+ center_Q = current_position[q_axis] - rvec[1],
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ start_L = current_position[l_axis];
+
+ uint16_t min_segments = args_.min_arc_segments > 0 ? args_.min_arc_segments : 1;
+
+ // Angle of rotation between position and target from the circle center.
+ float angular_travel, abs_angular_travel;
+
+ // Do a full circle if starting and ending positions are "identical"
+ if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) {
+ // Preserve direction for circles
+ angular_travel = clockwise ? -utilities::radiansf(360.0f) : utilities::radiansf(360.0f);
+ abs_angular_travel = utilities::radiansf(360.0f);
+ min_segments = min_circle_segments;
+ }
+ else {
+ // Calculate the angle
+ angular_travel = utilities::atan2f(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y);
+
+ // Angular travel too small to detect? Just return.
+ if (!angular_travel) return;
+
+ // Make sure angular travel over 180 degrees goes the other way around.
+ switch (((angular_travel < 0) << 1) | (int)clockwise) {
+ case 1: angular_travel -= utilities::radiansf(360.0f); break; // Positive but CW? Reverse direction.
+ case 2: angular_travel += utilities::radiansf(360.0f); break; // Negative but CCW? Reverse direction.
+ }
+
+ abs_angular_travel = utilities::absf(angular_travel);
+
+ // Apply minimum segments to the arc
+ const float portion_of_circle = abs_angular_travel / utilities::radiansf(360.0f); // Portion of a complete circle (0 < N < 1)
+ min_segments = (uint16_t)utilities::ceilf((min_circle_segments)*portion_of_circle); // Minimum segments for the arc
+ }
+
+ float travel_L = cart[Z_AXIS] - start_L;
+ float travel_E = cart[E_AXIS] - current_position[E_AXIS];
+
+ // Millimeters in the arc, assuming it's flat
+ const float flat_mm = radius * abs_angular_travel;
+ if (flat_mm < 0.001f
+ && travel_L < 0.001f
+ ) return;
+
+ // Feedrate for the move, scaled by the feedrate multiplier
+ const float scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ // Get the nominal segment length based on settings
+ float nominal_segment_mm;
+ if (args_.arc_segments_per_sec > 0) {
+ nominal_segment_mm = utilities::constrainf(scaled_fr_mm_s * utilities::reciprocalf((float)args_.arc_segments_per_sec), (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm());
+ }
+ else {
+ nominal_segment_mm = (float)args_.get_max_arc_segment_mm();
+ }
+ // Number of whole segments based on the nominal segment length
+ const float nominal_segments = utilities::maxf(utilities::floorf(flat_mm / nominal_segment_mm), min_segments);
+
+ // A new segment length based on the required minimum
+ const float segment_mm = utilities::constrainf(flat_mm / nominal_segments, (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm());
+
+ // The number of whole segments in the arc, ignoring the remainder
+ uint16_t segments = (uint16_t)utilities::floorf(flat_mm / segment_mm);
+
+ // Are the segments now too few to reach the destination?
+ const float segmented_length = segment_mm * segments;
+ const bool tooshort = segmented_length < flat_mm - 0.0001f;
+ const float proportion = tooshort ? segmented_length / flat_mm : 1.0f;
+
+
+ /**
+ * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ * and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ * r_T = [cos(phi) -sin(phi);
+ * sin(phi) cos(phi)] * r ;
+ *
+ * For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ * defined from the circle center to the initial position. Each line segment is formed by successive
+ * vector rotations. This requires only two cos() and sin() computations to form the rotation
+ * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ * all double numbers are single precision on the Arduino. (True double precision will not have
+ * round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ * tool precision in some cases. Therefore, arc path correction is implemented.
+ *
+ * Small angle approximation may be used to reduce computation overhead further. This approximation
+ * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words,
+ * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ * issue for CNC machines with the single precision Arduino calculations.
+ *
+ * This approximation also allows plan_arc to immediately insert a line segment into the planner
+ * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead.
+ * This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float raw[MARLIN_2_XYZE];
+ const float theta_per_segment = proportion * angular_travel / segments,
+ sq_theta_per_segment = utilities::sqf(theta_per_segment),
+ sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
+ cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
+
+
+ const float per_segment_L = proportion * travel_L / segments;
+ const float extruder_per_segment = proportion * travel_E / segments;
+
+ // For shortened segments, run all but the remainder in the loop
+ if (tooshort) segments++;
+
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+ // Initialize the extruder axis
+ raw[E_AXIS] = current_position[E_AXIS];
+
+ int8_t arc_recalc_count = args_.n_arc_correction;
+
+ for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
+
+ if (args_.n_arc_correction > 1 && --arc_recalc_count)
+ {
+ // Apply vector rotation matrix to previous rvec[0] / 1
+ const float r_new_Y = rvec[0] * sin_T + rvec[1] * cos_T;
+ rvec[0] = rvec[0] * cos_T - rvec[1] * sin_T;
+ rvec[1] = r_new_Y;
+ }
+ else
+ {
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ // To reduce stuttering, the sin and cos could be computed at different times.
+ // For now, compute both at the same time.
+ const float cos_Ti = (float)utilities::cos(i * (double)theta_per_segment);
+ const float sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + rvec[0];
+ raw[q_axis] = center_Q + rvec[1];
+ raw[l_axis] = start_L, raw[l_axis] + per_segment_L;
+ raw[E_AXIS] += extruder_per_segment;
+
+ apply_motion_limits(raw);
+
+ if (!buffer_line(raw, scaled_fr_mm_s, 0))
+ {
+ break;
+ }
+ }
+
+ // Ensure last segment arrives at target location.
+ COPY(raw, cart);
+ raw[l_axis] = start_L;
+
+ apply_motion_limits(raw);
+
+
+ buffer_line(raw, scaled_fr_mm_s, 0);
+
+ raw[l_axis] = start_L;
+ COPY(current_position, raw);
+}
+// Marlin Function Defs
+void marlin_2::NOLESS(uint16_t& x, uint16_t y)
+{
+ if (x < y)
+ x = y;
+}
+float marlin_2::MMS_SCALED(float x)
+{
+ // No scaling
+ return x;
+}
+
+bool marlin_2::NEAR_ZERO(float x)
+{
+ return utilities::withinf(x, -0.000001f, 0.000001f);
+}
+bool marlin_2::NEAR(float x, float y)
+{
+ return NEAR_ZERO((x)-(y));
+}
+
+void marlin_2::COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE])
+{
+ // This is a slow copy, but speed isn't much of an issue here.
+ for (int i = 0; i < MARLIN_2_XYZE; i++)
+ {
+ target[i] = source[i];
+ }
+}
+
+void marlin_2::apply_motion_limits(float (&pos)[MARLIN_2_XYZE])
+{
+ // do nothing
+ return;
+}
+
+//void marlin::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+bool marlin_2::buffer_line(const float(&cart)[MARLIN_2_XYZE], double fr_mm_s, int active_extruder)
+{
+
+ // create the target position
+ firmware_position target;
+ target.x = cart[AxisEnum::X_AXIS];
+ target.y = cart[AxisEnum::Y_AXIS];
+ target.z = cart[AxisEnum::Z_AXIS];
+ target.e = cart[AxisEnum::E_AXIS];
+ target.f = fr_mm_s;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ return true;
+}
diff --git a/ArcWelderInverseProcessor/marlin_2.h b/ArcWelderInverseProcessor/marlin_2.h
new file mode 100644
index 0000000..2abea56
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_2.h
@@ -0,0 +1,93 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 2 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+#include "firmware.h"
+
+
+#define MARLIN_2_XYZE 4
+
+class marlin_2 :
+ public firmware
+{
+public:
+ enum class marlin_2_firmware_versions { V2_0_9_1 = 0, V2_0_9_2 = 1};
+ /// <summary>
+ /// Types and enums taken from https://github.com/MarlinFirmware/Marlin/blob/cce585f6ca2235d0a534e8f3043d6d502b3bd93b/Marlin/src/core/types.h
+ /// </summary>
+ // This enum was simplified
+ enum AxisEnum : uint8_t {
+ X_AXIS=0,
+ Y_AXIS=1,
+ Z_AXIS=2,
+ E_AXIS=3,
+ };
+
+ marlin_2(firmware_arguments args);
+ virtual ~marlin_2();
+ virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override;
+ virtual firmware_arguments get_default_arguments_for_current_version() const override;
+ virtual void apply_arguments() override;
+private:
+ marlin_2_firmware_versions marlin_2_version_;
+ std::string gcodes_;
+ float* current_position;
+ float feedrate_mm_s;
+ /// <summary>
+ /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name.
+ /// </summary>
+ typedef void(marlin_2::* plan_arc_func)(
+ const float(&cart)[MARLIN_2_XYZE], // Destination position
+ const float(&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise, // Clockwise?
+ const uint8_t circles // Take the scenic route
+ );
+
+ void plan_arc_2_0_9_1(
+ const float (&cart)[MARLIN_2_XYZE], // Destination position
+ const float (&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise, // Clockwise?
+ const uint8_t circles // Take the scenic route
+ );
+ void plan_arc_2_0_9_2(
+ const float(&cart)[MARLIN_2_XYZE], // Destination position
+ const float(&offset)[2], // Center of rotation relative to current_position
+ const bool clockwise, // Clockwise?
+ const uint8_t circles // Take the scenic route
+ );
+
+ bool buffer_line(const float(&cart)[MARLIN_2_XYZE], double fr_mm_s, int active_extruder);
+ void apply_motion_limits(float (&pos)[MARLIN_2_XYZE]);
+ plan_arc_func plan_arc_;
+
+ // Marlin Function Defs
+ void NOLESS(uint16_t& x, uint16_t y);
+ float MMS_SCALED(float x);
+ bool NEAR_ZERO(float x);
+ bool NEAR(float x, float y);
+ void COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE]);
+};
+
diff --git a/ArcWelderInverseProcessor/prusa.cpp b/ArcWelderInverseProcessor/prusa.cpp
new file mode 100644
index 0000000..34ee82b
--- /dev/null
+++ b/ArcWelderInverseProcessor/prusa.cpp
@@ -0,0 +1,466 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Prusa arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with mc_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#include "prusa.h"
+#include <cmath>
+#include "utilities.h"
+prusa::prusa(firmware_arguments args) : firmware(args) {
+ apply_arguments();
+};
+
+void prusa::apply_arguments()
+{
+ const std::vector<std::string> prusa_firmware_version_names{
+ "3.10.0", "3.11.0"
+ };
+ set_versions(prusa_firmware_version_names, "3.10.0");
+ prusa_version_ = (prusa::prusa_firmware_versions)version_index_;
+ std::vector<std::string> used_arguments;
+
+ switch (prusa_version_)
+ {
+ case prusa::prusa_firmware_versions::V3_11_0:
+ mc_arc_ = &prusa::mc_arc_3_11_0;
+ used_arguments = { "mm_per_arc_segment", "min_arc_segments", "min_mm_per_arc_segment", "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" };
+ break;
+ default:
+ mc_arc_ = &prusa::mc_arc_3_10_0;
+ used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" };
+ break;
+ }
+ args_.set_used_arguments(used_arguments);
+ cs.arc_segments_per_sec = args_.arc_segments_per_sec;
+ cs.min_arc_segments = args_.min_arc_segments;
+ cs.min_mm_per_arc_segment = (float)args_.min_mm_per_arc_segment;
+ cs.mm_per_arc_segment = (float)args_.mm_per_arc_segment;
+ cs.n_arc_correction = args_.n_arc_correction;
+
+}
+
+
+firmware_arguments prusa::get_default_arguments_for_current_version() const
+{
+ // Start off with the current args so they are set up correctly for this firmware type and version
+ firmware_arguments default_args = args_;
+
+ // firmware defaults
+ default_args.g90_g91_influences_extruder = false;
+ // Leave the switch in here in case we want to add more versions.
+ switch (prusa_version_)
+ {
+ case prusa::prusa_firmware_versions::V3_11_0:
+ // Active Settings
+ default_args.mm_per_arc_segment = 1.0f;
+ default_args.n_arc_correction = 25;
+ default_args.min_arc_segments = 24;
+ // Inactive Settings
+ default_args.arc_segments_per_r = 0;
+ default_args.min_mm_per_arc_segment = 0;
+ default_args.arc_segments_per_sec = 0;
+ break;
+ default:
+ // Active Settings
+ default_args.mm_per_arc_segment = 1.0f;
+ default_args.min_arc_segments = 24;
+ default_args.n_arc_correction = 25;
+ // Inactive Settings
+ default_args.arc_segments_per_r = 0;
+ default_args.min_mm_per_arc_segment = 0;
+ default_args.arc_segments_per_sec = 0;
+ break;
+ }
+ return default_args;
+}
+
+std::string prusa::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise)
+{
+ // Clear the current list of gcodes
+ gcodes_.clear();
+ // Set up the necessary values to call mc_arc
+ float prusa_position[4];
+ prusa_position[X_AXIS] = static_cast<float>(position_.x);
+ prusa_position[Y_AXIS] = static_cast<float>(position_.y);
+ prusa_position[Z_AXIS] = static_cast<float>(position_.z);
+ prusa_position[E_AXIS] = static_cast<float>(position_.e);
+ float prusa_target[4];
+ prusa_target[X_AXIS] = static_cast<float>(target.x);
+ prusa_target[Y_AXIS] = static_cast<float>(target.y);
+ prusa_target[Z_AXIS] = static_cast<float>(target.z);
+ prusa_target[E_AXIS] = static_cast<float>(target.e);
+ float prusa_offset[2];
+ prusa_offset[0] = static_cast<float>(i);
+ prusa_offset[1] = static_cast<float>(j);
+ float prusa_radius = static_cast<float>(r);
+ if (prusa_radius != 0)
+ {
+ prusa_radius = utilities::hypotf(prusa_offset[X_AXIS], prusa_offset[Y_AXIS]); // Compute arc radius for mc_arc
+ }
+ float prusa_f = static_cast<float>(target.f);
+
+ uint8_t prusa_isclockwise = is_clockwise ? 1 : 0;
+
+ (this->*mc_arc_)(prusa_position, prusa_target, prusa_offset, prusa_f, prusa_radius, prusa_isclockwise, 0);
+
+ return gcodes_;
+}
+
+/// <summary>
+/// This function was adapted from the 3.10.0 release of Prusa's firmware, which can be found at the following link:
+/// https://github.com/prusa3d/Prusa-Firmware/blob/04de9c0c8a49a4bee11b8c4789be3f18c1e1ccfe/Firmware/motion_control.cpp
+/// Copyright Notice found on that page:
+///
+/// motion_control.c - high level interface for issuing motion commands
+/// Part of Grbl
+/// Copyright(c) 2009 - 2011 Simen Svale Skogsrud
+/// Copyright(c) 2011 Sungeun K.Jeon
+///
+/// Grbl is free software : you can redistribute it and /or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, either version 3 of the License, or
+/// (at your option) any later version.
+/// Grbl is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU General Public License for more details.
+/// You should have received a copy of the GNU General Public License
+/// along with Grbl.If not, see < http://www.gnu.org/licenses/>.
+/// </summary>
+/// <param name="position">The current position</param>
+/// <param name="target">The target position</param>
+/// <param name="offset">The I and J offset</param>
+/// <param name="feed_rate">The target feedrate</param>
+/// <param name="radius">The radius of the arc.</param>
+/// <param name="isclockwise">Is the motion clockwise or counterclockwise</param>
+/// <param name="extruder">unknown</param>
+void prusa::mc_arc_3_10_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder)
+{
+ /// Setup * This code is NOT in the original source, and is added for compatibility with the function definition
+ uint8_t axis_0 = X_AXIS;
+ uint8_t axis_1 = Y_AXIS;
+ uint8_t axis_linear = Z_AXIS;
+
+ // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled();
+ // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc
+ float center_axis0 = position[axis_0] + offset[axis_0];
+ float center_axis1 = position[axis_1] + offset[axis_1];
+ float linear_travel = target[axis_linear] - position[axis_linear];
+ float extruder_travel = target[E_AXIS] - position[E_AXIS];
+ float r_axis0 = -offset[axis_0]; // Radius vector from center to current location
+ float r_axis1 = -offset[axis_1];
+ float rt_axis0 = target[axis_0] - center_axis0;
+ float rt_axis1 = target[axis_1] - center_axis1;
+
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ float angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1);
+ if (angular_travel < 0) { angular_travel += 2.0f * PI_FLOAT; }
+ if (isclockwise) { angular_travel -= 2.0f * PI_FLOAT; }
+
+ //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
+ //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
+ if (position[axis_0] == target[axis_0] && position[axis_1] == target[axis_1] && angular_travel == 0)
+ {
+ angular_travel += 2.0f * PI_FLOAT;
+ }
+ //end fix G03
+
+ float millimeters_of_travel = (float)utilities::hypot((double)angular_travel * radius, utilities::absf(linear_travel));
+ if (millimeters_of_travel < 0.001) { return; }
+ uint16_t segments = (uint16_t)utilities::floorf(millimeters_of_travel / cs.mm_per_arc_segment);
+ if (segments == 0) segments = 1;
+
+ /*
+ // Multiply inverse feed_rate to compensate for the fact that this movement is approximated
+ // by a number of discrete segments. The inverse feed_rate should be correct for the sum of
+ // all segments.
+ if (invert_feed_rate) { feed_rate *= segments; }
+ */
+ float theta_per_segment = angular_travel / segments;
+ float linear_per_segment = linear_travel / segments;
+ float extruder_per_segment = extruder_travel / segments;
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all double numbers are single precision on the Arduino. (True double precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ Small angle approximation may be used to reduce computation overhead further. This approximation
+ holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+ theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ issue for CNC machines with the single precision Arduino calculations.
+
+ This approximation also allows mc_arc to immediately insert a line segment into the planner
+ without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+ This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float cos_T = 1 - (float)0.5 * theta_per_segment * theta_per_segment; // Small angle approximation
+ float sin_T = theta_per_segment;
+
+ float arc_target[4];
+ float sin_Ti;
+ float cos_Ti;
+ float r_axisi;
+ uint16_t i;
+ int8_t count = 0;
+
+ // Initialize the linear axis
+ arc_target[axis_linear] = position[axis_linear];
+
+ // Initialize the extruder axis
+ arc_target[E_AXIS] = position[E_AXIS];
+
+ for (i = 1; i < segments; i++) { // Increment (segments-1)
+
+ if (count < cs.n_arc_correction) {
+ // Apply vector rotation matrix
+ r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
+ r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
+ r_axis1 = r_axisi;
+ count++;
+ }
+ else {
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ cos_Ti = (float)utilities::cos(i * (double)theta_per_segment);
+ sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ r_axis0 = -offset[axis_0] * cos_Ti + offset[axis_1] * sin_Ti;
+ r_axis1 = -offset[axis_0] * sin_Ti - offset[axis_1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[axis_0] = center_axis0 + r_axis0;
+ arc_target[axis_1] = center_axis1 + r_axis1;
+ arc_target[axis_linear] += linear_per_segment;
+ arc_target[E_AXIS] += extruder_per_segment;
+
+ clamp_to_software_endstops(arc_target);
+ plan_buffer_line(arc_target[X_AXIS], arc_target[Y_AXIS], arc_target[Z_AXIS], arc_target[E_AXIS], feed_rate, extruder, NULL);
+
+ }
+ // Ensure last segment arrives at target location.
+ plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, NULL);
+
+ // plan_set_acceleration_manager_enabled(acceleration_manager_was_enabled);
+}
+
+/// <summary>
+/// This function was taken from a pull request I submitted for the 3.11.0 release of Prusa's firmware, which can be found at the following link:
+/// https://github.com/FormerLurker/Prusa-Firmware/blob/MK3/Firmware/motion_control.cpp
+///
+/// It was forked from the original at: https://github.com/prusa3d/Prusa-Firmware
+///
+/// Copyright Notice found on that page:
+///
+/// motion_control.c - high level interface for issuing motion commands
+/// Part of Grbl
+/// Copyright(c) 2009 - 2011 Simen Svale Skogsrud
+/// Copyright(c) 2011 Sungeun K.Jeon
+/// Copyright(c) 2020 Brad Hochgesang
+/// Grbl is free software : you can redistribute it and /or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, either version 3 of the License, or
+/// (at your option) any later version.
+/// Grbl is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+/// GNU General Public License for more details.
+/// You should have received a copy of the GNU General Public License
+/// along with Grbl.If not, see < http://www.gnu.org/licenses/>.
+/// </summary>
+/// <param name="position">The current position</param>
+/// <param name="target">The target position</param>
+/// <param name="offset">The I and J offset</param>
+/// <param name="feed_rate">The target feedrate</param>
+/// <param name="radius">The radius of the arc.</param>
+/// <param name="isclockwise">Is the motion clockwise or counterclockwise</param>
+/// <param name="extruder">unknown</param>
+void prusa::mc_arc_3_11_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder)
+{
+
+ float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location
+ float r_axis_y = -offset[Y_AXIS];
+ float center_axis_x = position[X_AXIS] - r_axis_x;
+ float center_axis_y = position[Y_AXIS] - r_axis_y;
+ float travel_z = target[Z_AXIS] - position[Z_AXIS];
+ float rt_x = target[X_AXIS] - center_axis_x;
+ float rt_y = target[Y_AXIS] - center_axis_y;
+ // 20200419 - Add a variable that will be used to hold the arc segment length
+ float mm_per_arc_segment = cs.mm_per_arc_segment;
+ // 20210109 - Add a variable to hold the n_arc_correction value
+ uint8_t n_arc_correction = cs.n_arc_correction;
+
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ float angular_travel_total = (float)utilities::atan2((double)r_axis_x * rt_y - (double)r_axis_y * rt_x, (double)r_axis_x * rt_x + (double)r_axis_y * rt_y);
+ if (angular_travel_total < 0) { angular_travel_total += 2.0f * PI_FLOAT; }
+
+ if (cs.min_arc_segments > 0)
+ {
+ // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
+ // Do this before converting the angular travel for clockwise rotation
+ mm_per_arc_segment = radius * ((2.0f * PI_FLOAT) / cs.min_arc_segments);
+ }
+ if (cs.arc_segments_per_sec > 0)
+ {
+ // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
+ float mm_per_arc_segment_sec = (feed_rate / 60.0f) * (1.0f / (float)cs.arc_segments_per_sec);
+ if (mm_per_arc_segment_sec < mm_per_arc_segment)
+ mm_per_arc_segment = mm_per_arc_segment_sec;
+ }
+
+ // Note: no need to check to see if min_mm_per_arc_segment is enabled or not (i.e. = 0), since mm_per_arc_segment can never be below 0.
+ if (mm_per_arc_segment < cs.min_mm_per_arc_segment)
+ {
+ // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined
+ // This prevents a very high number of segments from being generated for curves of a short radius
+ mm_per_arc_segment = cs.min_mm_per_arc_segment;
+ }
+ else if (mm_per_arc_segment > cs.mm_per_arc_segment) {
+ // 20210113 - This can be implemented in an else if since we can't be below the min AND above the max at the same time.
+ // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined
+ mm_per_arc_segment = cs.mm_per_arc_segment;
+ }
+
+ // Adjust the angular travel if the direction is clockwise
+ if (isclockwise) { angular_travel_total -= 2.0f * PI_FLOAT; }
+
+ //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
+ //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
+ if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0)
+ {
+ angular_travel_total += 2.0f * PI_FLOAT;
+ }
+ //end fix G03
+
+ // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are
+ // calculating here
+ const float millimeters_of_travel_arc = (float)utilities::hypot(angular_travel_total * (double)radius, utilities::fabs(travel_z));
+ if (millimeters_of_travel_arc < 0.001) { return; }
+
+ // Calculate the number of arc segments
+ uint16_t segments = static_cast<uint16_t>(utilities::ceilf(millimeters_of_travel_arc / mm_per_arc_segment));
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all double numbers are single precision on the Arduino. (True double precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ The small angle approximation was removed because of excessive errors for small circles (perhaps unique to
+ 3d printing applications, causing significant path deviation and extrusion issues).
+ Now there will be no corrections applied, but an accurate initial sin and cos will be calculated.
+ This seems to work with a very high degree of accuracy and results in much simpler code.
+ Finding a faster way to approximate sin, knowing that there can be substantial deviations from the true
+ arc when using the previous approximation, would be beneficial.
+ */
+
+ // If there is only one segment, no need to do a bunch of work since this is a straight line!
+ if (segments > 1)
+ {
+ // Calculate theta per segments, and linear (z) travel per segment, e travel per segment
+ // as well as the small angle approximation for sin and cos.
+ const float theta_per_segment = angular_travel_total / segments,
+ linear_per_segment = travel_z / (segments),
+ segment_extruder_travel = (target[E_AXIS] - position[E_AXIS]) / (segments),
+ sq_theta_per_segment = theta_per_segment * theta_per_segment,
+ sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
+ cos_T = 1 - 0.5f * sq_theta_per_segment;
+ // Loop through all but one of the segments. The last one can be done simply
+ // by moving to the target.
+ for (uint16_t i = 1; i < segments; i++) {
+ if (n_arc_correction-- == 0) {
+ // Calculate the actual position for r_axis_x and r_axis_y
+ const float cos_Ti = (float)utilities::cos(i * (double)theta_per_segment), sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti;
+ r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti;
+ // reset n_arc_correction
+ n_arc_correction = cs.n_arc_correction;
+ }
+ else {
+ // Calculate X and Y using the small angle approximation
+ const float r_axisi = r_axis_x * sin_T + r_axis_y * cos_T;
+ r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T;
+ r_axis_y = r_axisi;
+ }
+
+ // Update Position
+ position[X_AXIS] = center_axis_x + r_axis_x;
+ position[Y_AXIS] = center_axis_y + r_axis_y;
+ position[Z_AXIS] += linear_per_segment;
+ position[E_AXIS] += segment_extruder_travel;
+ // Clamp to the calculated position.
+ clamp_to_software_endstops(position);
+ // Insert the segment into the buffer
+ plan_buffer_line(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS], feed_rate, extruder, position);
+ }
+ }
+ // Clamp to the target position.
+ clamp_to_software_endstops(target);
+ // Ensure last segment arrives at target location.
+ plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, target);
+}
+
+void prusa::clamp_to_software_endstops(float* target)
+{
+ // Do nothing, just added to keep mc_arc identical to the firmware version
+ return;
+}
+
+void prusa::plan_buffer_line(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+{
+ // create the target position
+ firmware_position target;
+ target.x = x;
+ target.y = y;
+ target.z = z;
+ target.e = e;
+ target.f = feed_rate;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ // update the current position
+ set_current_position(target);
+}
diff --git a/ArcWelderInverseProcessor/prusa.h b/ArcWelderInverseProcessor/prusa.h
new file mode 100644
index 0000000..f2b3d7e
--- /dev/null
+++ b/ArcWelderInverseProcessor/prusa.h
@@ -0,0 +1,83 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Inverse Processor Console Application
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include <string>
+#include "gcode_position.h"
+#include "firmware.h"
+#include "arc_interpolation_structs.h"
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <fstream>
+
+struct ConfigurationStore {
+ ConfigurationStore() {
+ mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT;
+ min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT;
+ min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS;
+ arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC;
+ n_arc_correction = DEFAULT_N_ARC_CORRECTIONS;
+ }
+ float mm_per_arc_segment; // This value is ALWAYS used.
+ float min_mm_per_arc_segment; // if less than or equal to 0, this is disabled
+ int min_arc_segments; // If less than or equal to zero, this is disabled
+ double arc_segments_per_sec; // If less than or equal to zero, this is disabled
+ int n_arc_correction;
+
+};
+class prusa :
+ public firmware
+{
+public:
+ enum class prusa_firmware_versions { V3_10_0 = 0, V3_11_0 = 1 };
+ typedef unsigned char uint8_t;
+ typedef unsigned short uint16_t;
+ typedef signed char int8_t;
+ enum AxisEnum { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E_AXIS = 3, X_HEAD = 4, Y_HEAD = 5 };
+ prusa(firmware_arguments args);
+ virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override;
+ virtual firmware_arguments get_default_arguments_for_current_version() const override;
+ virtual void apply_arguments() override;
+private:
+ std::string gcodes_;
+ /// <summary>
+ /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name.
+ /// </summary>
+ ConfigurationStore cs;
+ typedef void(prusa::*mc_arc_func)(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder);
+ void mc_arc_3_10_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder);
+ void mc_arc_3_11_0(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder);
+ void clamp_to_software_endstops(float* target);
+ void plan_buffer_line(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target);
+ mc_arc_func mc_arc_;
+ prusa::prusa_firmware_versions prusa_version_;
+};
+
+
+
+
+
diff --git a/ArcWelderInverseProcessor/repetier.cpp b/ArcWelderInverseProcessor/repetier.cpp
new file mode 100644
index 0000000..e5c34ec
--- /dev/null
+++ b/ArcWelderInverseProcessor/repetier.cpp
@@ -0,0 +1,468 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Repetier arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#include "repetier.h"
+#include "utilities.h"
+repetier::repetier(firmware_arguments args) : firmware(args) {
+
+ feedrate = 0;
+ apply_arguments();
+};
+
+void repetier::apply_arguments()
+{
+ static const std::vector<std::string> repetier_firmware_version_names{ "1.0.4", "1.0.5" };
+ set_versions(repetier_firmware_version_names, "1.0.4");
+ repetier_version_ = (repetier::repetier_firmware_versions)version_index_;
+ std::vector<std::string> used_arguments;
+ switch (repetier_version_)
+ {
+ case repetier::repetier_firmware_versions::V1_0_5:
+ used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" };
+ arc_ = &repetier::arc_1_0_5;
+ break;
+ default:
+ used_arguments = { "mm_per_arc_segment", "n_arc_correction", "g90_g91_influences_extruder" };
+ arc_ = &repetier::arc_1_0_4;
+ break;
+ }
+
+ args_.set_used_arguments(used_arguments);
+
+}
+firmware_arguments repetier::get_default_arguments_for_current_version() const
+{
+ // Start off with the current args so they are set up correctly for this firmware type and version
+ firmware_arguments default_args = args_;
+
+
+ // firmware defaults
+ default_args.g90_g91_influences_extruder = false;
+ // Leave the switch in here in case we want to add more versions.
+ switch (repetier_version_)
+ {
+ case repetier::repetier_firmware_versions::V1_0_5:
+ // Active Settings
+ default_args.mm_per_arc_segment = 1.0f;
+ default_args.n_arc_correction = 25;
+ default_args.min_arc_segments = 24;
+ // Inactive Settings
+ default_args.min_arc_segments = 0;
+ default_args.arc_segments_per_r = 0;
+ default_args.min_mm_per_arc_segment = 0;
+ default_args.arc_segments_per_sec = 0;
+ // Settings that do not apply
+ default_args.mm_max_arc_error = 0;
+ break;
+ default:
+ // Active Settings
+ default_args.mm_per_arc_segment = 1.0f;
+ default_args.n_arc_correction = 25;
+ // Inactive Settings
+ default_args.min_arc_segments = 0;
+ default_args.arc_segments_per_r = 0;
+ default_args.min_mm_per_arc_segment = 0;
+ default_args.arc_segments_per_sec = 0;
+ // Settings that do not apply
+ default_args.mm_max_arc_error = 0;
+ break;
+ }
+ return default_args;
+}
+
+repetier::~repetier()
+{
+}
+
+std::string repetier::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise)
+{
+ // Clear the current list of gcodes
+ gcodes_.clear();
+ // Set up the necessary values to call mc_arc
+ float repetier_position[4];
+ repetier_position[X_AXIS] = static_cast<float>(position_.x);
+ repetier_position[Y_AXIS] = static_cast<float>(position_.y);
+ repetier_position[Z_AXIS] = static_cast<float>(position_.z);
+ repetier_position[E_AXIS] = static_cast<float>(position_.e);
+ float repetier_target[4];
+ repetier_target[X_AXIS] = static_cast<float>(target.x);
+ repetier_target[Y_AXIS] = static_cast<float>(target.y);
+ repetier_target[Z_AXIS] = static_cast<float>(target.z);
+ repetier_target[E_AXIS] = static_cast<float>(target.e);
+ float repetier_offset[2];
+ repetier_offset[0] = static_cast<float>(i);
+ repetier_offset[1] = static_cast<float>(j);
+ float repetier_radius = static_cast<float>(r);
+ if (repetier_radius != 0)
+ {
+ repetier_radius = utilities::hypotf(repetier_offset[X_AXIS], repetier_offset[Y_AXIS]); // Compute arc radius for mc_arc
+ }
+ float repetier_f = static_cast<float>(target.f);
+
+ uint8_t repetier_isclockwise = is_clockwise ? 1 : 0;
+
+ feedrate = repetier_f;
+ (this->*arc_)(repetier_position, repetier_target, repetier_offset, repetier_radius, repetier_isclockwise);
+
+ return gcodes_;
+}
+
+/// <summary>
+/// This is intended as a possible replacement for the arc function in 1.0.4 (1.0.5?), which can be found at the following link:
+/// https://github.com/repetier/Repetier-Firmware/blob/2bbda51eb6407faf29a09987fd635c86818d32db/src/ArduinoAVR/Repetier/motion.cpp
+/// This copyright notice was taken from the link above:
+///
+/// This file is part of Repetier-Firmware.
+/// Repetier-Firmware is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, either version 3 of the License, or
+/// (at your option) any later version.
+/// Repetier-Firmware is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/// GNU General Public License for more details.
+/// You should have received a copy of the GNU General Public License
+/// along with Repetier-Firmware. If not, see <http://www.gnu.org/licenses/>.
+/// This firmware is a nearly complete rewrite of the sprinter firmware
+/// by kliment (https://github.com/kliment/Sprinter)
+/// which based on Tonokip RepRap firmware rewrite based off of Hydra-mmm firmware.
+/// Functions in this file are used to communicate using ascii or repetier protocol.
+/// </summary>
+/// <param name="cart">The target position</param>
+/// <param name="offset">The I and J offset</param>
+/// <param name="clockwise">Is the motion clockwise or counterclockwise</param>
+void repetier::arc_1_0_5(float* position, float* target, float* offset, float radius, uint8_t isclockwise)
+{
+ // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled();
+ // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc
+ float center_axis0 = position[X_AXIS] + offset[X_AXIS];
+ float center_axis1 = position[Y_AXIS] + offset[Y_AXIS];
+ float linear_travel = target[Z_AXIS] - position[Z_AXIS];
+ float extruder_travel = (target[E_AXIS] - position[E_AXIS]); // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does...
+ float r_axis0 = -offset[0]; // Radius vector from center to current location
+ float r_axis1 = -offset[1];
+ float rt_axis0 = target[0] - center_axis0;
+ float rt_axis1 = target[1] - center_axis1;
+
+ float angular_travel;
+ // First determine if we have a full circle by checking to see if the start and ending
+ // XY position is the same before and after the arc is drawn
+ //if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS])
+ if (repetier_is_close(position[X_AXIS], target[X_AXIS]) && repetier_is_close(position[Y_AXIS], target[Y_AXIS]))
+ {
+ // Preserve direction for circles
+ angular_travel = isclockwise ? -2.0f * PI_FLOAT : 2.0f * PI_FLOAT;
+ }
+ else
+ {
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1);
+
+ // No need to draw an arc if there is no angular travel
+ if (!angular_travel) return;
+
+ // Make sure angular travel over 180 degrees goes the other way around.
+ if (angular_travel > 0)
+ {
+ if (isclockwise)
+ {
+ angular_travel -= 2.0f * PI_FLOAT;
+ }
+ }
+ else if (!isclockwise)
+ {
+ angular_travel += 2.0f * PI_FLOAT;
+ }
+ }
+
+ // Determine the number of mm of total travel
+ float millimeters_of_travel = abs(angular_travel) * radius;
+ if (linear_travel)
+ {
+ // If we have any Z motion, add this to the total mm of travel.
+ millimeters_of_travel = utilities::hypotf(millimeters_of_travel, linear_travel);
+ }
+
+ if (millimeters_of_travel < 0.001f) {
+ return; // treat as succes because there is nothing to do;
+ }
+
+ // The speed restrictions will be based on some new parameters. Removing the hard coded values
+ // Increase segment size if printing faster then computation speed allows
+ //uint16_t segments = (feedrate > 60.0f ? floor(millimeters_of_travel / min(static_cast<float>(args_.mm_per_arc_segment), feedrate * 0.01666f * static_cast<float>(args_.mm_per_arc_segment))) : floor(millimeters_of_travel / static_cast<float>(args_.mm_per_arc_segment)));
+
+ // Use ceil here since the final segment could be nearly 2x as long as the rest.
+ // Example, assume millimeters_of_travel = 1.999. In this case segments will be 1,
+ // the interpolation loop will be skipped, and the segment drawn will be of length
+ // 1.999, which is not great.
+ uint16_t segments = (uint16_t)utilities::ceil(millimeters_of_travel / (float)args_.mm_per_arc_segment);
+
+ if (segments == 0)
+ segments = 1;
+ /*
+ // Multiply inverse feed_rate to compensate for the fact that this movement is approximated
+ // by a number of discrete segments. The inverse feed_rate should be correct for the sum of
+ // all segments.
+ if (invert_feed_rate) { feed_rate *= segments; }
+ */
+ float theta_per_segment = angular_travel / segments;
+ float linear_per_segment = linear_travel/segments;
+ float extruder_per_segment = extruder_travel / segments;
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all double numbers are single precision on the Arduino. (True double precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ Small angle approximation may be used to reduce computation overhead further. This approximation
+ holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+ theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ issue for CNC machines with the single precision Arduino calculations.
+ This approximation also allows mc_arc to immediately insert a line segment into the planner
+ without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+ This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ // Use two terms for better accuracy
+ float sq_theta_per_segment = theta_per_segment * theta_per_segment;
+ float cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
+ float sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6;
+
+ float arc_target[4];
+ float sin_Ti;
+ float cos_Ti;
+ float r_axisi;
+ uint16_t i;
+ int8_t count = 0;
+
+ // Initialize the linear axis
+ //arc_target[axis_linear] = position[axis_linear];
+
+ // Initialize the extruder axis
+ arc_target[E_AXIS] = position[E_AXIS]; // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does
+
+ for (i = 1; i < segments; i++) {
+ // Increment (segments-1)
+
+ if (count < args_.n_arc_correction) { //25 pieces
+ // Apply vector rotation matrix
+ r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
+ r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
+ r_axis1 = r_axisi;
+ count++;
+ }
+ else {
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ cos_Ti = (float)utilities::cos(i * (double)theta_per_segment);
+ sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[X_AXIS] = center_axis0 + r_axis0;
+ arc_target[Y_AXIS] = center_axis1 + r_axis1;
+ arc_target[Z_AXIS] += linear_per_segment;
+ arc_target[E_AXIS] += extruder_per_segment;
+ moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], position[Z_AXIS], arc_target[E_AXIS]);
+ }
+ // Ensure last segment arrives at target location.
+ moveToReal(target[X_AXIS], target[Y_AXIS], position[Z_AXIS], target[E_AXIS]);
+}
+
+/// <summary>
+/// This function was adapted from the 1.0.4 release of Repetier firmware, which can be found at the following link:
+/// https://github.com/repetier/Repetier-Firmware/blob/2bbda51eb6407faf29a09987fd635c86818d32db/src/ArduinoAVR/Repetier/motion.cpp
+/// This copyright notice was taken from the link above:
+///
+/// This file is part of Repetier-Firmware.
+/// Repetier-Firmware is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU General Public License as published by
+/// the Free Software Foundation, either version 3 of the License, or
+/// (at your option) any later version.
+/// Repetier-Firmware is distributed in the hope that it will be useful,
+/// but WITHOUT ANY WARRANTY; without even the implied warranty of
+/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/// GNU General Public License for more details.
+/// You should have received a copy of the GNU General Public License
+/// along with Repetier-Firmware. If not, see <http://www.gnu.org/licenses/>.
+/// This firmware is a nearly complete rewrite of the sprinter firmware
+/// by kliment (https://github.com/kliment/Sprinter)
+/// which based on Tonokip RepRap firmware rewrite based off of Hydra-mmm firmware.
+/// Functions in this file are used to communicate using ascii or repetier protocol.
+/// </summary>
+/// <param name="cart">The target position</param>
+/// <param name="offset">The I and J offset</param>
+/// <param name="clockwise">Is the motion clockwise or counterclockwise</param>
+void repetier::arc_1_0_4(float* position, float* target, float* offset, float radius, uint8_t isclockwise)
+{
+ // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled();
+ // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc
+ float center_axis0 = position[X_AXIS] + offset[X_AXIS];
+ float center_axis1 = position[Y_AXIS] + offset[Y_AXIS];
+ //float linear_travel = 0; //target[axis_linear] - position[axis_linear];
+ float extruder_travel = (target[E_AXIS] - position[E_AXIS]); // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does...
+ float r_axis0 = -offset[0]; // Radius vector from center to current location
+ float r_axis1 = -offset[1];
+ float rt_axis0 = target[0] - center_axis0;
+ float rt_axis1 = target[1] - center_axis1;
+ /*long xtarget = Printer::destinationSteps[X_AXIS];
+ long ytarget = Printer::destinationSteps[Y_AXIS];
+ long ztarget = Printer::destinationSteps[Z_AXIS];
+ long etarget = Printer::destinationSteps[E_AXIS];
+ */
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ float angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1);
+ if ((!isclockwise && angular_travel <= 0.00001) || (isclockwise && angular_travel < -0.000001)) {
+ angular_travel += 2.0f * PI_FLOAT;
+ }
+ if (isclockwise) {
+ angular_travel -= 2.0f * PI_FLOAT;
+ }
+
+ float millimeters_of_travel = (float)utilities::fabs(angular_travel) * radius;
+ if (millimeters_of_travel < 0.001f) {
+ return; // treat as succes because there is nothing to do;
+ }
+ //uint16_t segments = (radius>=BIG_ARC_RADIUS ? floor(millimeters_of_travel/MM_PER_ARC_SEGMENT_BIG) : floor(millimeters_of_travel/MM_PER_ARC_SEGMENT));
+ // Increase segment size if printing faster then computation speed allows
+ uint16_t segments = (uint16_t)(feedrate > 60.0f ? utilities::floorf(millimeters_of_travel / utilities::minf(static_cast<float>(args_.mm_per_arc_segment), feedrate * 0.01666f * static_cast<float>(args_.mm_per_arc_segment))) : utilities::floorf(millimeters_of_travel / static_cast<float>(args_.mm_per_arc_segment)));
+ if (segments == 0)
+ segments = 1;
+ /*
+ // Multiply inverse feed_rate to compensate for the fact that this movement is approximated
+ // by a number of discrete segments. The inverse feed_rate should be correct for the sum of
+ // all segments.
+ if (invert_feed_rate) { feed_rate *= segments; }
+ */
+ float theta_per_segment = angular_travel / segments;
+ //float linear_per_segment = linear_travel/segments;
+ float extruder_per_segment = extruder_travel / segments;
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all double numbers are single precision on the Arduino. (True double precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ Small angle approximation may be used to reduce computation overhead further. This approximation
+ holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+ theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ issue for CNC machines with the single precision Arduino calculations.
+ This approximation also allows mc_arc to immediately insert a line segment into the planner
+ without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+ This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float cos_T = 1 - 0.5f * theta_per_segment * theta_per_segment; // Small angle approximation
+ float sin_T = theta_per_segment;
+
+ float arc_target[4];
+ float sin_Ti;
+ float cos_Ti;
+ float r_axisi;
+ uint16_t i;
+ int8_t count = 0;
+
+ // Initialize the linear axis
+ //arc_target[axis_linear] = position[axis_linear];
+
+ // Initialize the extruder axis
+ arc_target[E_AXIS] = position[E_AXIS]; // * Printer::invAxisStepsPerMM[E_AXIS]; -- Not sure what this does
+
+ for (i = 1; i < segments; i++) {
+ // Increment (segments-1)
+
+ if (count < args_.n_arc_correction) { //25 pieces
+ // Apply vector rotation matrix
+ r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
+ r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
+ r_axis1 = r_axisi;
+ count++;
+ }
+ else {
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ cos_Ti = (float)utilities::cos(i * (double)theta_per_segment);
+ sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[X_AXIS] = center_axis0 + r_axis0;
+ arc_target[Y_AXIS] = center_axis1 + r_axis1;
+ //arc_target[axis_linear] += linear_per_segment;
+ arc_target[E_AXIS] += extruder_per_segment;
+ moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], position[Z_AXIS], arc_target[E_AXIS]);
+ }
+ // Ensure last segment arrives at target location.
+ moveToReal(target[X_AXIS], target[Y_AXIS], position[Z_AXIS], target[E_AXIS]);
+}
+
+
+//void repetier::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+void repetier::moveToReal(float x, float y, float z, float e)
+{
+
+ // create the target position
+ firmware_position target;
+ target.x = x;
+ target.y = y;
+ target.z = z;
+ target.e = e;
+ target.f = feedrate;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ // update the current position
+ set_current_position(target);
+}
diff --git a/ArcWelderInverseProcessor/repetier.h b/ArcWelderInverseProcessor/repetier.h
new file mode 100644
index 0000000..0c381c5
--- /dev/null
+++ b/ArcWelderInverseProcessor/repetier.h
@@ -0,0 +1,38 @@
+#pragma once
+#include <cstdint>
+#include "firmware.h"
+#include "utilities.h"
+#define repetier_is_close_value 0.001f
+#define repetier_is_close(x,y) ( repetier_is_close_value > utilities::fabs(x-y) )
+class repetier :
+ public firmware
+{
+public:
+ enum class repetier_firmware_versions { V1_0_4 = 0, V1_0_5};
+
+ repetier(firmware_arguments args);
+ virtual ~repetier();
+ virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override;
+ virtual firmware_arguments get_default_arguments_for_current_version() const override;
+ virtual void apply_arguments() override;
+private:
+ repetier_firmware_versions repetier_version_;
+ std::string gcodes_;
+ const static int REPETIER_XYZE = 4;
+ enum AxisEnum { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E_AXIS = 3};
+ /// <summary>
+ /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name.
+ /// </summary>
+ typedef void(repetier::* arc_func)(float* position, float* target, float* offset, float radius, uint8_t isclockwise);
+
+ void arc_1_0_4(float* position, float* target, float* offset, float radius, uint8_t isclockwise);
+ void arc_1_0_5(float* position, float* target, float* offset, float radius, uint8_t isclockwise);
+
+ arc_func arc_;
+
+ // Note that trailing underscore are sometimes dropped to keep the ported function as close as possible to the original
+ float feedrate;
+ // Repetier Function Defs
+ void moveToReal(float x, float y, float z, float e);
+};
+
diff --git a/ArcWelderInverseProcessor/smoothieware.cpp b/ArcWelderInverseProcessor/smoothieware.cpp
new file mode 100644
index 0000000..a747489
--- /dev/null
+++ b/ArcWelderInverseProcessor/smoothieware.cpp
@@ -0,0 +1,274 @@
+#include "smoothieware.h"
+#include "utilities.h"
+smoothieware::smoothieware(firmware_arguments args) : firmware(args) {
+ feed_rate = 0;
+ for (int i = 0; i < REPETIER_XYZE; i++)
+ {
+ machine_position[i] = 0;
+ }
+ THEKERNEL = new SmoothiewareKernel();
+ apply_arguments();
+};
+
+void smoothieware::apply_arguments()
+{
+ static const std::vector<std::string> smoothieware_firmware_version_names{ "2021-06-19" };
+ set_versions(smoothieware_firmware_version_names, "2021-06-19");
+ smoothieware_version_ = (smoothieware::smoothieware_firmware_versions)version_index_;
+ std::vector<std::string> used_arguments;
+ // Add switch back in if we ever add more versions
+ //switch (smoothieware_version_)
+ //{
+ //default:
+ append_arc_ = &smoothieware::append_arc_2021_06_19;
+ used_arguments = { "mm_per_arc_segment", "mm_max_arc_error", "n_arc_correction", "g90_g91_influences_extruder" };
+ //break;
+ //}
+
+ args_.set_used_arguments(used_arguments);
+
+}
+
+smoothieware::~smoothieware()
+{
+ delete THEKERNEL;
+}
+
+firmware_arguments smoothieware::get_default_arguments_for_current_version() const
+{
+ // Start off with the current args so they are set up correctly for this firmware type and version
+ firmware_arguments default_args = args_;
+ // firmware defaults
+ default_args.g90_g91_influences_extruder = true;
+ // Add the switch back if we ever need to add more versions
+ //switch (smoothieware_version_)
+ //{
+ //default:
+ // Active Settings
+ default_args.mm_per_arc_segment = 0.0f;
+ default_args.mm_max_arc_error = 0.01;
+ default_args.n_arc_correction = 5;
+ //break;
+ //}
+ return default_args;
+}
+
+std::string smoothieware::interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise)
+{
+ // Clear the current list of gcodes
+ gcodes_.clear();
+
+ // Setup the current position
+ machine_position[X_AXIS] = static_cast<float>(position_.x);
+ machine_position[Y_AXIS] = static_cast<float>(position_.y);
+ machine_position[Z_AXIS] = static_cast<float>(position_.z);
+ machine_position[E_AXIS] = static_cast<float>(position_.e);
+ float smoothieware_target[k_max_actuators];
+ smoothieware_target[X_AXIS] = static_cast<float>(target.x);
+ smoothieware_target[Y_AXIS] = static_cast<float>(target.y);
+ smoothieware_target[Z_AXIS] = static_cast<float>(target.z);
+ smoothieware_target[E_AXIS] = static_cast<float>(target.e);
+ float smoothieware_offset[2];
+ smoothieware_offset[0] = static_cast<float>(i);
+ smoothieware_offset[1] = static_cast<float>(j);
+ float radius = static_cast<float>(r);
+
+
+ // Set the feedrate
+ feed_rate = static_cast<float>(target.f);
+ uint8_t smoothieware_isclockwise = is_clockwise ? 1 : 0;
+
+ (this->*append_arc_)(&gcode_, smoothieware_target, smoothieware_offset, radius, smoothieware_isclockwise);
+
+ return gcodes_;
+}
+// Append an arc to the queue ( cutting it into segments as needed )
+bool smoothieware::append_arc_2021_06_19(SmoothiewareGcode* gcode, const float target[], const float offset[], float radius, bool is_clockwise)
+{
+ float rate_mm_s = this->feed_rate / seconds_per_minute;
+ // catch negative or zero feed rates and return the same error as GRBL does
+ if (rate_mm_s <= 0.0F) {
+ gcode->is_error = true;
+ gcode->txt_after_ok = (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
+ return false;
+ }
+
+ // Scary math.
+ float center_axis0 = this->machine_position[this->plane_axis_0] + offset[this->plane_axis_0];
+ float center_axis1 = this->machine_position[this->plane_axis_1] + offset[this->plane_axis_1];
+ float linear_travel = target[this->plane_axis_2] - this->machine_position[this->plane_axis_2];
+ float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to start position
+ float r_axis1 = -offset[this->plane_axis_1];
+ float rt_axis0 = target[this->plane_axis_0] - this->machine_position[this->plane_axis_0] - offset[this->plane_axis_0]; // Radius vector from center to target position
+ float rt_axis1 = target[this->plane_axis_1] - this->machine_position[this->plane_axis_1] - offset[this->plane_axis_1];
+ float angular_travel = 0;
+ //check for condition where atan2 formula will fail due to everything canceling out exactly
+ if ((this->machine_position[this->plane_axis_0] == target[this->plane_axis_0]) && (this->machine_position[this->plane_axis_1] == target[this->plane_axis_1])) {
+ if (is_clockwise) { // set angular_travel to -2pi for a clockwise full circle
+ angular_travel = (-2 * PI_FLOAT);
+ }
+ else { // set angular_travel to 2pi for a counterclockwise full circle
+ angular_travel = (2 * PI_FLOAT);
+ }
+ }
+ else {
+ // Patch from GRBL Firmware - Christoph Baumann 04072015
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ // Only run if not a full circle or angular travel will incorrectly result in 0.0f
+ angular_travel = (float)utilities::atan2((double)r_axis0 * rt_axis1 - (double)r_axis1 * rt_axis0, (double)r_axis0 * rt_axis0 + (double)r_axis1 * rt_axis1);
+ if (plane_axis_2 == Y_AXIS) { is_clockwise = !is_clockwise; } //Math for XZ plane is reverse of other 2 planes
+ if (is_clockwise) { // adjust angular_travel to be in the range of -2pi to 0 for clockwise arcs
+ if (angular_travel > 0) { angular_travel -= (2 * PI_FLOAT); }
+ }
+ else { // adjust angular_travel to be in the range of 0 to 2pi for counterclockwise arcs
+ if (angular_travel < 0) { angular_travel += (2 * PI_FLOAT); }
+ }
+ }
+
+ // initialize linear travel for ABC
+#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3
+ float abc_travel[n_motors - 3];
+ for (int i = A_AXIS; i < n_motors; i++) {
+ abc_travel[i - 3] = target[i] - this->machine_position[i];
+ }
+#endif
+
+
+ // Find the distance for this gcode
+ float millimeters_of_travel = (float)utilities::hypot((double)angular_travel * radius, utilities::fabsf(linear_travel));
+
+ // We don't care about non-XYZ moves ( for example the extruder produces some of those )
+ if (millimeters_of_travel < 0.000001F) {
+ return false;
+ }
+
+ // limit segments by maximum arc error
+ float arc_segment = (float)args_.mm_per_arc_segment;
+ if ((args_.mm_max_arc_error > 0) && (2.0 * (double)radius > args_.mm_max_arc_error)) {
+ float min_err_segment = 2 * (float)utilities::sqrt((args_.mm_max_arc_error * (2.0 * (double)radius - args_.mm_max_arc_error)));
+ if (args_.mm_per_arc_segment < min_err_segment) {
+ arc_segment = min_err_segment;
+ }
+ }
+
+ // catch fall through on above
+ if (arc_segment < 0.0001F) {
+ arc_segment = 0.5F; /// the old default, so we avoid the divide by zero
+ }
+
+ // Figure out how many segments for this gcode
+ // TODO for deltas we need to make sure we are at least as many segments as requested, also if mm_per_line_segment is set we need to use the
+ uint16_t segments = (uint16_t)utilities::floorf(millimeters_of_travel / arc_segment);
+ bool moved = false;
+
+ if (segments > 1) {
+ float theta_per_segment = angular_travel / segments;
+ float linear_per_segment = linear_travel / segments;
+#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3
+ float abc_per_segment[n_motors - 3];
+ for (int i = 0; i < n_motors - 3; i++) {
+ abc_per_segment[i] = abc_travel[i] / segments;
+ }
+#endif
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all float numbers are single precision on the Arduino. (True float precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ Small angle approximation may be used to reduce computation overhead further. This approximation
+ holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+ theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ issue for CNC machines with the single precision Arduino calculations.
+ This approximation also allows mc_arc to immediately insert a line segment into the planner
+ without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+ This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
+ float sin_T = theta_per_segment;
+
+ float arc_target[n_motors];
+ float sin_Ti;
+ float cos_Ti;
+ float r_axisi;
+ uint16_t i;
+ int8_t count = 0;
+
+ // init array for all axis
+ utilities::memcpy(arc_target, machine_position, n_motors * sizeof(float));
+
+ // Initialize the linear axis
+ arc_target[this->plane_axis_2] = this->machine_position[this->plane_axis_2];
+
+ for (i = 1; i < segments; i++) { // Increment (segments-1)
+ if (THEKERNEL->is_halted()) return false; // don't queue any more segments
+
+ if (count < args_.n_arc_correction) {
+ // Apply vector rotation matrix
+ r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
+ r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
+ r_axis1 = r_axisi;
+ count++;
+ }
+ else {
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ cos_Ti = (float)utilities::cos(i * (double)theta_per_segment);
+ sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
+ r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
+ arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
+ arc_target[this->plane_axis_2] += linear_per_segment;
+#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3
+ for (int a = A_AXIS; a < n_motors; a++) {
+ arc_target[a] += abc_per_segment[a - 3];
+ }
+#endif
+
+ // Append this segment to the queue
+ bool b = this->append_milestone(arc_target, rate_mm_s);
+ moved = moved || b;
+ }
+ }
+
+ // Ensure last segment arrives at target location.
+ if (this->append_milestone(target, rate_mm_s)) moved = true;
+
+ return moved;
+}
+
+bool smoothieware::append_milestone(const float target[], double rate_mm_s)
+{
+ double rate_mm_min = rate_mm_s * 60;
+ // create the target position
+ firmware_position gcode_target;
+ gcode_target.x = target[AxisEnum::X_AXIS];
+ gcode_target.y = target[AxisEnum::Y_AXIS];
+ gcode_target.z = target[AxisEnum::Z_AXIS];
+ gcode_target.e = target[AxisEnum::E_AXIS];
+ gcode_target.f = rate_mm_min;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(gcode_target);
+
+ return true;
+ return true;
+}
diff --git a/ArcWelderInverseProcessor/smoothieware.h b/ArcWelderInverseProcessor/smoothieware.h
new file mode 100644
index 0000000..77d54bd
--- /dev/null
+++ b/ArcWelderInverseProcessor/smoothieware.h
@@ -0,0 +1,64 @@
+#pragma once
+#include "firmware.h"
+
+#define SMOOTHIEWARE_MAX_ROBOT_ACTUATORS 4
+struct SmoothiewareGcode {
+ SmoothiewareGcode() {
+ is_error = false;
+ txt_after_ok = "";
+ }
+ bool is_error;
+ std::string txt_after_ok;
+
+};
+struct SmoothiewareKernel
+{
+ bool is_halted() {return false;}
+};
+class smoothieware :
+ public firmware
+{
+public:
+ enum class smoothieware_firmware_versions { V2021_06_19 = 0 };
+ smoothieware(firmware_arguments args);
+ virtual ~smoothieware();
+ virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override;
+ virtual firmware_arguments get_default_arguments_for_current_version() const override;
+ virtual void apply_arguments() override;
+private:
+ smoothieware::smoothieware_firmware_versions smoothieware_version_;
+ enum MOTION_MODE_T {
+ NONE,
+ SEEK, // G0
+ LINEAR, // G1
+ CW_ARC, // G2
+ CCW_ARC // G3
+ };
+ std::string gcodes_;
+ const static int REPETIER_XYZE = 4;
+ enum AxisEnum { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E_AXIS = 3, A_AXIS = 3 }; // A axis is the same as the E axis.
+ /// <summary>
+ /// A struct representing the prusa configuration store. Note: I didn't add the trailing underscore so this variable name will match the original source algorithm name.
+ /// </summary>
+ typedef bool(smoothieware::* append_arc_func)(SmoothiewareGcode* gcode, const float target[], const float offset[], float radius, bool is_clockwise);
+
+ bool append_arc_2021_06_19(SmoothiewareGcode* gcode, const float target[], const float offset[], float radius, bool is_clockwise);
+
+ append_arc_func append_arc_;
+
+ // Note that trailing underscore are sometimes dropped to keep the ported function as close as possible to the original
+ // Repetier Function Defs
+ bool append_milestone(const float target[], double rate_mm_s);
+ static const int seconds_per_minute = 60;
+ static const int k_max_actuators = SMOOTHIEWARE_MAX_ROBOT_ACTUATORS;
+ static const int n_motors = SMOOTHIEWARE_MAX_ROBOT_ACTUATORS;
+ float machine_position[k_max_actuators];
+ static const int plane_axis_0 = AxisEnum::X_AXIS;
+ static const int plane_axis_1 = AxisEnum::Y_AXIS;
+ static const int plane_axis_2 = AxisEnum::Z_AXIS;
+ static const int plane_axis_3 = AxisEnum::E_AXIS;
+ SmoothiewareGcode gcode_;
+ SmoothiewareKernel *THEKERNEL;
+ float feed_rate;
+};
+
diff --git a/ArcWelderInverseProcessor/sourcelist.cmake b/ArcWelderInverseProcessor/sourcelist.cmake
index d0ea63c..6ba1376 100644
--- a/ArcWelderInverseProcessor/sourcelist.cmake
+++ b/ArcWelderInverseProcessor/sourcelist.cmake
@@ -1,4 +1,20 @@
set(ArcWelderInverseProcessorSources ${ArcWelderInverseProcessorSources}
+ arc_interpolation.h
+ arc_interpolation.cpp
+ arc_interpolation_structs.h
+ ArcWelderInverseProcessor.h
ArcWelderInverseProcessor.cpp
- inverse_processor.cpp
+ firmware.cpp
+ firmware.h
+ firmware_types.h
+ marlin_1.cpp
+ marlin_1.h
+ marlin_2.cpp
+ marlin_2.h
+ prusa.cpp
+ prusa.h
+ repetier.cpp
+ repetier.h
+ smoothieware.cpp
+ smoothieware.h
) \ No newline at end of file
diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp
index 9add004..bd173d5 100644
--- a/ArcWelderTest/ArcWelderTest.cpp
+++ b/ArcWelderTest/ArcWelderTest.cpp
@@ -24,7 +24,6 @@
#include "ArcWelderTest.h"
#include "logger.h"
#include <iostream>
-#include "utilities.h"
int main(int argc, char* argv[])
{
@@ -34,7 +33,7 @@ int main(int argc, char* argv[])
int run_tests(int argc, char* argv[])
{
- _CrtMemState state;
+ _CrtMemState state1, state2, state3;
// This line will take a snapshot
// of the memory allocated at this point.
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
@@ -46,7 +45,7 @@ int run_tests(int argc, char* argv[])
//std::string filename = argv[1];
unsigned int num_runs = 1;
- _CrtMemCheckpoint(&state);
+ _CrtMemCheckpoint(&state1);
auto start = std::chrono::high_resolution_clock::now();
for (unsigned int index = 0; index < num_runs; index++)
@@ -94,7 +93,11 @@ int run_tests(int argc, char* argv[])
}
auto end = std::chrono::high_resolution_clock::now();
- _CrtMemDumpAllObjectsSince(&state);
+ _CrtMemCheckpoint(&state2);
+ if (_CrtMemDifference(&state3, &state1, &state2)) {
+ _CrtMemDumpStatistics(&state3);
+ }
+ //_CrtMemDumpAllObjectsSince(&state);
std::chrono::duration<double> diff = end - start;
std::cout << "Tests completed in " << diff.count() << " seconds";
//std::cout << "Has Memory Leak = " << has_leak << ".\r\n";
@@ -266,47 +269,19 @@ static gcode_position_args get_5_extruder_position_args()
static void TestAntiStutter(std::string filePath)
{
- //double max_resolution = DEFAULT_RESOLUTION_MM;
- double max_resolution = 0.05;
- double max_radius_mm = 100000;
- //int min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS;
- int min_arc_segments = 0;
- double mm_per_arc_segment = 0;
-
- //double path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; // 1 percent
- double path_tolerance_percent = 0.05;
- //double path_tolerance_percent = 0.05;
std::vector<std::string> logger_names;
logger_names.push_back("arc_welder.gcode_conversion");
std::vector<int> logger_levels;
- logger_levels.push_back(log_levels::NOSET);
- logger_levels.push_back(log_levels::VERBOSE);
- logger_levels.push_back(log_levels::DEBUG);
- logger_levels.push_back(log_levels::INFO);
- logger_levels.push_back(log_levels::WARNING);
- logger_levels.push_back(log_levels::ERROR);
- logger_levels.push_back(log_levels::CRITICAL);
+ logger_levels.push_back((int)log_levels::NOSET);
+ logger_levels.push_back((int)log_levels::VERBOSE);
+ logger_levels.push_back((int)log_levels::DEBUG);
+ logger_levels.push_back((int)log_levels::INFO);
+ logger_levels.push_back((int)log_levels::WARNING);
+ //logger_levels.push_back((int)(log_levels::ERROR));
+ logger_levels.push_back((int)log_levels::CRITICAL);
logger* p_logger = new logger(logger_names, logger_levels);
- p_logger->set_log_level(INFO);
- //p_logger->set_log_level_by_value(5);
- //arc_welder arc_welder_obj(BENCHY_0_5_MM_NO_WIPE, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, static_cast<progress_callback>(on_progress));
- //arc_welder arc_welder_obj(SIX_SPEED_TEST, "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode", p_logger, max_resolution, false, 50, on_progress);
- arc_welder arc_welder_obj(
- BENCHY_L1_DIFFICULT,
- "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode",
- p_logger,
- max_resolution,
- path_tolerance_percent,
- max_radius_mm,
- min_arc_segments,
- mm_per_arc_segment,
- false,
- true,
- DEFAULT_ALLOW_DYNAMIC_PRECISION,
- DEFAULT_XYZ_PRECISION,
- DEFAULT_E_PRECISION,
- DEFAULT_GCODE_BUFFER_SIZE,
- on_progress);
+ p_logger->set_log_level(log_levels::INFO);
+
//FIRMWARE_COMPENSATION_TEST_1
//BENCHY_MIN_RADIUS_TEST
//BENCHY_DIFFICULT
@@ -330,15 +305,28 @@ static void TestAntiStutter(std::string filePath)
// BENCHY_L1_DIFFICULT
// SPIRAL_TEST
// SPIRAL_VASE_TEST_FUNNEL
+ std::string source_path = SPIRAL_VASE_TEST_SINGLE_LAYER_CYLINDER;
+ std::string target_path = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\test_output.gcode";
+ arc_welder_args args(source_path, target_path, p_logger);
+ args.box_encoding = args.box_encoding = utilities::box_drawing::HTML;
+ args.callback = on_progress;
+ // override any arguments here;
+ args.allow_travel_arcs = true;
+ args.allow_3d_arcs = true;
+ args.max_radius_mm = 9999;
+ args.resolution_mm = 0.05;
+ args.extrusion_rate_variance_percent = 1000;
+ arc_welder arc_welder_obj(args);
+
arc_welder_results results = arc_welder_obj.process();
- p_logger->log(0, INFO, results.progress.detail_str());
- p_logger->log(0, INFO, "Processing Complete.");
+ p_logger->log(0, log_levels::INFO, results.progress.detail_str());
+ p_logger->log(0, log_levels::INFO, "Processing Complete.");
delete p_logger;
}
bool on_progress(arc_welder_progress progress, logger * p_logger, int logger_type)
{
- p_logger->log(logger_type, INFO, progress.str());
+ p_logger->log(logger_type, log_levels::INFO, progress.str());
return true;
}
diff --git a/ArcWelderTest/ArcWelderTest.h b/ArcWelderTest/ArcWelderTest.h
index 249d70b..4c8f217 100644
--- a/ArcWelderTest/ArcWelderTest.h
+++ b/ArcWelderTest/ArcWelderTest.h
@@ -82,7 +82,8 @@ static std::string ISSUE_PRICKLYPEAR_LAYER_0_114 = "C:\\Users\\Brad\\Documents\\
// Sanity tests
static std::string COLINEAR_TEST_1 = "C:\\Users\\Brad\\Documents\\AntiStutter\\Sanity Checks\\G2_colinear_test.gcode";
static std::string SPIRAL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TEST.gcode";
-
+static std::string SPIRAL_TRAVEL_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TRAVEL_TEST.gcode";
+static std::string TravelWipeTest = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\TravelWipeTest.gcode";
static std::string SPIRAL_TEST_PRECISION = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\smoothietest\\SPIRAL_TEST_precision.gcode";
static std::string SPIRAL_VASE_TEST_DOUBLE_SPIRAL = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\SpiralVaseTest\\SpiralVaseTest_DOUBLE_SPIRAL.gcode";
static std::string SPIRAL_VASE_TEST_CYLINDER = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\SpiralVaseTest\\SpiralVaseTest_Cylinder.gcode";
@@ -94,6 +95,8 @@ static std::string BENCHY_MIN_RADIUS_TEST = "C:\\Users\\Brad\\Documents\\3DPrint
static std::string ISSUE_93 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\93\\FailingGCode.gcode";
static std::string ISSUE_99 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\99\\FailingGCode.gcode";
static std::string ISSUE_134 = "C:\\Users\\Brad\\Documents\\AntiStutter\\Issues\\134\\BirdHouse [PETG] [brim]+Infill [20%,cubic]+Noz [0.6]+LH [0.2]+LW-[0.6]+Temps [240+70]+50.0mms+Support [normal (56)]+Coast-[False].gcode" ;
+static std::string ISSUE_170 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\170\\lampenring.gcode";
+static std::string ISSUE_184 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\184\\Dome_60_1.2mm_max_test15_0.2mm_PETG_MK3S_2h1m.gcode";
static std::string CONE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\ConeTest.gcode";
static std::string CONE_TEST_VASE = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\ConeTestVase.gcode";
@@ -107,7 +110,9 @@ static std::string ISSUE_34 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutte
static std::string DIFFICULT_ARCS_ISSUE_34 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\DifficultArcs\\issue_34.gcode";
static std::string TORTURE_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\performance\\torture_test.gcode";
-
+static std::string CURA_PLUGIN_ISSUE_18 = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\Issues\\CuraPlugin\\18\\Unwelded.gcode";
+static std::string METALTEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\MetalAltered.gcode";
+static std::string TEMP_TEST = "C:\\Users\\Brad\\Documents\\3DPrinter\\AntiStutter\\temp.gcode";
diff --git a/GcodeProcessorLib/GcodeProcessorLib.vcxproj b/GcodeProcessorLib/GcodeProcessorLib.vcxproj
index 956df18..778d3f3 100644
--- a/GcodeProcessorLib/GcodeProcessorLib.vcxproj
+++ b/GcodeProcessorLib/GcodeProcessorLib.vcxproj
@@ -210,8 +210,6 @@
<ClInclude Include="version.h" />
</ItemGroup>
<ItemGroup>
- <ClCompile Include="array_list.cpp" />
- <ClCompile Include="circular_buffer.cpp" />
<ClCompile Include="extruder.cpp" />
<ClCompile Include="fpconv.cpp" />
<ClCompile Include="gcode_comment_processor.cpp" />
diff --git a/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters b/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters
index 60b8ca9..3aed5ba 100644
--- a/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters
+++ b/GcodeProcessorLib/GcodeProcessorLib.vcxproj.filters
@@ -18,9 +18,6 @@
<ClInclude Include="array_list.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="circular_buffer.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="extruder.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -54,14 +51,11 @@
<ClInclude Include="fpconv.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="circular_buffer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
- <ClCompile Include="array_list.cpp">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="circular_buffer.cpp">
- <Filter>Source Files</Filter>
- </ClCompile>
<ClCompile Include="extruder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/GcodeProcessorLib/array_list.cpp b/GcodeProcessorLib/array_list.cpp
deleted file mode 100644
index b2e46ea..0000000
--- a/GcodeProcessorLib/array_list.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Gcode Processor Library
-//
-// Tools for parsing gcode and calculating printer state from parsed gcode commands.
-//
-// Copyright(C) 2020 - Brad Hochgesang
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// This program is free software : you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
-// GNU Affero General Public License for more details.
-//
-//
-// You can contact the author at the following email address:
-// FormerLurker@pm.me
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#include "array_list.h" \ No newline at end of file
diff --git a/GcodeProcessorLib/array_list.h b/GcodeProcessorLib/array_list.h
index 53ed43e..a69fc28 100644
--- a/GcodeProcessorLib/array_list.h
+++ b/GcodeProcessorLib/array_list.h
@@ -35,7 +35,7 @@ public:
count_ = 0;
items_ = new T[max_size_];
}
-
+
array_list(int max_size)
{
auto_grow_ = false;
@@ -44,11 +44,11 @@ public:
count_ = 0;
items_ = new T[max_size];
}
-
+
virtual ~array_list() {
delete[] items_;
}
-
+
void resize(int max_size)
{
T* new_items = new T[max_size];
@@ -71,7 +71,7 @@ public:
}
return index_position;
}
-
+
void push_front(T object)
{
if (count_ == max_size_)
@@ -93,7 +93,7 @@ public:
count_++;
items_[front_index_] = object;
}
-
+
void push_back(T object)
{
if (count_ == max_size_)
@@ -110,7 +110,7 @@ public:
items_[pos] = object;
count_++;
}
-
+
T& pop_front()
{
if (count_ == 0)
@@ -135,7 +135,7 @@ public:
{
throw std::exception();
}
- int pos = get_index_position(count_-1);
+ int pos = get_index_position(count_ - 1);
count_--;
return items_[pos];
}
@@ -151,23 +151,23 @@ public:
int opos = get_index_position(index);
return items_[opos];
}
-
+
int count() const
{
return count_;
}
-
+
int get_max_size() const
{
return max_size_;
}
-
+
void clear()
{
count_ = 0;
front_index_ = 0;
}
-
+
void copy(const array_list<T>& source)
{
if (max_size_ < source.max_size_)
diff --git a/GcodeProcessorLib/circular_buffer.cpp b/GcodeProcessorLib/circular_buffer.cpp
deleted file mode 100644
index c17c240..0000000
--- a/GcodeProcessorLib/circular_buffer.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Gcode Processor Library
-//
-// Tools for parsing gcode and calculating printer state from parsed gcode commands.
-//
-// Copyright(C) 2020 - Brad Hochgesang
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// This program is free software : you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
-// GNU Affero General Public License for more details.
-//
-//
-// You can contact the author at the following email address:
-// FormerLurker@pm.me
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#include "circular_buffer.h" \ No newline at end of file
diff --git a/GcodeProcessorLib/circular_buffer.h b/GcodeProcessorLib/circular_buffer.h
index 0312a29..97f25ab 100644
--- a/GcodeProcessorLib/circular_buffer.h
+++ b/GcodeProcessorLib/circular_buffer.h
@@ -1,7 +1,9 @@
+#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Gcode Processor Library
//
// Tools for parsing gcode and calculating printer state from parsed gcode commands.
+
//
// Copyright(C) 2020 - Brad Hochgesang
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -19,6 +21,7 @@
// You can contact the author at the following email address:
// FormerLurker@pm.me
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
#pragma once
#include <exception>
template <typename T>
@@ -32,6 +35,7 @@ public:
count_ = 0;
items_ = new T[max_size_];
}
+
circular_buffer(int max_size)
{
max_size_ = max_size;
@@ -39,13 +43,24 @@ public:
count_ = 0;
items_ = new T[max_size];
}
+
virtual ~circular_buffer() {
delete[] items_;
}
+
+ void initialize(T object)
+ {
+ for (int index = 0; index < max_size_; index++)
+ {
+ push_back(object);
+ }
+ count_ = 0;
+ front_index_ = 0;
+ }
+
void resize(int max_size)
{
T* new_items = new T[max_size];
- int count = count_;
for (int index = 0; index < count_; index++)
{
new_items[index] = items_[(front_index_ + index + max_size_) % max_size_];
@@ -55,12 +70,61 @@ public:
items_ = new_items;
max_size_ = max_size;
}
+
+ void resize(int max_size, T object)
+ {
+ T* new_items = new T[max_size];
+ for (int index = 0; index < count_; index++)
+ {
+ new_items[index] = items_[(front_index_ + index + max_size_) % max_size_];
+ }
+ // Initialize the rest of the entries
+ for (int index = count_; index < max_size; index++)
+ {
+ new_items[index] = object;
+ }
+ front_index_ = 0;
+ delete[] items_;
+ items_ = new_items;
+ max_size_ = max_size;
+ }
+
+ inline int get_index_position(int index) const
+ {
+ int index_position = index + front_index_ + max_size_;
+ while (index_position >= max_size_)
+ {
+ index_position = index_position - max_size_;
+ }
+ return index_position;
+ }
+
void push_front(T object)
{
- front_index_ = (front_index_ - 1 + max_size_) % max_size_;
- count_++;
+ //front_index_ = (front_index_ - 1 + max_size_) % max_size_;
+ front_index_ -= 1;
+ if (front_index_ < 0)
+ {
+ front_index_ = max_size_ - 1;
+ }
+ if (count_ != max_size_)
+ {
+ count_++;
+ }
items_[front_index_] = object;
}
+
+ void push_back(T object)
+ {
+ int pos = get_index_position(count_);
+ items_[pos] = object;
+ count_++;
+ if (count_ != max_size_)
+ {
+ count_++;
+ }
+ }
+
T& pop_front()
{
if (count_ == 0)
@@ -69,30 +133,55 @@ public:
}
int prev_start = front_index_;
- front_index_ = (front_index_ + 1 + max_size_) % max_size_;
+
+ front_index_ += 1;
+ if (front_index_ >= max_size_)
+ {
+ front_index_ = 0;
+ }
count_--;
return items_[prev_start];
}
- T& get(int index)
+ T& pop_back()
{
- return items_[(front_index_ + index + max_size_) % max_size_];
+ if (count_ == 0)
+ {
+ throw std::exception();
+ }
+ int pos = get_index_position(count_ - 1);
+ count_--;
+ return items_[pos];
}
- int count()
+ T& operator[] (int index) const
{
- return count_;
+ //int opos = get_index_position(index);
+ return items_[get_index_position(index)];
+ }
+
+ T& get(int index) const
+ {
+ int opos = get_index_position(index);
+ return items_[opos];
+ }
+ int count() const
+ {
+ return count_;
}
- int get_max_size()
+
+ int get_max_size() const
{
return max_size_;
}
+
void clear()
{
count_ = 0;
front_index_ = 0;
}
+
void copy(const circular_buffer<T>& source)
{
if (max_size_ < source.max_size_)
@@ -106,7 +195,6 @@ public:
}
front_index_ = source.front_index_;
count_ = source.count_;
-
}
protected:
diff --git a/GcodeProcessorLib/fpconv.cpp b/GcodeProcessorLib/fpconv.cpp
index e22731a..9c2b8ee 100644
--- a/GcodeProcessorLib/fpconv.cpp
+++ b/GcodeProcessorLib/fpconv.cpp
@@ -190,7 +190,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K)
for (divp = tens + 10; kappa > 0; divp++) {
unsigned long long div = *divp;
- unsigned digit = part1 / div;
+ unsigned digit = (unsigned int)(part1 / div);
if (digit || idx) {
digits[idx++] = digit + '0';
@@ -216,7 +216,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K)
delta *= 10;
kappa--;
- unsigned digit = part2 >> -one.exp;
+ unsigned digit = (unsigned int)(part2 >> -one.exp);
if (digit || idx) {
digits[idx++] = digit + '0';
}
diff --git a/GcodeProcessorLib/fpconv.h b/GcodeProcessorLib/fpconv.h
index 6896a3a..8afcb21 100644
--- a/GcodeProcessorLib/fpconv.h
+++ b/GcodeProcessorLib/fpconv.h
@@ -149,7 +149,7 @@ static Fp find_cachedpow10(int exp, int* k)
{
const double one_log_ten = 0.30102999566398114;
- int approx = -(exp + npowers) * one_log_ten;
+ int approx = (int)(-(exp + npowers) * one_log_ten);
int idx = (approx - firstpower) / steppowers;
while (1) {
diff --git a/GcodeProcessorLib/gcode_position.cpp b/GcodeProcessorLib/gcode_position.cpp
index fc469cd..daf66e4 100644
--- a/GcodeProcessorLib/gcode_position.cpp
+++ b/GcodeProcessorLib/gcode_position.cpp
@@ -206,10 +206,9 @@ void gcode_position_args::delete_y_firmware_offsets()
}
}
-gcode_position::gcode_position()
+gcode_position::gcode_position() : positions_(50), initial_position_(1)
{
position_buffer_size_ = 50;
- positions_ = new position[position_buffer_size_];
autodetect_position_ = false;
home_x_ = 0;
home_y_ = 0;
@@ -247,23 +246,19 @@ gcode_position::gcode_position()
z_max_ = 0;
is_circular_bed_ = false;
- cur_pos_ = -1;
- num_pos_ = 0;
- for(int index = 0; index < position_buffer_size_; index ++)
- {
- position initial_pos(num_extruders_);
- initial_pos.set_xyz_axis_mode(xyz_axis_default_mode_);
- initial_pos.set_e_axis_mode(e_axis_default_mode_);
- initial_pos.set_units_default(units_default_);
- add_position(initial_pos);
- }
- num_pos_ = 0;
+
+ position initial_pos(num_extruders_);
+ initial_pos.set_xyz_axis_mode(xyz_axis_default_mode_);
+ initial_pos.set_e_axis_mode(e_axis_default_mode_);
+ initial_pos.set_units_default(units_default_);
+
+ positions_.initialize(initial_pos);
+ initial_position_ = initial_pos;
}
-gcode_position::gcode_position(gcode_position_args args)
+gcode_position::gcode_position(gcode_position_args args) : positions_(args.position_buffer_size)
{
position_buffer_size_ = args.position_buffer_size;
- positions_ = new position[args.position_buffer_size] ;
autodetect_position_ = args.autodetect_position;
home_x_ = args.home_x;
home_y_ = args.home_y;
@@ -330,9 +325,6 @@ gcode_position::gcode_position(gcode_position_args args)
z_max_ = args.z_max;
is_circular_bed_ = args.is_circular_bed;
-
- cur_pos_ = -1;
- num_pos_ = 0;
num_extruders_ = args.num_extruders;
// Configure the initial position
@@ -346,15 +338,11 @@ gcode_position::gcode_position(gcode_position_args args)
initial_pos.p_extruders[index].x_firmware_offset = args.x_firmware_offsets[index];
initial_pos.p_extruders[index].y_firmware_offset = args.y_firmware_offsets[index];
}
-
- for (int index = 0; index < position_buffer_size_; index++)
- {
-
- add_position(initial_pos);
- }
- num_pos_ = 0;
+ positions_.initialize(initial_pos);
+ initial_position_ = initial_pos;
}
+
gcode_position::gcode_position(const gcode_position &source)
{
// Private copy constructor - you can't copy this class
@@ -362,11 +350,7 @@ gcode_position::gcode_position(const gcode_position &source)
gcode_position::~gcode_position()
{
- if (positions_ != NULL)
- {
- delete [] positions_;
- positions_ = NULL;
- }
+
delete_retraction_lengths_();
delete_z_lift_heights_();
}
@@ -376,7 +360,6 @@ bool gcode_position::get_g90_91_influences_extruder()
return g90_influences_extruder_;
}
-
void gcode_position::set_num_extruders(int num_extruders)
{
delete_retraction_lengths_();
@@ -413,32 +396,44 @@ void gcode_position::delete_z_lift_heights_()
int gcode_position::get_num_positions()
{
- return num_pos_;
+ return positions_.count();
+}
+
+int gcode_position::get_max_positions()
+{
+ return positions_.get_max_size();
+}
+
+void gcode_position::grow_max_positions(int size)
+{
+ int current_size = positions_.get_max_size();
+ if (size < current_size)
+ {
+ return;
+ }
+ positions_.resize(size, initial_position_);
+
}
void gcode_position::add_position(position& pos)
{
- cur_pos_ = (cur_pos_+1) % position_buffer_size_;
- positions_[cur_pos_] = pos;
- if (num_pos_ < position_buffer_size_)
- num_pos_++;
+ positions_.push_front(pos);
}
void gcode_position::add_position(parsed_command& cmd)
{
- const int prev_pos = cur_pos_;
- cur_pos_ = (cur_pos_+1) % position_buffer_size_;
- positions_[cur_pos_] = positions_[prev_pos];
- positions_[cur_pos_].reset_state();
- positions_[cur_pos_].command = cmd;
- positions_[cur_pos_].is_empty = false;
- if (num_pos_ < position_buffer_size_)
- num_pos_++;
+
+ position current_position = positions_[0];
+ current_position.reset_state();
+ current_position.command = cmd;
+ current_position.is_empty = false;
+ positions_.push_front(current_position);
+
}
position gcode_position::get_position(int index)
{
- return positions_[(cur_pos_ - index + position_buffer_size_) % position_buffer_size_];
+ return positions_[index];
}
position gcode_position::get_current_position()
@@ -453,7 +448,7 @@ position gcode_position::get_previous_position()
position * gcode_position::get_position_ptr(int index)
{
- return &positions_[(cur_pos_ - index + position_buffer_size_) % position_buffer_size_];
+ return &positions_[index];
}
position * gcode_position::get_current_position_ptr()
@@ -621,7 +616,7 @@ void gcode_position::update(parsed_command& command, const long file_line_number
{
double r;
r = snapshot_x_max_; // good stand in for radius
- const double dist = sqrt(p_current_pos->x*p_current_pos->x + p_current_pos->y*p_current_pos->y);
+ const double dist = utilities::sqrt(p_current_pos->x*p_current_pos->x + p_current_pos->y*p_current_pos->y);
is_in_bounds = utilities::less_than_or_equal(dist, r);
}
@@ -697,11 +692,7 @@ void gcode_position::update(parsed_command& command, const long file_line_number
void gcode_position::undo_update()
{
- if (num_pos_ != 0)
- {
- cur_pos_ = (cur_pos_ - 1 + position_buffer_size_) % position_buffer_size_;
- num_pos_--;
- }
+ positions_.pop_front();
}
position* gcode_position::undo_update(int num_updates)
@@ -715,21 +706,9 @@ position* gcode_position::undo_update(int num_updates)
// add the positions we will undo to the array
for (int index = 0; index < num_updates; index++)
{
- p_undo_positions[index] = get_position(index);
- }
-
- if (num_pos_ < num_updates)
- {
- num_pos_ = 0;
- cur_pos_ = 0;
- }
- else
- {
- cur_pos_ = (cur_pos_ - num_updates + position_buffer_size_) % position_buffer_size_;
- num_pos_ -= num_updates;
+ p_undo_positions[index] = positions_.pop_front();
}
return p_undo_positions;
-
}
// Private Members
diff --git a/GcodeProcessorLib/gcode_position.h b/GcodeProcessorLib/gcode_position.h
index 521f0d3..5930cf6 100644
--- a/GcodeProcessorLib/gcode_position.h
+++ b/GcodeProcessorLib/gcode_position.h
@@ -25,6 +25,7 @@
#include <string>
#include <vector>
#include <map>
+#include "circular_buffer.h"
#include "gcode_parser.h"
#include "position.h"
#include "gcode_comment_processor.h"
@@ -139,6 +140,8 @@ public:
void undo_update();
position * undo_update(int num_updates);
int get_num_positions();
+ int get_max_positions();
+ void grow_max_positions(int size);
position get_position(int index);
position get_current_position();
position get_previous_position();
@@ -149,10 +152,9 @@ public:
bool get_g90_91_influences_extruder();
private:
gcode_position(const gcode_position &source);
+ position initial_position_;
int position_buffer_size_;
- position* positions_;
- int cur_pos_;
- int num_pos_;
+ circular_buffer<position> positions_;
void add_position(parsed_command &);
void add_position(position &);
bool autodetect_position_;
diff --git a/GcodeProcessorLib/logger.cpp b/GcodeProcessorLib/logger.cpp
index 5b1e4eb..6aa591a 100644
--- a/GcodeProcessorLib/logger.cpp
+++ b/GcodeProcessorLib/logger.cpp
@@ -36,7 +36,7 @@ logger::logger(std::vector<std::string> names, std::vector<int> levels) {
logger_names_[index] = names[index];
logger_levels_[index] = levels[index];
}
- set_log_level_by_value(NOSET);
+ set_log_level_by_value((int)log_levels::NOSET);
}
@@ -57,22 +57,36 @@ void logger::set_log_level_by_value(const int level_value)
logger_levels_[type_index] = log_level;
}
}
-void logger::set_log_level(const int logger_type, const int log_level)
+
+void logger::set_log_level(const int logger_type, log_levels log_level)
{
- logger_levels_[logger_type] = log_level;
+ logger_levels_[logger_type] = (int)log_level;
+}
+std::string logger::get_log_level_name(std::string logger_name)
+{
+ std::string log_level_name = "UNKNOWN";
+ for (int type_index = 0; type_index < num_loggers_; type_index++)
+ {
+ if (logger_names_[type_index] == logger_name)
+ {
+ log_level_name = log_level_names[logger_levels_[type_index]];
+ break;
+ }
+ }
+ return log_level_name;
}
-void logger::set_log_level(const int log_level)
+void logger::set_log_level(log_levels log_level)
{
for (int type_index = 0; type_index < num_loggers_; type_index++)
{
- logger_levels_[type_index] = log_level;
+ logger_levels_[type_index] = (int)log_level;
}
}
-int logger::get_log_level_value(const int log_level)
+int logger::get_log_level_value(log_levels log_level)
{
- return log_level_values[log_level];
+ return log_level_values[(int)log_level];
}
int logger::get_log_level_for_value(int log_level_value)
{
@@ -83,12 +97,12 @@ int logger::get_log_level_for_value(int log_level_value)
}
return 0;
}
-bool logger::is_log_level_enabled(const int logger_type, const int log_level)
+bool logger::is_log_level_enabled(const int logger_type, log_levels log_level)
{
- return logger_levels_[logger_type] <= log_level;
+ return logger_levels_[logger_type] <= (int)log_level;
}
-void logger::create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output)
+void logger::create_log_message(const int logger_type, log_levels log_level, const std::string& message, std::string& output)
{
// example message
// 2020-04-20 21:36:59,414 - arc_welder.__init__ - INFO - MESSAGE_GOES_HERE
@@ -102,7 +116,7 @@ void logger::create_log_message(const int logger_type, const int log_level, cons
// add a spacer
output.append(" - ");
// add the log level name
- output.append(log_level_names[log_level]);
+ output.append(log_level_names[(int)log_level]);
// add a spacer
output.append(" - ");
// add the message
@@ -114,12 +128,12 @@ void logger::log_exception(const int logger_type, const std::string& message)
log(logger_type, log_levels::ERROR, message, true);
}
-void logger::log(const int logger_type, const int log_level, const std::string& message)
+void logger::log(const int logger_type, log_levels log_level, const std::string& message)
{
log(logger_type, log_level, message, false);
}
-void logger::log(const int logger_type, const int log_level, const std::string& message, bool is_exception)
+void logger::log(const int logger_type, log_levels log_level, const std::string& message, bool is_exception)
{
// Make sure the loggers have been initialized
if (!loggers_created_)
diff --git a/GcodeProcessorLib/logger.h b/GcodeProcessorLib/logger.h
index 9db2df1..8cbd5e3 100644
--- a/GcodeProcessorLib/logger.h
+++ b/GcodeProcessorLib/logger.h
@@ -36,7 +36,7 @@ enum log_levels { NOSET, VERBOSE, DEBUG, INFO, WARNING , ERROR, CRITICAL};
static const int log_level_names_size = 7;
static const char* log_level_names[] = {"NOSET", "VERBOSE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"};
const static int log_level_values[LOG_LEVEL_COUNT] = { 0, 5, 10, 20, 30, 40, 50};
-
+#define DEFAULT_LOG_LEVEL_VALUE 40
class logger
{
public:
@@ -46,17 +46,18 @@ public:
void set_log_level_by_value(const int logger_type, const int log_level_value);
void set_log_level_by_value(const int log_level_value);
- void set_log_level(const int logger_type, const int log_level);
- void set_log_level(const int log_level);
-
- virtual void log(const int logger_type, const int log_level, const std::string& message);
- virtual void log(const int logger_type, const int log_level, const std::string& message, bool is_exception);
+ void set_log_level(const int logger_type, log_levels log_level);
+ void set_log_level(log_levels log_level);
+
+ std::string get_log_level_name(std::string logger_name);
+ virtual void log(const int logger_type, log_levels log_level, const std::string& message);
+ virtual void log(const int logger_type, log_levels log_level, const std::string& message, bool is_exception);
virtual void log_exception(const int logger_type, const std::string& message);
- static int get_log_level_value(const int log_level);
+ static int get_log_level_value(log_levels log_level);
static int get_log_level_for_value(int log_level_value);
- virtual bool is_log_level_enabled(const int logger_type, const int log_level);
+ virtual bool is_log_level_enabled(const int logger_type, log_levels log_level);
protected:
- virtual void create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output);
+ virtual void create_log_message(const int logger_type, log_levels log_level, const std::string& message, std::string& output);
bool loggers_created_;
private:
diff --git a/GcodeProcessorLib/position.cpp b/GcodeProcessorLib/position.cpp
index 6a85801..8fcd8cd 100644
--- a/GcodeProcessorLib/position.cpp
+++ b/GcodeProcessorLib/position.cpp
@@ -356,6 +356,10 @@ position& position::operator=(const position& pos) {
return *this;
}
+bool position::is_travel()
+{
+ return is_xyz_travel || is_xy_travel;
+}
void position::set_num_extruders(int num_extruders_)
{
if (num_extruders_ == num_extruders)
diff --git a/GcodeProcessorLib/position.h b/GcodeProcessorLib/position.h
index 0958cd5..7470ae9 100644
--- a/GcodeProcessorLib/position.h
+++ b/GcodeProcessorLib/position.h
@@ -102,5 +102,6 @@ struct position
void set_e_axis_mode(const std::string& e_axis_default_mode);
void set_units_default(const std::string& units_default);
bool can_take_snapshot();
+ bool is_travel();
};
#endif \ No newline at end of file
diff --git a/GcodeProcessorLib/sourcelist.cmake b/GcodeProcessorLib/sourcelist.cmake
index a4e86aa..0dcd0cb 100644
--- a/GcodeProcessorLib/sourcelist.cmake
+++ b/GcodeProcessorLib/sourcelist.cmake
@@ -1,7 +1,5 @@
set(GcodeProcessorLibSources ${GcodeProcessorLibSources}
- array_list.cpp
array_list.h
- circular_buffer.cpp
circular_buffer.h
extruder.cpp
extruder.h
diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp
index dbe23fb..74fef46 100644
--- a/GcodeProcessorLib/utilities.cpp
+++ b/GcodeProcessorLib/utilities.cpp
@@ -19,20 +19,113 @@
// You can contact the author at the following email address:
// FormerLurker@pm.me
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
#include "utilities.h"
-#include <cmath>
-#include <sstream>
-#include <iostream>
-#include <iomanip>
-#include "fpconv.h"
-const std::string utilities::WHITESPACE_ = " \n\r\t\f\v";
-const char utilities::GUID_RANGE[] = "0123456789abcdef";
-const bool utilities::GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 };
+namespace utilities {
+ // Box Drawing Consts
+ // String Consts
+ // Note: these ascii replacement characters must NOT appear in the text unless they will be replaced with box characters.
+ const char box_drawing::table_elements_replacement[8] = {char(128),char(129),char(130),char(131),char(132),char(133),char(134),char(135) };
+ //enum BoxElementEnum { HORIZONTAL = 0, VERTICAL = 1, UPPER_LEFT = 2, UPPER_RIGHT = 3, MIDDLE_LEFT = 4, MIDDLE_RIGHT = 5, LOWER_LEFT = 6, LOWER_RIGHT = 7 };
+ const std::string box_drawing::table_elements_ascii[8] = {"-","|","+" ,"+" ,"+" ,"+" ,"+" ,"+" };
+ const std::string box_drawing::table_elements_utf8[8] = { "\u2500","\u2502","\u250C" ,"\u2510" ,"\u251C" ,"\u2524" ,"\u2514" ,"\u2518" };
+ const std::string box_drawing::table_elements_html[8] = { "&#9472","&#9474", "&#9484","&#9488","&#9500","&#9508","&#9492","&#9496" };
+
+ box_drawing::box_drawing()
+ {
+ set_box_type(BoxEncodingEnum::ASCII);
+
+ width_ = 100;
+ }
+ box_drawing::box_drawing(BoxEncodingEnum encoding, int width)
+ {
+ set_box_type(encoding);
+ width_ = width;
+ }
+
+ void box_drawing::set_box_type(BoxEncodingEnum encoding)
+ {
+ box_encoding_ = encoding;
+ for (int index = 0; index < 8; index++)
+ {
+ switch (box_encoding_)
+ {
+ case BoxEncodingEnum::ASCII:
+ table_elements_[index] = table_elements_ascii[index];
+ break;
+ case BoxEncodingEnum::UTF8:
+ table_elements_[index] = table_elements_utf8[index];
+ break;
+ case BoxEncodingEnum::HTML:
+ table_elements_[index] = table_elements_html[index];
+ break;
+ default:
+ table_elements_[index] = table_elements_ascii[index];
+ }
+
+ }
+ }
+
+ void box_drawing::top(std::stringstream& stream)
+ {
+ stream << get_box_replacement_element(BoxElementEnum::UPPER_LEFT) << std::setw(width_) << std::setfill(get_box_replacement_element(BoxElementEnum::HORIZONTAL)) << "" << get_box_replacement_element(BoxElementEnum::UPPER_RIGHT) << "\n" << std::setfill(' ');
+ }
+
+ void box_drawing::row(std::stringstream& stream, std::string line)
+ {
+ stream << get_box_replacement_element(BoxElementEnum::VERTICAL) << line << get_box_replacement_element(BoxElementEnum::VERTICAL) << "\n" << std::setfill(' ');
+ }
+
+ void box_drawing::middle(std::stringstream& stream)
+ {
+ stream << get_box_replacement_element(BoxElementEnum::MIDDLE_LEFT) << std::setw(width_) << std::setfill(get_box_replacement_element(BoxElementEnum::HORIZONTAL)) << "" << get_box_replacement_element(BoxElementEnum::MIDDLE_RIGHT) << "\n" << std::setfill(' ');
+ }
+ void box_drawing::bottom(std::stringstream& stream)
+ {
+ stream << get_box_replacement_element(BoxElementEnum::LOWER_LEFT) << std::setw(width_) << std::setfill(get_box_replacement_element(BoxElementEnum::HORIZONTAL)) << "" << get_box_replacement_element(BoxElementEnum::LOWER_RIGHT) << "\n" << std::setfill(' ');
+ }
+
+ char box_drawing::get_box_replacement_element(BoxElementEnum element)
+ {
+ return table_elements_replacement[(int)element];
+ }
+
+ void box_drawing::make_replacements(std::string &box)
+ {
+ for (int index = 0; index < 8; index++)
+ {
+ char c = table_elements_replacement[index];
+ std::string search;
+ search += c;
+ box = utilities::replace(box, search, table_elements_[index]);
+ }
+
+ }
+
+
+
+
+ const std::string WHITESPACE_ = " \n\r\t\f\v";
+ const char GUID_RANGE[] = "0123456789abcdef";
+ const bool GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 };
+
+ extern const char PATH_SEPARATOR_ =
+#ifdef _WIN32
+ '\\';
+#else
+ '/';
+#endif
+
+}
bool utilities::is_zero(double x, double tolerance)
{
- return std::fabs(x) < tolerance;
+ return utilities::abs(x) < tolerance;
+}
+bool utilities::is_zero(double x)
+{
+ return utilities::abs(x) < ZERO_TOLERANCE;
}
int utilities::round_up_to_int(double x, double tolerance)
@@ -40,32 +133,63 @@ int utilities::round_up_to_int(double x, double tolerance)
return int(x + tolerance);
}
+int utilities::round_up_to_int(double x)
+{
+ return int(x + ZERO_TOLERANCE);
+}
+
bool utilities::is_equal(double x, double y, double tolerance)
{
- double abs_difference = std::fabs(x - y);
+ double abs_difference = utilities::abs(x - y);
return abs_difference < tolerance;
}
+bool utilities::is_equal(double x, double y)
+{
+ double abs_difference = utilities::abs(x - y);
+ return abs_difference < ZERO_TOLERANCE;
+}
+
bool utilities::greater_than(double x, double y, double tolerance)
{
return x > y && !is_equal(x, y, tolerance);
}
+bool utilities::greater_than(double x, double y)
+{
+ return x > y && !is_equal(x, y);
+}
+
bool utilities::greater_than_or_equal(double x, double y, double tolerance)
{
return x > y || is_equal(x, y, tolerance);
}
+bool utilities::greater_than_or_equal(double x, double y)
+{
+ return x > y || is_equal(x, y);
+}
+
bool utilities::less_than(double x, double y, double tolerance)
{
return x < y && !is_equal(x, y, tolerance);
}
+bool utilities::less_than(double x, double y)
+{
+ return x < y && !is_equal(x, y);
+}
+
bool utilities::less_than_or_equal(double x, double y, double tolerance)
{
return x < y || is_equal(x, y, tolerance);
}
+bool utilities::less_than_or_equal(double x, double y)
+{
+ return x < y || is_equal(x, y);
+}
+
double utilities::get_cartesian_distance(double x1, double y1, double x2, double y2)
{
@@ -73,7 +197,7 @@ double utilities::get_cartesian_distance(double x1, double y1, double x2, double
double xdif = x1 - x2;
double ydif = y1 - y2;
double dist_squared = xdif * xdif + ydif * ydif;
- return std::sqrt(dist_squared);
+ return utilities::sqrt(dist_squared);
}
double utilities::get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2)
@@ -83,7 +207,31 @@ double utilities::get_cartesian_distance(double x1, double y1, double z1, double
double ydif = y1 - y2;
double zdif = z1 - z2;
double dist_squared = xdif * xdif + ydif * ydif + zdif * zdif;
- return std::sqrt(dist_squared);
+ return utilities::sqrt(dist_squared);
+}
+
+double utilities::get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise)
+{
+ double center_x = x1 - i;
+ double center_y = y1 - j;
+ double radius = utilities::hypot(i, j);
+ double z_dist = z2 - z1;
+ double rt_x = x2 - center_x;
+ double rt_y = y2 - center_y;
+ double angular_travel_total = utilities::atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y);
+ if (angular_travel_total < 0) { angular_travel_total += 2.0 * PI_DOUBLE; }
+ // Adjust the angular travel if the direction is clockwise
+ if (is_clockwise) { angular_travel_total -= 2.0 * PI_DOUBLE; }
+ // Full circle fix.
+ if (x1 == x2 && y1 == y2 && angular_travel_total == 0)
+ {
+ angular_travel_total += 2.0 * PI_DOUBLE;
+ }
+
+ // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are
+ // calculating here
+ return utilities::hypot(angular_travel_total * radius, utilities::abs(z_dist));
+
}
std::string utilities::to_string(double value)
@@ -117,6 +265,33 @@ std::string utilities::trim(const std::string& s)
return rtrim(ltrim(s));
}
+std::string utilities::join(const std::string* strings, size_t length, std::string sep)
+{
+ std::string output;
+ for (int i = 0; i < length; i++)
+ {
+ if (i > 0)
+ {
+ output += sep;
+ }
+ output += strings[i];
+ }
+ return output;
+}
+
+std::string utilities::join(const std::vector<std::string> strings, std::string sep)
+{
+ std::string output;
+
+ for (std::vector<std::string>::const_iterator p = strings.begin();
+ p != strings.end(); ++p) {
+ output += *p;
+ if (p != strings.end() - 1)
+ output += sep;
+ }
+ return output;
+}
+
std::istream& utilities::safe_get_line(std::istream& is, std::string& t)
{
t.clear();
@@ -149,7 +324,7 @@ std::istream& utilities::safe_get_line(std::istream& is, std::string& t)
}
}
-std::string utilities::center(std::string input, int width)
+std::string utilities::center(std::string input, int width)
{
int input_width = (int)input.length();
int difference = width - input_width;
@@ -157,7 +332,7 @@ std::string utilities::center(std::string input, int width)
{
return input;
}
- int left_padding = difference /2;
+ int left_padding = difference / 2;
int right_padding = width - left_padding - input_width;
return std::string(left_padding, ' ') + input + std::string(right_padding, ' ');
}
@@ -171,6 +346,15 @@ double utilities::get_percent_change(int v1, int v2)
return 0;
}
+double utilities::get_percent_change(double v1, double v2)
+{
+ if (v1 != 0)
+ {
+ return ((v2 - v1) / v1);
+ }
+ return 0;
+}
+
std::string utilities::get_percent_change_string(int v1, int v2, int precision)
{
std::stringstream format_stream;
@@ -197,7 +381,7 @@ std::string utilities::get_percent_change_string(int v1, int v2, int precision)
int utilities::get_num_digits(int x)
{
- x = abs(x);
+ x = utilities::abs(x);
return (x < 10 ? 1 :
(x < 100 ? 2 :
(x < 1000 ? 3 :
@@ -207,12 +391,20 @@ int utilities::get_num_digits(int x)
(x < 10000000 ? 7 :
(x < 100000000 ? 8 :
(x < 1000000000 ? 9 :
- 10)))))))));
+ (x < 10000000000 ? 10 : -1))))))))));
+}
+
+int utilities::get_num_digits(double x, int precision)
+{
+ return get_num_digits(
+ (int)utilities::ceil(x * utilities::pow(10, precision) - .4999999999999)
+ / utilities::pow(10, precision)
+ );
}
int utilities::get_num_digits(double x)
{
- return get_num_digits((int) x);
+ return get_num_digits((int)x);
}
// Nice utility function found here: https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path
@@ -243,7 +435,7 @@ std::vector<std::string> utilities::splitpath(const std::string& str)
return result;
}
-bool utilities::get_file_path(const std::string& file_path, std::string & path)
+bool utilities::get_file_path(const std::string& file_path, std::string& path)
{
std::vector<std::string> file_parts = splitpath(file_path);
if (file_parts.size() == 0)
@@ -269,12 +461,12 @@ std::string utilities::create_uuid() {
bool utilities::get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path)
{
temp_file_path = "";
- if (!utilities::get_file_path(file_path, temp_file_path))
+ if (!get_file_path(file_path, temp_file_path))
{
return false;
}
temp_file_path = temp_file_path;
- temp_file_path += utilities::create_uuid();
+ temp_file_path += create_uuid();
temp_file_path += ".tmp";
return true;
}
@@ -289,7 +481,185 @@ double utilities::hypot(double x, double y)
}
if (y == 0.0) return x;
y /= x;
- return x * std::sqrt(1.0 + y * y);
+ return x * utilities::sqrt(1.0 + y * y);
+}
+
+float utilities::hypotf(float x, float y)
+{
+ if (x < 0.0f) x = -x;
+ if (y < 0.0f) y = -y;
+ if (x < y) {
+ float tmp = x;
+ x = y; y = tmp;
+ }
+ if (y == 0.0f) return x;
+ y /= x;
+ return x * utilities::sqrtf(1.0f + y * y);
+}
+
+double utilities::atan2(double y, double x)
+{
+ return std::atan2(y, x);
+}
+
+float utilities::atan2f(float y, float x)
+{
+ return std::atan2(y, x);
+}
+
+double utilities::floor(double x)
+{
+ return std::floor(x);
+}
+
+float utilities::floorf(float x)
+{
+ return std::floor(x);
+}
+
+double utilities::ceil(double x)
+{
+ return std::ceil(x);
+}
+
+float utilities::ceilf(float x)
+{
+ return std::ceil(x);
+}
+
+double utilities::cos(double x)
+{
+ return std::cos(x);
+}
+
+float utilities::cosf(float x)
+{
+ return std::cos(x);
+}
+
+double utilities::sin(double x)
+{
+ return std::sin(x);
+}
+
+float utilities::sinf(float x)
+{
+ return std::sin(x);
+}
+
+double utilities::abs(double x)
+{
+ return std::abs(x);
+}
+
+int utilities::abs(int x)
+{
+ return std::abs(x);
+}
+
+float utilities::absf(float x)
+{
+ return std::abs(x);
+}
+
+double utilities::fabs(double x)
+{
+ return std::fabs(x);
+}
+
+float utilities::fabsf(float x)
+{
+ return std::fabs(x);
+}
+
+double utilities::sqrt(double x)
+{
+ return std::sqrt(x);
+}
+
+float utilities::sqrtf(float x)
+{
+ return std::sqrt(x);
+}
+
+double utilities::pow(int e, double x)
+{
+ return std::pow(e, x);
+}
+
+double utilities::min(double x, double y)
+{
+ return std::min(x, y);
+}
+
+float utilities::minf(float x, float y)
+{
+ return std::min(x, y);
+}
+
+double utilities::max(double x, double y)
+{
+ return std::max(x, y);
+}
+
+float utilities::maxf(float x, float y)
+{
+ return std::max(x, y);
+}
+
+double utilities::radians(double x)
+{
+ return (x * PI_DOUBLE) / 180.0;
+}
+
+float utilities::radiansf(float x)
+{
+ return (x * PI_FLOAT) / 180.0f;
+}
+
+double utilities::sq(double x)
+{
+ return x * x;
+}
+
+float utilities::sqf(float x)
+{
+ return x * x;
+}
+
+bool utilities::within(double value, double min, double max)
+{
+ return ((value) >= (min) && (value) <= (max));
+}
+
+bool utilities::withinf(float value, float min, float max)
+{
+ return ((value) >= (min) && (value) <= (max));
+}
+
+double utilities::constrain(double value, double arg_min, double arg_max)
+{
+ return ((value) < (arg_min) ? (arg_min) : ((value) > (arg_max) ? (arg_max) : (value)));
+}
+
+float utilities::constrainf(float value, float arg_min, float arg_max)
+{
+ return ((value) < (arg_min) ? (arg_min) : ((value) > (arg_max) ? (arg_max) : (value)));
+}
+
+double utilities::reciprocal(double x)
+{
+ return 1.0 / x;
+}
+
+float utilities::reciprocalf(float x)
+{
+ return 1.0f / x;
+}
+
+void* utilities::memcpy(void* dest, const void* src, size_t n)
+{
+ return std::memcpy(dest, src, n);
}
std::string utilities::dtos(double x, unsigned char precision)
@@ -297,18 +667,61 @@ std::string utilities::dtos(double x, unsigned char precision)
static char buffer[FPCONV_BUFFER_LENGTH];
char* p = buffer;
buffer[fpconv_dtos(x, buffer, precision)] = '\0';
- /* This is code that can be used to compare the output of the
+ /* This is code that can be used to compare the output of the
modified fpconv_dtos function to the ofstream output
Note: It currently only fails for some checks where the original double does not store
perfectly. In this case I actually think the dtos output is better than ostringstream!
std::ostringstream stream;
stream << std::fixed;
stream << std::setprecision(precision) << x;
-
+
if (std::string(buffer) != stream.str())
{
std::cout << std::fixed << "Failed to convert: " << std::setprecision(24) << x << " Precision:" << std::setprecision(0) << static_cast <int> (precision) << " String:" << std::string(buffer) << " Stream:" << stream.str() << std::endl;
}
*/
return buffer;
+}
+/*
+bool case_insensitive_compare_char(char& c1, char& c2)
+{
+ if (c1 == c2)
+ return true;
+ else if (std::toupper(c1) == std::toupper(c2))
+ return true;
+ return false;
+}
+
+/*
+ * Case Insensitive String Comparision
+
+bool case_insensitive_compare(std::string& str1, std::string& str2)
+{
+ return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), &case_insensitive_compare_char));
+}
+
+*/
+
+std::string utilities::replace(std::string subject, const std::string& search, const std::string& replace) {
+ size_t pos = 0;
+ while ((pos = subject.find(search, pos)) != std::string::npos) {
+ subject.replace(pos, search.length(), replace);
+ pos += replace.length();
+ }
+ return subject;
+}
+
+double utilities::rand_range(double min, double max) {
+ double f = (double)std::rand() / RAND_MAX;
+ return min + f * (max - min);
+}
+
+unsigned char utilities::rand_range(unsigned char min, unsigned char max) {
+ double f = (double)std::rand() / RAND_MAX;
+ return static_cast<unsigned char>(static_cast<double>(min) + f * (static_cast<double>(max) - static_cast<double>(min)));
+}
+
+int utilities::rand_range(int min, int max) {
+ double f = (double)std::rand() / RAND_MAX;
+ return static_cast<int>(static_cast<double>(min) + f * (static_cast<double>(max) - static_cast<double>(min)));
} \ No newline at end of file
diff --git a/GcodeProcessorLib/utilities.h b/GcodeProcessorLib/utilities.h
index 401bed4..1b7923f 100644
--- a/GcodeProcessorLib/utilities.h
+++ b/GcodeProcessorLib/utilities.h
@@ -20,76 +20,201 @@
// FormerLurker@pm.me
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma once
+#ifndef UTILITIES_H
+#define UTILITIES_H
#include <string>
#include <vector>
#include <set>
+#include <cmath>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include <algorithm>
+#include <cstring>
+#include "fpconv.h"
+
#define FPCONV_BUFFER_LENGTH 25
// Had to increase the zero tolerance because prusa slicer doesn't always
// retract enough while wiping.
#define ZERO_TOLERANCE 0.000005
+#define PI_DOUBLE 3.14159265358979323846264338327950288
+#define PI_FLOAT 3.14159265358979323846264338327950288f
+
+namespace utilities{
+ extern const std::string WHITESPACE_;
+ extern const char GUID_RANGE[];
+ extern const bool GUID_DASHES[];
+
+ extern const char PATH_SEPARATOR_;
+ bool is_zero(double x, double tolerance);
+ bool is_zero(double x);
+
+ int round_up_to_int(double x, double tolerance);
+ int round_up_to_int(double x);
+
+ bool is_equal(double x, double y, double tolerance);
+
+ bool is_equal(double x, double y);
+
+ bool greater_than(double x, double y, double tolerance);
+
+ bool greater_than(double x, double y);
+
+ bool greater_than_or_equal(double x, double y, double tolerance);
+ bool greater_than_or_equal(double x, double y);
+
+ bool less_than(double x, double y, double tolerance);
+ bool less_than(double x, double y);
+
+ bool less_than_or_equal(double x, double y, double tolerance);
+
+ bool less_than_or_equal(double x, double y);
+
+ double get_cartesian_distance(double x1, double y1, double x2, double y2);
+
+ double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2);
+
+ double get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise);
+ std::string to_string(double value);
+
+ std::string to_string(int value);
+ std::string ltrim(const std::string& s);
+
+ std::string rtrim(const std::string& s);
-class utilities{
-public:
- static bool is_zero(double x, double tolerance = ZERO_TOLERANCE);
- static int round_up_to_int(double x, double tolerance = ZERO_TOLERANCE);
- static bool is_equal(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool greater_than(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool greater_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool less_than(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool less_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE);
+ std::string trim(const std::string& s);
+ std::string join(const std::string* strings, size_t length, std::string sep);
+
+ std::string join(const std::vector<std::string> strings, std::string sep);
+
+ std::istream& safe_get_line(std::istream& is, std::string& t);
+
+ std::string center(std::string input, int width);
+ double get_percent_change(int v1, int v2);
+ double get_percent_change(double v1, double v2);
+ std::string get_percent_change_string(int v1, int v2, int precision);
+
+ int get_num_digits(int x);
+ int get_num_digits(double x, int precision);
+
+ int get_num_digits(double x);
+ // Nice utility function found here: https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path
+ std::vector<std::string> splitpath(const std::string& str);
+
+ bool get_file_path(const std::string& file_path, std::string& path);
+
+ std::string create_uuid();
+
+ bool get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path);
+
+ double hypot(double x, double y);
- static double get_cartesian_distance(double x1, double y1, double x2, double y2);
- static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2);
- static std::string to_string(double value);
- static std::string to_string(int value);
- static std::string ltrim(const std::string& s);
- static std::string rtrim(const std::string& s);
- static std::string trim(const std::string& s);
- static std::istream& safe_get_line(std::istream& is, std::string& t);
- static std::string center(std::string input, int width);
- static double get_percent_change(int v1, int v2);
- static std::string get_percent_change_string(int v1, int v2, int precision);
-
- static int get_num_digits(int x);
- static int get_num_digits(double x);
-
- static std::vector<std::string> splitpath(const std::string& str);
- static bool get_file_path(const std::string& file_path, std::string& path);
- static bool get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path);
- static std::string create_uuid();
- // Man I can't wait till I can drop python 2.7 support so I can stop doing everything myself. s
- // td::hypot doesn't work for msvc for python 2.7....
- static double hypot(double x, double y);
- static std::string dtos(double x, unsigned char precision);
-
- static double rand_range(double min, double max) {
- double f = (double)std::rand() / RAND_MAX;
- return min + f * (max - min);
- }
-
- static unsigned char rand_range(unsigned char min, unsigned char max) {
- double f = (double)std::rand() / RAND_MAX;
- return static_cast<unsigned char>(static_cast<double>(min) + f * (static_cast<double>(max) - static_cast<double>(min)));
- }
-
- static int rand_range(int min, int max) {
- double f = (double)std::rand() / RAND_MAX;
- return static_cast<int>(static_cast<double>(min) + f * (static_cast<double>(max) - static_cast<double>(min)));
- }
+ float hypotf(float x, float y);
+ double atan2(double y, double x);
+
+ float atan2f(float y, float x);
+
+ double floor(double x);
+
+ float floorf(float x);
+
+ double ceil(double x);
-protected:
- static const std::string WHITESPACE_;
- static const char PATH_SEPARATOR_ =
-#ifdef _WIN32
- '\\';
-#else
- '/';
-#endif
- static const char GUID_RANGE[];
- static const bool GUID_DASHES[];
-private:
- utilities();
-
-};
+ float ceilf(float x);
+
+ double cos(double x);
+
+ float cosf(float x);
+
+ double sin(double x);
+
+ float sinf(float x);
+
+ double abs(double x);
+
+ int abs(int x);
+
+ float absf(float x);
+
+ double fabs(double x);
+
+ float fabsf(float x);
+
+ double sqrt(double x);
+
+ float sqrtf(float x);
+
+ double pow(int e, double x);
+
+
+ double min(double x, double y);
+
+ float minf(float x, float y);
+
+ double max(double x, double y);
+
+ float maxf(float x, float y);
+
+ double radians(double x);
+
+ float radiansf(float x);
+
+ double sq(double x);
+
+ float sqf(float x);
+
+ bool within(double n, double l, double h);
+
+ bool withinf(float n, float l, float h);
+
+ double constrain(double value, double arg_min, double arg_max);
+
+ float constrainf(float value, float arg_min, float arg_max);
+
+ double reciprocal(double x);
+
+ float reciprocalf(float x);
+
+ void* memcpy(void* dest, const void* src, size_t n);
+
+ std::string dtos(double x, unsigned char precision);
+
+ std::string replace(std::string subject, const std::string& search, const std::string& replace);
+
+ double rand_range(double min, double max);
+ unsigned char rand_range(unsigned char min, unsigned char max);
+ int rand_range(int min, int max);
+
+
+
+ class box_drawing {
+
+ public:
+ enum BoxElementEnum { HORIZONTAL = 0, VERTICAL = 1, UPPER_LEFT = 2, UPPER_RIGHT = 3, MIDDLE_LEFT = 4, MIDDLE_RIGHT = 5, LOWER_LEFT = 6, LOWER_RIGHT = 7 };
+ enum BoxEncodingEnum {ASCII=0, UTF8=1, HTML=2};
+ box_drawing();
+ box_drawing(BoxEncodingEnum encoding, int width);
+ static const char table_elements_replacement[8];
+ static const std::string table_elements_ascii[8];
+ static const std::string table_elements_utf8[8];
+ static const std::string table_elements_html[8];
+ char get_box_replacement_element(BoxElementEnum element);
+ void top(std::stringstream& stream);
+ void row(std::stringstream& stream, std::string line);
+ void middle(std::stringstream& stream);
+ void bottom(std::stringstream& stream);
+ void set_box_type(BoxEncodingEnum encoding);
+ void make_replacements(std::string &box);
+
+ private:
+ std::string table_elements_[8];
+ BoxEncodingEnum box_encoding_;
+ std::string output_;
+ std::stringstream output_stream_;
+ int width_;
+
+ };
+
+}
+#endif \ No newline at end of file
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 47ca31c..0000000
--- a/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-all:
- $(MAKE) -C GcodeProcessorLib all
- $(MAKE) -C ArcWelder all
- $(MAKE) -C ArcWelderConsole all
- $(MAKE) -C ArcWelderInverseProcessor all
-
-clean:
- $(MAKE) -C GcodeProcessorLib clean
- $(MAKE) -C ArcWelder clean
- $(MAKE) -C ArcWelderConsole clean
- $(MAKE) -C ArcWelderInverseProcessor clean
-
-
-cleanall: clean
-
-
-.PHONY: all clean
diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp
index 36588f2..2cdfde5 100644
--- a/PyArcWelder/py_arc_welder.cpp
+++ b/PyArcWelder/py_arc_welder.cpp
@@ -22,105 +22,431 @@
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "py_arc_welder.h"
-PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, std::string guid)
+PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress, std::string guid, bool include_detailed_statistics)
{
- std::string segment_statistics = progress.segment_statistics.str();
- PyObject* pyGuid = gcode_arc_converter::PyUnicode_SafeFromString(guid);
- if (pyGuid == NULL)
- return NULL;
- PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_statistics);
- if (pyMessage == NULL)
- return NULL;
- double total_count_reduction_percent = progress.segment_statistics.get_total_count_reduction_percent();
- PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i,s:f}",
- "percent_complete",
- progress.percent_complete, //1
- "seconds_elapsed",
- progress.seconds_elapsed, //2
- "seconds_remaining",
- progress.seconds_remaining, //3
- "gcodes_processed",
- progress.gcodes_processed, //4
- "lines_processed",
- progress.lines_processed, //5
- "points_compressed",
- progress.points_compressed, //6
- "arcs_created",
- progress.arcs_created, //7
- "num_firmware_compensations",
- progress.num_firmware_compensations, //8
- "source_file_position",
- progress.source_file_position, //9
- "source_file_size",
- progress.source_file_size, //10
- "target_file_size",
- progress.target_file_size, //11
- "compression_ratio",
- progress.compression_ratio, //12
- "compression_percent",
- progress.compression_percent, //13
- "source_file_total_length",
- progress.segment_statistics.total_length_source, //14
- "target_file_total_length",
- progress.segment_statistics.total_length_target, //15
- "source_file_total_count",
- progress.segment_statistics.total_count_source, //16
- "target_file_total_count",
- progress.segment_statistics.total_count_target, //17
- "total_count_reduction_percent",
- total_count_reduction_percent //18
-
- );
+ PyObject* pyGuid = gcode_arc_converter::PyUnicode_SafeFromString(guid);
+ if (pyGuid == NULL)
+ return NULL;
- if (py_progress == NULL)
- {
- return NULL;
- }
- // Due to a CRAZY issue, I have to add this item after building the py_progress object,
- // else it crashes in python 2.7. Looking forward to retiring this backwards
- // compatible code...
- PyDict_SetItemString(py_progress, "segment_statistics_text", pyMessage);
- PyDict_SetItemString(py_progress, "guid", pyGuid);
- return py_progress;
+ std::string segment_statistics = "";
+ std::string segment_travel_statistics = "";
+
+ if (include_detailed_statistics)
+ {
+ // Extrusion Statistics
+ source_target_segment_statistics combined_stats = source_target_segment_statistics::add(progress.segment_statistics, progress.segment_retraction_statistics);
+ segment_statistics = combined_stats.str("", utilities::box_drawing::HTML);
+ // Travel Statistics
+ segment_travel_statistics = progress.travel_statistics.str("", utilities::box_drawing::HTML);
+ }
+ PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_statistics);
+ if (pyMessage == NULL)
+ return NULL;
+ double total_count_reduction_percent = progress.segment_statistics.get_total_count_reduction_percent();
+
+ PyObject* pyTravelMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_travel_statistics);
+ if (pyTravelMessage == NULL)
+ return NULL;
+ double total_travel_count_reduction_percent = progress.travel_statistics.get_total_count_reduction_percent();
+ PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i,s:f,s:f,s:f,s:i,s:i,s:f}",
+ "percent_complete",
+ progress.percent_complete, //1
+ "seconds_elapsed",
+ progress.seconds_elapsed, //2
+ "seconds_remaining",
+ progress.seconds_remaining, //3
+ "gcodes_processed",
+ progress.gcodes_processed, //4
+ "lines_processed",
+ progress.lines_processed, //5
+ "points_compressed",
+ progress.points_compressed, //6
+ "arcs_created",
+ progress.arcs_created, //7
+ "arcs_aborted_by_flowrate",
+ progress.arcs_aborted_by_flow_rate, //8
+ "num_firmware_compensations",
+ progress.num_firmware_compensations, //9
+ "num_gcode_length_exceptions",
+ progress.num_gcode_length_exceptions, //10
+ "source_file_position",
+ progress.source_file_position, //11
+ "source_file_size",
+ progress.source_file_size, //12
+ "target_file_size",
+ progress.target_file_size, //13
+ "compression_ratio",
+ progress.compression_ratio, //14
+ "compression_percent",
+ progress.compression_percent, //15
+ "source_file_total_length",
+ progress.segment_statistics.total_length_source, //16
+ "target_file_total_length",
+ progress.segment_statistics.total_length_target, //17
+ "source_file_total_count",
+ progress.segment_statistics.total_count_source, //18
+ "target_file_total_count",
+ progress.segment_statistics.total_count_target, //19
+ "total_count_reduction_percent",
+ total_count_reduction_percent, //20
+ "source_file_total_travel_length",
+ progress.travel_statistics.total_length_source, //21
+ "target_file_total_travel_length",
+ progress.travel_statistics.total_length_target, //22
+ "source_file_total_travel_count",
+ progress.travel_statistics.total_count_source, //23
+ "target_file_total_travel_count",
+ progress.travel_statistics.total_count_target, //24
+ "total_travel_count_reduction_percent",
+ total_travel_count_reduction_percent //25
+
+ );
+
+ if (py_progress == NULL)
+ {
+ return NULL;
+ }
+ // Due to a CRAZY issue, I have to add this item after building the py_progress object,
+ // else it crashes in python 2.7. Looking forward to retiring this backwards
+ // compatible code...
+ PyDict_SetItemString(py_progress, "segment_statistics_text", pyMessage);
+ PyDict_SetItemString(py_progress, "segment_travel_statistics_text", pyTravelMessage);
+ PyDict_SetItemString(py_progress, "guid", pyGuid);
+ return py_progress;
}
bool py_arc_welder::on_progress_(const arc_welder_progress& progress)
{
- PyObject* py_dict = py_arc_welder::build_py_progress(progress, guid_);
- if (py_dict == NULL)
- {
- return false;
- }
- PyObject* func_args = Py_BuildValue("(O)", py_dict);
- if (func_args == NULL)
- {
- Py_DECREF(py_dict);
- return false; // This was returning true, I think it was a typo. Making a note just in case.
- }
-
- PyGILState_STATE gstate = PyGILState_Ensure();
- PyObject* pContinueProcessing = PyObject_CallObject(py_progress_callback_, func_args);
- PyGILState_Release(gstate);
- Py_DECREF(func_args);
- Py_DECREF(py_dict);
- bool continue_processing;
- if (pContinueProcessing == NULL)
- {
- // no return value was supply, assume true, but without decrefing pContinueProcessing
- continue_processing = true;
- }
- else
- {
- if (pContinueProcessing == Py_None)
- {
- continue_processing = true;
- }
- else
- {
- continue_processing = PyLong_AsLong(pContinueProcessing) > 0;
- }
- Py_DECREF(pContinueProcessing);
- }
-
- return continue_processing;
+ PyObject* py_dict = py_arc_welder::build_py_progress(progress, guid_, false);
+ if (py_dict == NULL)
+ {
+ return false;
+ }
+ PyObject* func_args = Py_BuildValue("(O)", py_dict);
+ if (func_args == NULL)
+ {
+ Py_DECREF(py_dict);
+ return false; // This was returning true, I think it was a typo. Making a note just in case.
+ }
+
+ PyGILState_STATE gstate = PyGILState_Ensure();
+ PyObject* pContinueProcessing = PyObject_CallObject(py_progress_callback_, func_args);
+ PyGILState_Release(gstate);
+ Py_DECREF(func_args);
+ Py_DECREF(py_dict);
+ bool continue_processing;
+ if (pContinueProcessing == NULL)
+ {
+ // no return value was supply, assume true, but without decrefing pContinueProcessing
+ continue_processing = true;
+ }
+ else
+ {
+ if (pContinueProcessing == Py_None)
+ {
+ continue_processing = true;
+ }
+ else
+ {
+ continue_processing = PyLong_AsLong(pContinueProcessing) > 0;
+ }
+ Py_DECREF(pContinueProcessing);
+ }
+
+ return continue_processing;
}
+
+bool py_gcode_arc_args::parse_args(PyObject* py_args, py_logger* p_py_logger, py_gcode_arc_args& args, PyObject** py_progress_callback)
+{
+ p_py_logger->log(
+ GCODE_CONVERSION, INFO,
+ "Parsing GCode Conversion Args."
+ );
+
+#pragma region Required_Arguments
+#pragma region guid
+ // Extract the job guid
+ PyObject* py_guid = PyDict_GetItemString(py_args, "guid");
+ if (py_guid == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve required parameter 'guid' from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ args.guid = gcode_arc_converter::PyUnicode_SafeAsString(py_guid);
+#pragma endregion guid
+#pragma region source_path
+ // Extract the source file path
+ PyObject* py_source_path = PyDict_GetItemString(py_args, "source_path");
+ if (py_source_path == NULL)
+ {
+ std::string message = "ParseArgs -Unable to retrieve required parameter 'source_path' from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ args.source_path = gcode_arc_converter::PyUnicode_SafeAsString(py_source_path);
+#pragma endregion source_path
+#pragma region target_path
+ // Extract the target file path
+ PyObject* py_target_path = PyDict_GetItemString(py_args, "target_path");
+ if (py_target_path == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve required parameter 'target_path' from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ args.target_path = gcode_arc_converter::PyUnicode_SafeAsString(py_target_path);
+#pragma endregion target_path
+#pragma region on_progress_received
+ // on_progress_received
+ PyObject* py_on_progress_received = PyDict_GetItemString(py_args, "on_progress_received");
+ if (py_on_progress_received == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve required parameter 'on_progress_received' from the args.";
+ p_py_logger->log_exception(GCODE_CONVERSION, message);
+ return false;
+ }
+ // need to incref this so it doesn't vanish later (borrowed reference we are saving)
+ Py_XINCREF(py_on_progress_received);
+ *py_progress_callback = py_on_progress_received;
+#pragma endregion on_progress_received
+#pragma endregion Required_Arguments
+
+#pragma region Optional_Arguments
+#pragma region resolution_mm
+ // Extract the resolution in millimeters
+ PyObject* py_resolution_mm = PyDict_GetItemString(py_args, "resolution_mm");
+ if (py_resolution_mm == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'resolution_mm' parameter from the args.";
+ p_py_logger->log(GCODE_CONVERSION, WARNING, message);
+ }
+ else {
+ args.resolution_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_resolution_mm);
+ if (args.resolution_mm <= 0)
+ {
+ args.resolution_mm = 0.05; // Set to the default if no resolution is provided, or if it is less than 0.
+ }
+ }
+#pragma endregion resolution_mm
+#pragma region allow_dynamic_precision
+ // extract allow_dynamic_precision
+ PyObject* py_allow_dynamic_precision = PyDict_GetItemString(py_args, "allow_dynamic_precision");
+ if (py_allow_dynamic_precision == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve 'allow_dynamic_precision' from the args.";
+ p_py_logger->log(GCODE_CONVERSION, WARNING, message);
+ }
+ else {
+ args.allow_dynamic_precision = PyLong_AsLong(py_allow_dynamic_precision) > 0;
+ }
+#pragma endregion allow_dynamic_precision
+#pragma region default_xyz_precision
+ // extract default_xyz_precision
+ PyObject* py_default_xyz_precision = PyDict_GetItemString(py_args, "default_xyz_precision");
+ if (py_default_xyz_precision == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'default_xyz_precision' parameter from the args.";
+ p_py_logger->log(GCODE_CONVERSION, WARNING, message);
+ }
+ else {
+ args.default_xyz_precision = (unsigned char)gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision);
+ if (args.default_xyz_precision < 3)
+ {
+ std::string message = "ParseArgs - The default XYZ precision received was less than 3, which could cause problems printing arcs. Setting to 3.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ args.default_xyz_precision = 3;
+ }
+ else if (args.default_xyz_precision > 6)
+ {
+ std::string message = "ParseArgs - The default XYZ precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ args.default_xyz_precision = 6;
+ }
+ }
+#pragma endregion default_xyz_precision
+#pragma region default_e_precision
+ // extract default_e_precision
+ PyObject* py_default_e_precision = PyDict_GetItemString(py_args, "default_e_precision");
+ if (py_default_e_precision == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'default_e_precision parameter' from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else {
+ args.default_e_precision = (unsigned char)gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision);
+ if (args.default_e_precision < 3)
+ {
+ std::string message = "ParseArgs - The default E precision received was less than 3, which could cause extrusion problems. Setting to 3.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ args.default_e_precision = 3;
+ }
+ else if (args.default_e_precision > 6)
+ {
+ std::string message = "ParseArgs - The default E precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ args.default_e_precision = 6;
+ }
+ }
+#pragma endregion default_e_precision
+#pragma region extrusion_rate_variance_percent
+ // Extract the extrusion_rate_variance
+ PyObject* py_extrusion_rate_variance_percent = PyDict_GetItemString(py_args, "extrusion_rate_variance_percent");
+ if (py_extrusion_rate_variance_percent == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'extrusion_rate_variance_percent' parameter from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.extrusion_rate_variance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_extrusion_rate_variance_percent);
+ if (args.extrusion_rate_variance_percent < 0)
+ {
+ args.extrusion_rate_variance_percent = DEFAULT_EXTRUSION_RATE_VARIANCE_PERCENT; // Set to the default if no resolution is provided, or if it is less than 0.
+ }
+ }
+#pragma endregion extrusion_rate_variance_percent
+#pragma region path_tolerance_percent
+ // Extract the path tolerance_percent
+ PyObject* py_path_tolerance_percent = PyDict_GetItemString(py_args, "path_tolerance_percent");
+ if (py_path_tolerance_percent == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'path_tolerance_percent' parameter from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.path_tolerance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_path_tolerance_percent);
+ if (args.path_tolerance_percent < 0)
+ {
+ args.path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; // Set to the default if no resolution is provided, or if it is less than 0.
+ }
+ }
+#pragma endregion path_tolerance_percent
+#pragma region max_radius_mm
+ // Extract the max_radius in mm
+ PyObject* py_max_radius_mm = PyDict_GetItemString(py_args, "max_radius_mm");
+ if (py_max_radius_mm == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'max_radius_mm' parameter from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.max_radius_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_max_radius_mm);
+ if (args.max_radius_mm > DEFAULT_MAX_RADIUS_MM)
+ {
+ args.max_radius_mm = DEFAULT_MAX_RADIUS_MM; // Set to the default if no resolution is provided, or if it is less than 0.
+ }
+ }
+#pragma endregion max_radius_mm
+#pragma region mm_per_arc_segment
+ // Extract the mm_per_arc_segment
+ PyObject* py_mm_per_arc_segment = PyDict_GetItemString(py_args, "mm_per_arc_segment");
+ if (py_mm_per_arc_segment == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'mm_per_arc_segment' parameter from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.mm_per_arc_segment = gcode_arc_converter::PyFloatOrInt_AsDouble(py_mm_per_arc_segment);
+ if (args.mm_per_arc_segment < 0)
+ {
+ args.mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT;
+ }
+ }
+#pragma endregion mm_per_arc_segment
+#pragma region min_arc_segments
+ // Extract min_arc_segments
+ PyObject* py_min_arc_segments = PyDict_GetItemString(py_args, "min_arc_segments");
+ if (py_min_arc_segments == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'min_arc_segments' parameter from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.min_arc_segments = (int)gcode_arc_converter::PyIntOrLong_AsLong(py_min_arc_segments);
+ if (args.min_arc_segments < 0)
+ {
+ args.min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS; // Set to the default if no resolution is provided, or if it is less than 0.
+ }
+ }
+#pragma endregion min_arc_segments
+#pragma region max_gcode_length
+ // Extract max_gcode_length
+ PyObject* py_max_gcode_length = PyDict_GetItemString(py_args, "max_gcode_length");
+ if (py_max_gcode_length == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve the 'max_gcode_length' parameter from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.max_gcode_length = (int)gcode_arc_converter::PyIntOrLong_AsLong(py_max_gcode_length);
+ if (args.max_gcode_length < 0)
+ {
+ args.max_gcode_length = DEFAULT_MAX_GCODE_LENGTH;
+ }
+ }
+#pragma endregion max_gcode_length
+#pragma region allow_3d_arcs
+ // extract allow_3d_arcs
+ PyObject* py_allow_3d_arcs = PyDict_GetItemString(py_args, "allow_3d_arcs");
+ if (py_allow_3d_arcs == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve 'allow_3d_arcs' from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.allow_3d_arcs = PyLong_AsLong(py_allow_3d_arcs) > 0;
+ }
+#pragma endregion allow_3d_arcs
+#pragma region allow_travel_arcs
+ // extract allow_travel_arcs
+ PyObject* py_allow_travel_arcs = PyDict_GetItemString(py_args, "allow_travel_arcs");
+ if (py_allow_travel_arcs == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve 'allow_travel_arcs' from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.allow_travel_arcs = PyLong_AsLong(py_allow_travel_arcs) > 0;
+ }
+#pragma endregion allow_travel_arcs
+#pragma region g90_g91_influences_extruder
+ // Extract G90/G91 influences extruder
+ // g90_influences_extruder
+ PyObject* py_g90_g91_influences_extruder = PyDict_GetItemString(py_args, "g90_g91_influences_extruder");
+ if (py_g90_g91_influences_extruder == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve 'g90_g91_influences_extruder' from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ args.g90_g91_influences_extruder = PyLong_AsLong(py_g90_g91_influences_extruder) > 0;
+ }
+#pragma endregion g90_g91_influences_extruder
+#pragma region log_level
+ // Extract log_level
+ PyObject* py_log_level = PyDict_GetItemString(py_args, "log_level");
+ if (py_log_level == NULL)
+ {
+ std::string message = "ParseArgs - Unable to retrieve 'log_level' from the args.";
+ p_py_logger->log(WARNING, GCODE_CONVERSION, message);
+ }
+ else
+ {
+ int log_level_value = static_cast<int>(PyLong_AsLong(py_log_level));
+ // determine the log level as an index rather than as a value
+ args.log_level = p_py_logger->get_log_level_for_value(log_level_value);
+ }
+#pragma endregion log_level
+#pragma endregion Optional_Arguments
+
+ return true;
+} \ No newline at end of file
diff --git a/PyArcWelder/py_arc_welder.h b/PyArcWelder/py_arc_welder.h
index ed4e8cc..a8f0819 100644
--- a/PyArcWelder/py_arc_welder.h
+++ b/PyArcWelder/py_arc_welder.h
@@ -31,49 +31,37 @@
#else
#include <Python.h>
#endif
+
+struct py_gcode_arc_args : arc_welder_args {
+ py_gcode_arc_args() : arc_welder_args() {
+ log_level = INFO;
+ py_progress_callback = NULL;
+ guid = "";
+
+ };
+ py_gcode_arc_args(std::string source_path, std::string target_path, logger* log, std::string progress_guid, PyObject* progress_callback) : arc_welder_args(source_path, target_path, log) {
+ guid = progress_guid;
+ py_progress_callback = progress_callback;
+ };
+
+ static bool parse_args(PyObject* py_args, py_logger* p_py_logger, py_gcode_arc_args& args, PyObject** py_progress_callback);
+ int log_level;
+ std::string guid;
+ PyObject* py_progress_callback;
+};
+
class py_arc_welder : public arc_welder
{
public:
- py_arc_welder(
- std::string guid,
- std::string source_path,
- std::string target_path,
- py_logger* logger,
- double resolution_mm,
- double path_tolerance_percent,
- double max_radius,
- int min_arc_segments,
- double mm_per_arc_segment,
- bool g90_g91_influences_extruder,
- bool allow_3d_arcs,
- bool allow_dynamic_precision,
- unsigned char default_xyz_precision,
- unsigned char default_e_precision,
- int buffer_size,
- PyObject* py_progress_callback
- ): arc_welder(
- source_path,
- target_path,
- logger,
- resolution_mm,
- path_tolerance_percent,
- max_radius,
- min_arc_segments,
- mm_per_arc_segment,
- g90_g91_influences_extruder,
- allow_3d_arcs,
- allow_dynamic_precision,
- default_xyz_precision,
- default_e_precision,
- buffer_size
- ){
- guid_ = guid;
- py_progress_callback_ = py_progress_callback;
+ py_arc_welder(py_gcode_arc_args args): arc_welder( (arc_welder_args)args)
+ {
+ guid_ = args.guid;
+ py_progress_callback_ = args.py_progress_callback;
}
virtual ~py_arc_welder() {
}
- static PyObject* build_py_progress(const arc_welder_progress& progress, std::string guid);
+ static PyObject* build_py_progress(const arc_welder_progress& progress, std::string guid, bool include_detailed_statistics);
protected:
std::string guid_;
virtual bool on_progress_(const arc_welder_progress& progress);
diff --git a/PyArcWelder/py_arc_welder_extension.cpp b/PyArcWelder/py_arc_welder_extension.cpp
index 1e0728b..7609b61 100644
--- a/PyArcWelder/py_arc_welder_extension.cpp
+++ b/PyArcWelder/py_arc_welder_extension.cpp
@@ -186,39 +186,29 @@ extern "C"
py_gcode_arc_args args;
PyObject* py_progress_callback = NULL;
- if (!ParseArgs(py_convert_file_args, args, &py_progress_callback))
+ if (!py_gcode_arc_args::parse_args(py_convert_file_args, p_py_logger, args, &py_progress_callback))
{
return NULL;
}
- p_py_logger->set_log_level(args.log_level);
+ p_py_logger->set_log_level((log_levels)args.log_level);
std::string message = "py_gcode_arc_converter.ConvertFile - Beginning Arc Conversion.";
- p_py_logger->log(GCODE_CONVERSION, INFO, message);
-
- py_arc_welder arc_welder_obj(
- args.guid,
- args.source_path,
- args.target_path,
- p_py_logger,
- args.resolution_mm,
- args.path_tolerance_percent,
- args.max_radius_mm,
- args.min_arc_segments,
- args.mm_per_arc_segment,
- args.g90_g91_influences_extruder,
- args.allow_3d_arcs,
- args.allow_dynamic_precision,
- args.default_xyz_precision,
- args.default_e_precision,
- DEFAULT_GCODE_BUFFER_SIZE,
- py_progress_callback
- );
- arc_welder_results results = arc_welder_obj.process();
+ p_py_logger->log(GCODE_CONVERSION, log_levels::INFO, message);
+
+ args.py_progress_callback = py_progress_callback;
+ args.log = p_py_logger;
+ // Set the encoding to html for the progress output
+ args.box_encoding = utilities::box_drawing::HTML;
+ py_arc_welder arc_welder_obj(args);
+ arc_welder_results results;
+ results = arc_welder_obj.process();
+
message = "py_gcode_arc_converter.ConvertFile - Arc Conversion Complete.";
- p_py_logger->log(GCODE_CONVERSION, INFO, message);
+ p_py_logger->log(GCODE_CONVERSION, log_levels::INFO, message);
Py_XDECREF(py_progress_callback);
// return the arguments
- PyObject* p_progress = py_arc_welder::build_py_progress(results.progress, args.guid);
+ PyObject* p_progress = py_arc_welder::build_py_progress(results.progress, args.guid, true);
+
if (p_progress == NULL)
p_progress = Py_None;
@@ -237,213 +227,5 @@ extern "C"
}
}
-static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** py_progress_callback)
-{
- p_py_logger->log(
- GCODE_CONVERSION, INFO,
- "Parsing GCode Conversion Args."
- );
-
- // Extract the job guid
- PyObject* py_guid = PyDict_GetItemString(py_args, "guid");
- if (py_guid == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the guid parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.guid = gcode_arc_converter::PyUnicode_SafeAsString(py_guid);
-
- // Extract the source file path
- PyObject* py_source_path = PyDict_GetItemString(py_args, "source_path");
- if (py_source_path == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the source_path parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.source_path = gcode_arc_converter::PyUnicode_SafeAsString(py_source_path);
- // Extract the target file path
- PyObject* py_target_path = PyDict_GetItemString(py_args, "target_path");
- if (py_target_path == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the target_path parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.target_path = gcode_arc_converter::PyUnicode_SafeAsString(py_target_path);
-
- // Extract the resolution in millimeters
- PyObject* py_resolution_mm = PyDict_GetItemString(py_args, "resolution_mm");
- if (py_resolution_mm == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the resolution_mm parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.resolution_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_resolution_mm);
- if (args.resolution_mm <= 0)
- {
- args.resolution_mm = 0.05; // Set to the default if no resolution is provided, or if it is less than 0.
- }
-
- // extract allow_dynamic_precision
- PyObject* py_allow_dynamic_precision = PyDict_GetItemString(py_args, "allow_dynamic_precision");
- if (py_allow_dynamic_precision == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve allow_dynamic_precision from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.allow_dynamic_precision = PyLong_AsLong(py_allow_dynamic_precision) > 0;
-
- // extract default_xyz_precision
- PyObject* py_default_xyz_precision = PyDict_GetItemString(py_args, "default_xyz_precision");
- if (py_default_xyz_precision == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the default_xyz_precision parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.default_xyz_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision);
- if (args.default_xyz_precision < 3)
- {
- std::string message = "ParseArgs - The default XYZ precision received was less than 3, which could cause problems printing arcs. Setting to 3.";
- p_py_logger->log(WARNING, GCODE_CONVERSION, message);
- args.default_xyz_precision = 3;
- }
- else if (args.default_xyz_precision > 6)
- {
- std::string message = "ParseArgs - The default XYZ precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6.";
- p_py_logger->log(WARNING, GCODE_CONVERSION, message);
- args.default_xyz_precision = 6;
- }
-
- // extract default_e_precision
- PyObject* py_default_e_precision = PyDict_GetItemString(py_args, "default_e_precision");
- if (py_default_e_precision == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the default_e_precision parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.default_e_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision);
- if (args.default_e_precision < 3)
- {
- std::string message = "ParseArgs - The default E precision received was less than 3, which could cause extrusion problems. Setting to 3.";
- p_py_logger->log(WARNING, GCODE_CONVERSION, message);
- args.default_e_precision = 3;
- }
- else if (args.default_e_precision > 6)
- {
- std::string message = "ParseArgs - The default E precision received was greater than 6, which could can cause checksum errors depending on your firmware. Setting to 6.";
- p_py_logger->log(WARNING, GCODE_CONVERSION, message);
- args.default_e_precision = 6;
- }
-
- // Extract the path tolerance_percent
- PyObject* py_path_tolerance_percent = PyDict_GetItemString(py_args, "path_tolerance_percent");
- if (py_path_tolerance_percent == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the py_path_tolerance_percent parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.path_tolerance_percent = gcode_arc_converter::PyFloatOrInt_AsDouble(py_path_tolerance_percent);
- if (args.path_tolerance_percent < 0)
- {
- args.path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT; // Set to the default if no resolution is provided, or if it is less than 0.
- }
-
- // Extract the max_radius in mm
- PyObject* py_max_radius_mm = PyDict_GetItemString(py_args, "max_radius_mm");
- if (py_max_radius_mm == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the max_radius_mm parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.max_radius_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_max_radius_mm);
- if (args.max_radius_mm > DEFAULT_MAX_RADIUS_MM)
- {
- args.max_radius_mm = DEFAULT_MAX_RADIUS_MM; // Set to the default if no resolution is provided, or if it is less than 0.
- }
-
- // Extract the mm_per_arc_segment
- PyObject* py_mm_per_arc_segment = PyDict_GetItemString(py_args, "mm_per_arc_segment");
- if (py_mm_per_arc_segment == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the mm_per_arc_segment parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.mm_per_arc_segment = gcode_arc_converter::PyFloatOrInt_AsDouble(py_mm_per_arc_segment);
- if (args.mm_per_arc_segment < 0)
- {
- args.mm_per_arc_segment = 0;
- }
-
- // Extract min_arc_segments
- PyObject* py_min_arc_segments = PyDict_GetItemString(py_args, "min_arc_segments");
- if (py_min_arc_segments == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve the min_arc_segments parameter from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.min_arc_segments = (int) gcode_arc_converter::PyIntOrLong_AsLong(py_min_arc_segments);
- if (args.min_arc_segments < 0)
- {
- args.min_arc_segments = 0; // Set to the default if no resolution is provided, or if it is less than 0.
- }
-
- // extract allow_3d_arcs
- PyObject* py_allow_3d_arcs = PyDict_GetItemString(py_args, "allow_3d_arcs");
- if (py_allow_3d_arcs == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve allow_3d_arcs from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.allow_3d_arcs = PyLong_AsLong(py_allow_3d_arcs) > 0;
-
- // Extract G90/G91 influences extruder
- // g90_influences_extruder
- PyObject* py_g90_g91_influences_extruder = PyDict_GetItemString(py_args, "g90_g91_influences_extruder");
- if (py_g90_g91_influences_extruder == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve g90_g91_influences_extruder from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- args.g90_g91_influences_extruder = PyLong_AsLong(py_g90_g91_influences_extruder) > 0;
-
- // on_progress_received
- PyObject* py_on_progress_received = PyDict_GetItemString(py_args, "on_progress_received");
- if (py_on_progress_received == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve on_progress_received from the stabilization args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
- // need to incref this so it doesn't vanish later (borrowed reference we are saving)
- Py_XINCREF(py_on_progress_received);
- *py_progress_callback = py_on_progress_received;
-
- // Extract log_level
- PyObject* py_log_level = PyDict_GetItemString(py_args, "log_level");
- if (py_log_level == NULL)
- {
- std::string message = "ParseArgs - Unable to retrieve log_level from the args.";
- p_py_logger->log_exception(GCODE_CONVERSION, message);
- return false;
- }
-
- int log_level_value = static_cast<int>(PyLong_AsLong(py_log_level));
- // determine the log level as an index rather than as a value
- args.log_level = p_py_logger->get_log_level_for_value(log_level_value);
-
- return true;
-}
diff --git a/PyArcWelder/py_arc_welder_extension.h b/PyArcWelder/py_arc_welder_extension.h
index 2c2ecec..b71f680 100644
--- a/PyArcWelder/py_arc_welder_extension.h
+++ b/PyArcWelder/py_arc_welder_extension.h
@@ -29,6 +29,7 @@
#include <Python.h>
#endif
#include <string>
+#include "py_arc_welder.h"
#include "py_logger.h"
#include "arc_welder.h"
extern "C"
@@ -41,69 +42,6 @@ extern "C"
static PyObject* ConvertFile(PyObject* self, PyObject* args);
}
-struct py_gcode_arc_args {
- py_gcode_arc_args() {
- guid = "";
- source_path = "";
- target_path = "";
- resolution_mm = DEFAULT_RESOLUTION_MM;
- path_tolerance_percent = ARC_LENGTH_PERCENT_TOLERANCE_DEFAULT;
- max_radius_mm = DEFAULT_MAX_RADIUS_MM;
- min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS;
- mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT;
- g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTREUDER;
- allow_3d_arcs = DEFAULT_ALLOW_3D_ARCS;
- log_level = 0;
- }
- py_gcode_arc_args(
- std::string guid_,
- std::string source_path_,
- std::string target_path_,
- double resolution_mm_,
- double path_tolerance_percent_,
- double max_radius_mm_,
- int min_arc_segments_,
- double mm_per_arc_segment_,
- bool g90_g91_influences_extruder_,
- bool allow_3d_arcs_,
- bool allow_dynamic_precision_,
- unsigned char default_xyz_precision_,
- unsigned char default_e_precision_,
- int log_level_
- ) {
- guid = guid_;
- source_path = source_path_;
- target_path = target_path_;
- resolution_mm = resolution_mm_;
- path_tolerance_percent = path_tolerance_percent_;
- max_radius_mm = max_radius_mm_;
- min_arc_segments = min_arc_segments_;
- mm_per_arc_segment = mm_per_arc_segment_;
- allow_3d_arcs = allow_3d_arcs_;
- allow_dynamic_precision = allow_dynamic_precision_;
- default_xyz_precision = default_xyz_precision_;
- default_e_precision = default_e_precision_;
- g90_g91_influences_extruder = g90_g91_influences_extruder_;
- log_level = log_level_;
- }
- std::string guid;
- std::string source_path;
- std::string target_path;
- double resolution_mm;
- double path_tolerance_percent;
- bool allow_3d_arcs;
- bool allow_dynamic_precision;
- unsigned char default_xyz_precision;
- unsigned char default_e_precision;
- bool g90_g91_influences_extruder;
- double max_radius_mm;
- int min_arc_segments;
- double mm_per_arc_segment;
- int log_level;
-};
-
-static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** p_py_progress_callback);
-
// global logger
py_logger* p_py_logger = NULL;
/*
diff --git a/PyArcWelder/py_logger.cpp b/PyArcWelder/py_logger.cpp
index 0418ef5..3269c42 100644
--- a/PyArcWelder/py_logger.cpp
+++ b/PyArcWelder/py_logger.cpp
@@ -119,7 +119,7 @@ void py_logger::set_internal_log_levels(bool check_real_time)
void py_logger::log_exception(const int logger_type, const std::string& message)
{
- log(logger_type, ERROR, message, true);
+ log(logger_type, log_levels::ERROR, message, true);
}
void py_logger::log(const int logger_type, const int log_level, const std::string& message)
@@ -176,24 +176,24 @@ void py_logger::log(const int logger_type, const int log_level, const std::strin
}
else
{
- switch (log_level)
+ switch ((log_levels)log_level)
{
- case INFO:
+ case log_levels::INFO:
pyFunctionName = py_info_function_name;
break;
- case WARNING:
+ case log_levels::WARNING:
pyFunctionName = py_warn_function_name;
break;
- case ERROR:
+ case log_levels::ERROR:
pyFunctionName = py_error_function_name;
break;
- case DEBUG:
+ case log_levels::DEBUG:
pyFunctionName = py_debug_function_name;
break;
- case VERBOSE:
+ case log_levels::VERBOSE:
pyFunctionName = py_verbose_function_name;
break;
- case CRITICAL:
+ case log_levels::CRITICAL:
pyFunctionName = py_critical_function_name;
break;
default:
@@ -203,7 +203,9 @@ void py_logger::log(const int logger_type, const int log_level, const std::strin
return;
}
}
- PyObject* pyMessage = gcode_arc_converter::PyBytesOrString_FromString(message);
+ //PyObject* pyMessage = gcode_arc_converter::PyBytesOrString_FromString(message);
+ PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(message);
+
if (pyMessage == NULL)
{
std::cout << "Unable to convert the log message '" << message.c_str() << "' to a PyString/Unicode message.\r\n";
diff --git a/readme.md b/readme.md
index 9e5502e..b97e457 100644
--- a/readme.md
+++ b/readme.md
@@ -13,6 +13,19 @@ The archive will contain two folders:
* **bin** - Here you will find the two console applications: ArcWelder and ArcStraightener. Just copy these to your local machine and you can run them from the command line, from a script, or directly within most slicers. See the sections below for instructions on how to use these applications.
* **lib** - This folder contains pre-compiled ArcWelder libraries that can be integrated into applications. The GcodeProcessorLib library has functions for parsing gcode, tracking the printer's state and position, as well as several other goodies. The ArcWelder library (requires GcodeProcessorLib) contains the core welding algorithm.
+# Building from source
+
+From the repository root, create a build directory, generate makefile, and build:
+
+```
+mkdir build
+cd build
+cmake ..
+make
+```
+
+The resulting console application is located in `build/ArcWelderConsole/`. You might want to create the build directory out of the repository, or make `git` ignore it, to avoid a dirty tree.
+
# Arc Welder Console Application
This is a multiplatform console application that can be used to run the Arc Welder algorithm from a command prompt. Binaries are available for Windows, Linux, Raspbian, and MacOs. See the [installation][#installation] section for information on how to download the console application.