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-06 22:59:27 +0300
committerFormerLurker <hochgebe@gmail.com>2021-11-06 22:59:27 +0300
commitb57d34b8e69d6d5680ba271d6040734899295d05 (patch)
tree5184a94bd4120ba7683a3abe92f5abe49854a82c
parent441742cfd83667fe8e2507a35cc0207687519650 (diff)
Rewrite of ArcStraightener to support multiple firmware types and versions. Convert utilities class to namespace.
-rw-r--r--ArcWelder/CMakeLists.txt2
-rw-r--r--ArcWelder/segmented_arc.cpp2
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp421
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.h5
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj27
-rw-r--r--ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters42
-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.h373
-rw-r--r--ArcWelderInverseProcessor/firmware_types.cpp1
-rw-r--r--ArcWelderInverseProcessor/firmware_types.h10
-rw-r--r--ArcWelderInverseProcessor/marlin_1.cpp357
-rw-r--r--ArcWelderInverseProcessor/marlin_1.h101
-rw-r--r--ArcWelderInverseProcessor/marlin_2.cpp677
-rw-r--r--ArcWelderInverseProcessor/marlin_2.h107
-rw-r--r--ArcWelderInverseProcessor/marlin_2_arc.cpp448
-rw-r--r--ArcWelderInverseProcessor/marlin_2_arc.h95
-rw-r--r--ArcWelderInverseProcessor/prusa.cpp466
-rw-r--r--ArcWelderInverseProcessor/prusa.h83
-rw-r--r--ArcWelderInverseProcessor/repetier.cpp476
-rw-r--r--ArcWelderInverseProcessor/repetier.h39
-rw-r--r--ArcWelderInverseProcessor/repiter_arc.cpp97
-rw-r--r--ArcWelderInverseProcessor/repiter_arc.h7
-rw-r--r--ArcWelderInverseProcessor/smoothieware.cpp274
-rw-r--r--ArcWelderInverseProcessor/smoothieware.h65
-rw-r--r--ArcWelderInverseProcessor/sourcelist.cmake20
-rw-r--r--ArcWelderTest/ArcWelderTest.cpp2
-rw-r--r--GcodeProcessorLib/fpconv.cpp4
-rw-r--r--GcodeProcessorLib/fpconv.h2
-rw-r--r--GcodeProcessorLib/utilities.cpp244
-rw-r--r--GcodeProcessorLib/utilities.h192
-rw-r--r--PyArcWelder/py_arc_welder.cpp4
36 files changed, 4240 insertions, 878 deletions
diff --git a/ArcWelder/CMakeLists.txt b/ArcWelder/CMakeLists.txt
index 738983c..f84fd23 100644
--- a/ArcWelder/CMakeLists.txt
+++ b/ArcWelder/CMakeLists.txt
@@ -31,3 +31,5 @@ set(${PROJECT_NAME}_DEFINITIONS ${GcodeProcessorLib_DEFINITIONS}
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/
${GcodeProcessorLib_INCLUDE_DIRS}
CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
+
+ \ No newline at end of file
diff --git a/ArcWelder/segmented_arc.cpp b/ArcWelder/segmented_arc.cpp
index 60d50cd..6f238a5 100644
--- a/ArcWelder/segmented_arc.cpp
+++ b/ArcWelder/segmented_arc.cpp
@@ -27,8 +27,6 @@
#include "utilities.h"
#include "segmented_shape.h"
#include <iostream>
-//#include <iomanip>
-//#include <sstream>
#include <stdio.h>
#include <cmath>
diff --git a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
index ca64ff3..a3147eb 100644
--- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.cpp
@@ -26,22 +26,31 @@
#define _CRT_SECURE_NO_DEPRECATE
#endif
-#include "marlin_2_arc.h"
#include "ArcWelderInverseProcessor.h"
-#include <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;
@@ -195,10 +443,10 @@ int main(int argc, char* argv[])
std::stringstream log_messages;
std::string temp_file_path = "";
log_messages << std::fixed << std::setprecision(DEFAULT_ARG_DOUBLE_PRECISION);
- if (source_file_path == target_file_path)
+ if (args.source_path == args.target_path)
{
overwrite_source_file = true;
- if (!utilities::get_temp_file_path_for_file(source_file_path, temp_file_path))
+ if (!utilities::get_temp_file_path_for_file(args.source_path, temp_file_path))
{
log_messages << "The source and target path are the same, but a temporary file path could not be created. Is the path empty?";
p_logger->log(0, INFO, log_messages.str());
@@ -208,71 +456,78 @@ int main(int argc, char* argv[])
// create a uuid with a tmp extension for the temporary file
- log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << target_file_path;
+ log_messages << "Source and target path are the same. The source file will be overwritten. Temporary file path: " << temp_file_path;
p_logger->log(0, INFO, log_messages.str());
log_messages.clear();
log_messages.str("");
}
- log_messages << "Processing Gcode\n";
- log_messages << "\tSource File Path : " << source_file_path << "\n";
+ log_messages << "Arguments: \n";
+ log_messages << "\tSource File Path : " << args.source_path << "\n";
if (overwrite_source_file)
{
- log_messages << "\tTarget File Path (overwrite) : " << target_file_path << "\n";
+ log_messages << "\tTarget File Path (overwrite) : " << args.target_path << "\n";
log_messages << "\tTemporary File Path : " << temp_file_path << "\n";
}
else
{
- log_messages << "\tTarget File File : " << target_file_path << "\n";
+ log_messages << "\tTarget File File : " << args.target_path << "\n";
}
log_messages << "\tLog Level : " << log_level_string << "\n";
- p_logger->log(0, INFO, log_messages.str());
-
+
+
if (overwrite_source_file)
{
- target_file_path = temp_file_path;
+ args.target_path = temp_file_path;
}
-
- marlin_2_arc processor(source_file_path, target_file_path, g90_g91_influences_extruder, 50, cs);
- processor.process();
- // Todo: get some results!
- if (true)
- {
- log_messages.clear();
- log_messages.str("");
- log_messages << "Target file at '" << target_file_path << "' created.";
- if (overwrite_source_file)
- {
- log_messages.clear();
- log_messages.str("");
- log_messages << "Deleting the original source file at '" << source_file_path << "'.";
- p_logger->log(0, INFO, log_messages.str());
- log_messages.clear();
- log_messages.str("");
- std::remove(source_file_path.c_str());
- log_messages << "Renaming temporary file at '" << target_file_path << "' to '" << source_file_path << "'.";
- p_logger->log(0, INFO, log_messages.str());
- std::rename(target_file_path.c_str(), source_file_path.c_str());
- }
- /*
- log_messages.clear();
- log_messages.str("");
- log_messages << std::endl << results.progress.segment_statistics.str();
- p_logger->log(0, INFO, log_messages.str());
- */
+ arc_interpolation interpolator(args);
+ log_messages << interpolator.get_firmware_argument_description();
+ p_logger->log(0, INFO, log_messages.str());
+
+ p_logger->log(0, INFO, "Running interpolation...");
+ interpolator.process();
+ p_logger->log(0, INFO, "Interpolation Complete.");
+
+ log_messages.clear();
+ log_messages.str("");
+ log_messages << "Target file at '" << args.target_path << "' created.";
+
+ if (overwrite_source_file)
+ {
log_messages.clear();
log_messages.str("");
- log_messages << "Process completed successfully.";
+ log_messages << "Deleting the original source file at '" << args.source_path << "'.";
p_logger->log(0, INFO, log_messages.str());
- }
- else
- {
log_messages.clear();
log_messages.str("");
- log_messages << "File processing failed.";
+ std::remove(args.source_path.c_str());
+ log_messages << "Renaming temporary file at '" << args.target_path << "' to '" << args.source_path << "'.";
p_logger->log(0, INFO, log_messages.str());
+ std::rename(args.target_path.c_str(), args.source_path.c_str());
}
+
+ log_messages.clear();
+ log_messages.str("");
+ log_messages << "Process completed successfully.";
+ p_logger->log(0, INFO, log_messages.str());
+
return 0;
}
+
+
+std::string get_available_arguments_string(std::vector<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 f67571c..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,15 +203,25 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="ArcWelderInverseProcessor.h" />
+ <ClInclude Include="arc_interpolation.h" />
+ <ClInclude Include="arc_interpolation_structs.h" />
+ <ClInclude Include="firmware.h" />
<ClInclude Include="firmware_types.h" />
- <ClInclude Include="marlin_2_arc.h" />
- <ClInclude Include="repiter_arc.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="firmware_types.cpp" />
- <ClCompile Include="marlin_2_arc.cpp" />
- <ClCompile Include="repiter_arc.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 bc338f9..476116b 100644
--- a/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters
+++ b/ArcWelderInverseProcessor/ArcWelderInverseProcessor.vcxproj.filters
@@ -18,13 +18,31 @@
<ClInclude Include="ArcWelderInverseProcessor.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="repiter_arc.h">
+ <ClInclude Include="firmware_types.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="marlin_2_arc.h">
+ <ClInclude Include="repetier.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="firmware_types.h">
+ <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>
@@ -32,13 +50,25 @@
<ClCompile Include="ArcWelderInverseProcessor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="repiter_arc.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_arc.cpp">
+ <ClCompile Include="marlin_2.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="firmware_types.cpp">
+ <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..a11b057
--- /dev/null
+++ b/ArcWelderInverseProcessor/firmware.h
@@ -0,0 +1,373 @@
+#pragma once
+#include "firmware_types.h"
+#include <vector>
+#include <sstream>
+#include <iomanip>
+#include <algorithm>
+
+#define M_PI 3.14159265358979323846 // pi
+#define DEFAULT_FIRMWARE_TYPE firmware_types::MARLIN_2
+#define LATEST_FIRMWARE_VERSION_NAME "LATEST_RELEASE"
+#define DEFAULT_FIRMWARE_VERSION_NAME LATEST_FIRMWARE_VERSION_NAME
+// Arc interpretation settings:
+#define DEFAULT_MM_PER_ARC_SEGMENT 1.0 // REQUIRED - The enforced maximum length of an arc segment
+#define DEFAULT_ARC_SEGMENTS_PER_R 0;
+#define DEFAULT_MIN_MM_PER_ARC_SEGMENT 0 /* OPTIONAL - the enforced minimum length of an interpolated segment. Must be smaller than
+ MM_PER_ARC_SEGMENT. Only has an effect if MIN_ARC_SEGMENTS > 0 or ARC_SEGMENTS_PER_SEC > 0 */
+ // If both MIN_ARC_SEGMENTS and ARC_SEGMENTS_PER_SEC is defined, the minimum calculated segment length is used.
+#define DEFAULT_MIN_ARC_SEGMENTS 0 // OPTIONAL - The enforced minimum segments in a full circle of the same radius.
+#define DEFAULT_ARC_SEGMENTS_PER_SEC 0 // OPTIONAL - Use feedrate to choose segment length.
+// approximation will not be used for the first segment. Subsequent segments will be corrected following DEFAULT_N_ARC_CORRECTION.
+#define DEFAULT_N_ARC_CORRECTIONS 24
+// This setting is for the gcode position processor to help interpret G90/G91 behavior
+#define DEFAULT_G90_G91_INFLUENCES_EXTRUDER false
+// This currently is only used in Smoothieware. The maximum error for line segments that divide arcs. Set to 0 to disable.
+#define DEFAULT_MM_MAX_ARC_ERROR 0.01
+
+struct firmware_state {
+ firmware_state() {
+ is_relative = false;
+ is_extruder_relative = false;
+ }
+ bool is_relative;
+ bool is_extruder_relative;
+};
+
+struct firmware_position {
+ firmware_position() {
+ x = 0;
+ y = 0;
+ z = 0;
+ e = 0;
+ f = 0;
+ }
+ double x;
+ double y;
+ double z;
+ double e;
+ double f;
+};
+
+struct firmware_arguments {
+public:
+
+ firmware_arguments() {
+ mm_per_arc_segment = DEFAULT_MM_PER_ARC_SEGMENT;
+ arc_segments_per_r = DEFAULT_ARC_SEGMENTS_PER_R;
+ min_mm_per_arc_segment = DEFAULT_MIN_MM_PER_ARC_SEGMENT;
+ min_arc_segments = DEFAULT_MIN_ARC_SEGMENTS;
+ arc_segments_per_sec = DEFAULT_ARC_SEGMENTS_PER_SEC;
+ n_arc_correction = DEFAULT_N_ARC_CORRECTIONS;
+ g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTRUDER;
+ mm_max_arc_error = DEFAULT_MM_MAX_ARC_ERROR;
+ version = DEFAULT_FIRMWARE_VERSION_NAME;
+ firmware_type = (firmware_types)DEFAULT_FIRMWARE_TYPE;
+ latest_release_version = LATEST_FIRMWARE_VERSION_NAME;
+
+ // add a list of all possible arguments, including aliases
+ all_arguments_.clear();
+ all_arguments_.push_back("mm_per_arc_segment");
+ all_arguments_.push_back("arc_segments_per_r");
+ all_arguments_.push_back("min_mm_per_arc_segment");
+ all_arguments_.push_back("min_arc_segments");
+ all_arguments_.push_back("arc_segments_per_sec");
+ all_arguments_.push_back("n_arc_correction");
+ all_arguments_.push_back("g90_g91_influences_extruder");
+ all_arguments_.push_back("mm_max_arc_error");
+ all_arguments_.push_back("min_circle_segments");
+ all_arguments_.push_back("min_arc_segment_mm");
+ all_arguments_.push_back("max_arc_segment_mm");
+ };
+
+ /// <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.cpp b/ArcWelderInverseProcessor/firmware_types.cpp
deleted file mode 100644
index 61820cf..0000000
--- a/ArcWelderInverseProcessor/firmware_types.cpp
+++ /dev/null
@@ -1 +0,0 @@
-#include "firmware_types.h"
diff --git a/ArcWelderInverseProcessor/firmware_types.h b/ArcWelderInverseProcessor/firmware_types.h
index b3507ad..775a731 100644
--- a/ArcWelderInverseProcessor/firmware_types.h
+++ b/ArcWelderInverseProcessor/firmware_types.h
@@ -1,4 +1,12 @@
#pragma once
-enum firmware_types { Marlin2, Repiter };
+#include <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/marlin_1.cpp b/ArcWelderInverseProcessor/marlin_1.cpp
new file mode 100644
index 0000000..6269645
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_1.cpp
@@ -0,0 +1,357 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 1 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#include "marlin_1.h"
+#include "utilities.h"
+marlin_1::marlin_1(firmware_arguments args) : firmware(args) {
+ feedrate_mm_s = 0;
+ current_position = new float[MARLIN_XYZE];
+ apply_arguments();
+};
+
+marlin_1::~marlin_1()
+{
+ delete current_position;
+}
+
+void marlin_1::apply_arguments()
+{
+ static const std::vector<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 = HYPOT(r_P, r_Q),
+ center_P = current_position[p_axis] - r_P,
+ center_Q = current_position[q_axis] - r_Q,
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ linear_travel = cart[l_axis] - current_position[l_axis],
+ extruder_travel = cart[E_CART] - current_position[E_CART];
+
+ // CCW angle of rotation between position and target from the circle center. Only one atan2() trig computation required.
+ float angular_travel = ATAN2(r_P * rt_Y - r_Q * rt_X, r_P * rt_X + r_Q * rt_Y);
+ if (angular_travel < 0) angular_travel += RADIANS(360);
+ if (clockwise) angular_travel -= RADIANS(360);
+
+ // Make a circle if the angular rotation is 0 and the target is current position
+ if (angular_travel == 0 && current_position[p_axis] == cart[p_axis] && current_position[q_axis] == cart[q_axis])
+ angular_travel = RADIANS(360);
+
+ const float flat_mm = radius * angular_travel,
+ mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : ABS(flat_mm);
+ if (mm_of_travel < 0.001f) return;
+
+ uint16_t segments = (uint16_t)FLOOR(mm_of_travel / (float)(args_.mm_per_arc_segment));
+ NOLESS(segments, 1);
+
+ /**
+ * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ * and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ * r_T = [cos(phi) -sin(phi);
+ * sin(phi) cos(phi)] * r ;
+ *
+ * For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ * defined from the circle center to the initial position. Each line segment is formed by successive
+ * vector rotations. This requires only two cos() and sin() computations to form the rotation
+ * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ * all double numbers are single precision on the Arduino. (True double precision will not have
+ * round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ * tool precision in some cases. Therefore, arc path correction is implemented.
+ *
+ * Small angle approximation may be used to reduce computation overhead further. This approximation
+ * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words,
+ * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ * issue for CNC machines with the single precision Arduino calculations.
+ *
+ * This approximation also allows plan_arc to immediately insert a line segment into the planner
+ * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead.
+ * This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float raw[MARLIN_XYZE];
+ const float theta_per_segment = angular_travel / segments,
+ linear_per_segment = linear_travel / segments,
+ extruder_per_segment = extruder_travel / segments,
+ sin_T = theta_per_segment,
+ cos_T = 1 - 0.5f * sq(theta_per_segment); // Small angle approximation
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+
+ // Initialize the extruder axis
+ raw[E_CART] = current_position[E_CART];
+
+ const float fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ int8_t arc_recalc_count = 0;
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+
+
+ for (uint16_t i = 1; i < segments; i++) // Iterate (segments-1) times
+ {
+
+ if (args_.n_arc_correction > 1 && --arc_recalc_count)
+ {
+ // Apply vector rotation matrix to previous r_P / 1
+ const float r_new_Y = r_P * sin_T + r_Q * cos_T;
+ r_P = r_P * cos_T - r_Q * sin_T;
+ r_Q = r_new_Y;
+ }
+ else
+ {
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ // To reduce stuttering, the sin and cos could be computed at different times.
+ // For now, compute both at the same time.
+ const float cos_Ti = (float)utilities::cos(i * (double)theta_per_segment), sin_Ti = (float)utilities::sin(i * (double)theta_per_segment);
+ r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ r_Q = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + r_P;
+ raw[q_axis] = center_Q + r_Q;
+ raw[l_axis] += linear_per_segment;
+ raw[E_CART] += extruder_per_segment;
+
+ clamp_to_software_endstops(raw);
+
+
+ if (!buffer_line_kinematic(raw, feedrate_mm_s, active_extruder))
+ break;
+ }
+
+ buffer_line_kinematic(cart, feedrate_mm_s, active_extruder);
+
+ COPY(current_position, cart);
+}
+
+// Marlin Function Defs
+float marlin_1::HYPOT(float x, float y)
+{
+ return (float)utilities::hypot(x, y);
+}
+
+float marlin_1::ATAN2(float x, float y)
+{
+ return (float)utilities::atan2(x, y);
+}
+
+float marlin_1::RADIANS(float x)
+{
+ return (x * (float)M_PI) / 180;
+}
+
+float marlin_1::ABS(float x)
+{
+ return (float)utilities::abs((double)x);
+}
+
+float marlin_1::FLOOR(float x)
+{
+ return (float)utilities::floor((double)x);
+}
+
+float marlin_1::NOLESS(uint16_t x, uint16_t y)
+{
+ if (x < y)
+ return y;
+ return x;
+}
+
+float marlin_1::sq(float x)
+{
+ return x * x;
+}
+
+float marlin_1::MMS_SCALED(float x)
+{
+ // No scaling
+ return x;
+}
+
+void marlin_1::COPY(float target[MARLIN_XYZE], const float(&source)[MARLIN_XYZE])
+{
+ // This is a slow copy, but speed isn't much of an issue here.
+ for (int i = 0; i < MARLIN_XYZE; i++)
+ {
+ target[i] = source[i];
+ }
+}
+
+
+void marlin_1::clamp_to_software_endstops(const float(&raw)[MARLIN_XYZE])
+{
+ // Do nothing, just added to keep mc_arc identical to the firmware version
+ return;
+}
+
+//void marlin::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+bool marlin_1::buffer_line_kinematic(const float(&cart)[MARLIN_XYZE], double fr_mm_s, int active_extruder)
+{
+
+ // create the target position
+ firmware_position target;
+ target.x = cart[AxisEnum::X_AXIS];
+ target.y = cart[AxisEnum::Y_AXIS];
+ target.z = cart[AxisEnum::Z_AXIS];
+ target.e = cart[AxisEnum::E_AXIS];
+ target.f = fr_mm_s;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ // update the current position
+ set_current_position(target);
+ return true;
+}
diff --git a/ArcWelderInverseProcessor/marlin_1.h b/ArcWelderInverseProcessor/marlin_1.h
new file mode 100644
index 0000000..9579bfb
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_1.h
@@ -0,0 +1,101 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 1 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+#include "firmware.h"
+
+#define NUM_MARLIN_1_FIRMWARE_VERSIONS 1
+
+
+#define MARLIN_XYZE 5
+
+class marlin_1 :
+ public firmware
+{
+public:
+ enum class marlin_1_firmware_versions { V1_1_9_1 = 0 };
+ /// <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
+ float HYPOT(float x, float y);
+ float ATAN2(float x, float y);
+ float RADIANS(float x);
+ float ABS(float x);
+ float FLOOR(float x);
+ float NOLESS(uint16_t x, uint16_t y);
+ float sq(float x);
+ float MMS_SCALED(float x);
+ void COPY(float target[MARLIN_XYZE], const float (&source)[MARLIN_XYZE]);
+ bool buffer_line_kinematic(const float (&cart)[MARLIN_XYZE], double fr_mm_s, int active_extruder);
+ void clamp_to_software_endstops(const float (&raw)[MARLIN_XYZE]);
+};
+
diff --git a/ArcWelderInverseProcessor/marlin_2.cpp b/ArcWelderInverseProcessor/marlin_2.cpp
new file mode 100644
index 0000000..4893dd1
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_2.cpp
@@ -0,0 +1,677 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 2 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "marlin_2.h"
+#include "utilities.h"
+marlin_2::marlin_2(firmware_arguments args) : firmware(args) {
+ feedrate_mm_s = 0;
+ current_position = new float[MARLIN_2_XYZE];
+ apply_arguments();
+};
+
+marlin_2::~marlin_2()
+{
+ delete current_position;
+}
+
+void marlin_2::apply_arguments()
+{
+ static const std::vector<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 = HYPOT(rvec[0], rvec[1]),
+ center_P = current_position[p_axis] - rvec[0],
+ center_Q = current_position[q_axis] - rvec[1],
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ start_L = current_position[l_axis];
+
+ uint16_t min_segments = args_.min_arc_segments > 0 ? args_.min_arc_segments : 1;
+
+ // Angle of rotation between position and target from the circle center.
+ float angular_travel;
+
+ // Do a full circle if starting and ending positions are "identical"
+ if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) {
+ // Preserve direction for circles
+ angular_travel = clockwise ? -RADIANS(360) : RADIANS(360);
+ }
+ else {
+ // Calculate the angle
+ angular_travel = ATAN2(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y);
+
+ // Angular travel too small to detect? Just return.
+ if (!angular_travel) return;
+
+ // Make sure angular travel over 180 degrees goes the other way around.
+ switch (((angular_travel < 0) << 1) | (int)clockwise) {
+ case 1: angular_travel -= RADIANS(360); break; // Positive but CW? Reverse direction.
+ case 2: angular_travel += RADIANS(360); break; // Negative but CCW? Reverse direction.
+ }
+
+ if (args_.min_arc_segments > 1)
+ {
+ min_segments = (uint16_t)CEIL(min_segments * ABS(angular_travel) / RADIANS(360));
+ min_segments = (uint16_t)NOLESS(min_segments, 1U);
+ }
+ }
+
+
+ float linear_travel = cart[Z_AXIS] - start_L;
+ float extruder_travel = cart[E_AXIS] - current_position[E_AXIS];
+
+ const float flat_mm = radius * angular_travel,
+ mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : ABS(flat_mm);
+ if (mm_of_travel < 0.001f) return;
+
+ const float scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ // Start with a nominal segment length
+ float seg_length = (float)args_.mm_per_arc_segment;
+ if (args_.arc_segments_per_r > 0)
+ {
+ seg_length = constrain((float)args_.mm_per_arc_segment * radius, (float)args_.mm_per_arc_segment, (float)args_.arc_segments_per_r);
+ }
+ else if (args_.arc_segments_per_sec > 0)
+ {
+ seg_length = _MAX(scaled_fr_mm_s * RECIPROCAL((float)args_.arc_segments_per_sec), (float)args_.mm_per_arc_segment);
+ }
+
+ // Divide total travel by nominal segment length
+ uint16_t segments = (uint16_t)FLOOR(mm_of_travel / seg_length);
+ //uint16_t segments = FLOOR(mm_of_travel / seg_length);
+ segments = (uint16_t)NOLESS(segments, min_segments); // At least some segments
+ seg_length = mm_of_travel / segments;
+
+ /**
+ * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ * and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ * r_T = [cos(phi) -sin(phi);
+ * sin(phi) cos(phi)] * r ;
+ *
+ * For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ * defined from the circle center to the initial position. Each line segment is formed by successive
+ * vector rotations. This requires only two cos() and sin() computations to form the rotation
+ * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ * all double numbers are single precision on the Arduino. (True double precision will not have
+ * round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ * tool precision in some cases. Therefore, arc path correction is implemented.
+ *
+ * Small angle approximation may be used to reduce computation overhead further. This approximation
+ * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words,
+ * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ * issue for CNC machines with the single precision Arduino calculations.
+ *
+ * This approximation also allows plan_arc to immediately insert a line segment into the planner
+ * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead.
+ * This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float raw[MARLIN_2_XYZE];
+ const float theta_per_segment = angular_travel / segments,
+ sq_theta_per_segment = sq(theta_per_segment),
+ sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
+ cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
+
+
+ const float linear_per_segment = linear_travel / segments;
+ const float extruder_per_segment = extruder_travel / segments;
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+
+ // Initialize the extruder axis
+ raw[E_AXIS] = current_position[E_AXIS];
+
+ int8_t arc_recalc_count = args_.n_arc_correction;
+
+ for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
+
+ if (args_.n_arc_correction > 1 && --arc_recalc_count)
+ {
+ // Apply vector rotation matrix to previous rvec[0] / 1
+ const float r_new_Y = rvec[0] * sin_T + rvec[1] * cos_T;
+ rvec[0] = rvec[0] * cos_T - rvec[1] * sin_T;
+ rvec[1] = r_new_Y;
+ }
+ else
+ {
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ // To reduce stuttering, the sin and cos could be computed at different times.
+ // For now, compute both at the same time.
+ const float cos_Ti = COS(i * theta_per_segment);
+ const float sin_Ti = SIN(i * theta_per_segment);
+ rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + rvec[0];
+ raw[q_axis] = center_Q + rvec[1];
+ raw[l_axis] = start_L, raw[l_axis] + linear_per_segment;
+
+ raw[E_AXIS] += extruder_per_segment;
+
+ apply_motion_limits(raw);
+
+ if (!buffer_line(raw, scaled_fr_mm_s, 0))
+ {
+ break;
+ }
+ }
+
+ // Ensure last segment arrives at target location.
+ COPY(raw, cart);
+ raw[l_axis] = start_L;
+
+ apply_motion_limits(raw);
+
+
+ buffer_line(raw, scaled_fr_mm_s, 0);
+
+ raw[l_axis] = start_L;
+ COPY(current_position, raw);
+}
+
+
+
+/// <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 = HYPOT(rvec[0], rvec[1]),
+ center_P = current_position[p_axis] - rvec[0],
+ center_Q = current_position[q_axis] - rvec[1],
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ start_L = current_position[l_axis];
+
+ uint16_t min_segments = args_.min_arc_segments > 0 ? args_.min_arc_segments : 1;
+
+ // Angle of rotation between position and target from the circle center.
+ float angular_travel, abs_angular_travel;
+
+ // Do a full circle if starting and ending positions are "identical"
+ if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) {
+ // Preserve direction for circles
+ angular_travel = clockwise ? -RADIANS(360) : RADIANS(360);
+ abs_angular_travel = RADIANS(360);
+ min_segments = min_circle_segments;
+ }
+ else {
+ // Calculate the angle
+ angular_travel = ATAN2(rvec[0] * rt_Y - rvec[1] * rt_X, rvec[0] * rt_X + rvec[1] * rt_Y);
+
+ // Angular travel too small to detect? Just return.
+ if (!angular_travel) return;
+
+ // Make sure angular travel over 180 degrees goes the other way around.
+ switch (((angular_travel < 0) << 1) | (int)clockwise) {
+ case 1: angular_travel -= RADIANS(360); break; // Positive but CW? Reverse direction.
+ case 2: angular_travel += RADIANS(360); break; // Negative but CCW? Reverse direction.
+ }
+
+ abs_angular_travel = ABS(angular_travel);
+
+ // Apply minimum segments to the arc
+ const float portion_of_circle = abs_angular_travel / RADIANS(360); // Portion of a complete circle (0 < N < 1)
+ min_segments = (uint16_t)CEIL((min_circle_segments)*portion_of_circle); // Minimum segments for the arc
+ }
+
+ float travel_L = cart[Z_AXIS] - start_L;
+ float travel_E = cart[E_AXIS] - current_position[E_AXIS];
+
+ // Millimeters in the arc, assuming it's flat
+ const float flat_mm = radius * abs_angular_travel;
+ if (flat_mm < 0.001f
+ && travel_L < 0.001f
+ ) return;
+
+ // Feedrate for the move, scaled by the feedrate multiplier
+ const float scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ // Get the nominal segment length based on settings
+ float nominal_segment_mm;
+ if (args_.arc_segments_per_sec > 0) {
+ nominal_segment_mm = constrain(scaled_fr_mm_s * RECIPROCAL((float)args_.arc_segments_per_sec), (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm());
+ }
+ else {
+ nominal_segment_mm = (float)args_.get_max_arc_segment_mm();
+ }
+ // Number of whole segments based on the nominal segment length
+ const float nominal_segments = _MAX(FLOOR(flat_mm / nominal_segment_mm), min_segments);
+
+ // A new segment length based on the required minimum
+ const float segment_mm = constrain(flat_mm / nominal_segments, (float)args_.get_min_arc_segment_mm(), (float)args_.get_max_arc_segment_mm());
+
+ // The number of whole segments in the arc, ignoring the remainder
+ uint16_t segments = (uint16_t)FLOOR(flat_mm / segment_mm);
+
+ // Are the segments now too few to reach the destination?
+ const float segmented_length = segment_mm * segments;
+ const bool tooshort = segmented_length < flat_mm - 0.0001f;
+ const float proportion = tooshort ? segmented_length / flat_mm : 1.0f;
+
+
+ /**
+ * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ * and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ * r_T = [cos(phi) -sin(phi);
+ * sin(phi) cos(phi)] * r ;
+ *
+ * For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ * defined from the circle center to the initial position. Each line segment is formed by successive
+ * vector rotations. This requires only two cos() and sin() computations to form the rotation
+ * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ * all double numbers are single precision on the Arduino. (True double precision will not have
+ * round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ * tool precision in some cases. Therefore, arc path correction is implemented.
+ *
+ * Small angle approximation may be used to reduce computation overhead further. This approximation
+ * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words,
+ * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ * issue for CNC machines with the single precision Arduino calculations.
+ *
+ * This approximation also allows plan_arc to immediately insert a line segment into the planner
+ * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead.
+ * This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float raw[MARLIN_2_XYZE];
+ const float theta_per_segment = proportion * angular_travel / segments,
+ sq_theta_per_segment = sq(theta_per_segment),
+ sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
+ cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
+
+
+ const float per_segment_L = proportion * travel_L / segments;
+ const float extruder_per_segment = proportion * travel_E / segments;
+
+ // For shortened segments, run all but the remainder in the loop
+ if (tooshort) segments++;
+
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+ // Initialize the extruder axis
+ raw[E_AXIS] = current_position[E_AXIS];
+
+ int8_t arc_recalc_count = args_.n_arc_correction;
+
+ for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
+
+ if (args_.n_arc_correction > 1 && --arc_recalc_count)
+ {
+ // Apply vector rotation matrix to previous rvec[0] / 1
+ const float r_new_Y = rvec[0] * sin_T + rvec[1] * cos_T;
+ rvec[0] = rvec[0] * cos_T - rvec[1] * sin_T;
+ rvec[1] = r_new_Y;
+ }
+ else
+ {
+ if (args_.n_arc_correction > 1)
+ {
+ arc_recalc_count = args_.n_arc_correction;
+ }
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ // To reduce stuttering, the sin and cos could be computed at different times.
+ // For now, compute both at the same time.
+ const float cos_Ti = COS(i * theta_per_segment);
+ const float sin_Ti = SIN(i * theta_per_segment);
+ rvec[0] = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ rvec[1] = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + rvec[0];
+ raw[q_axis] = center_Q + rvec[1];
+ raw[l_axis] = start_L, raw[l_axis] + per_segment_L;
+ raw[E_AXIS] += extruder_per_segment;
+
+ apply_motion_limits(raw);
+
+ if (!buffer_line(raw, scaled_fr_mm_s, 0))
+ {
+ break;
+ }
+ }
+
+ // Ensure last segment arrives at target location.
+ COPY(raw, cart);
+ raw[l_axis] = start_L;
+
+ apply_motion_limits(raw);
+
+
+ buffer_line(raw, scaled_fr_mm_s, 0);
+
+ raw[l_axis] = start_L;
+ COPY(current_position, raw);
+}
+// Marlin Function Defs
+float marlin_2::HYPOT(float x, float y)
+{
+ return (float)utilities::hypot(x, y);
+}
+
+float marlin_2::ATAN2(float x, float y)
+{
+ return (float)utilities::atan2(x, y);
+}
+
+float marlin_2::RADIANS(float x)
+{
+ return (x * (float)M_PI) / 180;
+}
+
+float marlin_2::ABS(float x)
+{
+ return (float)utilities::abs(x);
+}
+
+float marlin_2::FLOOR(float x)
+{
+ return (float)utilities::floor(x);
+}
+
+float marlin_2::COS(float x)
+{
+ return (float)utilities::cos(x);
+}
+
+float marlin_2::SIN(float x)
+{
+ return (float)utilities::sin(x);
+}
+
+float marlin_2::NOLESS(uint16_t x, uint16_t y)
+{
+ if (x < y)
+ return y;
+ return x;
+}
+
+float marlin_2::sq(float x)
+{
+ return x * x;
+}
+
+float marlin_2::MMS_SCALED(float x)
+{
+ // No scaling
+ return x;
+}
+
+bool marlin_2::WITHIN(float N, float L, float H)
+{
+ return ((N) >= (L) && (N) <= (H));
+}
+bool marlin_2::NEAR_ZERO(float x)
+{
+ return WITHIN(x, -0.000001f, 0.000001f);
+}
+bool marlin_2::NEAR(float x, float y)
+{
+ return NEAR_ZERO((x)-(y));
+}
+
+float marlin_2::CEIL(float x)
+{
+ return (float)utilities::ceil(x);
+}
+
+float marlin_2::constrain(float value, float arg_min, float arg_max)
+{
+ return ((value) < (arg_min) ? (arg_min) : ((value) > (arg_max) ? (arg_max) : (value)));
+}
+
+float marlin_2::_MAX(float x, float y)
+{
+ if (x>y) return x;
+ return y;
+}
+float marlin_2::_MIN(float x, float y)
+{
+ if (x < y) return x;
+ return y;
+}
+
+float marlin_2::RECIPROCAL(float x)
+{
+ return (float)1.0/x;
+}
+void marlin_2::COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE])
+{
+ // This is a slow copy, but speed isn't much of an issue here.
+ for (int i = 0; i < MARLIN_2_XYZE; i++)
+ {
+ target[i] = source[i];
+ }
+}
+
+void marlin_2::apply_motion_limits(float (&pos)[MARLIN_2_XYZE])
+{
+ // do nothing
+ return;
+}
+
+//void marlin::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+bool marlin_2::buffer_line(const float(&cart)[MARLIN_2_XYZE], double fr_mm_s, int active_extruder)
+{
+
+ // create the target position
+ firmware_position target;
+ target.x = cart[AxisEnum::X_AXIS];
+ target.y = cart[AxisEnum::Y_AXIS];
+ target.z = cart[AxisEnum::Z_AXIS];
+ target.e = cart[AxisEnum::E_AXIS];
+ target.f = fr_mm_s;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ return true;
+}
diff --git a/ArcWelderInverseProcessor/marlin_2.h b/ArcWelderInverseProcessor/marlin_2.h
new file mode 100644
index 0000000..f9735ec
--- /dev/null
+++ b/ArcWelderInverseProcessor/marlin_2.h
@@ -0,0 +1,107 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Marlin 2 arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+#include "firmware.h"
+
+
+#define MARLIN_2_XYZE 4
+
+class marlin_2 :
+ public firmware
+{
+public:
+ enum class marlin_2_firmware_versions { V2_0_9_1 = 0, V2_0_9_2 = 1};
+ /// <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
+ float HYPOT(float x, float y);
+ float ATAN2(float x, float y);
+ float RADIANS(float x);
+ float COS(float x);
+ float SIN(float s);
+ float ABS(float x);
+ float FLOOR(float x);
+ float NOLESS(uint16_t x, uint16_t y);
+ float sq(float x);
+ float MMS_SCALED(float x);
+ bool NEAR_ZERO(float x);
+ bool NEAR(float x, float y);
+ bool WITHIN(float N, float L, float H);
+ float CEIL(float x);
+ float constrain(float value, float arg_min, float arg_max);
+ float _MAX(float x, float y);
+ float _MIN(float x, float y);
+ float RECIPROCAL(float x);
+ void COPY(float target[MARLIN_2_XYZE], const float(&source)[MARLIN_2_XYZE]);
+};
+
diff --git a/ArcWelderInverseProcessor/marlin_2_arc.cpp b/ArcWelderInverseProcessor/marlin_2_arc.cpp
deleted file mode 100644
index 0656015..0000000
--- a/ArcWelderInverseProcessor/marlin_2_arc.cpp
+++ /dev/null
@@ -1,448 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Arc Welder: Inverse Processor Console Application
-//
-// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
-// This reduces file size and the number of gcodes per second.
-//
-// Built using the 'Arc Welder: Anti Stutter' library
-//
-// Copyright(C) 2020 - Brad Hochgesang
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// This program is free software : you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
-// GNU Affero General Public License for more details.
-//
-//
-// You can contact the author at the following email address:
-// FormerLurker@pm.me
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// This file includes portions from Marlin's motion_control.c file since it is intended to test some firmware modifications.
-// This file was included in the AntiStutter project for convenience, and will not be included within the final version.
-/*
- motion_control.c - high level interface for issuing motion commands
- Part of Grbl
-
- Copyright (c) 2009-2011 Simen Svale Skogsrud
- Copyright (c) 2011 Sungeun K. Jeon
- Copyright (c) 2020 Brad Hochgesang
-
- Grbl is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Grbl is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Grbl. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "marlin_2_arc.h"
-#include <cmath>
-
-//#include "Marlin.h"
-//#include "stepper.h"
-//#include "planner.h"
-
-marlin_2_arc::marlin_2_arc(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs)
-{
- source_path_ = source_path;
- target_path_ = target_path;
- p_source_position_ = new gcode_position(get_args_(g90_g91_influences_extruder, buffer_size));
- cs_ = cs;
- offset_absolute_e_ = 0;
- // ** Gloabal Variable Definition **
- // 20200417 - FormerLurker - Declare two globals and pre-calculate some values that will reduce the
- // amount of trig funcitons we need to call while printing. For the price of having two globals we
- // save one trig calc per G2/G3 for both MIN_ARC_SEGMENTS and MIN_MM_PER_ARC_SEGMENT. This is a good trade IMHO.
-
-
-#ifdef MIN_ARC_SEGMENTS
-// Determines the radius at which the transition from using MM_PER_ARC_SEGMENT to MIN_ARC_SEGMENTS
- arc_max_radius_threshold = MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS));
-#endif
-/*
-#if defined(MIN_ARC_SEGMENTS) && defined(MIN_MM_PER_ARC_SEGMENT)
- // Determines the radius at which the transition from using MIN_ARC_SEGMENTS to MIN_MM_PER_ARC_SEGMENT.
- arc_min_radius_threshold = MIN_MM_PER_ARC_SEGMENT / (2.0F * sin(M_PI / MIN_ARC_SEGMENTS));
-#endif
-*/
-}
-
-gcode_position_args marlin_2_arc::get_args_(bool g90_g91_influences_extruder, int buffer_size)
-{
- gcode_position_args args;
- // Configure gcode_position_args
- args.g90_influences_extruder = g90_g91_influences_extruder;
- args.position_buffer_size = buffer_size;
- args.autodetect_position = true;
- args.home_x = 0;
- args.home_x_none = true;
- args.home_y = 0;
- args.home_y_none = true;
- args.home_z = 0;
- args.home_z_none = true;
- args.shared_extruder = true;
- args.zero_based_extruder = true;
-
-
- args.default_extruder = 0;
- args.xyz_axis_default_mode = "absolute";
- args.e_axis_default_mode = "absolute";
- args.units_default = "millimeters";
- args.location_detection_commands = std::vector<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;
-}
-
-marlin_2_arc::~marlin_2_arc()
-{
- delete p_source_position_;
-}
-
-void marlin_2_arc::process()
-{
- // Create a stringstream we can use for messaging.
- std::stringstream stream;
-
- int read_lines_before_clock_check = 5000;
- //std::cout << "stabilization::process_file - Processing file.\r\n";
- stream << "Decompressing gcode file.";
- stream << "Source File: " << source_path_ << "\n";
- stream << "Target File: " << target_path_ << "\n";
- std::cout << stream.str();
- const clock_t start_clock = clock();
-
- // Create the source file read stream and target write stream
- std::ifstream gcode_file;
- gcode_file.open(source_path_.c_str());
- output_file_.open(target_path_.c_str());
- std::string line;
- int lines_with_no_commands = 0;
- gcode_file.sync_with_stdio(false);
- output_file_.sync_with_stdio(false);
- gcode_parser parser;
- int gcodes_processed = 0;
- if (gcode_file.is_open())
- {
- if (output_file_.is_open())
- {
- //stream.clear();
- //stream.str("");
- //stream << "Opened file for reading. File Size: " << file_size_ << "\n";
- //std::cout << stream.str();
- parsed_command cmd;
- // Communicate every second
- while (std::getline(gcode_file, line))
- {
- lines_processed_++;
-
- cmd.clear();
- parser.try_parse_gcode(line.c_str(), cmd);
- bool has_gcode = false;
- if (cmd.gcode.length() > 0)
- {
- has_gcode = true;
- gcodes_processed++;
- }
- else
- {
- lines_with_no_commands++;
- }
-
- p_source_position_->update(cmd, lines_processed_, gcodes_processed, -1);
-
- if (cmd.command == "G2" || cmd.command == "G3")
- {
- position* p_cur_pos = p_source_position_->get_current_position_ptr();
- position* p_pre_pos = p_source_position_->get_previous_position_ptr();
- float position[4];
- position[X_AXIS] = static_cast<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[X_AXIS] = 0.0;
- offset[Y_AXIS] = 0.0;
- for (unsigned int index = 0; index < cmd.parameters.size(); index++)
- {
- parsed_command_parameter p = cmd.parameters[index];
- if (p.name == "I")
- {
- offset[X_AXIS] = static_cast<float>(p.double_value);
- }
- else if (p.name == "J")
- {
- offset[Y_AXIS] = 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;
- offset_absolute_e_ = p_pre_pos->get_current_extruder().get_offset_e();
- 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 marlin_2_arc::mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder)
-{
- // Extract the position to reduce indexing at the cost of a few bytes of mem
- float p_x = position[X_AXIS];
- float p_y = position[Y_AXIS];
- float p_z = position[Z_AXIS];
- float p_e = position[E_AXIS];
-
- float t_x = target[X_AXIS];
- float t_y = target[Y_AXIS];
- float t_z = target[Z_AXIS];
- float t_e = target[E_AXIS];
-
- float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location
- float r_axis_y = -offset[Y_AXIS];
- float center_axis_x = p_x - r_axis_x;
- float center_axis_y = p_y - r_axis_y;
- float travel_z = t_z - p_z;
- float extruder_travel_total = t_e - p_e;
-
- float rt_x = t_x - center_axis_x;
- float rt_y = t_y - center_axis_y;
- // 20200419 - Add a variable that will be used to hold the arc segment length
- float mm_per_arc_segment = cs_.mm_per_arc_segment;
-
- // CCW angle between position and target from circle center. Only one atan2() trig computation required.
- float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y);
- if (angular_travel_total < 0) { angular_travel_total += (float)(2 * M_PI); }
-
- bool check_mm_per_arc_segment_max = false;
- if (cs_.min_arc_segments > 0)
- {
- // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
- // Do this before converting the angular travel for clockwise rotation
- mm_per_arc_segment = (float)(radius * ((2.0f * M_PI) / cs_.min_arc_segments));
- check_mm_per_arc_segment_max = true;
- }
-
- if (cs_.arc_segments_per_sec > 0)
- {
- // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
- float mm_per_arc_segment_sec = (float)((feed_rate / 60.0f) * (1.0f / cs_.arc_segments_per_sec));
- if (mm_per_arc_segment_sec < mm_per_arc_segment)
- mm_per_arc_segment = mm_per_arc_segment_sec;
- check_mm_per_arc_segment_max = true;
- }
-
- if (cs_.min_mm_per_arc_segment > 0)
- {
- check_mm_per_arc_segment_max = true;
- // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined
- // This prevents a very high number of segments from being generated for curves of a short radius
- if (mm_per_arc_segment < cs_.min_mm_per_arc_segment) mm_per_arc_segment = cs_.min_mm_per_arc_segment;
- }
-
- if (check_mm_per_arc_segment_max && mm_per_arc_segment > cs_.mm_per_arc_segment) mm_per_arc_segment = cs_.mm_per_arc_segment;
-
-
-
- // Adjust the angular travel if the direction is clockwise
- if (isclockwise) { angular_travel_total -= (float)(2 * M_PI); }
-
- //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
- //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
- if (p_x == t_x && p_y == t_y && angular_travel_total == 0)
- {
- angular_travel_total += (float)(2 * M_PI);
- }
- //end fix G03
-
- // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are
- // calculating here
- float millimeters_of_travel_arc = hypot(angular_travel_total * radius, std::fabs(travel_z));
- if (millimeters_of_travel_arc < 0.001) { return; }
- // Calculate the total travel per segment
- // Calculate the number of arc segments
- uint16_t segments = static_cast<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 /*&& theta_per_segment > 0.001 */) {
- // Apply vector rotation matrix
- r_axisi = r_axis_x * sin_T + r_axis_y * cos_T;
- r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T;
- r_axis_y = r_axisi;
- count++;
- }
- else {
- // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
- // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
- cos_Ti = cos(i * theta_per_segment);
- sin_Ti = sin(i * theta_per_segment);
- r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti;
- r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti;
- count = 0;
-
- }
-
-
-
- // Update arc_target location
- p_x = center_axis_x + r_axis_x;
- p_y = center_axis_y + r_axis_y;
- p_z += linear_per_segment;
- p_e += segment_extruder_travel;
- // We can't clamp to the target because we are interpolating! We would need to update a position, clamp to it
- // after updating from calculated values.
- //clamp_to_software_endstops(position);
- plan_buffer_line(p_x, p_y, travel_z > 0, p_z, p_e, feed_rate, extruder);
- }
- }
- // Ensure last segment arrives at target location.
- // Here we could clamp, but why bother. We would need to update our current position, clamp to it
- //clamp_to_software_endstops(target);
- plan_buffer_line(t_x, t_y, travel_z> 0, t_z, t_e, feed_rate, extruder);
- position[X_AXIS] = t_x;
- position[Y_AXIS] = t_y;
- position[Z_AXIS] = t_z;
- position[E_AXIS] = t_e;
-}
-
-void marlin_2_arc::clamp_to_software_endstops(float* target)
-{
- // Do nothing, just added to keep mc_arc identical to the firmware version
- return;
-}
-
-void marlin_2_arc::plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
-{
- std::stringstream stream;
- stream << std::fixed;
-
- position * previous_pos = p_source_position_->get_previous_position_ptr();
- position* current_pos = p_source_position_->get_current_position_ptr();
-
- stream << "G1 X" << std::setprecision(3) << x << " Y" << y;
- if (has_z)
- {
- stream << " Z" << z;
- }
-
- double output_e = e;
- if (previous_pos->is_extruder_relative)
- {
- output_e = e - offset_absolute_e_;
- offset_absolute_e_ = e;
- }
-
- stream << std::setprecision(5) << " E" << output_e;
-
-
- if (feed_rate != previous_pos->f)
- {
- stream << std::setprecision(0) << " F" << feed_rate;
- }
-
- if (!current_pos->command.comment.empty())
- {
- stream << ";" << current_pos->command.comment;
- }
- stream << "\n";
- output_file_ << stream.str();
-}
diff --git a/ArcWelderInverseProcessor/marlin_2_arc.h b/ArcWelderInverseProcessor/marlin_2_arc.h
deleted file mode 100644
index a7a2ac3..0000000
--- a/ArcWelderInverseProcessor/marlin_2_arc.h
+++ /dev/null
@@ -1,95 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Arc Welder: Inverse Processor Console Application
-//
-// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
-// This reduces file size and the number of gcodes per second.
-//
-// Built using the 'Arc Welder: Anti Stutter' library
-//
-// Copyright(C) 2020 - Brad Hochgesang
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// This program is free software : you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
-// GNU Affero General Public License for more details.
-//
-//
-// You can contact the author at the following email address:
-// FormerLurker@pm.me
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma once
-
-#include <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 marlin_2_arc {
-public:
- marlin_2_arc(std::string source_path, std::string target_path, bool g90_g91_influences_extruder, int buffer_size, ConfigurationStore cs = ConfigurationStore());
- virtual ~marlin_2_arc();
- void process();
- void mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, uint8_t isclockwise, uint8_t extruder);
-
-private:
- ConfigurationStore cs_;
- gcode_position_args get_args_(bool g90_g91_influences_extruder, int buffer_size);
- std::string source_path_;
- std::string target_path_;
- gcode_position* p_source_position_;
- std::ofstream output_file_;
- bool output_relative_;
- double offset_absolute_e_;
- float arc_max_radius_threshold;
- //float arc_min_radius_threshold;
- float total_e_adjustment;
- int trig_calc_count = 0;
- int lines_processed_ = 0;
- void clamp_to_software_endstops(float* target);
-
- void plan_buffer_line(float x, float y, bool has_z, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target=NULL);
-
-};
-
-
-
-
-
diff --git a/ArcWelderInverseProcessor/prusa.cpp b/ArcWelderInverseProcessor/prusa.cpp
new file mode 100644
index 0000000..ddd8a53
--- /dev/null
+++ b/ArcWelderInverseProcessor/prusa.cpp
@@ -0,0 +1,466 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Prusa arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with mc_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#include "prusa.h"
+#include <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 = (float)utilities::hypot(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 = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
+ if (angular_travel < 0) { angular_travel += 2 * (float)M_PI; }
+ if (isclockwise) { angular_travel -= 2 * (float)M_PI; }
+
+ //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
+ //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
+ if (position[axis_0] == target[axis_0] && position[axis_1] == target[axis_1] && angular_travel == 0)
+ {
+ angular_travel += 2 * (float)M_PI;
+ }
+ //end fix G03
+
+ float millimeters_of_travel = (float)utilities::hypot(angular_travel * radius, fabs(linear_travel));
+ if (millimeters_of_travel < 0.001) { return; }
+ uint16_t segments = (uint16_t)floor(millimeters_of_travel / cs.mm_per_arc_segment);
+ if (segments == 0) segments = 1;
+
+ /*
+ // Multiply inverse feed_rate to compensate for the fact that this movement is approximated
+ // by a number of discrete segments. The inverse feed_rate should be correct for the sum of
+ // all segments.
+ if (invert_feed_rate) { feed_rate *= segments; }
+ */
+ float theta_per_segment = angular_travel / segments;
+ float linear_per_segment = linear_travel / segments;
+ float extruder_per_segment = extruder_travel / segments;
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all double numbers are single precision on the Arduino. (True double precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ Small angle approximation may be used to reduce computation overhead further. This approximation
+ holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+ theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ issue for CNC machines with the single precision Arduino calculations.
+
+ This approximation also allows mc_arc to immediately insert a line segment into the planner
+ without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+ This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float cos_T = 1 - (float)0.5 * theta_per_segment * theta_per_segment; // Small angle approximation
+ float sin_T = theta_per_segment;
+
+ float arc_target[4];
+ float sin_Ti;
+ float cos_Ti;
+ float r_axisi;
+ uint16_t i;
+ int8_t count = 0;
+
+ // Initialize the linear axis
+ arc_target[axis_linear] = position[axis_linear];
+
+ // Initialize the extruder axis
+ arc_target[E_AXIS] = position[E_AXIS];
+
+ for (i = 1; i < segments; i++) { // Increment (segments-1)
+
+ if (count < cs.n_arc_correction) {
+ // Apply vector rotation matrix
+ r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
+ r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
+ r_axis1 = r_axisi;
+ count++;
+ }
+ else {
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ cos_Ti = cos(i * theta_per_segment);
+ sin_Ti = sin(i * theta_per_segment);
+ r_axis0 = -offset[axis_0] * cos_Ti + offset[axis_1] * sin_Ti;
+ r_axis1 = -offset[axis_0] * sin_Ti - offset[axis_1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[axis_0] = center_axis0 + r_axis0;
+ arc_target[axis_1] = center_axis1 + r_axis1;
+ arc_target[axis_linear] += linear_per_segment;
+ arc_target[E_AXIS] += extruder_per_segment;
+
+ clamp_to_software_endstops(arc_target);
+ plan_buffer_line(arc_target[X_AXIS], arc_target[Y_AXIS], arc_target[Z_AXIS], arc_target[E_AXIS], feed_rate, extruder, NULL);
+
+ }
+ // Ensure last segment arrives at target location.
+ plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, NULL);
+
+ // plan_set_acceleration_manager_enabled(acceleration_manager_was_enabled);
+}
+
+/// <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 = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y);
+ if (angular_travel_total < 0) { angular_travel_total += 2 * (float)M_PI; }
+
+ if (cs.min_arc_segments > 0)
+ {
+ // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
+ // Do this before converting the angular travel for clockwise rotation
+ mm_per_arc_segment = radius * ((2.0f * (float)M_PI) / cs.min_arc_segments);
+ }
+ if (cs.arc_segments_per_sec > 0)
+ {
+ // 20200417 - FormerLurker - Implement MIN_ARC_SEGMENTS if it is defined - from Marlin 2.0 implementation
+ float mm_per_arc_segment_sec = (feed_rate / 60.0f) * (1.0f / (float)cs.arc_segments_per_sec);
+ if (mm_per_arc_segment_sec < mm_per_arc_segment)
+ mm_per_arc_segment = mm_per_arc_segment_sec;
+ }
+
+ // Note: no need to check to see if min_mm_per_arc_segment is enabled or not (i.e. = 0), since mm_per_arc_segment can never be below 0.
+ if (mm_per_arc_segment < cs.min_mm_per_arc_segment)
+ {
+ // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined
+ // This prevents a very high number of segments from being generated for curves of a short radius
+ mm_per_arc_segment = cs.min_mm_per_arc_segment;
+ }
+ else if (mm_per_arc_segment > cs.mm_per_arc_segment) {
+ // 20210113 - This can be implemented in an else if since we can't be below the min AND above the max at the same time.
+ // 20200417 - FormerLurker - Implement MIN_MM_PER_ARC_SEGMENT if it is defined
+ mm_per_arc_segment = cs.mm_per_arc_segment;
+ }
+
+ // Adjust the angular travel if the direction is clockwise
+ if (isclockwise) { angular_travel_total -= 2 * (float)M_PI; }
+
+ //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
+ //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
+ if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0)
+ {
+ angular_travel_total += 2 * (float)M_PI;
+ }
+ //end fix G03
+
+ // 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are
+ // calculating here
+ const float millimeters_of_travel_arc = hypot(angular_travel_total * radius, fabs(travel_z));
+ if (millimeters_of_travel_arc < 0.001) { return; }
+
+ // Calculate the number of arc segments
+ uint16_t segments = static_cast<uint16_t>(ceil(millimeters_of_travel_arc / mm_per_arc_segment));
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all double numbers are single precision on the Arduino. (True double precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ The small angle approximation was removed because of excessive errors for small circles (perhaps unique to
+ 3d printing applications, causing significant path deviation and extrusion issues).
+ Now there will be no corrections applied, but an accurate initial sin and cos will be calculated.
+ This seems to work with a very high degree of accuracy and results in much simpler code.
+ Finding a faster way to approximate sin, knowing that there can be substantial deviations from the true
+ arc when using the previous approximation, would be beneficial.
+ */
+
+ // If there is only one segment, no need to do a bunch of work since this is a straight line!
+ if (segments > 1)
+ {
+ // Calculate theta per segments, and linear (z) travel per segment, e travel per segment
+ // as well as the small angle approximation for sin and cos.
+ const float theta_per_segment = angular_travel_total / segments,
+ linear_per_segment = travel_z / (segments),
+ segment_extruder_travel = (target[E_AXIS] - position[E_AXIS]) / (segments),
+ sq_theta_per_segment = theta_per_segment * theta_per_segment,
+ sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
+ cos_T = 1 - 0.5f * sq_theta_per_segment;
+ // Loop through all but one of the segments. The last one can be done simply
+ // by moving to the target.
+ for (uint16_t i = 1; i < segments; i++) {
+ if (n_arc_correction-- == 0) {
+ // Calculate the actual position for r_axis_x and r_axis_y
+ const float cos_Ti = cos(i * theta_per_segment), sin_Ti = sin(i * theta_per_segment);
+ r_axis_x = -offset[X_AXIS] * cos_Ti + offset[Y_AXIS] * sin_Ti;
+ r_axis_y = -offset[X_AXIS] * sin_Ti - offset[Y_AXIS] * cos_Ti;
+ // reset n_arc_correction
+ n_arc_correction = cs.n_arc_correction;
+ }
+ else {
+ // Calculate X and Y using the small angle approximation
+ const float r_axisi = r_axis_x * sin_T + r_axis_y * cos_T;
+ r_axis_x = r_axis_x * cos_T - r_axis_y * sin_T;
+ r_axis_y = r_axisi;
+ }
+
+ // Update Position
+ position[X_AXIS] = center_axis_x + r_axis_x;
+ position[Y_AXIS] = center_axis_y + r_axis_y;
+ position[Z_AXIS] += linear_per_segment;
+ position[E_AXIS] += segment_extruder_travel;
+ // Clamp to the calculated position.
+ clamp_to_software_endstops(position);
+ // Insert the segment into the buffer
+ plan_buffer_line(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS], feed_rate, extruder, position);
+ }
+ }
+ // Clamp to the target position.
+ clamp_to_software_endstops(target);
+ // Ensure last segment arrives at target location.
+ plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, target);
+}
+
+void prusa::clamp_to_software_endstops(float* target)
+{
+ // Do nothing, just added to keep mc_arc identical to the firmware version
+ return;
+}
+
+void prusa::plan_buffer_line(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+{
+ // create the target position
+ firmware_position target;
+ target.x = x;
+ target.y = y;
+ target.z = z;
+ target.e = e;
+ target.f = feed_rate;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ // update the current position
+ set_current_position(target);
+}
diff --git a/ArcWelderInverseProcessor/prusa.h b/ArcWelderInverseProcessor/prusa.h
new file mode 100644
index 0000000..f2b3d7e
--- /dev/null
+++ b/ArcWelderInverseProcessor/prusa.h
@@ -0,0 +1,83 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Inverse Processor Console Application
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2020 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include <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..7d5798c
--- /dev/null
+++ b/ArcWelderInverseProcessor/repetier.cpp
@@ -0,0 +1,476 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Arc Welder: Repetier arc interpolation simulator. Please see the copyright notices in the function definitions
+// starting with plan_arc_ for the original license.
+//
+// Converts G2/G3(arc) commands back to G0/G1 commands. Intended to test firmware changes to improve arc support.
+// This reduces file size and the number of gcodes per second.
+//
+// Built using the 'Arc Welder: Anti Stutter' library
+//
+// Copyright(C) 2021 - Brad Hochgesang
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// This program is free software : you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+// GNU Affero General Public License for more details.
+//
+//
+// You can contact the author at the following email address:
+// FormerLurker@pm.me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#include "repetier.h"
+#include "utilities.h"
+repetier::repetier(firmware_arguments args) : firmware(args) {
+
+ feedrate = 0;
+ apply_arguments();
+};
+
+void repetier::apply_arguments()
+{
+ static const std::vector<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 = (float)utilities::hypot(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 * (float)M_PI : 2.0f * (float)M_PI;
+ }
+ else
+ {
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ angular_travel = (float)utilities::atan2((double)(r_axis0 * rt_axis1) - (double)(r_axis1 * rt_axis0), (double)(r_axis0 * rt_axis0) + (double)(r_axis1 * rt_axis1));
+
+ // No need to draw an arc if there is no angular travel
+ if (!angular_travel) return;
+
+ // Make sure angular travel over 180 degrees goes the other way around.
+ if (angular_travel > 0)
+ {
+ if (isclockwise)
+ {
+ angular_travel -= 2.0f * (float)M_PI;
+ }
+ }
+ else if (!isclockwise)
+ {
+ angular_travel += 2.0f * (float)M_PI;
+ }
+ }
+
+ // Determine the number of mm of total travel
+ float millimeters_of_travel = abs(angular_travel) * radius;
+ if (linear_travel)
+ {
+ // If we have any Z motion, add this to the total mm of travel.
+ millimeters_of_travel = (float)utilities::hypot(millimeters_of_travel, linear_travel);
+ }
+
+ if (millimeters_of_travel < 0.001f) {
+ return; // treat as succes because there is nothing to do;
+ }
+
+ // The speed restrictions will be based on some new parameters. Removing the hard coded values
+ // Increase segment size if printing faster then computation speed allows
+ //uint16_t segments = (feedrate > 60.0f ? floor(millimeters_of_travel / min(static_cast<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 * theta_per_segment);
+ sin_Ti = (float)utilities::sin(i * theta_per_segment);
+ r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[X_AXIS] = center_axis0 + r_axis0;
+ arc_target[Y_AXIS] = center_axis1 + r_axis1;
+ arc_target[Z_AXIS] += linear_per_segment;
+ arc_target[E_AXIS] += extruder_per_segment;
+ moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], position[Z_AXIS], arc_target[E_AXIS]);
+ }
+ // Ensure last segment arrives at target location.
+ moveToReal(target[X_AXIS], target[Y_AXIS], position[Z_AXIS], target[E_AXIS]);
+}
+
+/// <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 * (float)M_PI;
+ }
+ if (isclockwise) {
+ angular_travel -= 2.0f * (float)M_PI;
+ }
+
+ float millimeters_of_travel = (float)utilities::fabs(angular_travel) * radius; //hypot(angular_travel*radius, fabs(linear_travel));
+ if (millimeters_of_travel < 0.001f) {
+ return; // treat as succes because there is nothing to do;
+ }
+ //uint16_t segments = (radius>=BIG_ARC_RADIUS ? floor(millimeters_of_travel/MM_PER_ARC_SEGMENT_BIG) : floor(millimeters_of_travel/MM_PER_ARC_SEGMENT));
+ // Increase segment size if printing faster then computation speed allows
+ uint16_t segments = (uint16_t)(feedrate > 60.0f ? utilities::floor(millimeters_of_travel / utilities::min(static_cast<float>(args_.mm_per_arc_segment), feedrate * 0.01666f * static_cast<float>(args_.mm_per_arc_segment))) : utilities::floor(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 * theta_per_segment);
+ sin_Ti = (float)utilities::sin(i * theta_per_segment);
+ r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[X_AXIS] = center_axis0 + r_axis0;
+ arc_target[Y_AXIS] = center_axis1 + r_axis1;
+ //arc_target[axis_linear] += linear_per_segment;
+ arc_target[E_AXIS] += extruder_per_segment;
+ moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], position[Z_AXIS], arc_target[E_AXIS]);
+ }
+ // Ensure last segment arrives at target location.
+ moveToReal(target[X_AXIS], target[Y_AXIS], position[Z_AXIS], target[E_AXIS]);
+}
+
+float repetier::min(float x, float y)
+{
+ if (x < y)
+ {
+ return x;
+ }
+ return y;
+}
+
+//void repetier::buffer_line_kinematic(float x, float y, float z, const float& e, float feed_rate, uint8_t extruder, const float* gcode_target)
+void repetier::moveToReal(float x, float y, float z, float e)
+{
+
+ // create the target position
+ firmware_position target;
+ target.x = x;
+ target.y = y;
+ target.z = z;
+ target.e = e;
+ target.f = feedrate;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(target);
+
+ // update the current position
+ set_current_position(target);
+}
diff --git a/ArcWelderInverseProcessor/repetier.h b/ArcWelderInverseProcessor/repetier.h
new file mode 100644
index 0000000..6b4bcb3
--- /dev/null
+++ b/ArcWelderInverseProcessor/repetier.h
@@ -0,0 +1,39 @@
+#pragma once
+#include <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
+ float min(float x, float y);
+ void moveToReal(float x, float y, float z, float e);
+};
+
diff --git a/ArcWelderInverseProcessor/repiter_arc.cpp b/ArcWelderInverseProcessor/repiter_arc.cpp
deleted file mode 100644
index 83818a0..0000000
--- a/ArcWelderInverseProcessor/repiter_arc.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#include "repiter_arc.h"
-
-
-/*
-// Arc function taken from grbl
-// The arc is approximated by generating a huge number of tiny, linear segments. The length of each
-// segment is configured in settings.mm_per_arc_segment.
-void repiter_arc::arc(float* position, float* target, float* offset, float radius, uint8_t isclockwise) {
- // int acceleration_manager_was_enabled = plan_is_acceleration_manager_enabled();
- // plan_set_acceleration_manager_enabled(false); // disable acceleration management for the duration of the arc
- float center_axis0 = position[X_AXIS] + offset[X_AXIS];
- float center_axis1 = position[Y_AXIS] + offset[Y_AXIS];
- //float linear_travel = 0; //target[axis_linear] - position[axis_linear];
- float extruder_travel = (Printer::destinationSteps[E_AXIS] - Printer::currentPositionSteps[E_AXIS]) * Printer::invAxisStepsPerMM[E_AXIS];
- float r_axis0 = -offset[0]; // Radius vector from center to current location
- float r_axis1 = -offset[1];
- float rt_axis0 = target[0] - center_axis0;
- float rt_axis1 = target[1] - center_axis1;
- // CCW angle between position and target from circle center. Only one atan2() trig computation required.
- float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
- if ((!isclockwise && angular_travel <= 0.00001) || (isclockwise && angular_travel < -0.000001)) {
- angular_travel += 2.0f * M_PI;
- }
- if (isclockwise) {
- angular_travel -= 2.0f * M_PI;
- }
-
- float millimeters_of_travel = fabs(angular_travel) * radius; //hypot(angular_travel*radius, fabs(linear_travel));
- if (millimeters_of_travel < 0.001f) {
- return; // treat as succes because there is nothing to do;
- }
- //uint16_t segments = (radius>=BIG_ARC_RADIUS ? floor(millimeters_of_travel/MM_PER_ARC_SEGMENT_BIG) : floor(millimeters_of_travel/MM_PER_ARC_SEGMENT));
- // Increase segment size if printing faster then computation speed allows
- uint16_t segments = (Printer::feedrate > 60.0f ? floor(millimeters_of_travel / RMath::min(static_cast<float>(MM_PER_ARC_SEGMENT_BIG), Printer::feedrate * 0.01666f * static_cast<float>(MM_PER_ARC_SEGMENT))) : floor(millimeters_of_travel / static_cast<float>(MM_PER_ARC_SEGMENT)));
- if (segments == 0)
- segments = 1;
-
- float theta_per_segment = angular_travel / segments;
- //float linear_per_segment = linear_travel/segments;
- float extruder_per_segment = extruder_travel / segments;
-
-
- // Vector rotation matrix values
- float cos_T = 1 - 0.5 * theta_per_segment * theta_per_segment; // Small angle approximation
- float sin_T = theta_per_segment;
-
- float arc_target[4];
- float sin_Ti;
- float cos_Ti;
- float r_axisi;
- uint16_t i;
- int8_t count = 0;
-
- // Initialize the linear axis
- //arc_target[axis_linear] = position[axis_linear];
-
- // Initialize the extruder axis
- arc_target[E_AXIS] = Printer::currentPositionSteps[E_AXIS] * Printer::invAxisStepsPerMM[E_AXIS];
-
- for (i = 1; i < segments; i++) {
- // Increment (segments-1)
-
- if ((count & 3) == 0) {
- //GCode::readFromSerial();
- Commands::checkForPeriodicalActions(false);
- UI_MEDIUM; // do check encoder
- }
-
- if (count < N_ARC_CORRECTION) { //25 pieces
- // Apply vector rotation matrix
- r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
- r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
- r_axis1 = r_axisi;
- count++;
- }
- else {
- // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
- // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
- cos_Ti = cos(i * theta_per_segment);
- sin_Ti = sin(i * theta_per_segment);
- r_axis0 = -offset[0] * cos_Ti + offset[1] * sin_Ti;
- r_axis1 = -offset[0] * sin_Ti - offset[1] * cos_Ti;
- count = 0;
- }
-
- // Update arc_target location
- arc_target[X_AXIS] = center_axis0 + r_axis0;
- arc_target[Y_AXIS] = center_axis1 + r_axis1;
- //arc_target[axis_linear] += linear_per_segment;
- arc_target[E_AXIS] += extruder_per_segment;
- Printer::moveToReal(arc_target[X_AXIS], arc_target[Y_AXIS], IGNORE_COORDINATE, arc_target[E_AXIS], IGNORE_COORDINATE);
- }
- // Ensure last segment arrives at target location.
- Printer::moveToReal(target[X_AXIS], target[Y_AXIS], IGNORE_COORDINATE, target[E_AXIS], IGNORE_COORDINATE);
-}
-
-*/ \ No newline at end of file
diff --git a/ArcWelderInverseProcessor/repiter_arc.h b/ArcWelderInverseProcessor/repiter_arc.h
deleted file mode 100644
index 353c040..0000000
--- a/ArcWelderInverseProcessor/repiter_arc.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-#include <cstdint>
-class repiter_arc
-{
- void arc(float* position, float* target, float* offset, float radius, uint8_t isclockwise);
-};
-
diff --git a/ArcWelderInverseProcessor/smoothieware.cpp b/ArcWelderInverseProcessor/smoothieware.cpp
new file mode 100644
index 0000000..bf17934
--- /dev/null
+++ b/ArcWelderInverseProcessor/smoothieware.cpp
@@ -0,0 +1,274 @@
+#include "smoothieware.h"
+#include "utilities.h"
+smoothieware::smoothieware(firmware_arguments args) : firmware(args) {
+ feed_rate = 0;
+ for (int i = 0; i < REPETIER_XYZE; i++)
+ {
+ machine_position[i] = 0;
+ }
+ THEKERNEL = new SmoothiewareKernel();
+ apply_arguments();
+};
+
+void smoothieware::apply_arguments()
+{
+ static const std::vector<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 * (float)PI);
+ }
+ else { // set angular_travel to 2pi for a counterclockwise full circle
+ angular_travel = (2 * (float)PI);
+ }
+ }
+ else {
+ // Patch from GRBL Firmware - Christoph Baumann 04072015
+ // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+ // Only run if not a full circle or angular travel will incorrectly result in 0.0f
+ angular_travel = atan2f(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
+ if (plane_axis_2 == Y_AXIS) { is_clockwise = !is_clockwise; } //Math for XZ plane is reverse of other 2 planes
+ if (is_clockwise) { // adjust angular_travel to be in the range of -2pi to 0 for clockwise arcs
+ if (angular_travel > 0) { angular_travel -= (2 * (float)PI); }
+ }
+ else { // adjust angular_travel to be in the range of 0 to 2pi for counterclockwise arcs
+ if (angular_travel < 0) { angular_travel += (2 * (float)PI); }
+ }
+ }
+
+ // initialize linear travel for ABC
+#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3
+ float abc_travel[n_motors - 3];
+ for (int i = A_AXIS; i < n_motors; i++) {
+ abc_travel[i - 3] = target[i] - this->machine_position[i];
+ }
+#endif
+
+
+ // Find the distance for this gcode
+ float millimeters_of_travel = (float)utilities::hypot(angular_travel * radius, utilities::fabsf(linear_travel));
+
+ // We don't care about non-XYZ moves ( for example the extruder produces some of those )
+ if (millimeters_of_travel < 0.000001F) {
+ return false;
+ }
+
+ // limit segments by maximum arc error
+ float arc_segment = (float)args_.mm_per_arc_segment;
+ if ((args_.mm_max_arc_error > 0) && (2 * radius > args_.mm_max_arc_error)) {
+ float min_err_segment = 2 * (float)utilities::sqrtf((args_.mm_max_arc_error * (2 * radius - args_.mm_max_arc_error)));
+ if (args_.mm_per_arc_segment < min_err_segment) {
+ arc_segment = min_err_segment;
+ }
+ }
+
+ // catch fall through on above
+ if (arc_segment < 0.0001F) {
+ arc_segment = 0.5F; /// the old default, so we avoid the divide by zero
+ }
+
+ // Figure out how many segments for this gcode
+ // TODO for deltas we need to make sure we are at least as many segments as requested, also if mm_per_line_segment is set we need to use the
+ uint16_t segments = (uint16_t)floorf(millimeters_of_travel / arc_segment);
+ bool moved = false;
+
+ if (segments > 1) {
+ float theta_per_segment = angular_travel / segments;
+ float linear_per_segment = linear_travel / segments;
+#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3
+ float abc_per_segment[n_motors - 3];
+ for (int i = 0; i < n_motors - 3; i++) {
+ abc_per_segment[i] = abc_travel[i] / segments;
+ }
+#endif
+
+ /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+ and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+ r_T = [cos(phi) -sin(phi);
+ sin(phi) cos(phi] * r ;
+ For arc generation, the center of the circle is the axis of rotation and the radius vector is
+ defined from the circle center to the initial position. Each line segment is formed by successive
+ vector rotations. This requires only two cos() and sin() computations to form the rotation
+ matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+ all float numbers are single precision on the Arduino. (True float precision will not have
+ round off issues for CNC applications.) Single precision error can accumulate to be greater than
+ tool precision in some cases. Therefore, arc path correction is implemented.
+ Small angle approximation may be used to reduce computation overhead further. This approximation
+ holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+ theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+ to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+ numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+ issue for CNC machines with the single precision Arduino calculations.
+ This approximation also allows mc_arc to immediately insert a line segment into the planner
+ without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+ a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+ This is important when there are successive arc motions.
+ */
+ // Vector rotation matrix values
+ float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
+ float sin_T = theta_per_segment;
+
+ float arc_target[n_motors];
+ float sin_Ti;
+ float cos_Ti;
+ float r_axisi;
+ uint16_t i;
+ int8_t count = 0;
+
+ // init array for all axis
+ utilities::memcpy(arc_target, machine_position, n_motors * sizeof(float));
+
+ // Initialize the linear axis
+ arc_target[this->plane_axis_2] = this->machine_position[this->plane_axis_2];
+
+ for (i = 1; i < segments; i++) { // Increment (segments-1)
+ if (THEKERNEL->is_halted()) return false; // don't queue any more segments
+
+ if (count < args_.n_arc_correction) {
+ // Apply vector rotation matrix
+ r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
+ r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
+ r_axis1 = r_axisi;
+ count++;
+ }
+ else {
+ // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+ // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+ cos_Ti = (float)utilities::cosf(i * theta_per_segment);
+ sin_Ti = (float)utilities::sinf(i * theta_per_segment);
+ r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
+ r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
+ count = 0;
+ }
+
+ // Update arc_target location
+ arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
+ arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
+ arc_target[this->plane_axis_2] += linear_per_segment;
+#if SMOOTHIEWARE_MAX_ROBOT_ACTUATORS > 3
+ for (int a = A_AXIS; a < n_motors; a++) {
+ arc_target[a] += abc_per_segment[a - 3];
+ }
+#endif
+
+ // Append this segment to the queue
+ bool b = this->append_milestone(arc_target, rate_mm_s);
+ moved = moved || b;
+ }
+ }
+
+ // Ensure last segment arrives at target location.
+ if (this->append_milestone(target, rate_mm_s)) moved = true;
+
+ return moved;
+}
+
+bool smoothieware::append_milestone(const float target[], double rate_mm_s)
+{
+ double rate_mm_min = rate_mm_s * 60;
+ // create the target position
+ firmware_position gcode_target;
+ gcode_target.x = target[AxisEnum::X_AXIS];
+ gcode_target.y = target[AxisEnum::Y_AXIS];
+ gcode_target.z = target[AxisEnum::Z_AXIS];
+ gcode_target.e = target[AxisEnum::E_AXIS];
+ gcode_target.f = rate_mm_min;
+ if (gcodes_.size() > 0)
+ {
+ gcodes_ += "\n";
+ }
+ // Generate the gcode
+ gcodes_ += g1_command(gcode_target);
+
+ return true;
+ return true;
+}
diff --git a/ArcWelderInverseProcessor/smoothieware.h b/ArcWelderInverseProcessor/smoothieware.h
new file mode 100644
index 0000000..61461c6
--- /dev/null
+++ b/ArcWelderInverseProcessor/smoothieware.h
@@ -0,0 +1,65 @@
+#pragma once
+#include "firmware.h"
+
+#define SMOOTHIEWARE_MAX_ROBOT_ACTUATORS 4
+struct SmoothiewareGcode {
+ SmoothiewareGcode() {
+ is_error = false;
+ txt_after_ok = "";
+ }
+ bool is_error;
+ std::string txt_after_ok;
+
+};
+struct SmoothiewareKernel
+{
+ bool is_halted() {return false;}
+};
+class smoothieware :
+ public firmware
+{
+public:
+ enum class smoothieware_firmware_versions { V2021_06_19 = 0 };
+ smoothieware(firmware_arguments args);
+ virtual ~smoothieware();
+ virtual std::string interpolate_arc(firmware_position& target, double i, double j, double r, bool is_clockwise) override;
+ virtual firmware_arguments get_default_arguments_for_current_version() const override;
+ virtual void apply_arguments() override;
+private:
+ smoothieware::smoothieware_firmware_versions smoothieware_version_;
+ enum MOTION_MODE_T {
+ NONE,
+ SEEK, // G0
+ LINEAR, // G1
+ CW_ARC, // G2
+ CCW_ARC // G3
+ };
+ std::string gcodes_;
+ const static int REPETIER_XYZE = 4;
+ enum AxisEnum { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E_AXIS = 3, A_AXIS = 3 }; // A axis is the same as the E axis.
+ /// <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;
+ const double PI = M_PI;
+ SmoothiewareGcode gcode_;
+ SmoothiewareKernel *THEKERNEL;
+ float feed_rate;
+};
+
diff --git a/ArcWelderInverseProcessor/sourcelist.cmake b/ArcWelderInverseProcessor/sourcelist.cmake
index d5f94b2..6ba1376 100644
--- a/ArcWelderInverseProcessor/sourcelist.cmake
+++ b/ArcWelderInverseProcessor/sourcelist.cmake
@@ -1,10 +1,20 @@
set(ArcWelderInverseProcessorSources ${ArcWelderInverseProcessorSources}
+ arc_interpolation.h
+ arc_interpolation.cpp
+ arc_interpolation_structs.h
ArcWelderInverseProcessor.h
ArcWelderInverseProcessor.cpp
+ firmware.cpp
+ firmware.h
firmware_types.h
- firmware_types.cpp
- marlin_2_arc.h
- marlin_2_arc.cpp
- repiter_arc.h
- repiter_arc.cpp
+ marlin_1.cpp
+ marlin_1.h
+ marlin_2.cpp
+ marlin_2.h
+ prusa.cpp
+ prusa.h
+ repetier.cpp
+ repetier.h
+ smoothieware.cpp
+ smoothieware.h
) \ No newline at end of file
diff --git a/ArcWelderTest/ArcWelderTest.cpp b/ArcWelderTest/ArcWelderTest.cpp
index 2ac30b1..23588fd 100644
--- a/ArcWelderTest/ArcWelderTest.cpp
+++ b/ArcWelderTest/ArcWelderTest.cpp
@@ -24,7 +24,7 @@
#include "ArcWelderTest.h"
#include "logger.h"
#include <iostream>
-#include "utilities.h"
+
int main(int argc, char* argv[])
{
diff --git a/GcodeProcessorLib/fpconv.cpp b/GcodeProcessorLib/fpconv.cpp
index e22731a..9c2b8ee 100644
--- a/GcodeProcessorLib/fpconv.cpp
+++ b/GcodeProcessorLib/fpconv.cpp
@@ -190,7 +190,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K)
for (divp = tens + 10; kappa > 0; divp++) {
unsigned long long div = *divp;
- unsigned digit = part1 / div;
+ unsigned digit = (unsigned int)(part1 / div);
if (digit || idx) {
digits[idx++] = digit + '0';
@@ -216,7 +216,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K)
delta *= 10;
kappa--;
- unsigned digit = part2 >> -one.exp;
+ unsigned digit = (unsigned int)(part2 >> -one.exp);
if (digit || idx) {
digits[idx++] = digit + '0';
}
diff --git a/GcodeProcessorLib/fpconv.h b/GcodeProcessorLib/fpconv.h
index 6896a3a..8afcb21 100644
--- a/GcodeProcessorLib/fpconv.h
+++ b/GcodeProcessorLib/fpconv.h
@@ -149,7 +149,7 @@ static Fp find_cachedpow10(int exp, int* k)
{
const double one_log_ten = 0.30102999566398114;
- int approx = -(exp + npowers) * one_log_ten;
+ int approx = (int)(-(exp + npowers) * one_log_ten);
int idx = (approx - firstpower) / steppowers;
while (1) {
diff --git a/GcodeProcessorLib/utilities.cpp b/GcodeProcessorLib/utilities.cpp
index e4b8526..c1ac9fc 100644
--- a/GcodeProcessorLib/utilities.cpp
+++ b/GcodeProcessorLib/utilities.cpp
@@ -19,21 +19,30 @@
// You can contact the author at the following email address:
// FormerLurker@pm.me
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
#include "utilities.h"
-#include <cmath>
-#include <sstream>
-#include <iostream>
-#include <iomanip>
-#include <algorithm>
-#include "fpconv.h"
-const std::string utilities::WHITESPACE_ = " \n\r\t\f\v";
-const char utilities::GUID_RANGE[] = "0123456789abcdef";
-const bool utilities::GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 };
+namespace utilities {
+ const std::string WHITESPACE_ = " \n\r\t\f\v";
+ const char GUID_RANGE[] = "0123456789abcdef";
+ const bool GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 };
+
+ extern const char PATH_SEPARATOR_ =
+#ifdef _WIN32
+ '\\';
+#else
+ '/';
+#endif
+
+}
bool utilities::is_zero(double x, double tolerance)
{
- return std::fabs(x) < tolerance;
+ return fabs(x) < tolerance;
+}
+bool utilities::is_zero(double x)
+{
+ return fabs(x) < ZERO_TOLERANCE;
}
int utilities::round_up_to_int(double x, double tolerance)
@@ -41,32 +50,63 @@ int utilities::round_up_to_int(double x, double tolerance)
return int(x + tolerance);
}
+int utilities::round_up_to_int(double x)
+{
+ return int(x + ZERO_TOLERANCE);
+}
+
bool utilities::is_equal(double x, double y, double tolerance)
{
- double abs_difference = std::fabs(x - y);
+ double abs_difference = fabs(x - y);
return abs_difference < tolerance;
}
+bool utilities::is_equal(double x, double y)
+{
+ double abs_difference = fabs(x - y);
+ return abs_difference < ZERO_TOLERANCE;
+}
+
bool utilities::greater_than(double x, double y, double tolerance)
{
return x > y && !is_equal(x, y, tolerance);
}
+bool utilities::greater_than(double x, double y)
+{
+ return x > y && !is_equal(x, y);
+}
+
bool utilities::greater_than_or_equal(double x, double y, double tolerance)
{
return x > y || is_equal(x, y, tolerance);
}
+bool utilities::greater_than_or_equal(double x, double y)
+{
+ return x > y || is_equal(x, y);
+}
+
bool utilities::less_than(double x, double y, double tolerance)
{
return x < y && !is_equal(x, y, tolerance);
}
+bool utilities::less_than(double x, double y)
+{
+ return x < y && !is_equal(x, y);
+}
+
bool utilities::less_than_or_equal(double x, double y, double tolerance)
{
return x < y || is_equal(x, y, tolerance);
}
+bool utilities::less_than_or_equal(double x, double y)
+{
+ return x < y || is_equal(x, y);
+}
+
double utilities::get_cartesian_distance(double x1, double y1, double x2, double y2)
{
@@ -74,7 +114,7 @@ double utilities::get_cartesian_distance(double x1, double y1, double x2, double
double xdif = x1 - x2;
double ydif = y1 - y2;
double dist_squared = xdif * xdif + ydif * ydif;
- return std::sqrt(dist_squared);
+ return sqrt(dist_squared);
}
double utilities::get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2)
@@ -84,7 +124,7 @@ double utilities::get_cartesian_distance(double x1, double y1, double z1, double
double ydif = y1 - y2;
double zdif = z1 - z2;
double dist_squared = xdif * xdif + ydif * ydif + zdif * zdif;
- return std::sqrt(dist_squared);
+ return sqrt(dist_squared);
}
double utilities::get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise)
@@ -92,10 +132,10 @@ double utilities::get_arc_distance(double x1, double y1, double z1, double x2, d
double center_x = x1 - i;
double center_y = y1 - j;
double radius = hypot(i, j);
- double z_dist = z2-z1;
+ double z_dist = z2 - z1;
double rt_x = x2 - center_x;
double rt_y = y2 - center_y;
- double angular_travel_total = std::atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y);
+ double angular_travel_total = atan2(i * rt_y - j * rt_x, i * rt_x + j * rt_y);
if (angular_travel_total < 0) { angular_travel_total += (double)(2.0 * PI_DOUBLE); }
// Adjust the angular travel if the direction is clockwise
if (is_clockwise) { angular_travel_total -= (float)(2 * PI_DOUBLE); }
@@ -104,10 +144,10 @@ double utilities::get_arc_distance(double x1, double y1, double z1, double x2, d
{
angular_travel_total += (float)(2 * PI_DOUBLE);
}
-
+
// 20200417 - FormerLurker - rename millimeters_of_travel to millimeters_of_travel_arc to better describe what we are
// calculating here
- return hypot(angular_travel_total * radius, std::fabs(z_dist));
+ return hypot(angular_travel_total * radius, fabs(z_dist));
}
@@ -142,6 +182,33 @@ std::string utilities::trim(const std::string& s)
return rtrim(ltrim(s));
}
+std::string utilities::join(const std::string* strings, size_t length, std::string sep)
+{
+ std::string output;
+ for (int i = 0; i < length; i++)
+ {
+ if (i > 0)
+ {
+ output += sep;
+ }
+ output += strings[i];
+ }
+ return output;
+}
+
+std::string utilities::join(const std::vector<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();
@@ -174,7 +241,7 @@ std::istream& utilities::safe_get_line(std::istream& is, std::string& t)
}
}
-std::string utilities::center(std::string input, int width)
+std::string utilities::center(std::string input, int width)
{
int input_width = (int)input.length();
int difference = width - input_width;
@@ -182,7 +249,7 @@ std::string utilities::center(std::string input, int width)
{
return input;
}
- int left_padding = difference /2;
+ int left_padding = difference / 2;
int right_padding = width - left_padding - input_width;
return std::string(left_padding, ' ') + input + std::string(right_padding, ' ');
}
@@ -231,7 +298,7 @@ std::string utilities::get_percent_change_string(int v1, int v2, int precision)
int utilities::get_num_digits(int x)
{
- x = abs(x);
+ x = (int)abs(x);
return (x < 10 ? 1 :
(x < 100 ? 2 :
(x < 1000 ? 3 :
@@ -247,14 +314,14 @@ int utilities::get_num_digits(int x)
int utilities::get_num_digits(double x, int precision)
{
return get_num_digits(
- (int) std::ceil(x * std::pow(10, (double)precision) - .4999999999999)
- / std::pow(10, (double)precision)
+ (int)ceil(x * pow(10, (double)precision) - .4999999999999)
+ / pow(10, (double)precision)
);
}
int utilities::get_num_digits(double x)
{
- return get_num_digits((int) x);
+ return get_num_digits((int)x);
}
// Nice utility function found here: https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path
@@ -285,7 +352,7 @@ std::vector<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)
@@ -311,12 +378,12 @@ std::string utilities::create_uuid() {
bool utilities::get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path)
{
temp_file_path = "";
- if (!utilities::get_file_path(file_path, temp_file_path))
+ if (!get_file_path(file_path, temp_file_path))
{
return false;
}
temp_file_path = temp_file_path;
- temp_file_path += utilities::create_uuid();
+ temp_file_path += create_uuid();
temp_file_path += ".tmp";
return true;
}
@@ -331,7 +398,92 @@ double utilities::hypot(double x, double y)
}
if (y == 0.0) return x;
y /= x;
- return x * std::sqrt(1.0 + y * y);
+ return x * sqrt(1.0 + y * y);
+}
+
+double utilities::atan2(double y, double x)
+{
+ return std::atan2(y, x);
+}
+
+double utilities::atan2f(double y, double x)
+{
+ return std::atan2(y, x);
+}
+
+double utilities::floor(double x)
+{
+ return std::floor(x);
+}
+
+double utilities::floorf(double x)
+{
+ return std::floor(x);
+}
+
+double utilities::ceil(double x)
+{
+ return std::ceil(x);
+}
+
+double utilities::cos(double x)
+{
+ return std::cos(x);
+}
+
+double utilities::sin(double x)
+{
+ return std::sin(x);
+}
+
+double utilities::cosf(double x)
+{
+ return std::cos(x);
+}
+
+double utilities::sinf(double x)
+{
+ return std::sin(x);
+}
+
+double utilities::abs(double x)
+{
+ return std::abs(x);
+}
+
+double utilities::fabs(double x)
+{
+ return std::fabs(x);
+}
+
+double utilities::fabsf(double x)
+{
+ return std::fabs(x);
+}
+
+double utilities::sqrt(double x)
+{
+ return std::sqrt(x);
+}
+
+double utilities::sqrtf(double x)
+{
+ return std::sqrt(x);
+}
+
+double utilities::pow(int e, double x)
+{
+ return std::pow(e, x);
+}
+
+double utilities::min(float x, float y)
+{
+ return std::min(x, y);
+}
+
+void* utilities::memcpy(void* dest, const void* src, size_t n)
+{
+ return std::memcpy(dest, src, n);
}
std::string utilities::dtos(double x, unsigned char precision)
@@ -339,14 +491,14 @@ std::string utilities::dtos(double x, unsigned char precision)
static char buffer[FPCONV_BUFFER_LENGTH];
char* p = buffer;
buffer[fpconv_dtos(x, buffer, precision)] = '\0';
- /* This is code that can be used to compare the output of the
+ /* This is code that can be used to compare the output of the
modified fpconv_dtos function to the ofstream output
Note: It currently only fails for some checks where the original double does not store
perfectly. In this case I actually think the dtos output is better than ostringstream!
std::ostringstream stream;
stream << std::fixed;
stream << std::setprecision(precision) << x;
-
+
if (std::string(buffer) != stream.str())
{
std::cout << std::fixed << "Failed to convert: " << std::setprecision(24) << x << " Precision:" << std::setprecision(0) << static_cast <int> (precision) << " String:" << std::string(buffer) << " Stream:" << stream.str() << std::endl;
@@ -355,7 +507,7 @@ std::string utilities::dtos(double x, unsigned char precision)
return buffer;
}
/*
-bool utilities::case_insensitive_compare_char(char& c1, char& c2)
+bool case_insensitive_compare_char(char& c1, char& c2)
{
if (c1 == c2)
return true;
@@ -366,10 +518,34 @@ bool utilities::case_insensitive_compare_char(char& c1, char& c2)
/*
* Case Insensitive String Comparision
-
-bool utilities::case_insensitive_compare(std::string& str1, std::string& str2)
+
+bool case_insensitive_compare(std::string& str1, std::string& str2)
{
- return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), &utilities::case_insensitive_compare_char));
+ return ((str1.size() == str2.size()) && std::equal(str1.begin(), str1.end(), str2.begin(), &case_insensitive_compare_char));
+}
+
+*/
+
+std::string utilities::replace(std::string subject, const std::string& search, const std::string& replace) {
+ size_t pos = 0;
+ while ((pos = subject.find(search, pos)) != std::string::npos) {
+ subject.replace(pos, search.length(), replace);
+ pos += replace.length();
+ }
+ return subject;
+}
+
+double utilities::rand_range(double min, double max) {
+ double f = (double)std::rand() / RAND_MAX;
+ return min + f * (max - min);
+}
+
+unsigned char utilities::rand_range(unsigned char min, unsigned char max) {
+ double f = (double)std::rand() / RAND_MAX;
+ return static_cast<unsigned char>(static_cast<double>(min) + f * (static_cast<double>(max) - static_cast<double>(min)));
}
-*/ \ No newline at end of file
+int utilities::rand_range(int min, int max) {
+ double f = (double)std::rand() / RAND_MAX;
+ return static_cast<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 cf3a97b..7e2f80b 100644
--- a/GcodeProcessorLib/utilities.h
+++ b/GcodeProcessorLib/utilities.h
@@ -20,83 +20,137 @@
// FormerLurker@pm.me
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma once
+#ifndef UTILITIES_H
+#define UTILITIES_H
#include <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
-class utilities{
-public:
- static bool is_zero(double x, double tolerance = ZERO_TOLERANCE);
- static int round_up_to_int(double x, double tolerance = ZERO_TOLERANCE);
- static bool is_equal(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool greater_than(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool greater_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool less_than(double x, double y, double tolerance = ZERO_TOLERANCE);
- static bool less_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE);
-
- static double get_cartesian_distance(double x1, double y1, double x2, double y2);
- static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2);
- static double get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise);
- /* Todo: Implement for gcode comment processor
- static bool case_insensitive_compare_char(char& c1, char& c2);
- static bool case_insensitive_compare(std::string& str1, std::string& str2);
- */
- static std::string to_string(double value);
- static std::string to_string(int value);
- static std::string ltrim(const std::string& s);
- static std::string rtrim(const std::string& s);
- static std::string trim(const std::string& s);
- static std::istream& safe_get_line(std::istream& is, std::string& t);
- static std::string center(std::string input, int width);
- static double get_percent_change(int v1, int v2);
- static double get_percent_change(double v1, double v2);
- static std::string get_percent_change_string(int v1, int v2, int precision);
- static int get_num_digits(int x);
- static int get_num_digits(double x);
- static int get_num_digits(double x, int precision);
-
- static std::vector<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)));
- }
+namespace utilities{
+ extern const std::string WHITESPACE_;
+ extern const char GUID_RANGE[];
+ extern const bool GUID_DASHES[];
+
+ extern const char PATH_SEPARATOR_;
+ bool is_zero(double x, double tolerance);
+ bool is_zero(double x);
+
+ int round_up_to_int(double x, double tolerance);
+ int round_up_to_int(double x);
+
+ bool is_equal(double x, double y, double tolerance);
+
+ bool is_equal(double x, double y);
+
+ bool greater_than(double x, double y, double tolerance);
+
+ bool greater_than(double x, double y);
+
+ bool greater_than_or_equal(double x, double y, double tolerance);
+ bool greater_than_or_equal(double x, double y);
+
+ bool less_than(double x, double y, double tolerance);
+ bool less_than(double x, double y);
+
+ bool less_than_or_equal(double x, double y, double tolerance);
+
+ bool less_than_or_equal(double x, double y);
+
+ double get_cartesian_distance(double x1, double y1, double x2, double y2);
+
+ double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2);
+
+ double get_arc_distance(double x1, double y1, double z1, double x2, double y2, double z2, double i, double j, double r, bool is_clockwise);
+ std::string to_string(double value);
+
+ std::string to_string(int value);
+ std::string ltrim(const std::string& s);
+
+ std::string rtrim(const std::string& s);
+
+ std::string trim(const std::string& s);
+ std::string join(const std::string* strings, size_t length, std::string sep);
+
+ std::string join(const std::vector<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);
+
+ double atan2(double y, double x);
+
+ double atan2f(double y, double x);
+
+ double floor(double x);
+
+ double floorf(double x);
+
+ double ceil(double x);
+
+ double cos(double x);
+
+ double sin(double x);
+
+ double cosf(double x);
+
+ double sinf(double x);
+
+ double abs(double x);
+
+ double fabs(double x);
+
+ double fabsf(double x);
+
+ double sqrt(double x);
+
+ double sqrtf(double x);
+
+ double pow(int e, double x);
+
+ double min(float x, float y);
+
+ void* memcpy(void* dest, const void* src, size_t n);
+
+ std::string dtos(double x, unsigned char precision);
-protected:
- static const std::string WHITESPACE_;
- static const char PATH_SEPARATOR_ =
-#ifdef _WIN32
- '\\';
-#else
- '/';
-#endif
- static const char GUID_RANGE[];
- static const bool GUID_DASHES[];
-private:
- utilities();
-
-};
+ std::string replace(std::string subject, const std::string& search, const std::string& replace);
+
+ double rand_range(double min, double max);
+ unsigned char rand_range(unsigned char min, unsigned char max);
+ int rand_range(int min, int max);
+
+
+
+}
+#endif \ No newline at end of file
diff --git a/PyArcWelder/py_arc_welder.cpp b/PyArcWelder/py_arc_welder.cpp
index 482c09b..fcb4b71 100644
--- a/PyArcWelder/py_arc_welder.cpp
+++ b/PyArcWelder/py_arc_welder.cpp
@@ -241,7 +241,7 @@ bool py_gcode_arc_args::parse_args(PyObject* py_args, py_logger* p_py_logger, py
p_py_logger->log(GCODE_CONVERSION, WARNING, message);
}
else {
- args.default_xyz_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision);
+ args.default_xyz_precision = (unsigned char)gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_xyz_precision);
if (args.default_xyz_precision < 3)
{
std::string message = "ParseArgs - The default XYZ precision received was less than 3, which could cause problems printing arcs. Setting to 3.";
@@ -265,7 +265,7 @@ bool py_gcode_arc_args::parse_args(PyObject* py_args, py_logger* p_py_logger, py
p_py_logger->log(WARNING, GCODE_CONVERSION, message);
}
else {
- args.default_e_precision = gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision);
+ args.default_e_precision = (unsigned char)gcode_arc_converter::PyFloatOrInt_AsDouble(py_default_e_precision);
if (args.default_e_precision < 3)
{
std::string message = "ParseArgs - The default E precision received was less than 3, which could cause extrusion problems. Setting to 3.";