├── LICENSE ├── MANIFEST.in ├── babel.cfg ├── octoprint_arc_welder ├── __init__.py ├── _version.py ├── data │ └── lib │ │ └── c │ │ ├── arc_welder │ │ ├── arc_welder.cpp │ │ ├── arc_welder.h │ │ ├── segmented_arc.cpp │ │ ├── segmented_arc.h │ │ ├── segmented_shape.cpp │ │ ├── segmented_shape.h │ │ └── unwritten_command.h │ │ ├── gcode_processor_lib │ │ ├── array_list.cpp │ │ ├── array_list.h │ │ ├── circular_buffer.cpp │ │ ├── circular_buffer.h │ │ ├── extruder.cpp │ │ ├── extruder.h │ │ ├── gcode_comment_processor.cpp │ │ ├── gcode_comment_processor.h │ │ ├── gcode_parser.cpp │ │ ├── gcode_parser.h │ │ ├── gcode_position.cpp │ │ ├── gcode_position.h │ │ ├── logger.cpp │ │ ├── logger.h │ │ ├── parsed_command.cpp │ │ ├── parsed_command.h │ │ ├── parsed_command_parameter.cpp │ │ ├── parsed_command_parameter.h │ │ ├── position.cpp │ │ ├── position.h │ │ ├── utilities.cpp │ │ └── utilities.h │ │ └── py_arc_welder │ │ ├── py_arc_welder.cpp │ │ ├── py_arc_welder.h │ │ ├── py_arc_welder_extension.cpp │ │ ├── py_arc_welder_extension.h │ │ ├── py_logger.cpp │ │ ├── py_logger.h │ │ ├── python_helpers.cpp │ │ └── python_helpers.h ├── log.py ├── preprocessor.py ├── static │ ├── css │ │ └── arc_welder.css │ ├── docs │ │ └── help │ │ │ ├── settings.delete_source.md │ │ │ ├── settings.enabled.md │ │ │ ├── settings.file_processing.md │ │ │ ├── settings.g90_g91_influences_extruder.md │ │ │ ├── settings.max_radius_mm.md │ │ │ ├── settings.overwrite_source_file.md │ │ │ ├── settings.resolution_mm.md │ │ │ ├── settings.show_completed_notification.md │ │ │ ├── settings.show_progress_bar.md │ │ │ ├── settings.show_started_notification.md │ │ │ ├── settings.target_postfix.md │ │ │ ├── settings.target_prefix.md │ │ │ └── settings.use_octoprint_settings.md │ └── js │ │ ├── arc_welder.js │ │ ├── arc_welder.settings.js │ │ ├── markdown_help.js │ │ ├── pnotify_extensions.js │ │ └── showdown.min.js ├── templates │ ├── arc_welder_settings.jinja2 │ ├── arc_welder_settings_about.jinja2 │ └── arc_welder_tab.jinja2 └── utilities.py ├── octoprint_arc_welder_setuptools └── __init__.py ├── requirements.txt ├── setup.cfg ├── setup.py └── versioneer.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | recursive-include octoprint_arc_welder/templates * 3 | recursive-include octoprint_arc_welder/static * 4 | -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: */**.py] 2 | [jinja2: */**.jinja2] 3 | extensions=jinja2.ext.autoescape, jinja2.ext.with_ 4 | 5 | [javascript: */**.js] 6 | extract_messages = gettext, ngettext 7 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/arc_welder/segmented_arc.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Library 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality. 8 | // 9 | // Copyright(C) 2020 - Brad Hochgesang 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // This program is free software : you can redistribute it and/or modify 12 | // it under the terms of the GNU Affero General Public License as published 13 | // by the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This program is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 19 | // GNU Affero General Public License for more details. 20 | // 21 | // 22 | // You can contact the author at the following email address: 23 | // FormerLurker@pm.me 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | #include "segmented_arc.h" 27 | #include "utilities.h" 28 | #include "segmented_shape.h" 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | segmented_arc::segmented_arc() : segmented_shape(DEFAULT_MIN_SEGMENTS, DEFAULT_MAX_SEGMENTS, DEFAULT_RESOLUTION_MM) 35 | { 36 | max_radius_mm_ = DEFAULT_MAX_RADIUS_MM; 37 | } 38 | 39 | segmented_arc::segmented_arc(int min_segments, int max_segments, double resolution_mm, double max_radius_mm) : segmented_shape(min_segments, max_segments, resolution_mm) 40 | { 41 | if (max_radius_mm > DEFAULT_MAX_RADIUS_MM) max_radius_mm_ = DEFAULT_MAX_RADIUS_MM; 42 | else max_radius_mm_ = max_radius_mm; 43 | } 44 | 45 | segmented_arc::~segmented_arc() 46 | { 47 | } 48 | 49 | point segmented_arc::pop_front(double e_relative) 50 | { 51 | e_relative_ -= e_relative; 52 | if (points_.count() == get_min_segments()) 53 | { 54 | set_is_shape(false); 55 | } 56 | return points_.pop_front(); 57 | } 58 | point segmented_arc::pop_back(double e_relative) 59 | { 60 | e_relative_ -= e_relative; 61 | return points_.pop_back(); 62 | if (points_.count() == get_min_segments()) 63 | { 64 | set_is_shape(false); 65 | } 66 | } 67 | double segmented_arc::get_max_radius() const 68 | { 69 | return max_radius_mm_; 70 | } 71 | bool segmented_arc::is_shape() const 72 | { 73 | /* 74 | if (is_shape_) 75 | { 76 | arc a; 77 | return arc::try_create_arc(arc_circle_, points_, original_shape_length_, resolution_mm_, a);; 78 | } */ 79 | return is_shape_; 80 | } 81 | 82 | bool segmented_arc::try_add_point(point p, double e_relative) 83 | { 84 | 85 | bool point_added = false; 86 | // if we don't have enough segnemts to check the shape, just add 87 | if (points_.count() > get_max_segments() - 1) 88 | { 89 | // Too many points, we can't add more 90 | return false; 91 | } 92 | double distance = 0; 93 | if (points_.count() > 0) 94 | { 95 | point p1 = points_[points_.count() - 1]; 96 | distance = utilities::get_cartesian_distance(p1.x, p1.y, p.x, p.y); 97 | if (!utilities::is_equal(p1.z, p.z)) 98 | { 99 | // Arcs require that z is equal for all points 100 | //std::cout << " failed - z change.\n"; 101 | 102 | return false; 103 | } 104 | 105 | if (utilities::is_zero(distance)) 106 | { 107 | // there must be some distance between the points 108 | // to make an arc. 109 | //std::cout << " failed - no distance change.\n"; 110 | return false; 111 | } 112 | 113 | } 114 | 115 | if (points_.count() < get_min_segments() - 1) 116 | { 117 | point_added = true; 118 | points_.push_back(p); 119 | original_shape_length_ += distance; 120 | if (points_.count() == get_min_segments()) 121 | { 122 | arc a; 123 | if (!arc::try_create_arc(arc_circle_, points_, original_shape_length_, resolution_mm_, a)) 124 | { 125 | point_added = false; 126 | points_.pop_back(); 127 | original_shape_length_ -= distance; 128 | } 129 | } 130 | 131 | 132 | } 133 | else 134 | { 135 | // if we're here, we need to see if the new point can be included in the shape 136 | point_added = try_add_point_internal_(p, distance); 137 | } 138 | if (point_added) 139 | { 140 | 141 | if (points_.count() > 1) 142 | { 143 | // Only add the relative distance to the second point on up. 144 | e_relative_ += e_relative; 145 | } 146 | //std::cout << " success - " << points_.count() << " points.\n"; 147 | } 148 | else if (points_.count() < get_min_segments() && points_.count() > 1) 149 | { 150 | // If we haven't added a point, and we have exactly min_segments_, 151 | // pull off the initial arc point and try again 152 | point old_initial_point = points_.pop_front(); 153 | // We have to remove the distance and e relative value 154 | // accumulated between the old arc start point and the new 155 | point new_initial_point = points_[0]; 156 | original_shape_length_ -= utilities::get_cartesian_distance(old_initial_point.x, old_initial_point.y, new_initial_point.x, new_initial_point.y); 157 | e_relative_ -= new_initial_point.e_relative; 158 | //std::cout << " failed - removing start point and retrying current point.\n"; 159 | return try_add_point(p, e_relative); 160 | } 161 | 162 | return point_added; 163 | } 164 | 165 | bool segmented_arc::try_add_point_internal_(point p, double pd) 166 | { 167 | // If we don't have enough points (at least min_segments) return false 168 | if (points_.count() < get_min_segments() - 1) 169 | return false; 170 | 171 | // Create a test circle 172 | circle test_circle; 173 | bool circle_created; 174 | // Find a point in the middle of our list for p2 175 | int mid_point_index = ((points_.count() - 2) / 2)+1; 176 | circle_created = circle::try_create_circle(points_[0], points_[mid_point_index], p, max_radius_mm_, test_circle); 177 | 178 | if (circle_created) 179 | { 180 | 181 | // If we got a circle, make sure all of the points fit within the tolerance. 182 | bool circle_fits_points; 183 | 184 | // the circle is new.. we have to test it now, which is expensive :( 185 | points_.push_back(p); 186 | double previous_shape_length = original_shape_length_; 187 | original_shape_length_ += pd; 188 | 189 | circle_fits_points = does_circle_fit_points_(test_circle); 190 | if (circle_fits_points) 191 | { 192 | arc_circle_ = test_circle; 193 | } 194 | else 195 | { 196 | points_.pop_back(); 197 | original_shape_length_ = previous_shape_length; 198 | } 199 | 200 | // Only set is_shape if it goes from false to true 201 | if (!is_shape()) 202 | set_is_shape(circle_fits_points); 203 | 204 | return circle_fits_points; 205 | } 206 | 207 | //std::cout << " failed - could not create a circle from the points.\n"; 208 | return false; 209 | 210 | } 211 | 212 | bool segmented_arc::does_circle_fit_points_(circle& c) const 213 | { 214 | // We know point 1 must fit (we used it to create the circle). Check the other points 215 | // Note: We have not added the current point, but that's fine since it is guaranteed to fit too. 216 | // If this works, it will be added. 217 | 218 | double distance_from_center; 219 | double difference_from_radius; 220 | 221 | // Check the endpoints to make sure they fit the current circle 222 | for (int index = 1; index < points_.count(); index++) 223 | { 224 | // Make sure the length from the center of our circle to the test point is 225 | // at or below our max distance. 226 | distance_from_center = utilities::get_cartesian_distance(points_[index].x, points_[index].y, c.center.x, c.center.y); 227 | double difference_from_radius = std::abs(distance_from_center - c.radius); 228 | if (utilities::greater_than(difference_from_radius, resolution_mm_)) 229 | { 230 | //std::cout << " failed - end points do not lie on circle.\n"; 231 | return false; 232 | } 233 | } 234 | 235 | // Check the point perpendicular from the segment to the circle's center, if any such point exists 236 | for (int index = 0; index < points_.count() - 1; index++) 237 | { 238 | point point_to_test; 239 | if (segment::get_closest_perpendicular_point(points_[index], points_[index + 1], c.center, point_to_test)) 240 | { 241 | distance_from_center = utilities::get_cartesian_distance(point_to_test.x, point_to_test.y, c.center.x, c.center.y); 242 | difference_from_radius = std::abs(distance_from_center - c.radius); 243 | // Test allowing more play for the midpoints. 244 | if (utilities::greater_than(difference_from_radius, resolution_mm_)) 245 | { 246 | return false; 247 | } 248 | } 249 | 250 | } 251 | 252 | // get the current arc and compare the total length to the original length 253 | arc a; 254 | return arc::try_create_arc(c, points_, original_shape_length_, resolution_mm_, a); 255 | 256 | } 257 | 258 | bool segmented_arc::try_get_arc(arc & target_arc) 259 | { 260 | //int mid_point_index = ((points_.count() - 2) / 2) + 1; 261 | //return arc::try_create_arc(arc_circle_, points_[0], points_[mid_point_index], points_[points_.count() - 1], original_shape_length_, resolution_mm_, target_arc); 262 | return arc::try_create_arc(arc_circle_ ,points_, original_shape_length_, resolution_mm_, target_arc); 263 | } 264 | 265 | bool segmented_arc::try_get_arc_(const circle& c, arc &target_arc) 266 | { 267 | //int mid_point_index = ((points_.count() - 1) / 2) + 1; 268 | //return arc::try_create_arc(c, points_[0], points_[mid_point_index], endpoint, original_shape_length_ + additional_distance, resolution_mm_, target_arc); 269 | return arc::try_create_arc(c, points_, original_shape_length_, resolution_mm_, target_arc); 270 | } 271 | 272 | std::string segmented_arc::get_shape_gcode_absolute(double e, double f) 273 | { 274 | bool has_e = e_relative_ != 0; 275 | return get_shape_gcode_(has_e, e, f); 276 | } 277 | std::string segmented_arc::get_shape_gcode_relative(double f) 278 | { 279 | bool has_e = e_relative_ != 0; 280 | return get_shape_gcode_(has_e, e_relative_, f); 281 | } 282 | 283 | std::string segmented_arc::get_shape_gcode_(bool has_e, double e, double f) const 284 | { 285 | 286 | char buf[20]; 287 | std::string gcode; 288 | arc c; 289 | arc::try_create_arc(arc_circle_, points_, original_shape_length_, resolution_mm_, c); 290 | 291 | double i = c.center.x - c.start_point.x; 292 | double j = c.center.y - c.start_point.y; 293 | // Here is where the performance part kicks in (these are expensive calls) that makes things a bit ugly. 294 | // there are a few cases we need to take into consideration before choosing our sprintf string 295 | // create the XYZ portion 296 | 297 | if (utilities::less_than(c.angle_radians, 0)) 298 | { 299 | gcode = "G2"; 300 | } 301 | else 302 | { 303 | gcode = "G3"; 304 | 305 | } 306 | // Add X, Y, I and J 307 | gcode += " X"; 308 | gcode += utilities::to_string(c.end_point.x, 3, buf); 309 | 310 | gcode += " Y"; 311 | gcode += utilities::to_string(c.end_point.y, 3, buf); 312 | 313 | gcode += " I"; 314 | gcode += utilities::to_string(i, 3, buf); 315 | 316 | gcode += " J"; 317 | gcode += utilities::to_string(j, 3, buf); 318 | 319 | // Add E if it appears 320 | if (has_e) 321 | { 322 | gcode += " E"; 323 | gcode += utilities::to_string(e, 5, buf); 324 | } 325 | 326 | // Add F if it appears 327 | if (utilities::greater_than_or_equal(f, 1)) 328 | { 329 | gcode += " F"; 330 | gcode += utilities::to_string(f, 0, buf); 331 | } 332 | 333 | return gcode; 334 | 335 | } 336 | 337 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/arc_welder/segmented_arc.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Library 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality. 8 | // 9 | // Copyright(C) 2020 - Brad Hochgesang 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // This program is free software : you can redistribute it and/or modify 12 | // it under the terms of the GNU Affero General Public License as published 13 | // by the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This program is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 19 | // GNU Affero General Public License for more details. 20 | // 21 | // 22 | // You can contact the author at the following email address: 23 | // FormerLurker@pm.me 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | #pragma once 27 | #include "segmented_shape.h" 28 | #include 29 | #include 30 | 31 | #define GCODE_CHAR_BUFFER_SIZE 100 32 | #define DEFAULT_MAX_RADIUS_MM 1000000.0 // 1km 33 | class segmented_arc : 34 | public segmented_shape 35 | { 36 | public: 37 | segmented_arc(); 38 | segmented_arc(int min_segments = DEFAULT_MIN_SEGMENTS, int max_segments = DEFAULT_MAX_SEGMENTS, double resolution_mm = DEFAULT_RESOLUTION_MM, double max_radius_mm = DEFAULT_MAX_RADIUS_MM); 39 | virtual ~segmented_arc(); 40 | virtual bool try_add_point(point p, double e_relative); 41 | std::string get_shape_gcode_absolute(double e, double f); 42 | std::string get_shape_gcode_relative(double f); 43 | 44 | virtual bool is_shape() const; 45 | point pop_front(double e_relative); 46 | point pop_back(double e_relative); 47 | bool try_get_arc(arc & target_arc); 48 | double get_max_radius() const; 49 | // static gcode buffer 50 | 51 | private: 52 | bool try_add_point_internal_(point p, double pd); 53 | bool does_circle_fit_points_(circle& c) const; 54 | bool try_get_arc_(const circle& c, arc& target_arc); 55 | std::string get_shape_gcode_(bool has_e, double e, double f) const; 56 | circle arc_circle_; 57 | double max_radius_mm_; 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/arc_welder/segmented_shape.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Library 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality. 8 | // 9 | // Copyright(C) 2020 - Brad Hochgesang 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // This program is free software : you can redistribute it and/or modify 12 | // it under the terms of the GNU Affero General Public License as published 13 | // by the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This program is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 19 | // GNU Affero General Public License for more details. 20 | // 21 | // 22 | // You can contact the author at the following email address: 23 | // FormerLurker@pm.me 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | #pragma once 26 | #include 27 | #include 28 | #define PI_DOUBLE 3.14159265358979323846 29 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.0000000001 // fail 30 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.001 // pass 31 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.0001 // pass 32 | #define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.00001 // PASS 33 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.000001 // pass 34 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.0000001 // fail 35 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.0000005 // fail 36 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.00000075 // fail 37 | //#define CIRCLE_GENERATION_A_ZERO_TOLERANCE 0.000000875 // fail 38 | 39 | 40 | #include 41 | #include "utilities.h" 42 | #include "array_list.h" 43 | // The minimum theta value allowed between any two arc in order for an arc to be 44 | // created. This prevents sign calculation issues for very small values of theta 45 | 46 | //#define MIN_ALLOWED_ARC_THETA 0.0000046875f // Lowest discovered value for full theta 47 | 48 | struct point 49 | { 50 | public: 51 | point() { 52 | x = 0; 53 | y = 0; 54 | z = 0; 55 | e_relative = 0; 56 | } 57 | point(double p_x, double p_y, double p_z, double p_e_relative) { 58 | x = p_x; 59 | y = p_y; 60 | z = p_z; 61 | e_relative = p_e_relative; 62 | } 63 | double x; 64 | double y; 65 | double z; 66 | double e_relative; 67 | static point get_midpoint(point p1, point p2); 68 | }; 69 | 70 | struct segment 71 | { 72 | segment() 73 | { 74 | 75 | } 76 | segment(point p_1, point p_2) 77 | { 78 | p1.x = p_1.x; 79 | p1.y = p_1.y; 80 | p1.z = p_1.z; 81 | 82 | p2.x = p_2.x; 83 | p2.y = p_2.y; 84 | p2.z = p_2.z; 85 | } 86 | point p1; 87 | point p2; 88 | 89 | bool get_closest_perpendicular_point(point c, point& d); 90 | static bool get_closest_perpendicular_point(point p1, point p2, point c, point& d); 91 | }; 92 | 93 | struct vector : point 94 | { 95 | vector() { 96 | x = 0; 97 | y = 0; 98 | z = 0; 99 | } 100 | vector(double p_x, double p_y, double p_z) { 101 | x = p_x; 102 | y = p_y; 103 | z = p_z; 104 | } 105 | 106 | double get_magnitude(); 107 | static double cross_product_magnitude(vector v1, vector v2); 108 | 109 | }; 110 | 111 | struct circle { 112 | circle() { 113 | center.x = 0; 114 | center.y = 0; 115 | center.z = 0; 116 | radius = 0; 117 | }; 118 | circle(point p, double r) 119 | { 120 | center.x = p.x; 121 | center.y = p.y; 122 | center.z = p.z; 123 | radius = r; 124 | } 125 | point center; 126 | double radius; 127 | 128 | bool is_point_on_circle(point p, double resolution_mm); 129 | static bool try_create_circle(point p1, point p2, point p3, double max_radius, circle& new_circle); 130 | 131 | double get_radians(const point& p1, const point& p2) const; 132 | 133 | double get_polar_radians(const point& p1) const; 134 | 135 | point get_closest_point(const point& p) const; 136 | }; 137 | 138 | struct arc : circle 139 | { 140 | arc() { 141 | center.x = 0; 142 | center.y = 0; 143 | center.z = 0; 144 | radius = 0; 145 | length = 0; 146 | angle_radians = 0; 147 | start_point.x = 0; 148 | start_point.y = 0; 149 | start_point.z = 0; 150 | end_point.x = 0; 151 | end_point.y = 0; 152 | end_point.z = 0; 153 | is_arc = false; 154 | polar_start_theta = 0; 155 | polar_end_theta = 0; 156 | 157 | } 158 | 159 | bool is_arc; 160 | double length; 161 | double angle_radians; 162 | double polar_start_theta; 163 | double polar_end_theta; 164 | point start_point; 165 | point end_point; 166 | static bool try_create_arc(const circle& c, const point& start_point, const point& mid_point, const point& end_point, double approximate_length, double resolution, arc& target_arc); 167 | static bool try_create_arc(const circle& c, const array_list& points, double approximate_length, double resolution, arc& target_arc); 168 | }; 169 | double distance_from_segment(segment s, point p); 170 | 171 | #define DEFAULT_MIN_SEGMENTS 3 172 | #define DEFAULT_MAX_SEGMENTS 50 173 | #define DEFAULT_RESOLUTION_MM 0.05 174 | class segmented_shape 175 | { 176 | public: 177 | 178 | segmented_shape(int min_segments = DEFAULT_MIN_SEGMENTS, int max_segments = DEFAULT_MAX_SEGMENTS, double resolution_mm = DEFAULT_RESOLUTION_MM); 179 | segmented_shape& operator=(const segmented_shape& pos); 180 | virtual ~segmented_shape(); 181 | int get_num_segments(); 182 | int get_min_segments(); 183 | int get_max_segments(); 184 | double get_resolution_mm(); 185 | double get_shape_length(); 186 | double get_shape_e_relative(); 187 | void set_resolution_mm(double resolution_mm); 188 | virtual bool is_shape() const; 189 | // public virtual functions 190 | virtual void clear(); 191 | virtual point pop_front(); 192 | virtual point pop_back(); 193 | virtual bool try_add_point(point p, double e_relative); 194 | virtual std::string get_shape_gcode_absolute(double e_abs_start); 195 | virtual std::string get_shape_gcode_relative(); 196 | bool is_extruding(); 197 | protected: 198 | array_list points_; 199 | void set_is_shape(bool value); 200 | double original_shape_length_; 201 | double e_relative_; 202 | bool is_extruding_; 203 | double resolution_mm_; 204 | bool is_shape_; 205 | private: 206 | int min_segments_; 207 | int max_segments_; 208 | 209 | }; 210 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/arc_welder/unwritten_command.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Library 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality. 8 | // 9 | // Copyright(C) 2020 - Brad Hochgesang 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // This program is free software : you can redistribute it and/or modify 12 | // it under the terms of the GNU Affero General Public License as published 13 | // by the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // This program is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 19 | // GNU Affero General Public License for more details. 20 | // 21 | // 22 | // You can contact the author at the following email address: 23 | // FormerLurker@pm.me 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | #pragma once 26 | #include "parsed_command.h" 27 | #include "position.h" 28 | struct unwritten_command 29 | { 30 | unwritten_command() { 31 | is_extruder_relative = false; 32 | e_relative = 0; 33 | offset_e = 0; 34 | extrusion_length = 0; 35 | } 36 | unwritten_command(parsed_command &cmd, bool is_relative, double command_length) { 37 | is_extruder_relative = is_relative; 38 | command = cmd; 39 | extrusion_length = command_length; 40 | } 41 | unwritten_command(position* p, double command_length) { 42 | 43 | e_relative = p->get_current_extruder().e_relative; 44 | offset_e = p->get_current_extruder().get_offset_e(); 45 | is_extruder_relative = p->is_extruder_relative; 46 | command = p->command; 47 | extrusion_length = command_length; 48 | } 49 | bool is_extruder_relative; 50 | double e_relative; 51 | double offset_e; 52 | double extrusion_length; 53 | parsed_command command; 54 | 55 | std::string to_string(bool rewrite, std::string additional_comment) 56 | { 57 | command.comment.append(additional_comment); 58 | 59 | if (rewrite) 60 | { 61 | return command.rewrite_gcode_string(); 62 | } 63 | 64 | return command.to_string(); 65 | } 66 | }; 67 | 68 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/array_list.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #include "array_list.h" -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/array_list.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | 24 | #pragma once 25 | #include 26 | template 27 | class array_list 28 | { 29 | public: 30 | array_list() 31 | { 32 | auto_grow_ = true; 33 | max_size_ = 50; 34 | front_index_ = 0; 35 | count_ = 0; 36 | items_ = new T[max_size_]; 37 | } 38 | 39 | array_list(int max_size) 40 | { 41 | auto_grow_ = false; 42 | max_size_ = max_size; 43 | front_index_ = 0; 44 | count_ = 0; 45 | items_ = new T[max_size]; 46 | } 47 | 48 | virtual ~array_list() { 49 | delete[] items_; 50 | } 51 | 52 | void resize(int max_size) 53 | { 54 | T* new_items = new T[max_size]; 55 | for (int index = 0; index < count_; index++) 56 | { 57 | new_items[index] = items_[(front_index_ + index + max_size_) % max_size_]; 58 | } 59 | front_index_ = 0; 60 | delete[] items_; 61 | items_ = new_items; 62 | max_size_ = max_size; 63 | } 64 | 65 | void push_front(T object) 66 | { 67 | if (count_ == max_size_) 68 | { 69 | if (auto_grow_) 70 | { 71 | resize(max_size_ * 2); 72 | } 73 | else { 74 | throw std::exception(); 75 | } 76 | } 77 | front_index_ = (front_index_ - 1 + max_size_) % max_size_; 78 | count_++; 79 | items_[front_index_] = object; 80 | } 81 | 82 | void push_back(T object) 83 | { 84 | if (count_ == max_size_) 85 | { 86 | if (auto_grow_) 87 | { 88 | resize(max_size_ * 2); 89 | } 90 | else { 91 | throw std::exception(); 92 | } 93 | } 94 | items_[(front_index_ + count_ + max_size_) % max_size_] = object; 95 | count_++; 96 | } 97 | 98 | T pop_front() 99 | { 100 | if (count_ == 0) 101 | { 102 | throw std::exception(); 103 | } 104 | 105 | int prev_start = front_index_; 106 | front_index_ = (front_index_ + 1 + max_size_) % max_size_; 107 | count_--; 108 | return items_[prev_start]; 109 | } 110 | 111 | T pop_back() 112 | { 113 | if (count_ == 0) 114 | { 115 | throw std::exception(); 116 | } 117 | 118 | return items_[--count_]; 119 | } 120 | 121 | T& operator[] (const int index) const 122 | { 123 | return items_[(front_index_ + index + max_size_) % max_size_]; 124 | } 125 | 126 | T get(int index) const 127 | { 128 | return items_[(front_index_ + index + max_size_) % max_size_]; 129 | } 130 | 131 | int count() const 132 | { 133 | return count_; 134 | } 135 | 136 | int get_max_size() const 137 | { 138 | return max_size_; 139 | } 140 | 141 | void clear() 142 | { 143 | count_ = 0; 144 | front_index_ = 0; 145 | } 146 | 147 | void copy(const array_list& source) 148 | { 149 | if (max_size_ < source.max_size_) 150 | { 151 | resize(source.max_size_); 152 | } 153 | clear(); 154 | for (int index = 0; index < source.count_; index++) 155 | { 156 | items_[index] = source[index]; 157 | } 158 | front_index_ = source.front_index_; 159 | count_ = source.count_; 160 | } 161 | 162 | protected: 163 | T* items_; 164 | int max_size_; 165 | int front_index_; 166 | int count_; 167 | bool auto_grow_; 168 | }; -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/circular_buffer.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #include "circular_buffer.h" -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/circular_buffer.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #pragma once 23 | #include 24 | template 25 | class circular_buffer 26 | { 27 | public: 28 | circular_buffer() 29 | { 30 | max_size_ = 50; 31 | front_index_ = 0; 32 | count_ = 0; 33 | items_ = new T[max_size_]; 34 | } 35 | circular_buffer(int max_size) 36 | { 37 | max_size_ = max_size; 38 | front_index_ = 0; 39 | count_ = 0; 40 | items_ = new T[max_size]; 41 | } 42 | virtual ~circular_buffer() { 43 | delete[] items_; 44 | } 45 | void resize(int max_size) 46 | { 47 | T* new_items = new T[max_size]; 48 | int count = count_; 49 | for (int index = 0; index < count_; index++) 50 | { 51 | new_items[index] = items_[(front_index_ + index + max_size_) % max_size_]; 52 | } 53 | front_index_ = 0; 54 | delete[] items_; 55 | items_ = new_items; 56 | max_size_ = max_size; 57 | } 58 | void push_front(T object) 59 | { 60 | front_index_ = (front_index_ - 1 + max_size_) % max_size_; 61 | count_++; 62 | items_[front_index_] = object; 63 | } 64 | T pop_front() 65 | { 66 | if (count_ == 0) 67 | { 68 | throw std::exception(); 69 | } 70 | 71 | int prev_start = front_index_; 72 | front_index_ = (front_index_ + 1 + max_size_) % max_size_; 73 | count_--; 74 | return items_[prev_start]; 75 | } 76 | 77 | T get(int index) 78 | { 79 | return items_[(front_index_ + index + max_size_) % max_size_]; 80 | } 81 | 82 | int count() 83 | { 84 | return count_; 85 | 86 | } 87 | int get_max_size() 88 | { 89 | return max_size_; 90 | } 91 | void clear() 92 | { 93 | count_ = 0; 94 | front_index_ = 0; 95 | } 96 | void copy(const circular_buffer& source) 97 | { 98 | if (max_size_ < source.max_size_) 99 | { 100 | resize(source.max_size_); 101 | } 102 | clear(); 103 | for (int index = 0; index < source.count_; index++) 104 | { 105 | items_[index] = source[index]; 106 | } 107 | front_index_ = source.front_index_; 108 | count_ = source.count_; 109 | 110 | } 111 | 112 | protected: 113 | T* items_; 114 | int max_size_; 115 | int front_index_; 116 | int count_; 117 | }; -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/extruder.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #include "extruder.h" 24 | #include 25 | 26 | extruder::extruder() 27 | { 28 | x_firmware_offset = 0; 29 | y_firmware_offset = 0; 30 | z_firmware_offset = 0; 31 | e = 0; 32 | e_offset = 0; 33 | e_relative = 0; 34 | extrusion_length = 0; 35 | extrusion_length_total = 0; 36 | retraction_length = 0; 37 | deretraction_length = 0; 38 | is_extruding_start = false; 39 | is_extruding = false; 40 | is_primed = false; 41 | is_retracting_start = false; 42 | is_retracting = false; 43 | is_retracted = false; 44 | is_partially_retracted = false; 45 | is_deretracting_start = false; 46 | is_deretracting = false; 47 | is_deretracted = false; 48 | } 49 | 50 | double extruder::get_offset_e() const 51 | { 52 | return e - e_offset; 53 | } 54 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/extruder.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #pragma once 23 | #include 24 | struct extruder 25 | { 26 | extruder(); 27 | double x_firmware_offset; 28 | double y_firmware_offset; 29 | double z_firmware_offset; 30 | double e; 31 | double e_offset; 32 | double e_relative; 33 | double extrusion_length; 34 | double extrusion_length_total; 35 | double retraction_length; 36 | double deretraction_length; 37 | bool is_extruding_start; 38 | bool is_extruding; 39 | bool is_primed; 40 | bool is_retracting_start; 41 | bool is_retracting; 42 | bool is_retracted; 43 | bool is_partially_retracted; 44 | bool is_deretracting_start; 45 | bool is_deretracting; 46 | bool is_deretracted; 47 | double get_offset_e() const; 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/gcode_comment_processor.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #include "gcode_comment_processor.h" 23 | 24 | gcode_comment_processor::gcode_comment_processor() 25 | { 26 | current_section_ = section_type_no_section; 27 | processing_type_ = comment_process_type_unknown; 28 | } 29 | 30 | gcode_comment_processor::~gcode_comment_processor() 31 | { 32 | } 33 | 34 | comment_process_type gcode_comment_processor::get_comment_process_type() 35 | { 36 | return processing_type_; 37 | } 38 | 39 | void gcode_comment_processor::update(position& pos) 40 | { 41 | if (processing_type_ == comment_process_type_off) 42 | return; 43 | 44 | if (current_section_ != section_type_no_section) 45 | { 46 | update_feature_from_section(pos); 47 | return; 48 | } 49 | 50 | if (processing_type_ == comment_process_type_unknown || processing_type_ == comment_process_type_slic3r_pe) 51 | { 52 | if (update_feature_for_slic3r_pe_comment(pos, pos.command.comment)) 53 | processing_type_ = comment_process_type_slic3r_pe; 54 | } 55 | 56 | } 57 | 58 | bool gcode_comment_processor::update_feature_for_slic3r_pe_comment(position& pos, std::string &comment) const 59 | { 60 | if (comment == "perimeter" || comment == "move to first perimeter point") 61 | { 62 | pos.feature_type_tag = feature_type_unknown_perimeter_feature; 63 | return true; 64 | } 65 | if (comment == "infill" || comment == "move to first infill point") 66 | { 67 | pos.feature_type_tag = feature_type_infill_feature; 68 | return true; 69 | } 70 | if (comment == "infill(bridge)" || comment == "move to first infill(bridge) point") 71 | { 72 | pos.feature_type_tag = feature_type_bridge_feature; 73 | return true; 74 | } 75 | if (comment == "skirt" || comment == "move to first skirt point") 76 | { 77 | pos.feature_type_tag = feature_type_skirt_feature; 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | void gcode_comment_processor::update_feature_from_section(position& pos) const 84 | { 85 | if (processing_type_ == comment_process_type_off || current_section_ == section_type_no_section) 86 | return; 87 | 88 | switch (current_section_) 89 | { 90 | case(section_type_outer_perimeter_section): 91 | pos.feature_type_tag = feature_type_outer_perimeter_feature; 92 | break; 93 | case(section_type_inner_perimeter_section): 94 | pos.feature_type_tag = feature_type_inner_perimeter_feature; 95 | break; 96 | case(section_type_skirt_section): 97 | pos.feature_type_tag = feature_type_skirt_feature; 98 | break; 99 | case(section_type_solid_infill_section): 100 | pos.feature_type_tag = feature_type_solid_infill_feature; 101 | break; 102 | case(section_type_ooze_shield_section): 103 | pos.feature_type_tag = feature_type_ooze_shield_feature; 104 | break; 105 | case(section_type_infill_section): 106 | pos.feature_type_tag = feature_type_infill_feature; 107 | break; 108 | case(section_type_prime_pillar_section): 109 | pos.feature_type_tag = feature_type_prime_pillar_feature; 110 | break; 111 | case(section_type_gap_fill_section): 112 | pos.feature_type_tag = feature_type_gap_fill_feature; 113 | break; 114 | case(section_type_no_section): 115 | // Do Nothing 116 | break; 117 | } 118 | } 119 | 120 | void gcode_comment_processor::update(std::string & comment) 121 | { 122 | switch(processing_type_) 123 | { 124 | case comment_process_type_off: 125 | break; 126 | case comment_process_type_unknown: 127 | update_unknown_section(comment); 128 | break; 129 | case comment_process_type_cura: 130 | update_cura_section(comment); 131 | break; 132 | case comment_process_type_slic3r_pe: 133 | update_slic3r_pe_section(comment); 134 | break; 135 | case comment_process_type_simplify_3d: 136 | update_simplify_3d_section(comment); 137 | break; 138 | } 139 | } 140 | 141 | void gcode_comment_processor::update_unknown_section(std::string & comment) 142 | { 143 | if (comment.length() == 0) 144 | return; 145 | 146 | if (update_cura_section(comment)) 147 | { 148 | processing_type_ = comment_process_type_cura; 149 | return; 150 | } 151 | 152 | if (update_simplify_3d_section(comment)) 153 | { 154 | processing_type_ = comment_process_type_simplify_3d; 155 | return; 156 | } 157 | if(update_slic3r_pe_section(comment)) 158 | { 159 | processing_type_ = comment_process_type_slic3r_pe; 160 | return; 161 | } 162 | } 163 | 164 | bool gcode_comment_processor::update_cura_section(std::string &comment) 165 | { 166 | if (comment == "TYPE:WALL-OUTER") 167 | { 168 | current_section_ = section_type_outer_perimeter_section; 169 | return true; 170 | } 171 | else if (comment == "TYPE:WALL-INNER") 172 | { 173 | current_section_ = section_type_inner_perimeter_section; 174 | return true; 175 | } 176 | if (comment == "TYPE:FILL") 177 | { 178 | current_section_ = section_type_infill_section; 179 | return true; 180 | } 181 | if (comment == "TYPE:SKIN") 182 | { 183 | current_section_ = section_type_solid_infill_section; 184 | return true; 185 | } 186 | if (comment.rfind("LAYER:", 0) != std::string::npos || comment.rfind(";MESH:NONMESH", 0) != std::string::npos) 187 | { 188 | current_section_ = section_type_no_section; 189 | return false; 190 | } 191 | if (comment == "TYPE:SKIRT") 192 | { 193 | current_section_ = section_type_skirt_section; 194 | return true; 195 | } 196 | return false; 197 | } 198 | 199 | bool gcode_comment_processor::update_simplify_3d_section(std::string &comment) 200 | { 201 | // Apparently simplify 3d added the word 'feature' to the their feature comments 202 | // at some point to make my life more difficult :P 203 | if (comment.rfind("feature", 0) != std::string::npos) 204 | { 205 | if (comment == "feature outer perimeter") 206 | { 207 | current_section_ = section_type_outer_perimeter_section; 208 | return true; 209 | } 210 | if (comment == "feature inner perimeter") 211 | { 212 | current_section_ = section_type_inner_perimeter_section; 213 | return true; 214 | } 215 | if (comment == "feature infill") 216 | { 217 | current_section_ = section_type_infill_section; 218 | return true; 219 | } 220 | if (comment == "feature solid layer") 221 | { 222 | current_section_ = section_type_solid_infill_section; 223 | return true; 224 | } 225 | if (comment == "feature skirt") 226 | { 227 | current_section_ = section_type_skirt_section; 228 | return true; 229 | } 230 | if (comment == "feature ooze shield") 231 | { 232 | current_section_ = section_type_ooze_shield_section; 233 | return true; 234 | } 235 | if (comment == "feature prime pillar") 236 | { 237 | current_section_ = section_type_prime_pillar_section; 238 | return true; 239 | } 240 | if (comment == "feature gap fill") 241 | { 242 | current_section_ = section_type_gap_fill_section; 243 | return true; 244 | } 245 | } 246 | else 247 | { 248 | if (comment == "outer perimeter") 249 | { 250 | current_section_ = section_type_outer_perimeter_section; 251 | return true; 252 | } 253 | if (comment == "inner perimeter") 254 | { 255 | current_section_ = section_type_inner_perimeter_section; 256 | return true; 257 | } 258 | if (comment == "infill") 259 | { 260 | current_section_ = section_type_infill_section; 261 | return true; 262 | } 263 | if (comment == "solid layer") 264 | { 265 | current_section_ = section_type_solid_infill_section; 266 | return true; 267 | } 268 | if (comment == "skirt") 269 | { 270 | current_section_ = section_type_skirt_section; 271 | return true; 272 | } 273 | if (comment == "ooze shield") 274 | { 275 | current_section_ = section_type_ooze_shield_section; 276 | return true; 277 | } 278 | 279 | if (comment == "prime pillar") 280 | { 281 | current_section_ = section_type_prime_pillar_section; 282 | return true; 283 | } 284 | 285 | if (comment == "gap fill") 286 | { 287 | current_section_ = section_type_gap_fill_section; 288 | return true; 289 | } 290 | } 291 | 292 | 293 | return false; 294 | } 295 | 296 | bool gcode_comment_processor::update_slic3r_pe_section(std::string &comment) 297 | { 298 | if (comment == "CP TOOLCHANGE WIPE") 299 | { 300 | current_section_ = section_type_prime_pillar_section; 301 | return true; 302 | } 303 | if (comment == "CP TOOLCHANGE END") 304 | { 305 | current_section_ = section_type_no_section; 306 | return true; 307 | } 308 | return false; 309 | } 310 | 311 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/gcode_comment_processor.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #pragma once 23 | #include "position.h" 24 | #define NUM_FEATURE_TYPES 11 25 | static const std::string feature_type_name[NUM_FEATURE_TYPES] = { 26 | "unknown_feature", "bridge_feature", "outer_perimeter_feature", "unknown_perimeter_feature", "inner_perimeter_feature", "skirt_feature", "gap_fill_feature", "solid_infill_feature", "ooze_shield_feature", "infill_feature", "prime_pillar_feature" 27 | }; 28 | enum feature_type 29 | { 30 | feature_type_unknown_feature, 31 | feature_type_bridge_feature, 32 | feature_type_outer_perimeter_feature, 33 | feature_type_unknown_perimeter_feature, 34 | feature_type_inner_perimeter_feature, 35 | feature_type_skirt_feature, 36 | feature_type_gap_fill_feature, 37 | feature_type_solid_infill_feature, 38 | feature_type_ooze_shield_feature, 39 | feature_type_infill_feature, 40 | feature_type_prime_pillar_feature 41 | }; 42 | enum comment_process_type 43 | { 44 | comment_process_type_off, 45 | comment_process_type_unknown, 46 | comment_process_type_slic3r_pe, 47 | comment_process_type_cura, 48 | comment_process_type_simplify_3d 49 | }; 50 | // used for marking slicer sections for cura and simplify 3d 51 | enum section_type 52 | { 53 | section_type_no_section, 54 | section_type_outer_perimeter_section, 55 | section_type_inner_perimeter_section, 56 | section_type_infill_section, 57 | section_type_gap_fill_section, 58 | section_type_skirt_section, 59 | section_type_solid_infill_section, 60 | section_type_ooze_shield_section, 61 | section_type_prime_pillar_section 62 | }; 63 | 64 | class gcode_comment_processor 65 | { 66 | 67 | public: 68 | 69 | gcode_comment_processor(); 70 | ~gcode_comment_processor(); 71 | void update(position& pos); 72 | void update(std::string & comment); 73 | comment_process_type get_comment_process_type(); 74 | 75 | private: 76 | section_type current_section_; 77 | comment_process_type processing_type_; 78 | void update_feature_from_section(position& pos) const; 79 | bool update_feature_from_section_from_section(position& pos) const; 80 | bool update_feature_from_section_for_cura(position& pos) const; 81 | bool update_feature_from_section_for_simplify_3d(position& pos) const; 82 | bool update_feature_from_section_for_slice3r_pe(position& pos) const; 83 | void update_feature_for_unknown_slicer_comment(position& pos, std::string &comment); 84 | bool update_feature_for_slic3r_pe_comment(position& pos, std::string &comment) const; 85 | void update_unknown_section(std::string & comment); 86 | bool update_cura_section(std::string &comment); 87 | bool update_simplify_3d_section(std::string &comment); 88 | bool update_slic3r_pe_section(std::string &comment); 89 | }; 90 | 91 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/gcode_parser.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #ifndef GCODE_PARSER_H 24 | #define GCODE_PARSER_H 25 | #include 26 | #include 27 | #include 28 | #include "parsed_command.h" 29 | #include "parsed_command_parameter.h" 30 | static const std::string GCODE_WORDS = "GMT"; 31 | 32 | class gcode_parser 33 | { 34 | public: 35 | gcode_parser(); 36 | ~gcode_parser(); 37 | bool try_parse_gcode(const char * gcode, parsed_command & command); 38 | bool try_parse_gcode(const char* gcode, parsed_command& command, bool preserve_format); 39 | parsed_command parse_gcode(const char * gcode); 40 | parsed_command parse_gcode(const char* gcode, bool preserve_format); 41 | private: 42 | gcode_parser(const gcode_parser &source); 43 | // Variables and lookups 44 | std::set text_only_functions_; 45 | std::set parsable_commands_; 46 | // Functions 47 | bool try_extract_double(char ** p_p_gcode, double * p_double) const; 48 | static bool try_extract_gcode_command(char ** p_p_gcode, std::string * p_command); 49 | static bool try_extract_text_parameter(char ** p_p_gcode, std::string * p_parameter); 50 | bool try_extract_parameter(char ** p_p_gcode, parsed_command_parameter * parameter) const; 51 | static bool try_extract_t_parameter(char ** p_p_gcode, parsed_command_parameter * parameter); 52 | static bool try_extract_unsigned_long(char ** p_p_gcode, unsigned long * p_value); 53 | double static ten_pow(unsigned short n); 54 | bool try_extract_comment(char ** p_p_gcode, std::string * p_comment); 55 | static bool try_extract_at_command(char ** p_p_gcode, std::string * p_command); 56 | bool try_extract_octolapse_parameter(char ** p_p_gcode, parsed_command_parameter * p_parameter); 57 | }; 58 | #endif 59 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/gcode_position.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #ifndef GCODE_POSITION_H 24 | #define GCODE_POSITION_H 25 | #include 26 | #include 27 | #include 28 | #include "gcode_parser.h" 29 | #include "position.h" 30 | #include "gcode_comment_processor.h" 31 | struct gcode_position_args { 32 | gcode_position_args() { 33 | position_buffer_size = 50; 34 | // Wipe Variables 35 | shared_extruder = true; 36 | autodetect_position = true; 37 | is_circular_bed = false; 38 | home_x = 0; 39 | home_y = 0; 40 | home_z = 0; 41 | home_x_none = false; 42 | home_y_none = false; 43 | home_z_none = false; 44 | retraction_lengths = NULL; 45 | z_lift_heights = NULL; 46 | x_firmware_offsets = NULL; 47 | y_firmware_offsets = NULL; 48 | priming_height = 0; 49 | minimum_layer_height = 0; 50 | height_increment = 0; 51 | g90_influences_extruder = false; 52 | xyz_axis_default_mode = "absolute"; 53 | e_axis_default_mode = "absolute"; 54 | units_default = "millimeters"; 55 | is_bound_ = false; 56 | x_min = 0; 57 | x_max = 0; 58 | y_min = 0; 59 | y_max = 0; 60 | z_min = 0; 61 | z_max = 0; 62 | snapshot_x_min = 0; 63 | snapshot_x_max = 0; 64 | snapshot_y_min = 0; 65 | snapshot_y_max = 0; 66 | snapshot_z_min = 0; 67 | snapshot_z_max = 0; 68 | num_extruders = 1; 69 | default_extruder = 0; 70 | zero_based_extruder = true; 71 | std::vector location_detection_commands; // Final list of location detection commands 72 | set_num_extruders(num_extruders); 73 | } 74 | gcode_position_args(const gcode_position_args &pos); // Copy Constructor 75 | ~gcode_position_args() 76 | { 77 | delete_retraction_lengths(); 78 | delete_z_lift_heights(); 79 | delete_x_firmware_offsets(); 80 | delete_y_firmware_offsets(); 81 | } 82 | int position_buffer_size; 83 | bool autodetect_position; 84 | bool is_circular_bed; 85 | // Wipe variables 86 | double home_x; 87 | double home_y; 88 | double home_z; 89 | bool home_x_none; 90 | bool home_y_none; 91 | bool home_z_none; 92 | double* retraction_lengths; 93 | double* z_lift_heights; 94 | double* x_firmware_offsets; 95 | double* y_firmware_offsets; 96 | double priming_height; 97 | double minimum_layer_height; 98 | double height_increment; 99 | bool g90_influences_extruder; 100 | bool is_bound_; 101 | double snapshot_x_min; 102 | double snapshot_x_max; 103 | double snapshot_y_min; 104 | double snapshot_y_max; 105 | double snapshot_z_min; 106 | double snapshot_z_max; 107 | double x_min; 108 | double x_max; 109 | double y_min; 110 | double y_max; 111 | double z_min; 112 | double z_max; 113 | bool shared_extruder; 114 | bool zero_based_extruder; 115 | int num_extruders; 116 | int default_extruder; 117 | std::string xyz_axis_default_mode; 118 | std::string e_axis_default_mode; 119 | std::string units_default; 120 | std::vector location_detection_commands; // Final list of location detection commands 121 | gcode_position_args& operator=(const gcode_position_args& pos_args); 122 | void set_num_extruders(int num_extruders); 123 | void delete_retraction_lengths(); 124 | void delete_z_lift_heights(); 125 | void delete_x_firmware_offsets(); 126 | void delete_y_firmware_offsets(); 127 | }; 128 | 129 | class gcode_position 130 | { 131 | public: 132 | typedef void(gcode_position::*pos_function_type)(position*, parsed_command&); 133 | gcode_position(gcode_position_args args); 134 | gcode_position(); 135 | virtual ~gcode_position(); 136 | 137 | void update(parsed_command &command, long file_line_number, long gcode_number, const long file_position); 138 | void update_position(position *position, double x, bool update_x, double y, bool update_y, double z, bool update_z, double e, bool update_e, double f, bool update_f, bool force, bool is_g1_g0) const; 139 | void undo_update(); 140 | position * undo_update(int num_updates); 141 | int get_num_positions(); 142 | position get_position(int index); 143 | position get_current_position(); 144 | position get_previous_position(); 145 | position * get_position_ptr(int index); 146 | position * get_current_position_ptr(); 147 | position * get_previous_position_ptr(); 148 | gcode_comment_processor* get_gcode_comment_processor(); 149 | bool get_g90_91_influences_extruder(); 150 | private: 151 | gcode_position(const gcode_position &source); 152 | int position_buffer_size_; 153 | position* positions_; 154 | int cur_pos_; 155 | int num_pos_; 156 | void add_position(parsed_command &); 157 | void add_position(position &); 158 | bool autodetect_position_; 159 | double priming_height_; 160 | double home_x_; 161 | double home_y_; 162 | double home_z_; 163 | bool home_x_none_; 164 | bool home_y_none_; 165 | bool home_z_none_; 166 | double * retraction_lengths_; 167 | double * z_lift_heights_; 168 | double minimum_layer_height_; 169 | double height_increment_; 170 | bool g90_influences_extruder_; 171 | std::string e_axis_default_mode_; 172 | std::string xyz_axis_default_mode_; 173 | std::string units_default_; 174 | bool is_bound_; 175 | double x_min_; 176 | double x_max_; 177 | double y_min_; 178 | double y_max_; 179 | double z_min_; 180 | double z_max_; 181 | bool is_circular_bed_; 182 | double snapshot_x_min_; 183 | double snapshot_x_max_; 184 | double snapshot_y_min_; 185 | double snapshot_y_max_; 186 | double snapshot_z_min_; 187 | double snapshot_z_max_; 188 | int num_extruders_; 189 | bool shared_extruder_; 190 | bool zero_based_extruder_; 191 | 192 | std::map gcode_functions_; 193 | std::map::iterator gcode_functions_iterator_; 194 | 195 | std::map get_gcode_functions(); 196 | /// Process Gcode Command Functions 197 | void process_g0_g1(position*, parsed_command&); 198 | void process_g2(position*, parsed_command&); 199 | void process_g3(position*, parsed_command&); 200 | void process_g10(position*, parsed_command&); 201 | void process_g11(position*, parsed_command&); 202 | void process_g20(position*, parsed_command&); 203 | void process_g21(position*, parsed_command&); 204 | void process_g28(position*, parsed_command&); 205 | void process_g90(position*, parsed_command&); 206 | void process_g91(position*, parsed_command&); 207 | void process_g92(position*, parsed_command&); 208 | void process_m82(position*, parsed_command&); 209 | void process_m83(position*, parsed_command&); 210 | void process_m207(position*, parsed_command&); 211 | void process_m208(position*, parsed_command&); 212 | void process_m218(position*, parsed_command&); 213 | void process_m563(position*, parsed_command&); 214 | void process_t(position*, parsed_command&); 215 | 216 | gcode_comment_processor comment_processor_; 217 | void delete_retraction_lengths_(); 218 | void delete_z_lift_heights_(); 219 | void set_num_extruders(int num_extruders); 220 | }; 221 | 222 | #endif 223 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/logger.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #if _MSC_VER > 1200 23 | #define _CRT_SECURE_NO_DEPRECATE 24 | #endif 25 | #include "logger.h" 26 | logger::logger(std::vector names, std::vector levels) { 27 | // set to true by default, but can be changed by inheritance to support mandatory innitialization (for python or other integrations) 28 | loggers_created_ = true; 29 | num_loggers_ = static_cast(names.size()); 30 | logger_names_ = new std::string[static_cast(num_loggers_)]; 31 | logger_levels_ = new int[static_cast(num_loggers_)]; 32 | // this is slow due to the vectors, but it is trivial. Could switch to an iterator 33 | for (int index = 0; index < num_loggers_; index++) 34 | { 35 | logger_names_[index] = names[index]; 36 | logger_levels_[index] = levels[index]; 37 | } 38 | 39 | set_log_level_by_value(NOSET); 40 | } 41 | 42 | logger::~logger() { 43 | delete[] logger_names_; 44 | delete[] logger_levels_; 45 | } 46 | 47 | void logger::set_log_level_by_value(const int logger_type, const int level_value) 48 | { 49 | logger_levels_[logger_type] = get_log_level_for_value(level_value); 50 | } 51 | void logger::set_log_level_by_value(const int level_value) 52 | { 53 | for (int type_index = 0; type_index < num_loggers_; type_index++) 54 | { 55 | logger_levels_[type_index] = get_log_level_for_value(level_value); 56 | } 57 | } 58 | void logger::set_log_level(const int logger_type, const int log_level) 59 | { 60 | logger_levels_[logger_type] = log_level; 61 | } 62 | 63 | void logger::set_log_level(const int log_level) 64 | { 65 | for (int type_index = 0; type_index < num_loggers_; type_index++) 66 | { 67 | logger_levels_[type_index] = log_level; 68 | } 69 | } 70 | 71 | int logger::get_log_level_value(const int log_level) 72 | { 73 | return log_level_values[log_level]; 74 | } 75 | int logger::get_log_level_for_value(int log_level_value) 76 | { 77 | for (int log_level = 0; log_level < LOG_LEVEL_COUNT; log_level++) 78 | { 79 | if (log_level_values[log_level] == log_level_value) 80 | return log_level; 81 | } 82 | return 0; 83 | } 84 | bool logger::is_log_level_enabled(const int logger_type, const int log_level) 85 | { 86 | return logger_levels_[logger_type] <= log_level; 87 | } 88 | 89 | void logger::create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output) 90 | { 91 | // example message 92 | // 2020-04-20 21:36:59,414 - arc_welder.__init__ - INFO - MESSAGE_GOES_HERE 93 | 94 | // Create the time string in YYYY-MM-DD HH:MM:SS.ms format 95 | logger::get_timestamp(output); 96 | // Add a spacer 97 | output.append(" - "); 98 | // Add the logger name 99 | output.append(logger_names_[logger_type]); 100 | // add a spacer 101 | output.append(" - "); 102 | // add the log level name 103 | output.append(log_level_names[log_level]); 104 | // add a spacer 105 | output.append(" - "); 106 | // add the message 107 | output.append(message); 108 | } 109 | 110 | void logger::log_exception(const int logger_type, const std::string& message) 111 | { 112 | log(logger_type, log_levels::ERROR, message, true); 113 | } 114 | 115 | void logger::log(const int logger_type, const int log_level, const std::string& message) 116 | { 117 | log(logger_type, log_level, message, false); 118 | } 119 | 120 | void logger::log(const int logger_type, const int log_level, const std::string& message, bool is_exception) 121 | { 122 | // Make sure the loggers have been initialized 123 | if (!loggers_created_) 124 | return; 125 | // Make sure the current logger is enabled for the log_level 126 | if (!is_log_level_enabled(logger_type, log_level)) 127 | return; 128 | 129 | // create the log message 130 | std::string output; 131 | create_log_message(logger_type, log_level, message, output); 132 | 133 | // write the log 134 | if (is_exception) 135 | std::cerr << output << std::endl; 136 | else 137 | std::cout << output << std::endl; 138 | std::cout.flush(); 139 | 140 | } 141 | 142 | void logger::get_timestamp(std::string ×tamp) 143 | { 144 | std::time_t rawtime; 145 | std::tm* timeinfo; 146 | char buffer[80]; 147 | 148 | std::time(&rawtime); 149 | timeinfo = std::localtime(&rawtime); 150 | std::strftime(buffer, 80, "%Y-%m-%d %H:%M:%S.", timeinfo); 151 | 152 | timestamp = buffer; 153 | clock_t t = std::clock(); 154 | int ms = static_cast((t / CLOCKS_PER_MS)) % 1000; 155 | 156 | std::string s_miliseconds; 157 | sprintf(buffer, "%d", ms) ;// std::to_string(ms); 158 | s_miliseconds = buffer; 159 | timestamp.append(std::string(3 - s_miliseconds.length(), '0') + s_miliseconds); 160 | 161 | } 162 | 163 | /* 164 | 165 | Severity Code Description Project File Line Suppression State 166 | 167 | 168 | 169 | std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); 170 | std::chrono::milliseconds ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; 171 | const time_t now_time = std::chrono::system_clock::to_time_t(now); 172 | struct tm tstruct; 173 | char buf[25]; 174 | tstruct = *localtime(&now_time); 175 | // DOESN'T WORK WITH ALL COMPILERS... 176 | //localtime_s(&tstruct, &now_time); 177 | strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S.", &tstruct); 178 | output = buf; 179 | std::string s_miliseconds = std::to_string(ms.count()); 180 | // Add the milliseconds, padded with 0s, to the output 181 | output.append(std::string(3 - s_miliseconds.length(), '0') + s_miliseconds); 182 | 183 | */ -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/logger.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #pragma once 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | //#include 30 | #include 31 | 32 | #define LOG_LEVEL_COUNT 7 33 | #define CLOCKS_PER_MS (CLOCKS_PER_SEC / 1000.0) 34 | enum log_levels { NOSET, VERBOSE, DEBUG, INFO, WARNING , ERROR, CRITICAL}; 35 | //const std::array log_level_names = { {"NOSET", "VERBOSE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} }; 36 | static const int log_level_names_size = 7; 37 | static const char* log_level_names[] = {"NOSET", "VERBOSE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}; 38 | const static int log_level_values[LOG_LEVEL_COUNT] = { 0, 5, 10, 20, 30, 40, 50}; 39 | 40 | class logger 41 | { 42 | public: 43 | logger(std::vector names, std::vector levels); 44 | virtual ~logger(); 45 | 46 | void set_log_level_by_value(const int logger_type, const int log_level_value); 47 | void set_log_level_by_value(const int log_level_value); 48 | 49 | void set_log_level(const int logger_type, const int log_level); 50 | void set_log_level(const int log_level); 51 | 52 | virtual void log(const int logger_type, const int log_level, const std::string& message); 53 | virtual void log(const int logger_type, const int log_level, const std::string& message, bool is_exception); 54 | virtual void log_exception(const int logger_type, const std::string& message); 55 | static int get_log_level_value(const int log_level); 56 | static int get_log_level_for_value(int log_level_value); 57 | virtual bool is_log_level_enabled(const int logger_type, const int log_level); 58 | protected: 59 | virtual void create_log_message(const int logger_type, const int log_level, const std::string& message, std::string& output); 60 | 61 | bool loggers_created_; 62 | private: 63 | std::string* logger_names_; 64 | int * logger_levels_; 65 | int num_loggers_; 66 | static void get_timestamp(std::string ×tamp); 67 | 68 | }; 69 | 70 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/parsed_command.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #include "parsed_command.h" 24 | #include 25 | #include 26 | #include 27 | parsed_command::parsed_command() 28 | { 29 | 30 | command.reserve(8); 31 | gcode.reserve(128); 32 | comment.reserve(128); 33 | parameters.reserve(6); 34 | is_known_command = false; 35 | is_empty = true; 36 | } 37 | 38 | void parsed_command::clear() 39 | { 40 | 41 | command.clear(); 42 | gcode.clear(); 43 | comment.clear(); 44 | parameters.clear(); 45 | is_known_command = false; 46 | is_empty = true; 47 | } 48 | 49 | std::string parsed_command::rewrite_gcode_string() 50 | { 51 | std::stringstream stream; 52 | 53 | // add command 54 | stream << command; 55 | if (parameters.size() > 0) 56 | { 57 | for (unsigned int index = 0; index < parameters.size(); index++) 58 | { 59 | parsed_command_parameter p = parameters[index]; 60 | 61 | if (p.name == "E") 62 | { 63 | stream << std::fixed << std::setprecision(5); 64 | } 65 | else if (p.name == "F") 66 | { 67 | stream << std::fixed << std::setprecision(0); 68 | } 69 | else 70 | { 71 | stream << std::fixed << std::setprecision(3); 72 | } 73 | 74 | stream << " " << p.name; 75 | switch (p.value_type) 76 | { 77 | case 'S': 78 | stream << p.string_value; 79 | break; 80 | case 'F': 81 | stream << p.double_value; 82 | break; 83 | case 'U': 84 | stream << p.unsigned_long_value; 85 | break; 86 | } 87 | } 88 | } 89 | if (comment.size() > 0) 90 | { 91 | stream << ";" << comment; 92 | } 93 | return stream.str(); 94 | } 95 | 96 | std::string parsed_command::to_string() 97 | { 98 | if (comment.size() > 0) 99 | { 100 | return gcode + ";" + comment; 101 | } 102 | return gcode; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/parsed_command.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #ifndef PARSED_COMMAND_H 24 | #define PARSED_COMMAND_H 25 | #include 26 | #include 27 | #include "parsed_command_parameter.h" 28 | 29 | struct parsed_command 30 | { 31 | public: 32 | parsed_command(); 33 | std::string command; 34 | std::string gcode; 35 | std::string comment; 36 | bool is_empty; 37 | bool is_known_command; 38 | std::vector parameters; 39 | void clear(); 40 | std::string to_string(); 41 | std::string rewrite_gcode_string(); 42 | }; 43 | 44 | #endif -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/parsed_command_parameter.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #include "parsed_command_parameter.h" 24 | #include "parsed_command.h" 25 | parsed_command_parameter::parsed_command_parameter() 26 | { 27 | value_type = 'N'; 28 | name.reserve(1); 29 | } 30 | 31 | parsed_command_parameter::parsed_command_parameter(const std::string name, double value) : name(name), double_value(value) 32 | { 33 | value_type = 'F'; 34 | } 35 | 36 | parsed_command_parameter::parsed_command_parameter(const std::string name, const std::string value) : name(name), string_value(value) 37 | { 38 | value_type = 'S'; 39 | } 40 | 41 | parsed_command_parameter::parsed_command_parameter(const std::string name, const unsigned long value) : name(name), unsigned_long_value(value) 42 | { 43 | value_type = 'U'; 44 | } 45 | parsed_command_parameter::~parsed_command_parameter() 46 | { 47 | 48 | } 49 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/parsed_command_parameter.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #ifndef PARSED_COMMAND_PARAMETER_H 24 | #define PARSED_COMMAND_PARAMETER_H 25 | #include 26 | struct parsed_command_parameter 27 | { 28 | public: 29 | parsed_command_parameter(); 30 | ~parsed_command_parameter(); 31 | parsed_command_parameter(std::string name, double value); 32 | parsed_command_parameter(std::string name, std::string value); 33 | parsed_command_parameter(std::string name, unsigned long value); 34 | std::string name; 35 | char value_type; 36 | double double_value; 37 | unsigned long unsigned_long_value; 38 | std::string string_value; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/position.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #ifndef POSITION_H 24 | #define POSITION_H 25 | #include 26 | #include "parsed_command.h" 27 | #include "extruder.h" 28 | 29 | 30 | struct position 31 | { 32 | position(); 33 | position(int extruder_count); 34 | position(const position &pos); // Copy Constructor 35 | virtual ~position(); 36 | position& operator=(const position& pos); 37 | std::string to_string(bool rewrite, bool verbose, std::string additional_comment); 38 | void reset_state(); 39 | parsed_command command; 40 | int feature_type_tag; 41 | double f; 42 | bool f_null; 43 | double x; 44 | bool x_null; 45 | double x_offset; 46 | double x_firmware_offset; 47 | bool x_homed; 48 | double y; 49 | bool y_null; 50 | double y_offset; 51 | double y_firmware_offset; 52 | bool y_homed; 53 | double z; 54 | bool z_null; 55 | double z_offset; 56 | double z_firmware_offset; 57 | bool z_homed; 58 | bool is_metric; 59 | bool is_metric_null; 60 | double last_extrusion_height; 61 | bool last_extrusion_height_null; 62 | long layer; 63 | double height; 64 | int height_increment; 65 | int height_increment_change_count; 66 | bool is_printer_primed; 67 | bool has_definite_position; 68 | double z_relative; 69 | bool is_relative; 70 | bool is_relative_null; 71 | bool is_extruder_relative; 72 | bool is_extruder_relative_null; 73 | bool is_layer_change; 74 | bool is_height_change; 75 | bool is_height_increment_change; 76 | bool is_xy_travel; 77 | bool is_xyz_travel; 78 | bool is_zhop; 79 | bool has_position_changed; 80 | bool has_xy_position_changed; 81 | bool has_received_home_command; 82 | bool is_in_position; 83 | bool in_path_position; 84 | long file_line_number; 85 | long gcode_number; 86 | long file_position; 87 | bool gcode_ignored; 88 | bool is_in_bounds; 89 | bool is_empty; 90 | int current_tool; 91 | int num_extruders; 92 | bool has_been_deleted; 93 | extruder * p_extruders; 94 | extruder& get_current_extruder() const; 95 | extruder& get_extruder(int index) const; 96 | void set_num_extruders(int num_extruders_); 97 | void delete_extruders(); 98 | double get_gcode_x() const; 99 | double get_gcode_y() const; 100 | double get_gcode_z() const; 101 | void set_xyz_axis_mode(const std::string& xyz_axis_default_mode); 102 | void set_e_axis_mode(const std::string& e_axis_default_mode); 103 | void set_units_default(const std::string& units_default); 104 | bool can_take_snapshot(); 105 | }; 106 | #endif -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/utilities.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | #include "utilities.h" 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | // Had to increase the zero tolerance because prusa slicer doesn't always retract enough while wiping. 29 | const double ZERO_TOLERANCE = 0.000005; 30 | const std::string utilities::WHITESPACE_ = " \n\r\t\f\v"; 31 | const char utilities::GUID_RANGE[] = "0123456789abcdef"; 32 | const bool utilities::GUID_DASHES[] = { 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 }; 33 | 34 | bool utilities::is_zero(double x) 35 | { 36 | return std::abs(x) < ZERO_TOLERANCE; 37 | } 38 | 39 | int utilities::round_up_to_int(double x) 40 | { 41 | return int(x + ZERO_TOLERANCE); 42 | } 43 | 44 | bool utilities::is_equal(double x, double y) 45 | { 46 | double abs_difference = std::abs(x - y); 47 | return abs_difference < ZERO_TOLERANCE; 48 | } 49 | 50 | bool utilities::greater_than(double x, double y) 51 | { 52 | return x > y && !is_equal(x, y); 53 | } 54 | 55 | bool utilities::greater_than_or_equal(double x, double y) 56 | { 57 | return x > y || is_equal(x, y); 58 | } 59 | 60 | bool utilities::less_than(double x, double y) 61 | { 62 | return x < y && !is_equal(x, y); 63 | } 64 | 65 | bool utilities::less_than_or_equal(double x, double y) 66 | { 67 | return x < y || is_equal(x, y); 68 | } 69 | 70 | // custom tolerance functions 71 | bool utilities::is_zero(double x, double tolerance) 72 | { 73 | return std::abs(x) < tolerance; 74 | } 75 | 76 | bool utilities::is_equal(double x, double y, double tolerance) 77 | { 78 | double abs_difference = std::abs(x - y); 79 | return abs_difference < tolerance; 80 | } 81 | 82 | int utilities::round_up_to_int(double x, double tolerance) 83 | { 84 | return int(x + tolerance); 85 | } 86 | bool utilities::greater_than(double x, double y, double tolerance) 87 | { 88 | return x > y && !utilities::is_equal(x, y, tolerance); 89 | } 90 | bool utilities::greater_than_or_equal(double x, double y, double tolerance) 91 | { 92 | return x > y || utilities::is_equal(x, y, tolerance); 93 | } 94 | bool utilities::less_than(double x, double y, double tolerance) 95 | { 96 | return x < y && !utilities::is_equal(x, y, tolerance); 97 | } 98 | bool utilities::less_than_or_equal(double x, double y, double tolerance) 99 | { 100 | return x < y || utilities::is_equal(x, y, tolerance); 101 | } 102 | 103 | double utilities::get_cartesian_distance(double x1, double y1, double x2, double y2) 104 | { 105 | // Compare the saved points cartesian distance from the current point 106 | double xdif = x1 - x2; 107 | double ydif = y1 - y2; 108 | double dist_squared = xdif * xdif + ydif * ydif; 109 | return sqrt(dist_squared); 110 | } 111 | 112 | double utilities::get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2) 113 | { 114 | // Compare the saved points cartesian distance from the current point 115 | double xdif = x1 - x2; 116 | double ydif = y1 - y2; 117 | double zdif = z1 - z2; 118 | double dist_squared = xdif * xdif + ydif * ydif + zdif * zdif; 119 | return sqrt(dist_squared); 120 | } 121 | 122 | std::string utilities::to_string(double value) 123 | { 124 | std::ostringstream os; 125 | os << value; 126 | return os.str(); 127 | } 128 | 129 | std::string utilities::to_string(int value) 130 | { 131 | std::ostringstream os; 132 | os << value; 133 | return os.str(); 134 | } 135 | 136 | char * utilities::to_string(double value, unsigned short precision, char * str) 137 | { 138 | char reversed_int[20]; 139 | 140 | int char_count = 0, int_count = 0; 141 | bool is_negative = false; 142 | double integer_part, fractional_part; 143 | fractional_part = std::abs(std::modf(value, &integer_part)); //Separate integer/fractional parts 144 | if (value < 0) 145 | { 146 | str[char_count++] = '-'; 147 | integer_part *= -1; 148 | is_negative = true; 149 | } 150 | 151 | if (integer_part == 0) 152 | { 153 | str[char_count++] = '0'; 154 | } 155 | else 156 | { 157 | while (integer_part > 0) //Convert integer part, if any 158 | { 159 | reversed_int[int_count++] = '0' + (int)std::fmod(integer_part, 10); 160 | integer_part = std::floor(integer_part / 10); 161 | } 162 | } 163 | int start = is_negative ? 1 : 0; 164 | int end = char_count - start; 165 | for (int i = 0; i < int_count; i++) 166 | { 167 | str[char_count++] = reversed_int[int_count - i - 1]; 168 | } 169 | if (precision > 0) 170 | { 171 | str[char_count++] = '.'; //Decimal point 172 | 173 | while (fractional_part > 0 && precision-- > 0) //Convert fractional part, if any 174 | { 175 | fractional_part *= 10; 176 | fractional_part = std::modf(fractional_part, &integer_part); 177 | str[char_count++] = '0' + (int)integer_part; 178 | } 179 | } 180 | str[char_count] = 0; //String terminator 181 | return str; 182 | } 183 | 184 | std::string utilities::ltrim(const std::string& s) 185 | { 186 | size_t start = s.find_first_not_of(WHITESPACE_); 187 | return (start == std::string::npos) ? "" : s.substr(start); 188 | } 189 | 190 | std::string utilities::rtrim(const std::string& s) 191 | { 192 | size_t end = s.find_last_not_of(WHITESPACE_); 193 | return (end == std::string::npos) ? "" : s.substr(0, end + 1); 194 | } 195 | 196 | std::string utilities::trim(const std::string& s) 197 | { 198 | return rtrim(ltrim(s)); 199 | } 200 | 201 | std::istream& utilities::safe_get_line(std::istream& is, std::string& t) 202 | { 203 | t.clear(); 204 | // The characters in the stream are read one-by-one using a std::streambuf. 205 | // That is faster than reading them one-by-one using the std::istream. 206 | // Code that uses streambuf this way must be guarded by a sentry object. 207 | // The sentry object performs various tasks, 208 | // such as thread synchronization and updating the stream state. 209 | 210 | std::istream::sentry se(is, true); 211 | std::streambuf* sb = is.rdbuf(); 212 | 213 | for (;;) { 214 | const int c = sb->sbumpc(); 215 | switch (c) { 216 | case '\n': 217 | return is; 218 | case '\r': 219 | if (sb->sgetc() == '\n') 220 | sb->sbumpc(); 221 | return is; 222 | case EOF: 223 | // Also handle the case when the last line has no line ending 224 | if (t.empty()) 225 | is.setstate(std::ios::eofbit); 226 | return is; 227 | default: 228 | t += static_cast(c); 229 | } 230 | } 231 | } 232 | 233 | std::string utilities::center(std::string input, int width) 234 | { 235 | int input_width = input.length(); 236 | int difference = width - input_width; 237 | if (difference < 1) 238 | { 239 | return input; 240 | } 241 | int left_padding = difference /2; 242 | int right_padding = width - left_padding - input_width; 243 | return std::string(left_padding, ' ') + input + std::string(right_padding, ' '); 244 | } 245 | 246 | std::string utilities::get_percent_change_string(int v1, int v2, int precision) 247 | { 248 | std::stringstream format_stream; 249 | format_stream.str(std::string()); 250 | std::string percent_change_string; 251 | if (v1 == 0) 252 | { 253 | if (v2 > 0) 254 | { 255 | format_stream << "INF"; 256 | } 257 | else 258 | { 259 | format_stream << std::fixed << std::setprecision(1) << 0.0 << "%"; 260 | } 261 | } 262 | else 263 | { 264 | double percent_change = (((double)v2 - (double)v1) / (double)v1) * 100.0; 265 | format_stream << std::fixed << std::setprecision(precision) << percent_change << "%"; 266 | } 267 | return format_stream.str(); 268 | } 269 | 270 | int utilities::get_num_digits(int x) 271 | { 272 | x = abs(x); 273 | return (x < 10 ? 1 : 274 | (x < 100 ? 2 : 275 | (x < 1000 ? 3 : 276 | (x < 10000 ? 4 : 277 | (x < 100000 ? 5 : 278 | (x < 1000000 ? 6 : 279 | (x < 10000000 ? 7 : 280 | (x < 100000000 ? 8 : 281 | (x < 1000000000 ? 9 : 282 | 10))))))))); 283 | } 284 | 285 | int utilities::get_num_digits(double x) 286 | { 287 | return get_num_digits((int) x); 288 | } 289 | 290 | // Nice utility function found here: https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path 291 | std::vector utilities::splitpath(const std::string& str) 292 | { 293 | std::vector result; 294 | 295 | char const* pch = str.c_str(); 296 | char const* start = pch; 297 | for (; *pch; ++pch) 298 | { 299 | if (*pch == PATH_SEPARATOR_) 300 | { 301 | if (start != pch) 302 | { 303 | std::string str(start, pch); 304 | result.push_back(str); 305 | } 306 | else 307 | { 308 | result.push_back(""); 309 | } 310 | start = pch + 1; 311 | } 312 | } 313 | result.push_back(start); 314 | 315 | return result; 316 | } 317 | 318 | bool utilities::get_file_path(const std::string& file_path, std::string & path) 319 | { 320 | std::vector file_parts = splitpath(file_path); 321 | if (file_parts.size() == 0) 322 | return false; 323 | for (int index = 0; index < file_parts.size() - 1; index++) 324 | { 325 | path += file_parts[index]; 326 | path += PATH_SEPARATOR_; 327 | } 328 | return true; 329 | } 330 | 331 | std::string utilities::create_uuid() { 332 | std::string res; 333 | for (int i = 0; i < 16; i++) { 334 | if (GUID_DASHES[i]) res += "-"; 335 | res += GUID_RANGE[(int)(rand() % 16)]; 336 | res += GUID_RANGE[(int)(rand() % 16)]; 337 | } 338 | return res; 339 | } 340 | 341 | bool utilities::get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path) 342 | { 343 | temp_file_path = ""; 344 | if (!utilities::get_file_path(file_path, temp_file_path)) 345 | { 346 | return false; 347 | } 348 | temp_file_path = temp_file_path; 349 | temp_file_path += utilities::create_uuid(); 350 | temp_file_path += ".tmp"; 351 | return true; 352 | } 353 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/gcode_processor_lib/utilities.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Gcode Processor Library 3 | // 4 | // Tools for parsing gcode and calculating printer state from parsed gcode commands. 5 | // 6 | // Copyright(C) 2020 - Brad Hochgesang 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // This program is free software : you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published 10 | // by the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // 19 | // You can contact the author at the following email address: 20 | // FormerLurker@pm.me 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | #pragma once 24 | #include 25 | #include 26 | #include 27 | class utilities{ 28 | public: 29 | static bool is_zero(double x); 30 | static int round_up_to_int(double x); 31 | static bool is_equal(double x, double y); 32 | static bool greater_than(double x, double y); 33 | static bool greater_than_or_equal(double x, double y); 34 | static bool less_than(double x, double y); 35 | static bool less_than_or_equal(double x, double y); 36 | 37 | // custom tolerance functions 38 | static bool is_zero(double x, double tolerance); 39 | static bool is_equal(double x, double y, double tolerance); 40 | static int round_up_to_int(double x, double tolerance); 41 | static bool greater_than(double x, double y, double tolerance); 42 | static bool greater_than_or_equal(double x, double y, double tolerance); 43 | static bool less_than(double x, double y, double tolerance); 44 | static bool less_than_or_equal(double x, double y, double tolerance); 45 | 46 | static double get_cartesian_distance(double x1, double y1, double x2, double y2); 47 | static double get_cartesian_distance(double x1, double y1, double z1, double x2, double y2, double z2); 48 | static std::string to_string(double value); 49 | static std::string to_string(int value); 50 | static char* to_string(double value, unsigned short precision, char* str); 51 | static std::string ltrim(const std::string& s); 52 | static std::string rtrim(const std::string& s); 53 | static std::string trim(const std::string& s); 54 | static std::istream& safe_get_line(std::istream& is, std::string& t); 55 | static std::string center(std::string input, int width); 56 | static std::string get_percent_change_string(int v1, int v2, int precision); 57 | 58 | static int get_num_digits(int x); 59 | static int get_num_digits(double x); 60 | 61 | static std::vector splitpath(const std::string& str); 62 | static bool get_file_path(const std::string& file_path, std::string& path); 63 | static bool get_temp_file_path_for_file(const std::string& file_path, std::string& temp_file_path); 64 | static std::string create_uuid(); 65 | 66 | 67 | protected: 68 | static const std::string WHITESPACE_; 69 | static const char PATH_SEPARATOR_ = 70 | #ifdef _WIN32 71 | '\\'; 72 | #else 73 | '/'; 74 | #endif 75 | static const char GUID_RANGE[]; 76 | static const bool GUID_DASHES[]; 77 | private: 78 | utilities(); 79 | 80 | }; 81 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/py_arc_welder.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #include "py_arc_welder.h" 24 | 25 | PyObject* py_arc_welder::build_py_progress(const arc_welder_progress& progress) 26 | { 27 | std::string segment_statistics = progress.segment_statistics.str(); 28 | PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(segment_statistics); 29 | if (pyMessage == NULL) 30 | return NULL; 31 | PyObject* py_progress = Py_BuildValue("{s:d,s:d,s:d,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f,s:f,s:f,s:i,s:i}", 32 | "percent_complete", 33 | progress.percent_complete, //1 34 | "seconds_elapsed", 35 | progress.seconds_elapsed, //2 36 | "seconds_remaining", 37 | progress.seconds_remaining, //3 38 | "gcodes_processed", 39 | progress.gcodes_processed, //4 40 | "lines_processed", 41 | progress.lines_processed, //5 42 | "points_compressed", 43 | progress.points_compressed, //6 44 | "arcs_created", 45 | progress.arcs_created, //7 46 | "source_file_position", 47 | progress.source_file_position, //8 48 | "source_file_size", 49 | progress.source_file_size, //9 50 | "target_file_size", 51 | progress.target_file_size, //10 52 | "compression_ratio", 53 | progress.compression_ratio, //11 54 | "compression_percent", 55 | progress.compression_percent, //12 56 | "source_file_total_length", 57 | progress.segment_statistics.total_length_source, //13 58 | "target_file_total_length", 59 | progress.segment_statistics.total_length_target, //14 60 | "source_file_total_count", 61 | progress.segment_statistics.total_count_source, //15 62 | "target_file_total_count", 63 | progress.segment_statistics.total_length_target //16 64 | ); 65 | 66 | if (py_progress == NULL) 67 | { 68 | return NULL; 69 | } 70 | // Due to a CRAZY issue, I have to add this item after building the py_progress object, 71 | // else it crashes in python 2.7. Looking forward to retiring this backwards 72 | // compatible code... 73 | PyDict_SetItemString(py_progress, "segment_statistics_text", pyMessage); 74 | return py_progress; 75 | } 76 | 77 | bool py_arc_welder::on_progress_(const arc_welder_progress& progress) 78 | { 79 | PyObject* py_dict = py_arc_welder::build_py_progress(progress); 80 | if (py_dict == NULL) 81 | { 82 | return false; 83 | } 84 | PyObject* func_args = Py_BuildValue("(O)", py_dict); 85 | if (func_args == NULL) 86 | { 87 | Py_DECREF(py_dict); 88 | return false; // This was returning true, I think it was a typo. Making a note just in case. 89 | } 90 | 91 | PyGILState_STATE gstate = PyGILState_Ensure(); 92 | PyObject* pContinueProcessing = PyObject_CallObject(py_progress_callback_, func_args); 93 | Py_DECREF(func_args); 94 | Py_DECREF(py_dict); 95 | bool continue_processing; 96 | if (pContinueProcessing == NULL) 97 | { 98 | // no return value was supply, assume true, but without decrefing pContinueProcessing 99 | continue_processing = true; 100 | } 101 | else 102 | { 103 | if (pContinueProcessing == Py_None) 104 | { 105 | continue_processing = true; 106 | } 107 | else 108 | { 109 | continue_processing = PyLong_AsLong(pContinueProcessing) > 0; 110 | } 111 | Py_DECREF(pContinueProcessing); 112 | } 113 | PyGILState_Release(gstate); 114 | return continue_processing; 115 | } 116 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/py_arc_welder.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #pragma once 24 | #include 25 | #include 26 | #include "py_logger.h" 27 | #ifdef _DEBUG 28 | #undef _DEBUG 29 | #include 30 | #define _DEBUG 31 | #else 32 | #include 33 | #endif 34 | class py_arc_welder : public arc_welder 35 | { 36 | public: 37 | py_arc_welder(std::string source_path, std::string target_path, py_logger* logger, double resolution_mm, double max_radius, bool g90_g91_influences_extruder, int buffer_size, PyObject* py_progress_callback):arc_welder(source_path, target_path, logger, resolution_mm, max_radius, g90_g91_influences_extruder, buffer_size) 38 | { 39 | py_progress_callback_ = py_progress_callback; 40 | } 41 | virtual ~py_arc_welder() { 42 | 43 | } 44 | static PyObject* build_py_progress(const arc_welder_progress& progress); 45 | protected: 46 | virtual bool on_progress_(const arc_welder_progress& progress); 47 | private: 48 | PyObject* py_progress_callback_; 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/py_arc_welder_extension.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #include "py_arc_welder_extension.h" 24 | #include "py_arc_welder.h" 25 | #include 26 | #include 27 | #include 28 | #include "arc_welder.h" 29 | #include "py_logger.h" 30 | #include "python_helpers.h" 31 | 32 | #if PY_MAJOR_VERSION >= 3 33 | int main(int argc, char* argv[]) 34 | { 35 | wchar_t* program = Py_DecodeLocale(argv[0], NULL); 36 | if (program == NULL) { 37 | fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); 38 | exit(1); 39 | } 40 | 41 | // Add a built-in module, before Py_Initialize 42 | PyImport_AppendInittab("PyArcWelder", PyInit_PyArcWelder); 43 | 44 | // Pass argv[0] to the Python interpreter 45 | Py_SetProgramName(program); 46 | 47 | // Initialize the Python interpreter. Required. 48 | Py_Initialize(); 49 | // We are not using threads, do not enable. 50 | /*std::cout << "Initializing threads..."; 51 | if (!PyEval_ThreadsInitialized()) { 52 | PyEval_InitThreads(); 53 | }*/ 54 | // Optionally import the module; alternatively, import can be deferred until the embedded script imports it. 55 | PyImport_ImportModule("PyArcWelder"); 56 | PyMem_RawFree(program); 57 | return 0; 58 | } 59 | 60 | #else 61 | 62 | int main(int argc, char* argv[]) 63 | { 64 | Py_SetProgramName(argv[0]); 65 | Py_Initialize(); 66 | // We are not using threads, do not enable. 67 | /* std::cout << "Initializing threads..."; 68 | if (!PyEval_ThreadsInitialized()) { 69 | PyEval_InitThreads(); 70 | } 71 | */ 72 | initPyArcWelder(); 73 | return 0; 74 | 75 | } 76 | #endif 77 | 78 | struct module_state { 79 | PyObject* error; 80 | }; 81 | #if PY_MAJOR_VERSION >= 3 82 | #define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) 83 | #else 84 | #define GETSTATE(m) (&_state) 85 | static struct module_state _state; 86 | #endif 87 | 88 | // Python 2 module method definition 89 | static PyMethodDef PyArcWelderMethods[] = { 90 | { "ConvertFile", (PyCFunction)ConvertFile, METH_VARARGS ,"Converts segmented curve approximations to actual G2/G3 arcs within the supplied resolution." }, 91 | { NULL, NULL, 0, NULL } 92 | }; 93 | 94 | // Python 3 module method definition 95 | #if PY_MAJOR_VERSION >= 3 96 | static int PyArcWelder_traverse(PyObject* m, visitproc visit, void* arg) { 97 | Py_VISIT(GETSTATE(m)->error); 98 | return 0; 99 | } 100 | 101 | static int PyArcWelder_clear(PyObject* m) { 102 | Py_CLEAR(GETSTATE(m)->error); 103 | return 0; 104 | } 105 | 106 | static struct PyModuleDef moduledef = { 107 | PyModuleDef_HEAD_INIT, 108 | "PyArcWelder", 109 | NULL, 110 | sizeof(struct module_state), 111 | PyArcWelderMethods, 112 | NULL, 113 | PyArcWelder_traverse, 114 | PyArcWelder_clear, 115 | NULL 116 | }; 117 | 118 | #define INITERROR return NULL 119 | 120 | PyMODINIT_FUNC 121 | PyInit_PyArcWelder(void) 122 | 123 | #else 124 | #define INITERROR return 125 | 126 | extern "C" void initPyArcWelder(void) 127 | #endif 128 | { 129 | std::cout << "Initializing PyArcWelder V0.1.0rc1.dev2 - Copyright (C) 2019 Brad Hochgesang."; 130 | 131 | #if PY_MAJOR_VERSION >= 3 132 | std::cout << " Python 3+ Detected..."; 133 | PyObject* module = PyModule_Create(&moduledef); 134 | #else 135 | std::cout << " Python 2 Detected..."; 136 | PyObject* module = Py_InitModule("PyArcWelder", PyArcWelderMethods); 137 | #endif 138 | 139 | if (module == NULL) 140 | INITERROR; 141 | struct module_state* st = GETSTATE(module); 142 | 143 | st->error = PyErr_NewException((char*)"PyArcWelder.Error", NULL, NULL); 144 | if (st->error == NULL) { 145 | Py_DECREF(module); 146 | INITERROR; 147 | } 148 | std::vector logger_names; 149 | logger_names.push_back("arc_welder.gcode_conversion"); 150 | std::vector logger_levels; 151 | logger_levels.push_back(log_levels::DEBUG); 152 | p_py_logger = new py_logger(logger_names, logger_levels); 153 | p_py_logger->initialize_loggers(); 154 | std::string message = "PyArcWelder V0.1.0rc1.dev2 imported - Copyright (C) 2019 Brad Hochgesang..."; 155 | p_py_logger->log(GCODE_CONVERSION, INFO, message); 156 | p_py_logger->set_log_level_by_value(DEBUG); 157 | std::cout << " Initialization Complete\r\n"; 158 | 159 | #if PY_MAJOR_VERSION >= 3 160 | return module; 161 | #endif 162 | } 163 | 164 | extern "C" 165 | { 166 | static PyObject* ConvertFile(PyObject* self, PyObject* py_args) 167 | { 168 | PyObject* py_convert_file_args; 169 | if (!PyArg_ParseTuple( 170 | py_args, 171 | "O", 172 | &py_convert_file_args 173 | )) 174 | { 175 | std::string message = "py_gcode_arc_converter.ConvertFile - Cound not extract the parameters dictionary."; 176 | p_py_logger->log_exception(GCODE_CONVERSION, message); 177 | return NULL; 178 | } 179 | 180 | py_gcode_arc_args args; 181 | PyObject* py_progress_callback = NULL; 182 | 183 | if (!ParseArgs(py_convert_file_args, args, &py_progress_callback)) 184 | { 185 | return NULL; 186 | } 187 | p_py_logger->set_log_level_by_value(args.log_level); 188 | 189 | 190 | std::string message = "py_gcode_arc_converter.ConvertFile - Beginning Arc Conversion."; 191 | p_py_logger->log(GCODE_CONVERSION, INFO, message); 192 | 193 | py_arc_welder arc_welder_obj(args.source_file_path, args.target_file_path, p_py_logger, args.resolution_mm, args.max_radius_mm, args.g90_g91_influences_extruder, 50, py_progress_callback); 194 | arc_welder_results results = arc_welder_obj.process(); 195 | message = "py_gcode_arc_converter.ConvertFile - Arc Conversion Complete."; 196 | p_py_logger->log(GCODE_CONVERSION, INFO, message); 197 | Py_XDECREF(py_progress_callback); 198 | // return the arguments 199 | PyObject* p_progress = py_arc_welder::build_py_progress(results.progress); 200 | if (p_progress == NULL) 201 | p_progress = Py_None; 202 | 203 | PyObject* p_results = Py_BuildValue( 204 | "{s:i,s:i,s:s,s:O}", 205 | "success", 206 | results.success, 207 | "cancelled", 208 | results.cancelled, 209 | "message", 210 | results.message.c_str(), 211 | "progress", 212 | p_progress 213 | ); 214 | return p_results; 215 | } 216 | } 217 | 218 | static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** py_progress_callback) 219 | { 220 | p_py_logger->log( 221 | GCODE_CONVERSION, INFO, 222 | "Parsing GCode Conversion Args." 223 | ); 224 | 225 | // Extract the source file path 226 | PyObject* py_source_file_path = PyDict_GetItemString(py_args, "source_file_path"); 227 | if (py_source_file_path == NULL) 228 | { 229 | std::string message = "ParseArgs - Unable to retrieve the source_file_path parameter from the args."; 230 | p_py_logger->log_exception(GCODE_CONVERSION, message); 231 | return false; 232 | } 233 | args.source_file_path = gcode_arc_converter::PyUnicode_SafeAsString(py_source_file_path); 234 | 235 | // Extract the target file path 236 | PyObject* py_target_file_path = PyDict_GetItemString(py_args, "target_file_path"); 237 | if (py_target_file_path == NULL) 238 | { 239 | std::string message = "ParseArgs - Unable to retrieve the target_file_path parameter from the args."; 240 | p_py_logger->log_exception(GCODE_CONVERSION, message); 241 | return false; 242 | } 243 | args.target_file_path = gcode_arc_converter::PyUnicode_SafeAsString(py_target_file_path); 244 | 245 | // Extract the resolution in millimeters 246 | PyObject* py_resolution_mm = PyDict_GetItemString(py_args, "resolution_mm"); 247 | if (py_resolution_mm == NULL) 248 | { 249 | std::string message = "ParseArgs - Unable to retrieve the resolution_mm parameter from the args."; 250 | p_py_logger->log_exception(GCODE_CONVERSION, message); 251 | return false; 252 | } 253 | args.resolution_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_resolution_mm); 254 | if (args.resolution_mm <= 0) 255 | { 256 | args.resolution_mm = 0.05; // Set to the default if no resolution is provided, or if it is less than 0. 257 | } 258 | 259 | // Extract the max_radius in mm 260 | PyObject* py_max_radius_mm = PyDict_GetItemString(py_args, "max_radius_mm"); 261 | if (py_max_radius_mm == NULL) 262 | { 263 | std::string message = "ParseArgs - Unable to retrieve the max_radius_mm parameter from the args."; 264 | p_py_logger->log_exception(GCODE_CONVERSION, message); 265 | return false; 266 | } 267 | args.max_radius_mm = gcode_arc_converter::PyFloatOrInt_AsDouble(py_max_radius_mm); 268 | if (args.max_radius_mm > DEFAULT_MAX_RADIUS_MM) 269 | { 270 | args.max_radius_mm = DEFAULT_MAX_RADIUS_MM; // Set to the default if no resolution is provided, or if it is less than 0. 271 | } 272 | 273 | // Extract G90/G91 influences extruder 274 | // g90_influences_extruder 275 | PyObject* py_g90_g91_influences_extruder = PyDict_GetItemString(py_args, "g90_g91_influences_extruder"); 276 | if (py_g90_g91_influences_extruder == NULL) 277 | { 278 | std::string message = "ParseArgs - Unable to retrieve g90_g91_influences_extruder from the args."; 279 | p_py_logger->log_exception(GCODE_CONVERSION, message); 280 | return false; 281 | } 282 | args.g90_g91_influences_extruder = PyLong_AsLong(py_g90_g91_influences_extruder) > 0; 283 | 284 | // on_progress_received 285 | PyObject* py_on_progress_received = PyDict_GetItemString(py_args, "on_progress_received"); 286 | if (py_on_progress_received == NULL) 287 | { 288 | std::string message = "ParseArgs - Unable to retrieve on_progress_received from the stabilization args."; 289 | p_py_logger->log_exception(GCODE_CONVERSION, message); 290 | return false; 291 | } 292 | // need to incref this so it doesn't vanish later (borrowed reference we are saving) 293 | Py_XINCREF(py_on_progress_received); 294 | *py_progress_callback = py_on_progress_received; 295 | 296 | // Extract log_level 297 | PyObject* py_log_level = PyDict_GetItemString(py_args, "log_level"); 298 | if (py_log_level == NULL) 299 | { 300 | std::string message = "ParseArgs - Unable to retrieve log_level from the args."; 301 | p_py_logger->log_exception(GCODE_CONVERSION, message); 302 | return false; 303 | } 304 | 305 | int log_level_value = static_cast(PyLong_AsLong(py_log_level)); 306 | // determine the log level as an index rather than as a value 307 | args.log_level = p_py_logger->get_log_level_for_value(log_level_value); 308 | 309 | return true; 310 | } 311 | 312 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/py_arc_welder_extension.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #pragma once 24 | #ifdef _DEBUG 25 | #undef _DEBUG 26 | #include 27 | #define _DEBUG 28 | #else 29 | #include 30 | #endif 31 | #include 32 | #include "py_logger.h" 33 | #include "arc_welder.h" 34 | extern "C" 35 | { 36 | #if PY_MAJOR_VERSION >= 3 37 | PyMODINIT_FUNC PyInit_PyArcWelder(void); 38 | #else 39 | extern "C" void initPyArcWelder(void); 40 | #endif 41 | static PyObject* ConvertFile(PyObject* self, PyObject* args); 42 | } 43 | 44 | struct py_gcode_arc_args { 45 | py_gcode_arc_args() { 46 | source_file_path = ""; 47 | target_file_path = ""; 48 | resolution_mm = DEFAULT_RESOLUTION_MM; 49 | max_radius_mm = DEFAULT_MAX_RADIUS_MM; 50 | g90_g91_influences_extruder = DEFAULT_G90_G91_INFLUENCES_EXTREUDER; 51 | log_level = 0; 52 | } 53 | py_gcode_arc_args(std::string source_file_path_, std::string target_file_path_, double resolution_mm_, double max_radius_mm_, bool g90_g91_influences_extruder_, int log_level_) { 54 | source_file_path = source_file_path_; 55 | target_file_path = target_file_path_; 56 | resolution_mm = resolution_mm_; 57 | max_radius_mm = max_radius_mm_; 58 | g90_g91_influences_extruder = g90_g91_influences_extruder_; 59 | log_level = log_level_; 60 | } 61 | std::string source_file_path; 62 | std::string target_file_path; 63 | double resolution_mm; 64 | bool g90_g91_influences_extruder; 65 | double max_radius_mm; 66 | int log_level; 67 | }; 68 | 69 | static bool ParseArgs(PyObject* py_args, py_gcode_arc_args& args, PyObject** p_py_progress_callback); 70 | 71 | // global logger 72 | py_logger* p_py_logger = NULL; 73 | /* 74 | static void AtExit() 75 | { 76 | if (p_py_logger != NULL) delete p_py_logger; 77 | }*/ 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/py_logger.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #include "py_logger.h" 24 | 25 | py_logger::py_logger(std::vector names, std::vector levels) : logger(names, levels) 26 | { 27 | loggers_created_ = false; 28 | check_log_levels_real_time = true; 29 | py_logging_module = NULL; 30 | py_logging_configurator_name = NULL; 31 | py_logging_configurator = NULL; 32 | py_arc_welder_gcode_conversion_logger = NULL; 33 | gcode_conversion_log_level = 0; 34 | py_info_function_name = NULL; 35 | py_warn_function_name = NULL; 36 | py_error_function_name = NULL; 37 | py_debug_function_name = NULL; 38 | py_verbose_function_name = NULL; 39 | py_critical_function_name = NULL; 40 | py_get_effective_level_function_name = NULL; 41 | } 42 | void py_logger::initialize_loggers() 43 | { 44 | // Create all of the objects necessary for logging 45 | // Import the arc_welder.log module 46 | py_logging_module = PyImport_ImportModuleNoBlock("octoprint_arc_welder.log"); 47 | if (py_logging_module == NULL) 48 | { 49 | PyErr_SetString(PyExc_ImportError, "Could not import module 'arc_welder.log'."); 50 | return; 51 | } 52 | 53 | // Get the logging configurator attribute string 54 | py_logging_configurator_name = PyObject_GetAttrString(py_logging_module, "LoggingConfigurator"); 55 | if (py_logging_configurator_name == NULL) 56 | { 57 | PyErr_SetString(PyExc_ImportError, "Could not acquire the LoggingConfigurator attribute string."); 58 | return; 59 | } 60 | 61 | // Create a logging configurator 62 | PyGILState_STATE gstate = PyGILState_Ensure(); 63 | PyObject* funcArgs = Py_BuildValue("(s,s,s)", "arc_welder", "arc_welder.", "octoprint_arc_welder."); 64 | if (funcArgs == NULL) 65 | { 66 | std::cout << "Unable to create LoggingConfigurator arguments, exiting.\r\n"; 67 | PyErr_SetString(PyExc_ImportError, "Could not create LoggingConfigurator arguments."); 68 | return; 69 | } 70 | 71 | py_logging_configurator = PyObject_CallObject(py_logging_configurator_name, funcArgs); 72 | std::cout << "Complete.\r\n"; 73 | Py_DECREF(funcArgs); 74 | PyGILState_Release(gstate); 75 | if (py_logging_configurator == NULL) 76 | { 77 | std::cout << "The LoggingConfigurator is null, exiting.\r\n"; 78 | PyErr_SetString(PyExc_ImportError, "Could not create a new instance of LoggingConfigurator."); 79 | return; 80 | } 81 | 82 | // Create the gcode_parser logging object 83 | py_arc_welder_gcode_conversion_logger = PyObject_CallMethod(py_logging_configurator, (char*)"get_logger", (char*)"s", "octoprint_arc_welder.gcode_conversion"); 84 | if (py_arc_welder_gcode_conversion_logger == NULL) 85 | { 86 | std::cout << "No child logger was created, exiting.\r\n"; 87 | PyErr_SetString(PyExc_ImportError, "Could not create the arc_welder.gcode_parser child logger."); 88 | return; 89 | } 90 | 91 | // create the function name py objects 92 | py_info_function_name = gcode_arc_converter::PyString_SafeFromString("info"); 93 | py_warn_function_name = gcode_arc_converter::PyString_SafeFromString("warn"); 94 | py_error_function_name = gcode_arc_converter::PyString_SafeFromString("error"); 95 | py_debug_function_name = gcode_arc_converter::PyString_SafeFromString("debug"); 96 | py_verbose_function_name = gcode_arc_converter::PyString_SafeFromString("verbose"); 97 | py_critical_function_name = gcode_arc_converter::PyString_SafeFromString("critical"); 98 | py_get_effective_level_function_name = gcode_arc_converter::PyString_SafeFromString("getEffectiveLevel"); 99 | loggers_created_ = true; 100 | } 101 | 102 | void py_logger::set_internal_log_levels(bool check_real_time) 103 | { 104 | check_log_levels_real_time = check_real_time; 105 | if (!check_log_levels_real_time) 106 | { 107 | 108 | PyObject* py_gcode_conversion_log_level = PyObject_CallMethodObjArgs(py_arc_welder_gcode_conversion_logger, py_get_effective_level_function_name, NULL); 109 | if (py_gcode_conversion_log_level == NULL) 110 | { 111 | PyErr_Print(); 112 | PyErr_SetString(PyExc_ValueError, "Logging.arc_welder - Could not retrieve the log level for the gcode parser logger."); 113 | } 114 | gcode_conversion_log_level = gcode_arc_converter::PyIntOrLong_AsLong(py_gcode_conversion_log_level); 115 | 116 | Py_XDECREF(py_gcode_conversion_log_level); 117 | } 118 | } 119 | 120 | void py_logger::log_exception(const int logger_type, const std::string& message) 121 | { 122 | log(logger_type, ERROR, message, true); 123 | } 124 | 125 | void py_logger::log(const int logger_type, const int log_level, const std::string& message) 126 | { 127 | log(logger_type, log_level, message, false); 128 | } 129 | 130 | void py_logger::log(const int logger_type, const int log_level, const std::string& message, bool is_exception) 131 | { 132 | if (!loggers_created_) 133 | return; 134 | 135 | // Get the appropriate logger 136 | PyObject* py_logger; 137 | long current_log_level = 0; 138 | switch (logger_type) 139 | { 140 | case GCODE_CONVERSION: 141 | py_logger = py_arc_welder_gcode_conversion_logger; 142 | current_log_level = gcode_conversion_log_level; 143 | break; 144 | default: 145 | std::cout << "Logging.arc_welder_log - unknown logger_type.\r\n"; 146 | PyErr_SetString(PyExc_ValueError, "Logging.arc_welder_log - unknown logger_type."); 147 | return; 148 | } 149 | 150 | if (!check_log_levels_real_time) 151 | { 152 | //std::cout << "Current Log Level: " << current_log_level << " requested:" << log_level; 153 | // For speed we are going to check the log levels here before attempting to send any logging info to Python. 154 | if (current_log_level > log_level) 155 | { 156 | return; 157 | } 158 | } 159 | 160 | PyObject* pyFunctionName = NULL; 161 | 162 | PyObject* error_type = NULL; 163 | PyObject* error_value = NULL; 164 | PyObject* error_traceback = NULL; 165 | bool error_occurred = false; 166 | if (is_exception) 167 | { 168 | // if an error has occurred, use the exception function to log the entire error 169 | pyFunctionName = py_error_function_name; 170 | if (PyErr_Occurred()) 171 | { 172 | error_occurred = true; 173 | PyErr_Fetch(&error_type, &error_value, &error_traceback); 174 | PyErr_NormalizeException(&error_type, &error_value, &error_traceback); 175 | } 176 | } 177 | else 178 | { 179 | switch (log_level) 180 | { 181 | case INFO: 182 | pyFunctionName = py_info_function_name; 183 | break; 184 | case WARNING: 185 | pyFunctionName = py_warn_function_name; 186 | break; 187 | case ERROR: 188 | pyFunctionName = py_error_function_name; 189 | break; 190 | case DEBUG: 191 | pyFunctionName = py_debug_function_name; 192 | break; 193 | case VERBOSE: 194 | pyFunctionName = py_verbose_function_name; 195 | break; 196 | case CRITICAL: 197 | pyFunctionName = py_critical_function_name; 198 | break; 199 | default: 200 | std::cout << "An unknown log level of '" << log_level << " 'was supplied for the message: " << message.c_str() << "\r\n"; 201 | PyErr_Format(PyExc_ValueError, 202 | "An unknown log level was supplied for the message %s.", message.c_str()); 203 | return; 204 | } 205 | } 206 | PyObject* pyMessage = gcode_arc_converter::PyUnicode_SafeFromString(message); 207 | if (pyMessage == NULL) 208 | { 209 | std::cout << "Unable to convert the log message '" << message.c_str() << "' to a PyString/Unicode message.\r\n"; 210 | PyErr_Format(PyExc_ValueError, 211 | "Unable to convert the log message '%s' to a PyString/Unicode message.", message.c_str()); 212 | return; 213 | } 214 | PyGILState_STATE state = PyGILState_Ensure(); 215 | PyObject* ret_val = PyObject_CallMethodObjArgs(py_logger, pyFunctionName, pyMessage, NULL); 216 | // We need to decref our message so that the GC can remove it. Maybe? 217 | Py_DECREF(pyMessage); 218 | PyGILState_Release(state); 219 | if (ret_val == NULL) 220 | { 221 | if (!PyErr_Occurred()) 222 | { 223 | std::cout << "Logging.arc_welder_log - null was returned from the specified logger.\r\n"; 224 | PyErr_SetString(PyExc_ValueError, "Logging.arc_welder_log - null was returned from the specified logger."); 225 | return; 226 | } 227 | else 228 | { 229 | std::cout << "Logging.arc_welder_log - null was returned from the specified logger and an error was detected.\r\n"; 230 | std::cout << "\tLog Level: " << log_level <<", Logger Type: " << logger_type << ", Message: " << message.c_str() << "\r\n"; 231 | 232 | // I'm not sure what else to do here since I can't log the error. I will print it 233 | // so that it shows up in the console, but I can't log it, and there is no way to 234 | // return an error. 235 | PyErr_Print(); 236 | PyErr_Clear(); 237 | return; 238 | } 239 | } 240 | else 241 | { 242 | // Set the exception if we are doing exception logging. 243 | if (is_exception) 244 | { 245 | if (error_occurred) 246 | PyErr_Restore(error_type, error_value, error_traceback); 247 | else 248 | PyErr_SetString(PyExc_Exception, message.c_str()); 249 | } 250 | } 251 | Py_XDECREF(ret_val); 252 | } 253 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/py_logger.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include "logger.h" 29 | #ifdef _DEBUG 30 | #undef _DEBUG 31 | #include 32 | #define _DEBUG 33 | #else 34 | #include 35 | #endif 36 | #include 37 | #include "python_helpers.h" 38 | #include 39 | enum py_loggers { GCODE_CONVERSION }; 40 | 41 | class py_logger : public logger { 42 | public: 43 | py_logger(std::vector names, std::vector levels); 44 | virtual ~py_logger() { 45 | } 46 | void initialize_loggers(); 47 | void set_internal_log_levels(bool check_real_time); 48 | virtual void log(const int logger_type, const int log_level, const std::string& message); 49 | virtual void log(const int logger_type, const int log_level, const std::string& message, bool is_exception); 50 | virtual void log_exception(const int logger_type, const std::string& message); 51 | private: 52 | bool check_log_levels_real_time; 53 | PyObject* py_logging_module; 54 | PyObject* py_logging_configurator_name; 55 | PyObject* py_logging_configurator; 56 | PyObject* py_arc_welder_gcode_conversion_logger; 57 | long gcode_conversion_log_level; 58 | PyObject* py_info_function_name; 59 | PyObject* py_warn_function_name; 60 | PyObject* py_error_function_name; 61 | PyObject* py_debug_function_name; 62 | PyObject* py_verbose_function_name; 63 | PyObject* py_critical_function_name; 64 | PyObject* py_get_effective_level_function_name; 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/python_helpers.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #include "python_helpers.h" 24 | namespace gcode_arc_converter { 25 | 26 | int PyUnicode_SafeCheck(PyObject* py) 27 | { 28 | #if PY_MAJOR_VERSION >= 3 29 | return PyUnicode_Check(py); 30 | #else 31 | return PyUnicode_Check(py); 32 | #endif 33 | } 34 | 35 | const char* PyUnicode_SafeAsString(PyObject* py) 36 | { 37 | #if PY_MAJOR_VERSION >= 3 38 | return PyUnicode_AsUTF8(py); 39 | #else 40 | return (char*)PyString_AsString(py); 41 | #endif 42 | } 43 | 44 | PyObject* PyString_SafeFromString(const char* str) 45 | { 46 | #if PY_MAJOR_VERSION >= 3 47 | return PyUnicode_FromString(str); 48 | #else 49 | return PyString_FromString(str); 50 | #endif 51 | } 52 | 53 | PyObject* PyUnicode_SafeFromString(std::string str) 54 | { 55 | #if PY_MAJOR_VERSION >= 3 56 | return PyUnicode_FromString(str.c_str()); 57 | #else 58 | // TODO: try PyUnicode_DecodeUnicodeEscape maybe? 59 | //return PyUnicode_DecodeUTF8(str.c_str(), NULL, "replace"); 60 | PyObject* pyString = PyString_FromString(str.c_str()); 61 | if (pyString == NULL) 62 | { 63 | PyErr_Print(); 64 | std::string message = "Unable to convert the c_str to a python string: "; 65 | message += str; 66 | PyErr_SetString(PyExc_ValueError, message.c_str()); 67 | return NULL; 68 | } 69 | PyObject* pyUnicode = PyUnicode_FromEncodedObject(pyString, NULL, "replace"); 70 | Py_DECREF(pyString); 71 | return pyUnicode; 72 | #endif 73 | } 74 | 75 | double PyFloatOrInt_AsDouble(PyObject* py_double_or_int) 76 | { 77 | if (PyFloat_CheckExact(py_double_or_int)) 78 | return PyFloat_AsDouble(py_double_or_int); 79 | #if PY_MAJOR_VERSION < 3 80 | else if (PyInt_CheckExact(py_double_or_int)) 81 | return static_cast(PyInt_AsLong(py_double_or_int)); 82 | #endif 83 | else if (PyLong_CheckExact(py_double_or_int)) 84 | return static_cast(PyLong_AsLong(py_double_or_int)); 85 | return 0; 86 | } 87 | 88 | long PyIntOrLong_AsLong(PyObject* value) 89 | { 90 | long ret_val; 91 | #if PY_MAJOR_VERSION < 3 92 | if (PyInt_Check(value)) 93 | { 94 | ret_val = PyInt_AsLong(value); 95 | } 96 | else 97 | { 98 | ret_val = PyLong_AsLong(value); 99 | } 100 | #else 101 | ret_val = PyLong_AsLong(value); 102 | #endif 103 | return ret_val; 104 | } 105 | 106 | bool PyFloatLongOrInt_Check(PyObject* py_object) 107 | { 108 | return ( 109 | PyFloat_Check(py_object) || PyLong_Check(py_object) 110 | #if PY_MAJOR_VERSION < 3 111 | || PyInt_Check(py_object) 112 | #endif 113 | ); 114 | 115 | } 116 | } -------------------------------------------------------------------------------- /octoprint_arc_welder/data/lib/c/py_arc_welder/python_helpers.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Arc Welder: Anti-Stutter Python Extension for the OctoPrint Arc Welder plugin. 3 | // 4 | // Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. 5 | // This reduces file size and the number of gcodes per second. 6 | // 7 | // Copyright(C) 2020 - Brad Hochgesang 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // This program is free software : you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published 11 | // by the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // 20 | // You can contact the author at the following email address: 21 | // FormerLurker@pm.me 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | #pragma once 24 | #ifdef _DEBUG 25 | #undef _DEBUG 26 | #include 27 | #define _DEBUG 28 | #else 29 | #include 30 | #endif 31 | #include 32 | namespace gcode_arc_converter { 33 | int PyUnicode_SafeCheck(PyObject* py); 34 | const char* PyUnicode_SafeAsString(PyObject* py); 35 | PyObject* PyString_SafeFromString(const char* str); 36 | PyObject* PyUnicode_SafeFromString(std::string str); 37 | double PyFloatOrInt_AsDouble(PyObject* py_double_or_int); 38 | long PyIntOrLong_AsLong(PyObject* value); 39 | bool PyFloatLongOrInt_Check(PyObject* value); 40 | } -------------------------------------------------------------------------------- /octoprint_arc_welder/log.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # ################################################################################# 3 | # Arc Welder: Anti-Stutter 4 | # 5 | # A plugin for OctoPrint that converts G0/G1 commands into G2/G3 commands where possible and ensures that the tool 6 | # paths don't deviate by more than a predefined resolution. This compresses the gcode file sice, and reduces reduces 7 | # the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3) 8 | # 9 | # Copyright (C) 2020 Brad Hochgesang 10 | # ################################################################################# 11 | # This program is free software: 12 | # you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with this program. If not, see the following: 22 | # https://github.com/FormerLurker/ArcWelderPlugin/blob/master/LICENSE 23 | # 24 | # You can contact the author either through the git-hub repository, or at the 25 | # following email address: FormerLurker@pm.me 26 | ################################################################################## 27 | from __future__ import unicode_literals 28 | import logging 29 | import datetime as datetime 30 | import os 31 | import six 32 | from octoprint.logging.handlers import ( 33 | AsyncLogHandlerMixin, 34 | CleaningTimedRotatingFileHandler, 35 | ) 36 | 37 | class Singleton(type): 38 | _instances = {} 39 | 40 | def __call__(cls, *args, **kwargs): 41 | if cls not in cls._instances: 42 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 43 | return cls._instances[cls] 44 | # custom log level - VERBOSE 45 | VERBOSE = 5 46 | DEBUG = logging.DEBUG 47 | INFO = logging.INFO 48 | WARNING = logging.WARNING 49 | ERROR = logging.ERROR 50 | CRITICAL = logging.CRITICAL 51 | logging.addLevelName(VERBOSE, "VERBOSE") 52 | 53 | 54 | def verbose(self, msg, *args, **kwargs): 55 | if self.isEnabledFor(VERBOSE): 56 | self.log(VERBOSE, msg, *args, **kwargs) 57 | 58 | 59 | logging.Logger.verbose = verbose 60 | # end custom log level - VERBOSE 61 | 62 | 63 | def format_log_time(time_seconds): 64 | log_time = datetime.datetime.fromtimestamp(time_seconds) 65 | t = datetime.datetime.strftime( 66 | log_time, "%Y-%m-%d %H:%M:%S,{0:03}".format(int(log_time.microsecond / 1000)) 67 | ) 68 | return t 69 | 70 | 71 | class ArcWelderFormatter(logging.Formatter): 72 | def __init__( 73 | self, fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt=None 74 | ): 75 | super(ArcWelderFormatter, self).__init__(fmt=fmt, datefmt=datefmt) 76 | 77 | def formatTime(self, record, datefmt=None): 78 | ct = self.converter(record.created) 79 | if datefmt: 80 | s = ct.strftime(datefmt) 81 | else: 82 | return format_log_time(record.created) 83 | return s 84 | 85 | 86 | class ArcWelderConsoleHandler(logging.StreamHandler, AsyncLogHandlerMixin): 87 | def __init__(self, *args, **kwargs): 88 | super(ArcWelderConsoleHandler, self).__init__(*args, **kwargs) 89 | 90 | 91 | class ArcWelderFileHandler(CleaningTimedRotatingFileHandler, AsyncLogHandlerMixin): 92 | def __init__(self, *args, **kwargs): 93 | super(ArcWelderFileHandler, self).__init__(*args, **kwargs) 94 | 95 | def delete_all_backups(self): 96 | # clean up old files on handler start 97 | backup_count = self.backupCount 98 | self.backupCount = 0 99 | for s in self.getFilesToDelete(): 100 | os.remove(s) 101 | self.backupCount = backup_count 102 | 103 | 104 | @six.add_metaclass(Singleton) 105 | class LoggingConfigurator(object): 106 | BACKUP_COUNT = 3 107 | 108 | def __init__(self, root_logger_name, log_entry_prefix, log_file_prefix): 109 | self.logging_formatter = ArcWelderFormatter() 110 | self._root_logger_name = root_logger_name # "arc_welder" 111 | self._log_entry_prefix = log_entry_prefix # "arc_welder." 112 | self._log_file_prefix = log_file_prefix # "octoprint_arc_welder." 113 | self._root_logger = self._get_root_logger(self._root_logger_name) 114 | 115 | self._level = logging.DEBUG 116 | self._file_handler = None 117 | self._console_handler = None 118 | self.child_loggers = set() 119 | 120 | def _get_root_logger(self, name): 121 | """Get a logger instance that uses new-style string formatting""" 122 | log = logging.getLogger(name) 123 | log.setLevel(logging.NOTSET) 124 | log.propagate = False 125 | return log 126 | 127 | def get_logger_names(self): 128 | logger_names = [] 129 | for logger_name in self.child_loggers: 130 | logger_names.append(logger_name) 131 | return logger_names 132 | 133 | def get_root_logger(self): 134 | return self._root_logger 135 | 136 | def get_logger(self, name): 137 | if name == self._root_logger_name: 138 | return self._root_logger 139 | 140 | if name.startswith(self._log_file_prefix): 141 | name = name[len(self._log_file_prefix):] 142 | 143 | full_name = "arc_welder." + name 144 | 145 | self.child_loggers.add(full_name) 146 | child = self._root_logger.getChild(name) 147 | return child 148 | 149 | def _remove_handlers(self): 150 | if self._file_handler is not None: 151 | self._root_logger.removeHandler(self._file_handler) 152 | self._file_handler = None 153 | 154 | if self._console_handler is not None: 155 | self._root_logger.removeHandler(self._console_handler) 156 | self._console_handler = None 157 | 158 | def _add_file_handler(self, log_file_path, log_level): 159 | self._file_handler = ArcWelderFileHandler( 160 | log_file_path, when="D", backupCount=LoggingConfigurator.BACKUP_COUNT 161 | ) 162 | self._file_handler.setFormatter(self.logging_formatter) 163 | self._file_handler.setLevel(log_level) 164 | self._root_logger.addHandler(self._file_handler) 165 | 166 | def _add_console_handler(self, log_level): 167 | self._console_handler = ArcWelderConsoleHandler() 168 | self._console_handler.setFormatter(self.logging_formatter) 169 | self._console_handler.setLevel(log_level) 170 | self._root_logger.addHandler(self._console_handler) 171 | 172 | def do_rollover(self, clear_all=False): 173 | if self._file_handler is None: 174 | return 175 | # To clear everything, we'll roll over every file 176 | self._file_handler.doRollover() 177 | if clear_all: 178 | self._file_handler.delete_all_backups() 179 | 180 | def configure_loggers(self, log_file_path=None, logging_settings=None): 181 | default_log_level = logging.DEBUG 182 | log_to_console = True 183 | if logging_settings is not None: 184 | if "default_log_level" in logging_settings: 185 | default_log_level = logging_settings["default_log_level"] 186 | if "log_to_console" in logging_settings: 187 | log_to_console = logging_settings["log_to_console"] 188 | 189 | # clear any handlers from the root logger 190 | self._remove_handlers() 191 | # set the log level 192 | self._root_logger.setLevel(logging.NOTSET) 193 | 194 | if log_file_path is not None: 195 | # ensure that the logging path and file exist 196 | directory = os.path.dirname(log_file_path) 197 | import distutils.dir_util 198 | 199 | distutils.dir_util.mkpath(directory) 200 | if not os.path.isfile(log_file_path): 201 | open(log_file_path, "w").close() 202 | 203 | # add the file handler 204 | self._add_file_handler(log_file_path, logging.NOTSET) 205 | 206 | # if we are logging to console, add the console logging handler 207 | if log_to_console: 208 | self._add_console_handler(logging.NOTSET) 209 | for logger_full_name in self.child_loggers: 210 | 211 | if logger_full_name.startswith(self._log_entry_prefix): 212 | logger_name = logger_full_name[len(self._log_entry_prefix):] 213 | else: 214 | logger_name = logger_full_name 215 | if logging_settings is not None: 216 | current_logger = self._root_logger.getChild(logger_name) 217 | found_enabled_logger = None 218 | if "enabled_loggers" in logging_settings: 219 | for enabled_logger in logging_settings["enabled_loggers"]: 220 | if enabled_logger["name"] == logger_full_name: 221 | found_enabled_logger = enabled_logger 222 | break 223 | 224 | if found_enabled_logger is None or current_logger.level > logging.ERROR: 225 | current_logger.setLevel(logging.ERROR) 226 | 227 | elif found_enabled_logger is not None: 228 | current_logger.setLevel(found_enabled_logger["log_level"]) 229 | else: 230 | # log level critical + 1 will not log anything 231 | current_logger.setLevel(logging.CRITICAL + 1) 232 | else: 233 | current_logger = self._root_logger.getChild(logger_name) 234 | current_logger.setLevel(default_log_level) 235 | -------------------------------------------------------------------------------- /octoprint_arc_welder/preprocessor.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # ################################################################################# 3 | # Arc Welder: Anti-Stutter 4 | # 5 | # A plugin for OctoPrint that converts G0/G1 commands into G2/G3 commands where possible and ensures that the tool 6 | # paths don't deviate by more than a predefined resolution. This compresses the gcode file sice, and reduces reduces 7 | # the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3) 8 | # 9 | # Copyright (C) 2020 Brad Hochgesang 10 | # ################################################################################# 11 | # This program is free software: 12 | # you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with this program. If not, see the following: 22 | # https://github.com/FormerLurker/ArcWelderPlugin/blob/master/LICENSE 23 | # 24 | # You can contact the author either through the git-hub repository, or at the 25 | # following email address: FormerLurker@pm.me 26 | ################################################################################## 27 | from __future__ import absolute_import 28 | from __future__ import unicode_literals 29 | import threading 30 | import octoprint_arc_welder.utilities as utilities 31 | import octoprint_arc_welder.log as log 32 | import time 33 | import shutil 34 | import os 35 | import PyArcWelder as converter # must import AFTER log, else this will fail to log and may crasy 36 | try: 37 | import queue 38 | except ImportError: 39 | import Queue as queue 40 | 41 | logging_configurator = log.LoggingConfigurator("arc_welder", "arc_welder.", "octoprint_arc_welder.") 42 | root_logger = logging_configurator.get_root_logger() 43 | # so that we can 44 | logger = logging_configurator.get_logger(__name__) 45 | 46 | class PreProcessorWorker(threading.Thread): 47 | """Watch for rendering jobs via a rendering queue. Extract jobs from the queue, and spawn a rendering thread, 48 | one at a time for each rendering job. Notify the calling thread of the number of jobs in the queue on demand.""" 49 | def __init__( 50 | self, 51 | data_folder, 52 | task_queue, 53 | is_printing_callback, 54 | start_callback, 55 | progress_callback, 56 | cancel_callback, 57 | failed_callback, 58 | success_callback, 59 | completed_callback 60 | ): 61 | super(PreProcessorWorker, self).__init__() 62 | self._source_file_path = os.path.join(data_folder, "source.gcode") 63 | self._target_file_path = os.path.join(data_folder, "target.gcode") 64 | self._idle_sleep_seconds = 2.5 # wait at most 2.5 seconds for a rendering job from the queue 65 | self._task_queue = task_queue 66 | self._is_printing_callback = is_printing_callback 67 | self._start_callback = start_callback 68 | self._progress_callback = progress_callback 69 | self._cancel_callback = cancel_callback 70 | self._failed_callback = failed_callback 71 | self._success_callback = success_callback 72 | self._completed_callback = completed_callback 73 | self._is_processing = False 74 | self._current_file_processing_path = None 75 | self._is_cancelled = False 76 | self.r_lock = threading.RLock() 77 | 78 | def cancel_all(self): 79 | while not self._task_queue.empty(): 80 | path, processor_args = self._task_queue.get(False) 81 | logger.info("Preprocessing of %s has been cancelled.", processor_args["path"]) 82 | self._cancel_callback(path, processor_args) 83 | 84 | def is_processing(self): 85 | with self.r_lock: 86 | is_processing = (not self._task_queue.empty()) or self._is_processing 87 | return is_processing 88 | 89 | def run(self): 90 | while True: 91 | try: 92 | # see if there are any rendering tasks. 93 | time.sleep(self._idle_sleep_seconds) 94 | 95 | if not self._task_queue.empty(): 96 | # add an additional sleep in case this file was uploaded 97 | # from cura to give the printer state a chance to catch up. 98 | time.sleep(0.1) 99 | if self._is_printing_callback(): 100 | continue 101 | 102 | path, processor_args, additional_metadata, is_manual_request = self._task_queue.get(False) 103 | success = False 104 | try: 105 | self._process(path, processor_args, additional_metadata, is_manual_request) 106 | except Exception as e: 107 | logger.exception("An unhandled exception occurred while preprocessing the gcode file.") 108 | message = "An error occurred while preprocessing {0}. Check plugin_arc_welder.log for details.".\ 109 | format(path) 110 | self._failed_callback(message) 111 | finally: 112 | self._completed_callback() 113 | except queue.Empty: 114 | pass 115 | 116 | def _process(self, path, processor_args, additional_metadata, is_manual_request): 117 | self._start_callback(path, processor_args) 118 | logger.info( 119 | "Copying source gcode file at %s to %s for processing.", processor_args["path"], self._source_file_path 120 | ) 121 | if not os.path.exists(processor_args["path"]): 122 | message = "The source file path at '{0}' does not exist. It may have been moved or deleted". \ 123 | format(processor_args["path"]) 124 | self._failed_callback(message) 125 | return 126 | shutil.copy(processor_args["path"], self._source_file_path) 127 | source_filename = utilities.get_filename_from_path(processor_args["path"]) 128 | # Add arguments to the processor_args dict 129 | processor_args["on_progress_received"] = self._progress_received 130 | processor_args["source_file_path"] = self._source_file_path 131 | processor_args["target_file_path"] = self._target_file_path 132 | # Convert the file via the C++ extension 133 | logger.info( 134 | "Calling conversion routine on copied source gcode file to target at %s.", self._source_file_path 135 | ) 136 | try: 137 | results = converter.ConvertFile(processor_args) 138 | except Exception as e: 139 | # It would be better to catch only specific errors here, but we will log them. Any 140 | # unhandled errors that occur would shut down the worker thread until reboot. 141 | # Since exceptions are always logged, so this is reasonably safe. 142 | 143 | # Log the exception 144 | logger.exception( 145 | "An unexpected exception occurred while preprocessing %s.", processor_args["path"] 146 | ) 147 | # create results that will be sent back to the client for notification of failure. 148 | results = { 149 | "cancelled": False, 150 | "success": False, 151 | "message": "An unexpected exception occurred while preprocessing the gcode file at {0}. Please see " 152 | "plugin_arc_welder.log for more details.".format(processor_args["path"]) 153 | } 154 | # the progress payload will all be in bytes (str for python 2) format. 155 | # Make sure everything is in unicode (str for python3) because mixed encoding 156 | # messes with things. 157 | 158 | encoded_results = utilities.dict_encode(results) 159 | encoded_results["source_filename"] = source_filename 160 | if encoded_results["cancelled"]: 161 | logger.info("Preprocessing of %s has been cancelled.", processor_args["path"]) 162 | self._cancel_callback(path, processor_args) 163 | elif encoded_results["success"]: 164 | logger.info("Preprocessing of %s completed.", processor_args["path"]) 165 | # Save the produced gcode file 166 | self._success_callback(encoded_results, path, processor_args, additional_metadata, is_manual_request) 167 | else: 168 | self._failed_callback(encoded_results["message"]) 169 | 170 | logger.info("Deleting temporary source.gcode file.") 171 | if os.path.isfile(self._source_file_path): 172 | os.unlink(self._source_file_path) 173 | logger.info("Deleting temporary target.gcode file.") 174 | if os.path.isfile(self._target_file_path): 175 | os.unlink(self._target_file_path) 176 | 177 | 178 | def _progress_received(self, progress): 179 | # the progress payload will all be in bytes (str for python 2) format. 180 | # Make sure everything is in unicode (str for python3) because mixed encoding 181 | # messes with things. 182 | encoded_progresss = utilities.dict_encode(progress) 183 | logger.verbose("Progress Received: %s", encoded_progresss) 184 | return self._progress_callback(encoded_progresss) 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /octoprint_arc_welder/static/css/arc_welder.css: -------------------------------------------------------------------------------- 1 | /*################################################################################ 2 | # Arc Welder: Anti-Stutter 3 | # 4 | # A plugin for OctoPrint that converts G0/G1 commands into G2/G3 commands where possible and ensures that the tool 5 | # paths don't deviate by more than a predefined resolution. This compresses the gcode file sice, and reduces reduces 6 | # the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3) 7 | # 8 | # Copyright (C) 2020 Brad Hochgesang 9 | # ################################################################################# 10 | # This program is free software: 11 | # you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU Affero General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Affero General Public License 20 | # along with this program. If not, see the following: 21 | # https://github.com/FormerLurker/ArcWelderPlugin/blob/master/LICENSE 22 | # 23 | # You can contact the author either through the git-hub repository, or at the 24 | # following email address: FormerLurker@pm.me 25 | ###################################################################################*/ 26 | .arc-welder.progress-bar-container{ 27 | width:100%; 28 | display: inline-flex; 29 | flex-grow: 1; 30 | } 31 | 32 | .arc-welder.progress-bar { 33 | height: 100%; 34 | background: green; 35 | color: white; 36 | text-align: center; 37 | } 38 | 39 | .arc-welder.progress-bar.failed { 40 | background: red; 41 | } 42 | 43 | div.ui-pnotify.arc-welder { 44 | width:400px!important; 45 | } 46 | 47 | .arc-welder .control-label{ 48 | padding-right: 10px; 49 | } 50 | 51 | #tab_plugin_arc_welder .border 52 | { 53 | border: 1px solid transparent; 54 | border-color: inherit; 55 | } 56 | 57 | #tab_plugin_arc_welder .overflow-ellipsis { 58 | text-overflow: ellipsis; 59 | overflow: hidden; 60 | } 61 | -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.delete_source.md: -------------------------------------------------------------------------------- 1 | Arcwelder can automatically delete the source file for you. The available options are: 2 | 3 | * *Always Delete Source File* - The source file will always be deleted after processing is completed. 4 | * *Delete After Automatic Processing* - Automatically processed source files will be deleted, but manually processed source files will *NOT* be deleted. 5 | * *Delete After Manual Processing* - Only manually processed source files will be deleted. 6 | * *Disabled* - The source file will not be deleted. 7 | 8 | Arcwelder can only delete the source file if it is not printing. 9 | 10 | If the ```Overwrite Source File``` option is enabled, the source file will be overwritten with the processed file. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.enabled.md: -------------------------------------------------------------------------------- 1 | Check or uncheck to enable or disable the plugin. This prevents Arc Welder from converting any files and adding buttons to the file browser. It will not remove the plugin from the tabs or settings pages. If you want to truly disable Arc Welder please do so in the plugin manager. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.file_processing.md: -------------------------------------------------------------------------------- 1 | There are three options here: 2 | * *Automatic Processing Only* - Newly uploaded files will be compressed automatically. 3 | * *Manual Processing Only* - Convert files by clicking on the compress button in the file manager. Files that are already compressed will have the compress button disabled. 4 | * *Automatic and Manual Processing* - Newly uploaded files will automatically be converted **and** you will be able to compress files by clicking the compress button in the file manager. 5 | 6 | The default setting is *Automatic and Manual Processing*. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.g90_g91_influences_extruder.md: -------------------------------------------------------------------------------- 1 | If *Use Octoprint Printer Settings* is unchecked, **Arc Welder** will use this setting to determine if the G90/G91 command influences your extruder's axis mode. In general, Marlin 2.0 and forks should have this box checked. Many forks of Marlin 1.x should have this unchecked, like the Prusa MK2 and MK3. I will try to add a list of printers and the proper value for this setting at some point, as well as a gcode test script you can use to determine what setting to use. Keep in mind that most slicers produce code that will work fine no matter what setting you choose here. Default: Disabled -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.max_radius_mm.md: -------------------------------------------------------------------------------- 1 | This is a safety feature to prevent unusually large arcs from being generated. Internally Arc Welder uses a constant to prevent arcs with a very large radius from being generated where the path is essentially (but not exactly) a straight line. If it is not perfectly straight, and if my constant isn't conservative enough, an extremely large arc could be created that may have the wrong direction of rotation. The default value works fine for all of the gcode I've tested (it is about 1/7th of the radius of the worst errant arc I've encountered). If you discover that you need to adjust this setting because of errant arcs, please [create an issue](https://github.com/FormerLurker/ArcWelderPlugin/issues/new) and let me know! The default setting is **1000000 mm** or **1KM**. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.overwrite_source_file.md: -------------------------------------------------------------------------------- 1 | When selected, Arc Welder will overwrite the original file with the compressed version. 2 | 3 | Arc Welder *cannot* overwrite the source file if it is printing, and will return an error. 4 | 5 | Default Value: disabled. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.resolution_mm.md: -------------------------------------------------------------------------------- 1 | This setting controls how much play Arc Welder has in converting gcode points into arcs. If the arc deviates from the original points by + or - 1/2 of the resolution, the points will not be converted. The default setting is 0.05 which means the arcs may not deviate by more than +- 0.025mm (which is a really tiny deviation). Increasing the resolution will result in more arcs being converted, but will make the tool paths less accurate. Decreasing the resolution will result in fewer arcs, but more accurate toolpaths. I don't recommend going above 0.1MM. Higher values than that may result in print failure. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.show_completed_notification.md: -------------------------------------------------------------------------------- 1 | When enabled **Arc Welder** will create a toast showing that pre-processing has completed, including some interesting statistics. Default: Enabled -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.show_progress_bar.md: -------------------------------------------------------------------------------- 1 | When enabled **Arc Welder** will create a progress bar showing statistics about the file being currently processed. There is also a *Cancel* and *Cancel All* button on the progress display. Default: Enabled -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.show_started_notification.md: -------------------------------------------------------------------------------- 1 | When enabled **Arc Welder** will create a toast showing that pre-processing is initializing. Default: Enabled -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.target_postfix.md: -------------------------------------------------------------------------------- 1 | When *Overwrite Source File* is disabled **Arc Welder** will produce a new file with this postfix before the file extension. For example, if you use **.aw** for your postfix, and your source file is called **print.gcode** the resulting file would be called **print.aw.gcode**. Default: .aw 2 | 3 | Note: You can combine prefixes and postfixes if you like. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.target_prefix.md: -------------------------------------------------------------------------------- 1 | When *Overwrite Source File* is disabled, **Arc Welder** will produce a new file with this prefix. For example, if you use **AW_** as your prefix, and your source file is called **print.gcode** the output file would be called **AW_print.gcode**. Default: NO PREFIX 2 | 3 | Note: You can combine prefixes and postfixes if you like. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/docs/help/settings.use_octoprint_settings.md: -------------------------------------------------------------------------------- 1 | Octoprint has a setting for *G90/G91 influences extruder* in the *Features* tab. Enabling *Use Octoprint Printer Settings* will cause **Arc Welder** to use OctoPrint's setting. If this is unchecked, you can specify the *G90/G91 influences extruder* manually. Default: Enabled 2 | 3 | Note: This is an EXTREMELY important setting. In general, Marlin 2.0 and forks should have this box checked. Many forks of Marlin 1.x should have this unchecked, like the Prusa MK2 and MK3. If this setting is incorrect, inaccurate GCode may be produced. -------------------------------------------------------------------------------- /octoprint_arc_welder/static/js/arc_welder.settings.js: -------------------------------------------------------------------------------- 1 | /*################################################################################ 2 | # Arc Welder: Anti-Stutter 3 | # 4 | # A plugin for OctoPrint that converts G0/G1 commands into G2/G3 commands where possible and ensures that the tool 5 | # paths don't deviate by more than a predefined resolution. This compresses the gcode file sice, and reduces reduces 6 | # the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3) 7 | # 8 | # Copyright (C) 2020 Brad Hochgesang 9 | # ################################################################################# 10 | # This program is free software: 11 | # you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU Affero General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Affero General Public License 20 | # along with this program. If not, see the following: 21 | # https://github.com/FormerLurker/ArcWelderPlugin/blob/master/LICENSE 22 | # 23 | # You can contact the author either through the git-hub repository, or at the 24 | # following email address: FormerLurker@pm.me 25 | ###################################################################################*/ 26 | $(function() { 27 | 28 | function ArcWelderSettingsViewModel(parameters) { 29 | var self = this; 30 | 31 | self.settings = parameters[0]; 32 | self.plugin_settings = ko.observable(null); 33 | self.data = ko.observable(); 34 | self.data.logging_levels = [ 35 | {name:"Verbose", value: 5}, 36 | {name:"Debug", value: 10}, 37 | {name:"Info", value: 20}, 38 | {name:"Warning", value: 30}, 39 | ]; 40 | self.logger_name_add = ko.observable(); 41 | self.logger_level_add = ko.observable(); 42 | self.data.all_logger_names = ["arc_welder.__init__", "arc_welder.gcode_conversion"]; 43 | self.data.default_log_level = 20; 44 | 45 | self.onBeforeBinding = function() { 46 | // Make plugin setting access a little more terse 47 | self.plugin_settings(self.settings.settings.plugins.arc_welder); 48 | self.available_loggers = ko.computed(function () { 49 | var available_loggers = []; 50 | for (var logger_index = 0; logger_index < self.data.all_logger_names.length; logger_index++) { 51 | var logger_name = self.data.all_logger_names[logger_index]; 52 | var found_logger_index = self.get_enabled_logger_index_by_name(logger_name); 53 | if (found_logger_index === -1) { 54 | available_loggers.push({'name': logger_name, 'log_level': self.data.default_log_level}); 55 | } 56 | } 57 | return available_loggers; 58 | }, self); 59 | 60 | self.available_loggers_sorted = ko.computed(function () { 61 | return self.loggerNameSort(self.available_loggers) 62 | }, self); 63 | 64 | }; 65 | 66 | self.onAfterBinding = function() { 67 | ArcWelder.Help.bindHelpLinks("div#arc_welder_settings"); 68 | }; 69 | 70 | self.get_enabled_logger_index_by_name = function (name) { 71 | for (var index = 0; index < self.plugin_settings().logging_configuration.enabled_loggers().length; index++) { 72 | var logger = self.plugin_settings().logging_configuration.enabled_loggers()[index]; 73 | var cur_name = ""; 74 | if(ko.isObservable(logger.name)) 75 | cur_name = logger.name(); 76 | else 77 | cur_name = logger.name; 78 | if (cur_name === name) { 79 | return index; 80 | } 81 | } 82 | return -1; 83 | }; 84 | 85 | self.loggerNameSort = function (observable) { 86 | return observable().sort( 87 | function (left, right) { 88 | var leftName = left.name.toLowerCase(); 89 | var rightName = right.name.toLowerCase(); 90 | return leftName === rightName ? 0 : (leftName < rightName ? -1 : 1); 91 | }); 92 | }; 93 | 94 | self.removeLogger = function (logger) { 95 | //console.log("removing logger."); 96 | self.plugin_settings().logging_configuration.enabled_loggers.remove(logger); 97 | }; 98 | 99 | self.addLogger = function () { 100 | //console.log("Adding logger"); 101 | var index = self.get_enabled_logger_index_by_name(self.logger_name_add()); 102 | if (index === -1) { 103 | self.plugin_settings().logging_configuration.enabled_loggers.push({'name': self.logger_name_add(), 'log_level': self.logger_level_add()}); 104 | } 105 | }; 106 | 107 | self.restoreDefaultSettings = function() { 108 | PNotifyExtensions.showConfirmDialog( 109 | "restore_default_settings", 110 | "Restore Arc Welder Default Settings", 111 | "This will restore all of the default settings, are you sure?", 112 | function () { 113 | $.ajax({ 114 | url: ArcWelder.APIURL("restoreDefaultSettings"), 115 | type: "POST", 116 | contentType: "application/json", 117 | success: function (data) { 118 | var options = { 119 | title: "Arc Welder Default Settings Restored", 120 | text: "The settings have been restored.", 121 | type: 'success', 122 | hide: true, 123 | addclass: "arc_welder", 124 | desktop: { 125 | desktop: true 126 | } 127 | }; 128 | PNotifyExtensions.displayPopupForKey( 129 | options, 130 | ArcWelder.PopupKey("settings_restored"), 131 | ArcWelder.PopupKey("settings_restored") 132 | ); 133 | }, 134 | error: function (XMLHttpRequest, textStatus, errorThrown) { 135 | var message = "Unable to restore the default settings. Status: " + textStatus + ". Error: " + errorThrown; 136 | var options = { 137 | title: 'Restore Default Settings Error', 138 | text: message, 139 | type: 'error', 140 | hide: false, 141 | addclass: "arc_welder", 142 | desktop: { 143 | desktop: true 144 | } 145 | }; 146 | PNotifyExtensions.displayPopupForKey( 147 | options, 148 | ArcWelder.PopupKey("settings_restore_error"), 149 | ArcWelder.PopupKey("settings_restore_error") 150 | ); 151 | } 152 | }); 153 | } 154 | ); 155 | }; 156 | 157 | self.clearLog = function (clear_all) { 158 | var title; 159 | var message; 160 | if (clear_all) { 161 | title = "Clear All Logs"; 162 | message = "All Arc Welder log files will be cleared and deleted. Are you sure?"; 163 | } else { 164 | title = "Clear Log"; 165 | message = "The most recent Arc Welder log file will be cleared. Are you sure?"; 166 | } 167 | PNotifyExtensions.showConfirmDialog( 168 | "clear_log", 169 | title, 170 | message, 171 | function () { 172 | if (clear_all) { 173 | title = "Logs Cleared"; 174 | message = "All Arc Welder log files have been cleared."; 175 | } else { 176 | title = "Most Recent Log Cleared"; 177 | message = "The most recent Arc Welder log file has been cleared."; 178 | } 179 | var data = { 180 | clear_all: clear_all 181 | }; 182 | $.ajax({ 183 | url: ArcWelder.APIURL("clearLog"), 184 | type: "POST", 185 | data: JSON.stringify(data), 186 | contentType: "application/json", 187 | dataType: "json", 188 | success: function (data) { 189 | var options = { 190 | title: title, 191 | text: message, 192 | type: 'success', 193 | hide: true, 194 | addclass: "arc_welder", 195 | desktop: { 196 | desktop: true 197 | } 198 | }; 199 | PNotifyExtensions.displayPopupForKey( 200 | options, 201 | ArcWelder.PopupKey("log_file_cleared"), 202 | ArcWelder.PopupKey("log_file_cleared") 203 | ); 204 | }, 205 | error: function (XMLHttpRequest, textStatus, errorThrown) { 206 | var message = "Unable to clear the log.:( Status: " + textStatus + ". Error: " + errorThrown; 207 | var options = { 208 | title: 'Clear Log Error', 209 | text: message, 210 | type: 'error', 211 | hide: false, 212 | addclass: "arc_welder", 213 | desktop: { 214 | desktop: true 215 | } 216 | }; 217 | PNotifyExtensions.displayPopupForKey( 218 | options, 219 | ArcWelder.PopupKey("log_file_cleared"), 220 | ArcWelder.PopupKey("log_file_cleared") 221 | ); 222 | } 223 | }); 224 | } 225 | ); 226 | 227 | }; 228 | } 229 | 230 | OCTOPRINT_VIEWMODELS.push([ 231 | ArcWelderSettingsViewModel, 232 | ["settingsViewModel"], 233 | ["#arc_welder_settings"] 234 | ]); 235 | }); 236 | -------------------------------------------------------------------------------- /octoprint_arc_welder/static/js/pnotify_extensions.js: -------------------------------------------------------------------------------- 1 | /*################################################################################ 2 | # Arc Welder: Anti-Stutter 3 | # 4 | # A plugin for OctoPrint that converts G0/G1 commands into G2/G3 commands where possible and ensures that the tool 5 | # paths don't deviate by more than a predefined resolution. This compresses the gcode file sice, and reduces reduces 6 | # the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3) 7 | # 8 | # Copyright (C) 2020 Brad Hochgesang 9 | # ################################################################################# 10 | # This program is free software: 11 | # you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU Affero General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Affero General Public License 20 | # along with this program. If not, see the following: 21 | # https://github.com/FormerLurker/ArcWelderPlugin/blob/master/LICENSE 22 | # 23 | # You can contact the author either through the git-hub repository, or at the 24 | # following email address: FormerLurker@pm.me 25 | ################################################################################### */ 26 | 27 | $(function () { 28 | var createPNotifyExtensions = function (options) { 29 | var self = this; 30 | self.progressBar = function (initial_text, title, subtitle, cancel_callback, option_button_text, on_option) { 31 | var self = this; 32 | self.notice = null; 33 | self.$progress = null; 34 | self.$progressText = null; 35 | self.$subTitle = null; 36 | self.initial_text = initial_text; 37 | self.popup_margin = 15; 38 | self.popup_width_with_margin = 400; 39 | self.popup_width = self.popup_width_with_margin - self.popup_margin * 2; 40 | self.title = title; 41 | self.subtitle = subtitle; 42 | self.close = function () { 43 | if (self.loader != null) 44 | self.loader.remove(); 45 | }; 46 | 47 | self.update = function (percent_complete, progress_text) { 48 | self.notice.find(".remove_button").remove(); 49 | 50 | if (self.$progress == null) 51 | return null; 52 | if (percent_complete < 0) 53 | percent_complete = 0; 54 | if (percent_complete > 100) 55 | percent_complete = 100; 56 | if (percent_complete === 100) { 57 | //console.log("Received 100% complete progress message, removing progress bar."); 58 | self.loader.remove(); 59 | return null 60 | } 61 | var percent_complete_text = percent_complete.toFixed(1); 62 | self.$progress.width(percent_complete_text + "%").attr("aria-valuenow", percent_complete_text).find("span").html(percent_complete_text + "%"); 63 | self.$progressText.html(progress_text); 64 | return self; 65 | }; 66 | 67 | self.buttons = [{ 68 | text: 'Close', 69 | click: self.close 70 | }]; 71 | if (cancel_callback) { 72 | self.buttons.push({ 73 | text: 'Cancel', 74 | click: cancel_callback 75 | }); 76 | } 77 | if (option_button_text && on_option) { 78 | self.buttons.push({ 79 | text: option_button_text, 80 | click: on_option 81 | }); 82 | } 83 | // create the pnotify loader 84 | self.loader = new PNotify({ 85 | title: title, 86 | text: '
', 87 | addclass: "arc-welder", 88 | icon: 'fa fa-cog fa-spin', 89 | width: self.popup_width.toString() + "px", 90 | confirm: { 91 | confirm: true, 92 | buttons: self.buttons 93 | }, 94 | buttons: { 95 | closer: true, 96 | sticker: false 97 | }, 98 | hide: false, 99 | history: { 100 | history: false 101 | }, 102 | before_open: function (notice) { 103 | self.notice = notice.get(); 104 | self.$progress = self.notice.find("div.progress-bar"); 105 | self.$progressText = self.notice.find("div.progress-text"); 106 | self.notice.find(".remove_button").remove(); 107 | self.$subTitle = self.notice.find("div.progress-sub-title"); 108 | self.$subTitle.html(self.subtitle); 109 | self.update(0, self.initial_text); 110 | } 111 | }); 112 | return self; 113 | }; 114 | self.Popups = {}; 115 | self.displayPopupForKey = function (options, popup_key, remove_keys) { 116 | self.closePopupsForKeys(remove_keys); 117 | var popup = new PNotify(options); 118 | self.Popups[popup_key] = popup; 119 | return popup; 120 | }; 121 | self.closePopupsForKeys = function (remove_keys) { 122 | if (!$.isArray(remove_keys)) { 123 | remove_keys = [remove_keys]; 124 | } 125 | for (var index = 0; index < remove_keys.length; index++) { 126 | var key = remove_keys[index]; 127 | if (key in self.Popups) { 128 | var notice = self.Popups[key]; 129 | if (notice.state === "opening") { 130 | notice.options.animation = "none"; 131 | } 132 | notice.remove(); 133 | delete self.Popups[key]; 134 | } 135 | } 136 | }; 137 | self.removeKeyForClosedPopup = function (key) { 138 | if (key in self.Popups) { 139 | var notice = self.Popups[key]; 140 | delete self.Popups[key]; 141 | } 142 | }; 143 | self.checkPNotifyDefaultConfirmButtons = function () { 144 | // check to see if exactly two default pnotify confirm buttons exist. 145 | // If we keep running into problems we might need to inspect the buttons to make sure they 146 | // really are the defaults. 147 | if (PNotify.prototype.options.confirm.buttons.length !== 2) { 148 | // Someone removed the confirmation buttons, darnit! Report the error and re-add the buttons. 149 | var message = "Arc Welder detected the removal or addition of PNotify default confirmation buttons, " + 150 | "which should not be done in a shared environment. Some plugins may show strange behavior. Please " + 151 | "report this error at https://github.com/FormerLurker/ArcWelder/issues. ArcWelder will now clear " + 152 | "and re-add the default PNotify buttons."; 153 | console.error(message); 154 | 155 | // Reset the buttons in case extra buttons were added. 156 | PNotify.prototype.options.confirm.buttons = []; 157 | 158 | var buttons = [ 159 | { 160 | text: "Ok", 161 | addClass: "", 162 | promptTrigger: true, 163 | click: function (b, a) { 164 | b.remove(); 165 | b.get().trigger("pnotify.confirm", [b, a]) 166 | } 167 | }, 168 | { 169 | text: "Cancel", 170 | addClass: "", 171 | promptTrigger: true, 172 | click: function (b) { 173 | b.remove(); 174 | b.get().trigger("pnotify.cancel", b) 175 | } 176 | } 177 | ]; 178 | PNotify.prototype.options.confirm.buttons = buttons; 179 | } 180 | }; 181 | 182 | self.ConfirmDialogs = {}; 183 | self.closeConfirmDialogsForKeys = function (remove_keys) { 184 | if (!$.isArray(remove_keys)) { 185 | remove_keys = [remove_keys]; 186 | } 187 | for (var index = 0; index < remove_keys.length; index++) { 188 | var key = remove_keys[index]; 189 | if (key in self.ConfirmDialogs) { 190 | 191 | self.ConfirmDialogs[key].remove(); 192 | delete self.ConfirmDialogs[key]; 193 | } 194 | } 195 | }; 196 | 197 | self.showConfirmDialog = function (key, title, text, onConfirm, onCancel, onComplete, onOption, optionButtonText) { 198 | self.closeConfirmDialogsForKeys([key]); 199 | // Make sure that the default pnotify buttons exist 200 | self.checkPNotifyDefaultConfirmButtons(); 201 | options = { 202 | title: title, 203 | text: text, 204 | icon: 'fa fa-question', 205 | hide: false, 206 | addclass: "arc-welder", 207 | confirm: { 208 | confirm: true, 209 | }, 210 | buttons: { 211 | closer: false, 212 | sticker: false 213 | }, 214 | history: { 215 | history: false 216 | } 217 | }; 218 | if (onOption && optionButtonText) { 219 | var confirmButtons = [ 220 | { 221 | text: "Ok", 222 | addClass: "", 223 | promptTrigger: true, 224 | click: function (b, a) { 225 | b.remove(); 226 | b.get().trigger("pnotify.confirm", [b, a]) 227 | } 228 | }, 229 | { 230 | text: optionButtonText, 231 | click: function () { 232 | if (onOption) 233 | onOption(); 234 | if (onComplete) 235 | onComplete(); 236 | self.closeConfirmDialogsForKeys([key]); 237 | } 238 | }, 239 | { 240 | text: "Cancel", 241 | addClass: "", 242 | promptTrigger: true, 243 | click: function (b) { 244 | b.remove(); 245 | b.get().trigger("pnotify.cancel", b) 246 | } 247 | } 248 | ]; 249 | options.confirm.buttons = confirmButtons; 250 | } 251 | self.ConfirmDialogs[key] = ( 252 | new PNotify(options) 253 | ).get().on('pnotify.confirm', function () { 254 | if (onConfirm) 255 | onConfirm(); 256 | if (onComplete) { 257 | onComplete(); 258 | } 259 | }).on('pnotify.cancel', function () { 260 | if (onCancel) 261 | onCancel(); 262 | if (onComplete) { 263 | onComplete(); 264 | } 265 | }); 266 | }; 267 | } 268 | PNotifyExtensions = new createPNotifyExtensions({}); 269 | }); -------------------------------------------------------------------------------- /octoprint_arc_welder/templates/arc_welder_settings_about.jinja2: -------------------------------------------------------------------------------- 1 | 26 | 85 | -------------------------------------------------------------------------------- /octoprint_arc_welder/utilities.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # ################################################################################# 3 | # Arc Welder: Anti-Stutter 4 | # 5 | # A plugin for OctoPrint that converts G0/G1 commands into G2/G3 commands where possible and ensures that the tool 6 | # paths don't deviate by more than a predefined resolution. This compresses the gcode file sice, and reduces reduces 7 | # the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3) 8 | # 9 | # Copyright (C) 2020 Brad Hochgesang 10 | # ################################################################################# 11 | # This program is free software: 12 | # you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with this program. If not, see the following: 22 | # https://github.com/FormerLurker/ArcWelderPlugin/blob/master/LICENSE 23 | # 24 | # You can contact the author either through the git-hub repository, or at the 25 | # following email address: FormerLurker@pm.me 26 | ################################################################################## 27 | from __future__ import absolute_import 28 | from __future__ import unicode_literals 29 | import six 30 | import os 31 | import ntpath 32 | 33 | 34 | def remove_extension_from_filename(filename): 35 | return os.path.splitext(filename)[0] 36 | 37 | 38 | def get_filename_from_path(filepath): 39 | head, tail = ntpath.split(filepath) 40 | return tail or ntpath.basename(head) 41 | 42 | 43 | def get_extension_from_filename(filename): 44 | head, tail = ntpath.split(filename) 45 | file_name = tail or ntpath.basename(head) 46 | split_filename = os.path.splitext(file_name) 47 | if len(split_filename) > 1: 48 | extension = split_filename[1] 49 | if len(split_filename) > 1: 50 | return extension[1:] 51 | return "" 52 | 53 | 54 | def dict_encode(d): 55 | # helpers for dealing with bytes (string) values delivered by the converter 56 | # socks.js doesn't like mixed encoding 57 | def dict_key_value_encode(s): 58 | if isinstance(s, dict): 59 | return dict_encode(s) 60 | try: 61 | if isinstance(s, str): 62 | return unicode(s, errors='ignore', encoding='utf-8') 63 | except NameError: # Python 3 64 | if isinstance(s, bytes): 65 | return str(s, errors='ignore', encoding='utf-8') 66 | return s 67 | return {dict_key_value_encode(k): dict_key_value_encode(v) for k, v in six.iteritems(d)} -------------------------------------------------------------------------------- /octoprint_arc_welder_setuptools/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # ################################################################################# 3 | # Arc Welder: Anti-Stutter 4 | # 5 | # A plugin for OctoPrint that converts G0/G1 commands into G2/G3 commands where possible and ensures that the tool 6 | # paths don't deviate by more than a predefined resolution. This compresses the gcode file sice, and reduces reduces 7 | # the number of gcodes per second sent to a 3D printer that supports arc commands (G2 G3) 8 | # 9 | # Copyright (C) 2020 Brad Hochgesang 10 | # ################################################################################# 11 | # This program is free software: 12 | # you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with this program. If not, see the following: 22 | # https://github.com/FormerLurker/ArcWelderPlugin/blob/master/LICENSE 23 | # 24 | # You can contact the author either through the git-hub repository, or at the 25 | # following email address: FormerLurker@pm.me 26 | ################################################################################## 27 | 28 | from distutils import version 29 | import functools 30 | 31 | 32 | @functools.total_ordering 33 | class NumberedVersion(version.LooseVersion): 34 | """ 35 | Prerelease tags will ALWAYS compare as less than a version with the same initial tags if the prerelease tag 36 | exists. 37 | """ 38 | 39 | def __init__(self, vstring=None, pre_release_tags=["rc"], development_tags=["dev"]): 40 | # trim vstring 41 | if vstring != None: 42 | vstring = vstring.strip() 43 | self.pre_release_tags = pre_release_tags 44 | self.development_tags = development_tags 45 | self.original_string = vstring 46 | self._tag_version = [] 47 | self._pre_release_version = [] 48 | self._development_version = [] 49 | self._commit_version = [] 50 | self.commit_version_string = "" 51 | self.is_pre_release = False 52 | self.is_development = False 53 | self.has_commit_info = False 54 | self.commits_ahead = 0 55 | self.commit_guid = "" 56 | self.is_dirty = False 57 | 58 | # strip off any leading V or v 59 | if len(vstring) > 0 and vstring[0].upper() == "V": 60 | vstring = vstring[1:] 61 | 62 | # find any pluses that contain commit level info 63 | if "+" in vstring: 64 | index = vstring.find("+") 65 | # make sure there is info after the '+' 66 | if index < len(vstring) - 1: 67 | self.commit_version_string = vstring[index + 1 :] 68 | # strip off the plus symbox from the vstring 69 | vstring = vstring[:index] 70 | version.LooseVersion.__init__(self, vstring) 71 | 72 | def parse(self, vstring): 73 | version.LooseVersion.parse(self, vstring) 74 | # save the version without commit level info 75 | # set index = 0 76 | index = 0 77 | tag_length = len(self.version) 78 | # determine tag version 79 | for i in range(index, tag_length): 80 | index = i 81 | seg = self.version[i] 82 | if seg in self.pre_release_tags: 83 | self.is_pre_release = True 84 | break 85 | self._tag_version.append(seg) 86 | # determine pre release version 87 | for i in range(index + 1, tag_length): 88 | index = i 89 | seg = self.version[i] 90 | if seg in self.development_tags: 91 | self.is_development = True 92 | break 93 | self._pre_release_version.append(seg) 94 | # determine development version 95 | for i in range(index + 1, tag_length): 96 | index = i 97 | seg = self.version[i] 98 | self._development_version.append(seg) 99 | 100 | if len(self.commit_version_string) > 0: 101 | self.commits_ahead = None # we don't know how many commits we have yet 102 | # We have commit version info, let's add it to our version 103 | prel_segments = self.commit_version_string.split(".") 104 | # get num commits ahead 105 | if len(prel_segments) > 0: 106 | if prel_segments[0] != "u": 107 | try: 108 | self.commits_ahead = int(prel_segments[0]) 109 | except ValueError: 110 | # If you can't parse this, commits_ahead will be None 111 | pass 112 | # get commit guid 113 | if len(prel_segments) > 1: 114 | guid = prel_segments[1] 115 | if len(guid) == 8: 116 | self.commit_guid = guid 117 | self.has_commit_info = True 118 | 119 | # find out if the current version is 'dirty' yuck! 120 | if len(prel_segments) > 2: 121 | self.is_dirty = prel_segments[2] == "dirty" 122 | 123 | # add the new segments 124 | if self.has_commit_info: 125 | # add the commit level stuff to the version segments 126 | # Add the commit info separator (+) 127 | self._commit_version.append("+") 128 | # next, add the number of commits we are ahead 129 | self._commit_version.append( 130 | "u" if self.commits_ahead is None else self.commits_ahead 131 | ) 132 | # next add the guid 133 | self._commit_version.append(self.commit_guid) 134 | # if the version is dirty, add that segment 135 | if self.is_dirty: 136 | self._commit_version.append("dirty") 137 | self.version.extend(self._commit_version) 138 | 139 | def __repr__(self): 140 | return "{cls} ('{vstring}', {prerel_tags})".format( 141 | cls=self.__class__.__name__, 142 | vstring=str(self), 143 | prerel_tags=list(self.prerel_tags.keys()), 144 | ) 145 | 146 | def __str__(self): 147 | return self.original_string 148 | 149 | def __lt__(self, other): 150 | """ 151 | Compare versions and return True if the current version is less than other 152 | """ 153 | # first compare tags using LooseVersion 154 | cur_version = self._tag_version 155 | other_version = other._tag_version 156 | if cur_version < other_version: 157 | return True 158 | if cur_version > other_version: 159 | return False 160 | 161 | # cur_version == other_version, compare pre-release 162 | if self.is_pre_release and not other.is_pre_release: 163 | # the current version is a pre-release, but other is not. 164 | return True 165 | 166 | if other.is_pre_release and not self.is_pre_release: 167 | # other is a pre-release, but the current version is not. 168 | return False 169 | 170 | cur_version = self.pre_release_tags 171 | other_version = other.pre_release_tags 172 | if cur_version < other_version: 173 | return True 174 | if cur_version > other_version: 175 | return False 176 | 177 | # now compare development versions 178 | if self.is_development and not other.is_development: 179 | # the current version is development, but other is not. 180 | return True 181 | 182 | if other.is_development and not self.is_development: 183 | # other is a development, but the current version is not. 184 | return False 185 | # Both versions are development, compare dev versions 186 | cur_version = self.development_tags 187 | other_version = other.development_tags 188 | if cur_version < other_version: 189 | return True 190 | if cur_version > other_version: 191 | return False 192 | 193 | # Development versions are the same, now compare commit info 194 | # First, if either version has no 'commits_ahead', they are considered equal, return false 195 | if self.commits_ahead is None or other.commits_ahead is None: 196 | return False 197 | 198 | if self.commits_ahead < other.commits_ahead: 199 | return True 200 | if other.commits_ahead < self.commits_ahead: 201 | return False 202 | 203 | # we are the same number of commits ahead, so just check dirty (dirty > not dirty) 204 | if other.is_dirty and not self.is_dirty: 205 | return True 206 | return False 207 | 208 | def __eq__(self, other): 209 | return not (self < other) and not (self > other) 210 | 211 | def __gt__(self, other): 212 | """ 213 | Compare versions and return True if the current version is greater than other 214 | """ 215 | # first compare tags using LooseVersion 216 | cur_version = self._tag_version 217 | other_version = other._tag_version 218 | if cur_version > other_version: 219 | return True 220 | if cur_version < other_version: 221 | return False 222 | 223 | # cur_version == other_version, compare pre-release 224 | if self.is_pre_release and not other.is_pre_release: 225 | # the current version is a pre-release, but other is not. 226 | return False 227 | 228 | if other.is_pre_release and not self.is_pre_release: 229 | # other is a pre-release, but the current version is not. 230 | return True 231 | 232 | cur_version = self.pre_release_tags 233 | other_version = other.pre_release_tags 234 | if cur_version > other_version: 235 | return True 236 | if cur_version < other_version: 237 | return False 238 | 239 | # now compare development versions 240 | if self.is_development and not other.is_development: 241 | # the current version is development, but other is not. 242 | return False 243 | 244 | if other.is_development and not self.is_development: 245 | # other is a development, but the current version is not. 246 | return True 247 | # Both versions are development, compare dev versions 248 | cur_version = self.development_tags 249 | other_version = other.development_tags 250 | if cur_version > other_version: 251 | return True 252 | if cur_version < other_version: 253 | return False 254 | 255 | # Development versions are the same, now compare commit info 256 | # First, if either version has no 'commits_ahead', they are considered equal, return false 257 | if self.commits_ahead is None or other.commits_ahead is None: 258 | return False 259 | 260 | if self.commits_ahead > other.commits_ahead: 261 | return True 262 | if other.commits_ahead > self.commits_ahead: 263 | return False 264 | 265 | # we are the same number of commits ahead, so just check dirty (dirty > not dirty) 266 | if self.is_dirty and not other.is_dirty: 267 | return True 268 | return False 269 | 270 | 271 | def custom_version_compare(a,b): 272 | return NumberedVersion(a) >= NumberedVersion(b) 273 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormerLurker/ArcWelderPlugin/bb71e8fe20cc65f0ab28c0c13685fde39f730320/requirements.txt -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | 2 | # See the docstring in versioneer.py for instructions. Note that you must 3 | # re-run 'versioneer.py setup' after changing this section, and commit the 4 | # resulting files. 5 | 6 | [versioneer] 7 | VCS = git 8 | style = pep440 9 | versionfile_source = octoprint_arc_welder/_version.py 10 | versionfile_build = octoprint_arc_welder/_version.py 11 | tag_prefix = 12 | parentdir_prefix = 13 | 14 | --------------------------------------------------------------------------------